@agent-native/core 0.144.3 → 0.145.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/clips/changelog/2026-08-06-desktop-full-screen-recordings-now-normalize-microphone-leve.md +6 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +185 -69
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +47 -4
- package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
- package/dist/agent/production-agent.d.ts +6 -0
- package/dist/agent/run-manager.d.ts +44 -1
- package/dist/agent/run-manager.js +70 -8
- package/dist/chat-threads/store.js +6 -4
- package/dist/client/use-db-sync.js +9 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/ddl-guard.d.ts +8 -0
- package/dist/db/ddl-guard.js +148 -0
- package/dist/deploy/build.d.ts +36 -0
- package/dist/deploy/build.js +78 -5
- package/dist/framework-tools.d.ts +126 -0
- package/dist/framework-tools.js +114 -0
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/org/accept-pending.js +2 -2
- package/dist/org/auth-policy.js +4 -0
- package/dist/org/auto-join-domain.d.ts +9 -0
- package/dist/org/auto-join-domain.js +53 -4
- package/dist/org/context.js +12 -3
- package/dist/org/handlers.js +17 -5
- package/dist/org/request-org-cache.d.ts +24 -2
- package/dist/org/request-org-cache.js +57 -2
- package/dist/provider-api/actions/custom-provider-registration.d.ts +6 -6
- package/dist/provider-api/actions/provider-api.d.ts +6 -6
- package/dist/resources/store.d.ts +1 -1
- package/dist/resources/store.js +96 -7
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-discovery.d.ts +27 -0
- package/dist/server/action-discovery.js +89 -0
- package/dist/server/agent-chat/action-filters-a2a.d.ts +11 -0
- package/dist/server/agent-chat/action-filters-a2a.js +17 -1
- package/dist/server/agent-chat/framework-prompts.d.ts +2 -0
- package/dist/server/agent-chat/plugin-options.d.ts +30 -0
- package/dist/server/agent-chat/prompt-resources.d.ts +4 -1
- package/dist/server/agent-chat/prompt-resources.js +8 -2
- package/dist/server/agent-chat-plugin.js +138 -75
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.d.ts +3 -0
- package/dist/server/auth.js +22 -1
- package/dist/server/collab-plugin.js +10 -4
- package/dist/server/core-routes-plugin.js +3 -0
- package/dist/server/prompts/framework-core-compact.d.ts +5 -0
- package/dist/server/prompts/framework-core-compact.js +28 -12
- package/dist/server/prompts/framework-core.d.ts +5 -0
- package/dist/server/prompts/framework-core.js +33 -16
- package/dist/server/prompts/index.d.ts +1 -1
- package/dist/server/prompts/index.js +1 -1
- package/dist/server/prompts/shared-rules.d.ts +12 -4
- package/dist/server/prompts/shared-rules.js +14 -4
- package/dist/server/session-email-cache.d.ts +14 -0
- package/dist/server/session-email-cache.js +48 -0
- package/dist/server/sweep-runtime.d.ts +22 -0
- package/dist/server/sweep-runtime.js +28 -0
- package/dist/settings/store.js +63 -7
- package/dist/shared/ttl-cache.d.ts +59 -0
- package/dist/shared/ttl-cache.js +43 -0
- package/dist/sharing/actions/list-resource-shares.d.ts +3 -3
- package/dist/sharing/actions/list-resource-shares.js +4 -1
- package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -3
- package/dist/triggers/condition-evaluator.js +11 -27
- package/docs/content/deployment.mdx +95 -13
- package/docs/content/environment-variables.mdx +5 -0
- package/docs/content/key-concepts.mdx +13 -6
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -3
|
@@ -8,14 +8,37 @@
|
|
|
8
8
|
* - Detailed "Parallel Tool Calls" prose (replaced with one-liner)
|
|
9
9
|
* - Detailed "Resources" section (agent uses resources tool)
|
|
10
10
|
*/
|
|
11
|
+
import { frameworkGroupEnabled, } from "../../framework-tools.js";
|
|
11
12
|
import { hasDatabaseReadTools, hasDatabaseWriteTools, } from "../../scripts/db/tool-mode.js";
|
|
12
|
-
import { sharedRule8, SHARED_RULE_9,
|
|
13
|
+
import { sharedRule8, SHARED_RULE_9, sharedRule14, SHARED_RULE_15, SHARED_RULE_AGENT_WARNINGS, } from "./shared-rules.js";
|
|
13
14
|
/**
|
|
14
15
|
* Build the compact FRAMEWORK_CORE prompt string.
|
|
15
16
|
*
|
|
16
17
|
* @param examples Optional injectable provider/action examples for rule 8.
|
|
17
18
|
*/
|
|
18
19
|
export function buildFrameworkCoreCompact(examples, options) {
|
|
20
|
+
const groupOn = (group) => frameworkGroupEnabled(options?.disabledFrameworkGroups, group);
|
|
21
|
+
const resourcesSection = groupOn("resources")
|
|
22
|
+
? `### Resources
|
|
23
|
+
|
|
24
|
+
Use the \`resources\` tool for persistent notes and context files: \`action: "list"\`, \`"read"\`, \`"effective"\`, \`"write"\`, \`"promote"\`, or \`"delete"\`.
|
|
25
|
+
Resources have three levels: workspace defaults inherited from Dispatch, shared organization/app overrides, and personal overrides. Use \`resources\` with \`action: "effective"\` before editing when you need to explain or inspect which level is active for a path.
|
|
26
|
+
Workspace resources are user-facing by default. If you need temporary working files, write them as agent scratch (\`visibility: "agent_scratch"\`); scratch is hidden from the Workspace view by default and expires. Use \`visibility: "workspace"\` only when the user explicitly asked to save/manage that file, or for durable AGENTS.md, LEARNINGS.md, memory, skills, jobs, or custom agents.
|
|
27
|
+
`
|
|
28
|
+
: "";
|
|
29
|
+
const extendedCapabilityClauses = [
|
|
30
|
+
"inline embeds",
|
|
31
|
+
groupOn("chat") ? "chat history search (`chat-history`)" : "",
|
|
32
|
+
groupOn("automation") ? "recurring jobs (`manage-jobs`)" : "",
|
|
33
|
+
"structured memory (`save-memory`/`delete-memory`)",
|
|
34
|
+
"browser automation (`activate-browser` in production, `set-browser-control` locally)",
|
|
35
|
+
].filter(Boolean);
|
|
36
|
+
const extendedCapabilitiesList = `${extendedCapabilityClauses.slice(0, -1).join(", ")}, and ${extendedCapabilityClauses.at(-1)}`;
|
|
37
|
+
const callAgentSection = groupOn("workspaceApps")
|
|
38
|
+
? `
|
|
39
|
+
For generated media, prefer this app's native generation action; otherwise use \`call-agent\` with agent "assets".
|
|
40
|
+
`
|
|
41
|
+
: "";
|
|
19
42
|
const hasDatabaseTools = hasDatabaseReadTools(options?.databaseTools);
|
|
20
43
|
const hasDatabaseWrites = hasDatabaseWriteTools(options?.databaseTools);
|
|
21
44
|
const dataRule = hasDatabaseWrites
|
|
@@ -56,22 +79,15 @@ ${SHARED_RULE_9}
|
|
|
56
79
|
**Native widgets** — For table/chart/graph/report requests, prefer actions labeled \`Native chat widget\`; use \`render-data-widget\` for already-summarized data (≤50 rows) instead of markdown tables. Above that, give the total plus the top rows — never retype a full result set as widget arguments. Deliver files in chat, never just a path.
|
|
57
80
|
10. **Your tool list is not the whole surface** — Most app actions and connected MCP tools load on demand, so search the live registry with \`tool-search\` before concluding a capability doesn't exist.
|
|
58
81
|
11. **Relative dates use runtime context** — The \`<runtime-context>\` block gives the authoritative current date/time. Resolve "today", "yesterday", "last week", and similar phrases to explicit calendar dates before querying data or creating artifacts.
|
|
59
|
-
${
|
|
82
|
+
${sharedRule14(options)}
|
|
60
83
|
${SHARED_RULE_15}
|
|
61
84
|
${SHARED_RULE_AGENT_WARNINGS}
|
|
62
85
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
Use the \`resources\` tool for persistent notes and context files: \`action: "list"\`, \`"read"\`, \`"effective"\`, \`"write"\`, \`"promote"\`, or \`"delete"\`.
|
|
66
|
-
Resources have three levels: workspace defaults inherited from Dispatch, shared organization/app overrides, and personal overrides. Use \`resources\` with \`action: "effective"\` before editing when you need to explain or inspect which level is active for a path.
|
|
67
|
-
Workspace resources are user-facing by default. If you need temporary working files, write them as agent scratch (\`visibility: "agent_scratch"\`); scratch is hidden from the Workspace view by default and expires. Use \`visibility: "workspace"\` only when the user explicitly asked to save/manage that file, or for durable AGENTS.md, LEARNINGS.md, memory, skills, jobs, or custom agents.
|
|
68
|
-
|
|
86
|
+
${resourcesSection}
|
|
69
87
|
### Extended Capabilities
|
|
70
88
|
|
|
71
|
-
You also have tools for
|
|
89
|
+
You also have tools for ${extendedCapabilitiesList}. Call \`get-framework-context\` with the matching key — it lists its own topics — for full instructions when needed.
|
|
72
90
|
|
|
73
91
|
**Agent teams:** default to doing the work yourself. Delegate ONE sub-agent (\`agent-teams\` action "spawn") for self-contained heavy work; fan out to several only for genuinely independent units; never parallelize tightly-coupled work; cap fan-out around 3. Treat "background agent", "sub-agent", "parallel", "batch", "kick off", "run the rest", and "queued items" as delegation intent when the user is asking you to start or continue independent work items. After \`spawn\`, say the task started/running, not completed; use \`status\`/\`read-result\` before claiming the delegated work is done. Give each sub-agent a self-contained brief (objective, the specific context/IDs it needs, output format, boundaries) — it can't see this thread — then read all results and synthesize one integrated answer. Full details: \`get-framework-context\` key \`agent-teams\`.
|
|
74
|
-
|
|
75
|
-
For generated media, prefer this app's native generation action; otherwise use \`call-agent\` with agent "assets".
|
|
76
|
-
`;
|
|
92
|
+
${callAgentSection}`;
|
|
77
93
|
}
|
|
@@ -5,11 +5,16 @@
|
|
|
5
5
|
* Shared rules (8-9, 12-13) are imported from shared-rules.ts so the
|
|
6
6
|
* compact variant uses the same text and the two can never drift.
|
|
7
7
|
*/
|
|
8
|
+
import { type FrameworkToolGroup } from "../../framework-tools.js";
|
|
8
9
|
import { type DatabaseToolsOption } from "../../scripts/db/tool-mode.js";
|
|
9
10
|
import { type PromptExamples } from "./shared-rules.js";
|
|
10
11
|
export interface FrameworkCorePromptOptions {
|
|
11
12
|
databaseTools?: DatabaseToolsOption;
|
|
12
13
|
extensionTools?: boolean;
|
|
14
|
+
/** Framework tool groups this app switched off. Every block below that names
|
|
15
|
+
* a group's tool by name is gated on this — a prompt naming an absent tool
|
|
16
|
+
* makes the model call it, fail, and often report the capability as missing. */
|
|
17
|
+
disabledFrameworkGroups?: ReadonlySet<FrameworkToolGroup>;
|
|
13
18
|
}
|
|
14
19
|
/**
|
|
15
20
|
* Build the full FRAMEWORK_CORE prompt string.
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* Shared rules (8-9, 12-13) are imported from shared-rules.ts so the
|
|
6
6
|
* compact variant uses the same text and the two can never drift.
|
|
7
7
|
*/
|
|
8
|
+
import { frameworkGroupEnabled, } from "../../framework-tools.js";
|
|
8
9
|
import { hasDatabaseReadTools, hasDatabaseWriteTools, } from "../../scripts/db/tool-mode.js";
|
|
9
|
-
import { sharedRule8, SHARED_RULE_9,
|
|
10
|
+
import { sharedRule8, SHARED_RULE_9, sharedRule14, SHARED_RULE_15, SHARED_RULE_AGENT_WARNINGS, } from "./shared-rules.js";
|
|
10
11
|
/**
|
|
11
12
|
* Build the full FRAMEWORK_CORE prompt string.
|
|
12
13
|
*
|
|
@@ -40,6 +41,33 @@ export function buildFrameworkCore(examples, options) {
|
|
|
40
41
|
const actionSurface = options?.extensionTools === true
|
|
41
42
|
? "this app's registered actions, extensions, and connected MCP tools"
|
|
42
43
|
: "this app's registered actions and connected MCP tools";
|
|
44
|
+
const groupOn = (group) => frameworkGroupEnabled(options?.disabledFrameworkGroups, group);
|
|
45
|
+
const resourcesSection = groupOn("resources")
|
|
46
|
+
? `### Resources
|
|
47
|
+
|
|
48
|
+
You have access to a Resources system for persistent notes and context files.
|
|
49
|
+
Use the \`resources\` tool to manage resources: \`action: "list"\`, \`"read"\`, \`"effective"\`, \`"write"\`, \`"promote"\`, or \`"delete"\`.
|
|
50
|
+
Resources can be workspace defaults inherited from Dispatch, shared organization/app overrides, or personal overrides. By default, resources are personal. Workspace-scope resources are read-only from app agents; create shared or personal resources to override or narrow them.
|
|
51
|
+
|
|
52
|
+
When the user gives instructions that should apply to all users/sessions, update the shared "AGENTS.md" resource.
|
|
53
|
+
|
|
54
|
+
Workspace resources are user-facing by default. If you need temporary working files, use the \`resources\` tool with \`visibility: "agent_scratch"\`; scratch resources are hidden from the Workspace view by default and expire automatically. Use \`visibility: "workspace"\` only when the user explicitly asked to save/create/manage that file, or for durable control files such as \`AGENTS.md\`, \`LEARNINGS.md\`, \`memory/\`, \`skills/\`, \`jobs/\`, or \`agents/\`. If a scratch result becomes useful to the user, call \`resources\` with \`action: "promote"\` or rewrite it with \`visibility: "workspace"\`.
|
|
55
|
+
`
|
|
56
|
+
: "";
|
|
57
|
+
const extendedCapabilityClauses = [
|
|
58
|
+
"inline embeds (`embed` fenced code block)",
|
|
59
|
+
groupOn("chat") ? "chat history search (`chat-history`)" : "",
|
|
60
|
+
groupOn("automation") ? "recurring jobs (`manage-jobs`)" : "",
|
|
61
|
+
"connecting Builder.io (`connect-builder`)",
|
|
62
|
+
"browser automation (`set-browser-control`/`activate-browser`)",
|
|
63
|
+
"structured memory (`save-memory`/`delete-memory`)",
|
|
64
|
+
].filter(Boolean);
|
|
65
|
+
const extendedCapabilitiesList = `${extendedCapabilityClauses.slice(0, -1).join(", ")}, and ${extendedCapabilityClauses.at(-1)}`;
|
|
66
|
+
const callAgentSection = groupOn("workspaceApps")
|
|
67
|
+
? `
|
|
68
|
+
**call-agent** messages a DIFFERENT deployed app's agent over A2A — never use it for your own actions or to call yourself. For brand-consistent generated media when this app has no native generation action, call agent "assets".
|
|
69
|
+
`
|
|
70
|
+
: "";
|
|
43
71
|
return `
|
|
44
72
|
### How You Work
|
|
45
73
|
|
|
@@ -78,7 +106,7 @@ ${SHARED_RULE_9}
|
|
|
78
106
|
**Downloadable files** — When the user asks you to create or export a file, deliver it in the same chat turn. For compact tabular results, prefer \`render-data-widget\` so the user gets the native Download CSV action without a second stored copy. For a complete or durable file written through \`workspaceWrite\`, use a normal non-\`scratch/\` path and then call \`show-workspace-file\` with that path so chat renders a direct download card. Never finish by giving filesystem paths or telling the user to navigate elsewhere to find the file.
|
|
79
107
|
10. **Your tool list is not the whole surface** — Most app actions and connected MCP tools load on demand, so search the live registry with \`tool-search\` before concluding a capability doesn't exist.
|
|
80
108
|
11. **Relative dates use runtime context** — The \`<runtime-context>\` block gives the authoritative current date/time. Resolve "today", "yesterday", "last week", and similar phrases to explicit calendar dates before querying data or creating artifacts. When answering factual questions, include the exact date or date range you used.
|
|
81
|
-
${
|
|
109
|
+
${sharedRule14(options)}
|
|
82
110
|
${SHARED_RULE_15}
|
|
83
111
|
${SHARED_RULE_AGENT_WARNINGS}
|
|
84
112
|
|
|
@@ -86,22 +114,11 @@ ${SHARED_RULE_AGENT_WARNINGS}
|
|
|
86
114
|
|
|
87
115
|
Gather context efficiently: when you need several independent read-only lookups (reading state, querying different tables, searching, fetching unrelated records), issue those tool calls together in one batch rather than one at a time. Keep mutating actions ordered and sequential — anything that creates, updates, deletes, sends, or publishes runs one at a time so each can be confirmed before the next, and so writes that depend on each other stay consistent.
|
|
88
116
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
You have access to a Resources system for persistent notes and context files.
|
|
92
|
-
Use the \`resources\` tool to manage resources: \`action: "list"\`, \`"read"\`, \`"effective"\`, \`"write"\`, \`"promote"\`, or \`"delete"\`.
|
|
93
|
-
Resources can be workspace defaults inherited from Dispatch, shared organization/app overrides, or personal overrides. By default, resources are personal. Workspace-scope resources are read-only from app agents; create shared or personal resources to override or narrow them.
|
|
94
|
-
|
|
95
|
-
When the user gives instructions that should apply to all users/sessions, update the shared "AGENTS.md" resource.
|
|
96
|
-
|
|
97
|
-
Workspace resources are user-facing by default. If you need temporary working files, use the \`resources\` tool with \`visibility: "agent_scratch"\`; scratch resources are hidden from the Workspace view by default and expire automatically. Use \`visibility: "workspace"\` only when the user explicitly asked to save/create/manage that file, or for durable control files such as \`AGENTS.md\`, \`LEARNINGS.md\`, \`memory/\`, \`skills/\`, \`jobs/\`, or \`agents/\`. If a scratch result becomes useful to the user, call \`resources\` with \`action: "promote"\` or rewrite it with \`visibility: "workspace"\`.
|
|
98
|
-
|
|
117
|
+
${resourcesSection}
|
|
99
118
|
### Extended Capabilities
|
|
100
119
|
|
|
101
|
-
You also have tools for
|
|
120
|
+
You also have tools for ${extendedCapabilitiesList}. Call \`get-framework-context\` with the matching key — it lists its own topics — for full instructions before non-trivial work in any of these areas.
|
|
102
121
|
|
|
103
122
|
**Agent teams:** default to doing the work yourself in this thread, but treat "background agent", "sub-agent", "parallel", "batch", "kick off", "run the rest", and "queued items" as delegation intent when the user is asking you to start or continue independent work items. Delegate ONE sub-agent for self-contained heavy work (deep research, long multi-step generation, noisy scans); fan out to MULTIPLE only for genuinely independent units; never parallelize tightly-coupled work; cap fan-out around 3. After \`spawn\`, say the task started/running, not completed; use \`status\`/\`read-result\` before claiming delegated work is done. Give every sub-agent a self-contained brief (objective, the specific context/IDs it needs, output format, boundaries), then read all results and synthesize one integrated answer. Full details: \`get-framework-context\` key \`agent-teams\`.
|
|
104
|
-
|
|
105
|
-
**call-agent** messages a DIFFERENT deployed app's agent over A2A — never use it for your own actions or to call yourself. For brand-consistent generated media when this app has no native generation action, call agent "assets".
|
|
106
|
-
`;
|
|
123
|
+
${callAgentSection}`;
|
|
107
124
|
}
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export { buildFrameworkCore } from "./framework-core.js";
|
|
6
6
|
export { buildFrameworkCoreCompact } from "./framework-core-compact.js";
|
|
7
|
-
export { sharedRule8, SHARED_RULE_9,
|
|
7
|
+
export { sharedRule8, SHARED_RULE_9, sharedRule14, SHARED_RULE_15, type PromptExamples, type SharedRuleOptions, } from "./shared-rules.js";
|
|
8
8
|
export { getModelFamilyOverlay } from "./model-overlays.js";
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export { buildFrameworkCore } from "./framework-core.js";
|
|
6
6
|
export { buildFrameworkCoreCompact } from "./framework-core-compact.js";
|
|
7
|
-
export { sharedRule8, SHARED_RULE_9,
|
|
7
|
+
export { sharedRule8, SHARED_RULE_9, sharedRule14, SHARED_RULE_15, } from "./shared-rules.js";
|
|
8
8
|
export { getModelFamilyOverlay } from "./model-overlays.js";
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* rule) and 12–13 (planning discipline; collaborate through uncertainty) are
|
|
8
8
|
* reproduced verbatim in both prompts — keep them here.
|
|
9
9
|
*/
|
|
10
|
+
import { type FrameworkToolGroup } from "../../framework-tools.js";
|
|
10
11
|
import { type DatabaseToolsOption } from "../../scripts/db/tool-mode.js";
|
|
11
12
|
/**
|
|
12
13
|
* Injectable provider/action examples. Defaults are generic; templates that
|
|
@@ -21,6 +22,9 @@ export interface PromptExamples {
|
|
|
21
22
|
export interface SharedRuleOptions {
|
|
22
23
|
databaseTools?: DatabaseToolsOption;
|
|
23
24
|
extensionTools?: boolean;
|
|
25
|
+
/** Framework tool groups this app switched off. Any rule naming a group's
|
|
26
|
+
* tool must drop that reference — see `frameworkGroupEnabled`. */
|
|
27
|
+
disabledFrameworkGroups?: ReadonlySet<FrameworkToolGroup>;
|
|
24
28
|
}
|
|
25
29
|
/** Rule 8 — db-* tools are internal only (shared between full and compact). */
|
|
26
30
|
export declare function sharedRule8(examples?: PromptExamples, options?: SharedRuleOptions): string;
|
|
@@ -34,14 +38,18 @@ export declare function sharedRule8(examples?: PromptExamples, options?: SharedR
|
|
|
34
38
|
*/
|
|
35
39
|
export declare const SHARED_RULE_9 = "9. **Never fabricate \u2014 verify results, report failures honestly, and recover instead of giving up.**\n - **Never fabricate factual claims or records** \u2014 do not invent numbers, metrics, records, query results, URLs, citations, source attributions, customer names, dates, or success rates. This applies inside generated artifacts too: decks, documents, reports, dashboards, Slack/email replies, and charts. Only state factual numbers/claims the user provided or you retrieved with an action/tool. If a data source is unavailable, returns no rows, is missing credentials, or has a connection error, say so clearly; do not create placeholder rows or fetch unrelated providers to look complete unless the user explicitly asked you to import/sync/backfill. Prefer qualitative wording, placeholders like `[metric TBD]`, or clearly labeled draft assumptions over plausible-looking facts \u2014 presenting made-up data as real is worse than admitting the limitation.\n - **Never fabricate success from tool errors** \u2014 when a tool call returns an error (marked `isError: true`, contains \"Command failed\", \"Error:\", or non-zero exit output), the operation FAILED; do not synthesize a success narrative or describe what it \"would have\" produced. Report the failure verbatim (this applies especially to `bash(command=\"pnpm action ...\")` calls). Before telling the user a mutating action (create/update/delete/send/publish) is done, confirm it actually landed \u2014 check the tool result, or read the refreshed `<current-screen>` / re-query the data; having *called* an action is not proof it worked. If a result is ambiguous, check rather than assume.\n - **Recover instead of giving up** \u2014 treat a failure or ambiguous result as a signal to retry the obvious fix, try an alternate tool or approach, or clearly hand the blocker back with what you tried; never silently give up, and never paper over a failure by claiming success.";
|
|
36
40
|
/**
|
|
37
|
-
* Rule 12 (
|
|
38
|
-
*
|
|
39
|
-
*
|
|
41
|
+
* Rule 12 (name retained as `sharedRule14` for import stability) — Planning and
|
|
42
|
+
* progress. Deliberately states only when to open a progress run; the
|
|
43
|
+
* start/update/complete discipline lives in `manage-progress`'s own tool
|
|
40
44
|
* description (packages/core/src/progress/actions.ts). Re-adding the mechanics
|
|
41
45
|
* here means every turn pays for guidance the model only needs once it has the
|
|
42
46
|
* tool loaded.
|
|
47
|
+
*
|
|
48
|
+
* Drops the `manage-progress` reference when the `automation` group is off, so
|
|
49
|
+
* the rule never names a tool the request does not carry. The planning
|
|
50
|
+
* discipline itself still applies — only the tool call goes away.
|
|
43
51
|
*/
|
|
44
|
-
export declare
|
|
52
|
+
export declare function sharedRule14(options?: SharedRuleOptions): string;
|
|
45
53
|
/**
|
|
46
54
|
* Rule 14 — Relay agent warnings. Named for what it is rather than its number,
|
|
47
55
|
* since the `SHARED_RULE_14`/`15` names above no longer match theirs.
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* rule) and 12–13 (planning discipline; collaborate through uncertainty) are
|
|
8
8
|
* reproduced verbatim in both prompts — keep them here.
|
|
9
9
|
*/
|
|
10
|
+
import { frameworkGroupEnabled, } from "../../framework-tools.js";
|
|
10
11
|
import { normalizeDatabaseToolsMode, } from "../../scripts/db/tool-mode.js";
|
|
11
12
|
const DEFAULT_PROVIDER_ACTIONS = [
|
|
12
13
|
"provider-search",
|
|
@@ -64,14 +65,23 @@ export const SHARED_RULE_9 = `9. **Never fabricate — verify results, report fa
|
|
|
64
65
|
- **Never fabricate success from tool errors** — when a tool call returns an error (marked \`isError: true\`, contains "Command failed", "Error:", or non-zero exit output), the operation FAILED; do not synthesize a success narrative or describe what it "would have" produced. Report the failure verbatim (this applies especially to \`bash(command="pnpm action ...")\` calls). Before telling the user a mutating action (create/update/delete/send/publish) is done, confirm it actually landed — check the tool result, or read the refreshed \`<current-screen>\` / re-query the data; having *called* an action is not proof it worked. If a result is ambiguous, check rather than assume.
|
|
65
66
|
- **Recover instead of giving up** — treat a failure or ambiguous result as a signal to retry the obvious fix, try an alternate tool or approach, or clearly hand the blocker back with what you tried; never silently give up, and never paper over a failure by claiming success.`;
|
|
66
67
|
/**
|
|
67
|
-
* Rule 12 (
|
|
68
|
-
*
|
|
69
|
-
*
|
|
68
|
+
* Rule 12 (name retained as `sharedRule14` for import stability) — Planning and
|
|
69
|
+
* progress. Deliberately states only when to open a progress run; the
|
|
70
|
+
* start/update/complete discipline lives in `manage-progress`'s own tool
|
|
70
71
|
* description (packages/core/src/progress/actions.ts). Re-adding the mechanics
|
|
71
72
|
* here means every turn pays for guidance the model only needs once it has the
|
|
72
73
|
* tool loaded.
|
|
74
|
+
*
|
|
75
|
+
* Drops the `manage-progress` reference when the `automation` group is off, so
|
|
76
|
+
* the rule never names a tool the request does not carry. The planning
|
|
77
|
+
* discipline itself still applies — only the tool call goes away.
|
|
73
78
|
*/
|
|
74
|
-
export
|
|
79
|
+
export function sharedRule14(options) {
|
|
80
|
+
if (!frameworkGroupEnabled(options?.disabledFrameworkGroups, "automation")) {
|
|
81
|
+
return `12. **Plan and track multi-step work** — When a task spans several real steps, work through them in order and tell the user where you are as you go rather than only at the end. Skip ceremony for single-action lookups, simple reads, and anything that finishes in one tool call.`;
|
|
82
|
+
}
|
|
83
|
+
return `12. **Plan and track multi-step work** — When a task spans several real steps the user would want to watch, open a \`manage-progress\` run so the work is visible while it's still in flight, and keep it updated as you go rather than after the fact; the tool's own description carries the full discipline. Skip it for single-action lookups, simple reads, and anything that finishes in one tool call — never create single-step plans.`;
|
|
84
|
+
}
|
|
75
85
|
/**
|
|
76
86
|
* Rule 14 — Relay agent warnings. Named for what it is rather than its number,
|
|
77
87
|
* since the `SHARED_RULE_14`/`15` names above no longer match theirs.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare function getCachedSessionEmail(token: string): string | undefined;
|
|
2
|
+
export declare function setCachedSessionEmail(token: string, email: string): void;
|
|
3
|
+
export declare function forgetCachedSessionEmail(token: string): void;
|
|
4
|
+
/**
|
|
5
|
+
* Drop cached session resolutions. Call after ANY write to `sessions`.
|
|
6
|
+
*
|
|
7
|
+
* Clears every token rather than one: the bulk sign-out paths delete by email
|
|
8
|
+
* (`DELETE FROM sessions WHERE email = ?`) and do not know which tokens they
|
|
9
|
+
* removed, so a per-token eviction there would leave a signed-out session
|
|
10
|
+
* resolving for the rest of the TTL.
|
|
11
|
+
*/
|
|
12
|
+
export declare function invalidateSessionEmailCache(): void;
|
|
13
|
+
/** Test seam — the cache is module state, so suites must be able to clear it. */
|
|
14
|
+
export declare function __resetSessionEmailCacheForTests(): void;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createTtlCache } from "../shared/ttl-cache.js";
|
|
2
|
+
/**
|
|
3
|
+
* Cross-request cache for resolved legacy session tokens.
|
|
4
|
+
*
|
|
5
|
+
* A session token is immutable for its (30-day) lifetime, yet every
|
|
6
|
+
* authenticated request re-read it: production showed 494,848 of these for 3
|
|
7
|
+
* users, 1:1 with the request count. The TTL is far shorter than the session
|
|
8
|
+
* lifetime, so expiry is enforced at most `SESSION_EMAIL_TTL_MS` late — a 30-day
|
|
9
|
+
* boundary observed up to 15s late changes nothing.
|
|
10
|
+
*
|
|
11
|
+
* ONLY successful resolutions belong in here. A `null` — no such token, expired,
|
|
12
|
+
* revoked — must stay uncached so a sign-out or a token that has just been
|
|
13
|
+
* written is never answered from a stale negative, and so an auth failure can
|
|
14
|
+
* never be made sticky by a cache.
|
|
15
|
+
*
|
|
16
|
+
* Lives in its own module rather than in `auth.ts` because `auth.ts` imports
|
|
17
|
+
* `org/auth-policy.ts`, which also revokes sessions and so needs to invalidate
|
|
18
|
+
* this. Importing back into `auth.ts` from there would be a cycle.
|
|
19
|
+
*/
|
|
20
|
+
const SESSION_EMAIL_TTL_MS = 15_000;
|
|
21
|
+
const sessionEmailCache = createTtlCache({
|
|
22
|
+
ttlMs: SESSION_EMAIL_TTL_MS,
|
|
23
|
+
maxEntries: 4_096,
|
|
24
|
+
});
|
|
25
|
+
export function getCachedSessionEmail(token) {
|
|
26
|
+
return sessionEmailCache.get(token);
|
|
27
|
+
}
|
|
28
|
+
export function setCachedSessionEmail(token, email) {
|
|
29
|
+
sessionEmailCache.set(token, email);
|
|
30
|
+
}
|
|
31
|
+
export function forgetCachedSessionEmail(token) {
|
|
32
|
+
sessionEmailCache.delete(token);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Drop cached session resolutions. Call after ANY write to `sessions`.
|
|
36
|
+
*
|
|
37
|
+
* Clears every token rather than one: the bulk sign-out paths delete by email
|
|
38
|
+
* (`DELETE FROM sessions WHERE email = ?`) and do not know which tokens they
|
|
39
|
+
* removed, so a per-token eviction there would leave a signed-out session
|
|
40
|
+
* resolving for the rest of the TTL.
|
|
41
|
+
*/
|
|
42
|
+
export function invalidateSessionEmailCache() {
|
|
43
|
+
sessionEmailCache.clear();
|
|
44
|
+
}
|
|
45
|
+
/** Test seam — the cache is module state, so suites must be able to clear it. */
|
|
46
|
+
export function __resetSessionEmailCacheForTests() {
|
|
47
|
+
sessionEmailCache.clear();
|
|
48
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type SweepRuntimeEnvKey = "AGENT_NATIVE_DISABLE_INPROCESS_SWEEPS";
|
|
2
|
+
type SweepRuntimeEnv = Partial<Record<SweepRuntimeEnvKey, string | undefined>>;
|
|
3
|
+
/**
|
|
4
|
+
* Kill switch for the `setInterval` backstop sweeps.
|
|
5
|
+
*
|
|
6
|
+
* These timers are correct on a long-lived Node server: one process, one timer
|
|
7
|
+
* set, one query per tick. On a serverless host they are billed per WARM
|
|
8
|
+
* CONTAINER, not per deployment — every warm instance independently runs the
|
|
9
|
+
* whole timer set, so the sweep query rate scales with instance count, which no
|
|
10
|
+
* in-process backoff or `lastSweep` guard can observe. A production sample
|
|
11
|
+
* showed one 2-minute sweep issuing 237k queries, implying ~11 containers each
|
|
12
|
+
* ticking forever against a database that then never gets to autosuspend.
|
|
13
|
+
*
|
|
14
|
+
* Default OFF (sweeps run), because on every host where the timers are the only
|
|
15
|
+
* thing driving recovery, disabling them silently strands queued work. Turn this
|
|
16
|
+
* on ONLY where a durable platform scheduler already drives the same recovery —
|
|
17
|
+
* the shape `isNetlifyRecurringJobsRuntime` + the emitted scheduled sweep
|
|
18
|
+
* already implement for recurring jobs, and which these three sweeps should
|
|
19
|
+
* eventually adopt instead of needing this switch at all.
|
|
20
|
+
*/
|
|
21
|
+
export declare function shouldDisableInProcessSweeps(env?: SweepRuntimeEnv): boolean;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Gating for the in-process backstop sweep timers (automation redispatch,
|
|
3
|
+
// agent-teams orphan reconciliation, durable sandbox executions).
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
function isTruthyEnv(value) {
|
|
6
|
+
return /^(1|true|yes|on)$/i.test(value?.trim() ?? "");
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Kill switch for the `setInterval` backstop sweeps.
|
|
10
|
+
*
|
|
11
|
+
* These timers are correct on a long-lived Node server: one process, one timer
|
|
12
|
+
* set, one query per tick. On a serverless host they are billed per WARM
|
|
13
|
+
* CONTAINER, not per deployment — every warm instance independently runs the
|
|
14
|
+
* whole timer set, so the sweep query rate scales with instance count, which no
|
|
15
|
+
* in-process backoff or `lastSweep` guard can observe. A production sample
|
|
16
|
+
* showed one 2-minute sweep issuing 237k queries, implying ~11 containers each
|
|
17
|
+
* ticking forever against a database that then never gets to autosuspend.
|
|
18
|
+
*
|
|
19
|
+
* Default OFF (sweeps run), because on every host where the timers are the only
|
|
20
|
+
* thing driving recovery, disabling them silently strands queued work. Turn this
|
|
21
|
+
* on ONLY where a durable platform scheduler already drives the same recovery —
|
|
22
|
+
* the shape `isNetlifyRecurringJobsRuntime` + the emitted scheduled sweep
|
|
23
|
+
* already implement for recurring jobs, and which these three sweeps should
|
|
24
|
+
* eventually adopt instead of needing this switch at all.
|
|
25
|
+
*/
|
|
26
|
+
export function shouldDisableInProcessSweeps(env = process.env) {
|
|
27
|
+
return isTruthyEnv(env.AGENT_NATIVE_DISABLE_INPROCESS_SWEEPS);
|
|
28
|
+
}
|
package/dist/settings/store.js
CHANGED
|
@@ -25,6 +25,24 @@ function requestSettingsCache() {
|
|
|
25
25
|
}
|
|
26
26
|
return cache;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Per-request memo of the WHOLE settings table for `getAllSettings`.
|
|
30
|
+
*
|
|
31
|
+
* The single-key path above was already request-cached; the full-table read was
|
|
32
|
+
* not, and production showed 98,479 of them — an entire `SELECT key, value FROM
|
|
33
|
+
* settings` per call, several times per request via the MCP client routes and
|
|
34
|
+
* org settings.
|
|
35
|
+
*
|
|
36
|
+
* Holds raw JSON strings, like the single-key cache, so callers can't mutate a
|
|
37
|
+
* shared parsed object. Dropped by every write path, because a request that
|
|
38
|
+
* writes a setting and then re-reads all of them must see its own write.
|
|
39
|
+
*/
|
|
40
|
+
const _requestAllSettingsCache = new WeakMap();
|
|
41
|
+
function invalidateRequestAllSettings() {
|
|
42
|
+
const ctx = getRequestContext();
|
|
43
|
+
if (ctx && typeof ctx === "object")
|
|
44
|
+
_requestAllSettingsCache.delete(ctx);
|
|
45
|
+
}
|
|
28
46
|
// Created lazily so this module can be evaluated in the browser dev graph
|
|
29
47
|
// without a top-level `new EventEmitter()` tripping Vite's externalized
|
|
30
48
|
// `node:events` stub. The emitter drives server-side SSE fan-out only.
|
|
@@ -152,6 +170,7 @@ export async function mutateSetting(key, updater, options) {
|
|
|
152
170
|
if (result.rowsAffected === 0)
|
|
153
171
|
continue;
|
|
154
172
|
requestSettingsCache()?.set(key, nextRaw);
|
|
173
|
+
invalidateRequestAllSettings();
|
|
155
174
|
settingsEmitter().emit("settings", {
|
|
156
175
|
source: "settings",
|
|
157
176
|
type: "change",
|
|
@@ -173,6 +192,7 @@ export async function putSetting(key, value, options) {
|
|
|
173
192
|
args: [key, JSON.stringify(value), Date.now()],
|
|
174
193
|
});
|
|
175
194
|
requestSettingsCache()?.set(key, JSON.stringify(value));
|
|
195
|
+
invalidateRequestAllSettings();
|
|
176
196
|
settingsEmitter().emit("settings", {
|
|
177
197
|
source: "settings",
|
|
178
198
|
type: "change",
|
|
@@ -189,6 +209,7 @@ export async function deleteSetting(key, options) {
|
|
|
189
209
|
args: [key],
|
|
190
210
|
});
|
|
191
211
|
requestSettingsCache()?.set(key, null);
|
|
212
|
+
invalidateRequestAllSettings();
|
|
192
213
|
if (result.rowsAffected > 0) {
|
|
193
214
|
settingsEmitter().emit("settings", {
|
|
194
215
|
source: "settings",
|
|
@@ -215,6 +236,7 @@ export async function deleteSettingsByPrefix(prefix, options) {
|
|
|
215
236
|
args: [`${escaped}%`],
|
|
216
237
|
});
|
|
217
238
|
requestSettingsCache()?.clear();
|
|
239
|
+
invalidateRequestAllSettings();
|
|
218
240
|
if (result.rowsAffected > 0) {
|
|
219
241
|
settingsEmitter().emit("settings", {
|
|
220
242
|
source: "settings",
|
|
@@ -226,13 +248,47 @@ export async function deleteSettingsByPrefix(prefix, options) {
|
|
|
226
248
|
return result.rowsAffected;
|
|
227
249
|
}
|
|
228
250
|
export async function getAllSettings() {
|
|
229
|
-
await
|
|
230
|
-
const client = getDbExec();
|
|
231
|
-
const table = settingsTable();
|
|
232
|
-
const { rows } = await client.execute(`SELECT key, value FROM ${table}`);
|
|
251
|
+
const raw = await loadAllSettingsRaw();
|
|
233
252
|
const result = {};
|
|
234
|
-
for (const
|
|
235
|
-
result[
|
|
236
|
-
}
|
|
253
|
+
for (const [key, value] of raw)
|
|
254
|
+
result[key] = JSON.parse(value);
|
|
237
255
|
return result;
|
|
238
256
|
}
|
|
257
|
+
async function loadAllSettingsRaw() {
|
|
258
|
+
const ctx = getRequestContext();
|
|
259
|
+
const cached = ctx && typeof ctx === "object"
|
|
260
|
+
? _requestAllSettingsCache.get(ctx)
|
|
261
|
+
: undefined;
|
|
262
|
+
if (cached)
|
|
263
|
+
return cached;
|
|
264
|
+
const load = (async () => {
|
|
265
|
+
await ensureTable();
|
|
266
|
+
const client = getDbExec();
|
|
267
|
+
const table = settingsTable();
|
|
268
|
+
const { rows } = await client.execute(`SELECT key, value FROM ${table}`);
|
|
269
|
+
const raw = new Map();
|
|
270
|
+
for (const row of rows)
|
|
271
|
+
raw.set(row.key, row.value);
|
|
272
|
+
// Seed the single-key cache so a `getSetting` after `getAllSettings` in the
|
|
273
|
+
// same request is free rather than another round trip. Only fills keys it
|
|
274
|
+
// does not already hold: a value written earlier in this request is already
|
|
275
|
+
// written through there and must win over this snapshot.
|
|
276
|
+
const perKey = requestSettingsCache();
|
|
277
|
+
if (perKey) {
|
|
278
|
+
for (const [key, value] of raw) {
|
|
279
|
+
if (!perKey.has(key))
|
|
280
|
+
perKey.set(key, value);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return raw;
|
|
284
|
+
})();
|
|
285
|
+
if (ctx && typeof ctx === "object") {
|
|
286
|
+
// Evicted on failure so one transient error is not memoized as the answer
|
|
287
|
+
// for the rest of the request.
|
|
288
|
+
_requestAllSettingsCache.set(ctx, load.catch((err) => {
|
|
289
|
+
_requestAllSettingsCache.delete(ctx);
|
|
290
|
+
throw err;
|
|
291
|
+
}));
|
|
292
|
+
}
|
|
293
|
+
return load;
|
|
294
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded process-level TTL cache for answers that are expensive to re-fetch
|
|
3
|
+
* and safe to serve slightly stale.
|
|
4
|
+
*
|
|
5
|
+
* ## Which cache do I want?
|
|
6
|
+
*
|
|
7
|
+
* This repo has exactly two general caching patterns. Reach for an existing one
|
|
8
|
+
* rather than hand-rolling a third `Map` with timestamps in it:
|
|
9
|
+
*
|
|
10
|
+
* 1. **Request-scoped** — `WeakMap<RequestContext, Map<...>>`, freed with the
|
|
11
|
+
* request. Use when repeated reads within ONE request must collapse but no
|
|
12
|
+
* answer may outlive it. See `settings/store.ts` (`requestSettingsCache`),
|
|
13
|
+
* `org/request-org-cache.ts`, `secrets/storage.ts`.
|
|
14
|
+
* 2. **Process-scoped with a TTL** — this module. Use when every request
|
|
15
|
+
* re-reads the same value and staleness bounded by the TTL is acceptable.
|
|
16
|
+
* Every consumer must also wire an invalidation path for the writes that
|
|
17
|
+
* change the answer; the TTL is a backstop, not the correctness story.
|
|
18
|
+
*
|
|
19
|
+
* Two caches deliberately do NOT use this and should stay as they are, because
|
|
20
|
+
* flattening them into this shape would lose behavior they depend on:
|
|
21
|
+
*
|
|
22
|
+
* - `server/poll.ts`'s collab access cache — different TTL per outcome
|
|
23
|
+
* (allow vs deny), a per-resource invalidation epoch, and in-flight request
|
|
24
|
+
* dedup.
|
|
25
|
+
* - `demo/redact.ts` — a bidirectional memo (forward map plus a produced-value
|
|
26
|
+
* reverse index) with refresh-on-read recency, needed for round-trip
|
|
27
|
+
* stability.
|
|
28
|
+
*
|
|
29
|
+
* The per-request memos elsewhere in this codebase (`event.context` caches,
|
|
30
|
+
* `requestSettingsCache`) collapse repeated reads WITHIN one request. They do
|
|
31
|
+
* nothing for a value every request re-reads identically — a session token's
|
|
32
|
+
* email, an account's org memberships — which on a remote Postgres is a round
|
|
33
|
+
* trip per request per value, forever.
|
|
34
|
+
*
|
|
35
|
+
* ## Only cache successful reads
|
|
36
|
+
*
|
|
37
|
+
* Never `set` a value that stands for "I could not read this". A cached
|
|
38
|
+
* "unreadable" is indistinguishable from a cached "absent" to every later
|
|
39
|
+
* caller, which is exactly how a transient blip turns into a
|
|
40
|
+
* permanent-looking wrong answer. Leave failures on the uncached path so they
|
|
41
|
+
* stay loud and retryable.
|
|
42
|
+
*
|
|
43
|
+
* ## Bounded on purpose
|
|
44
|
+
*
|
|
45
|
+
* Keys here are user-supplied (session tokens, emails), so an unbounded map is
|
|
46
|
+
* a memory leak that grows with traffic. `maxEntries` evicts oldest-written
|
|
47
|
+
* first once the cap is reached.
|
|
48
|
+
*/
|
|
49
|
+
export interface TtlCache<V> {
|
|
50
|
+
get(key: string): V | undefined;
|
|
51
|
+
set(key: string, value: V): void;
|
|
52
|
+
delete(key: string): void;
|
|
53
|
+
clear(): void;
|
|
54
|
+
readonly size: number;
|
|
55
|
+
}
|
|
56
|
+
export declare function createTtlCache<V>(options: {
|
|
57
|
+
ttlMs: number;
|
|
58
|
+
maxEntries: number;
|
|
59
|
+
}): TtlCache<V>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function createTtlCache(options) {
|
|
2
|
+
const { ttlMs, maxEntries } = options;
|
|
3
|
+
if (!(ttlMs > 0)) {
|
|
4
|
+
throw new Error(`createTtlCache: ttlMs must be positive, got ${ttlMs}`);
|
|
5
|
+
}
|
|
6
|
+
if (!(maxEntries > 0)) {
|
|
7
|
+
throw new Error(`createTtlCache: maxEntries must be positive, got ${maxEntries}`);
|
|
8
|
+
}
|
|
9
|
+
const entries = new Map();
|
|
10
|
+
return {
|
|
11
|
+
get(key) {
|
|
12
|
+
const hit = entries.get(key);
|
|
13
|
+
if (!hit)
|
|
14
|
+
return undefined;
|
|
15
|
+
if (hit.expiresAt <= Date.now()) {
|
|
16
|
+
entries.delete(key);
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
return hit.value;
|
|
20
|
+
},
|
|
21
|
+
set(key, value) {
|
|
22
|
+
// Delete before insert so Map insertion order tracks the newest write,
|
|
23
|
+
// which is what the eviction loop below reads as "oldest".
|
|
24
|
+
entries.delete(key);
|
|
25
|
+
entries.set(key, { value, expiresAt: Date.now() + ttlMs });
|
|
26
|
+
while (entries.size > maxEntries) {
|
|
27
|
+
const oldest = entries.keys().next();
|
|
28
|
+
if (oldest.done)
|
|
29
|
+
break;
|
|
30
|
+
entries.delete(oldest.value);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
delete(key) {
|
|
34
|
+
entries.delete(key);
|
|
35
|
+
},
|
|
36
|
+
clear() {
|
|
37
|
+
entries.clear();
|
|
38
|
+
},
|
|
39
|
+
get size() {
|
|
40
|
+
return entries.size;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -12,9 +12,9 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
12
12
|
orgId?: undefined;
|
|
13
13
|
role?: undefined;
|
|
14
14
|
} | {
|
|
15
|
-
ownerEmail:
|
|
16
|
-
orgId:
|
|
17
|
-
visibility:
|
|
15
|
+
ownerEmail: string;
|
|
16
|
+
orgId: string;
|
|
17
|
+
visibility: import("../schema.js").Visibility;
|
|
18
18
|
role: "owner" | import("../schema.js").ShareRole;
|
|
19
19
|
shares: any;
|
|
20
20
|
policy: {
|
|
@@ -43,7 +43,10 @@ export default defineAction({
|
|
|
43
43
|
allowPublic: reg.allowPublic !== false,
|
|
44
44
|
requireOrgMemberForUserShares: reg.requireOrgMemberForUserShares === true,
|
|
45
45
|
};
|
|
46
|
-
|
|
46
|
+
// Only `ownerEmail`/`orgId`/`visibility` are read below, all of which the
|
|
47
|
+
// projected load carries — so the share dialog never pulls a resource's
|
|
48
|
+
// body blob just to render who it is shared with.
|
|
49
|
+
const access = await resolveAccess(args.resourceType, args.resourceId, undefined, { skipResourceBody: true });
|
|
47
50
|
if (!access)
|
|
48
51
|
return { ownerEmail: null, visibility: null, shares: [], policy };
|
|
49
52
|
const db = reg.getDb();
|