@agent-native/core 0.70.3 → 0.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +51 -0
- package/corpus/core/docs/content/actions.md +23 -0
- package/corpus/core/docs/content/audit-log.md +111 -0
- package/corpus/core/docs/content/multi-app-workspace.md +2 -2
- package/corpus/core/docs/design/durable-agent-runs.md +458 -4
- package/corpus/core/package.json +2 -1
- package/corpus/core/src/action.ts +80 -1
- package/corpus/core/src/agent/durable-background.ts +192 -0
- package/corpus/core/src/agent/production-agent.ts +357 -13
- package/corpus/core/src/agent/run-manager.ts +66 -3
- package/corpus/core/src/agent/run-store.ts +143 -23
- package/corpus/core/src/agent/types.ts +20 -0
- package/corpus/core/src/application-state/store.ts +5 -0
- package/corpus/core/src/audit/actions/get-audit-event.ts +23 -0
- package/corpus/core/src/audit/actions/list-audit-events.ts +65 -0
- package/corpus/core/src/audit/cleanup-job.ts +100 -0
- package/corpus/core/src/audit/config.ts +91 -0
- package/corpus/core/src/audit/index.ts +43 -0
- package/corpus/core/src/audit/record.ts +143 -0
- package/corpus/core/src/audit/redact.ts +109 -0
- package/corpus/core/src/audit/store.ts +244 -0
- package/corpus/core/src/audit/types.ts +125 -0
- package/corpus/core/src/chat-threads/store.ts +10 -0
- package/corpus/core/src/client/AgentPanel.tsx +12 -9
- package/corpus/core/src/client/AssistantChat.tsx +6 -0
- package/corpus/core/src/client/blocks/library/FileTreeBlock.tsx +72 -14
- package/corpus/core/src/client/chat/repo-helpers.ts +38 -0
- package/corpus/core/src/db/client.ts +4 -0
- package/corpus/core/src/db/widen-columns.ts +75 -0
- package/corpus/core/src/deploy/build.ts +96 -0
- package/corpus/core/src/deploy/workspace-deploy.ts +121 -0
- package/corpus/core/src/mcp/build-server.ts +23 -13
- package/corpus/core/src/oauth-tokens/store.ts +6 -0
- package/corpus/core/src/provider-api/custom-registry.ts +8 -0
- package/corpus/core/src/resources/store.ts +10 -0
- package/corpus/core/src/scripts/runner.ts +6 -3
- package/corpus/core/src/server/action-discovery.ts +6 -0
- package/corpus/core/src/server/action-routes.ts +1 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +133 -66
- package/corpus/core/src/server/auth.ts +5 -0
- package/corpus/core/src/server/core-routes-plugin.ts +14 -0
- package/corpus/core/src/server/security-headers.ts +9 -6
- package/corpus/core/src/settings/store.ts +6 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/audit-log/SKILL.md +93 -0
- package/corpus/core/src/usage/store.ts +6 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +19 -0
- package/corpus/templates/analytics/AGENTS.md +8 -0
- package/corpus/templates/analytics/actions/compose-dashboard.ts +317 -0
- package/corpus/templates/analytics/changelog/2026-06-23-build-large-first-party-analytics-dashboards-in-one-fast-cal.md +6 -0
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +574 -0
- package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +60 -6
- package/corpus/templates/calendar/changelog/2026-06-23-added-a-heads-up-explaining-google-s-app-not-verified-screen.md +6 -0
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +20 -43
- package/corpus/templates/clips/app/components/recorder/recording-toolbar.tsx +5 -1
- package/corpus/templates/clips/app/lib/countdown-audio-cue.ts +3 -51
- package/corpus/templates/clips/changelog/2026-06-23-dragging-the-desktop-camera-bubble-now-glides-to-a-stop-at-t.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-public-clips-now-play-inline-in-slack-connect-a-workspac.md +6 -0
- package/corpus/templates/clips/chrome-extension/PERMISSIONS.md +72 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -10
- package/corpus/templates/clips/chrome-extension/src/background.ts +163 -32
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +301 -24
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +245 -67
- package/corpus/templates/clips/chrome-extension/src/overlay.css +77 -43
- package/corpus/templates/clips/chrome-extension/src/overlay.html +7 -1
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +102 -38
- package/corpus/templates/clips/chrome-extension/src/popup.html +62 -5
- package/corpus/templates/clips/chrome-extension/src/popup.ts +275 -4
- package/corpus/templates/clips/chrome-extension/src/styles.css +34 -0
- package/corpus/templates/clips/chrome-extension/vite.config.ts +5 -0
- package/corpus/templates/clips/desktop/src/lib/audio-cue.ts +21 -16
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +10 -17
- package/corpus/templates/clips/desktop/src/overlays/bubble.tsx +73 -23
- package/corpus/templates/clips/desktop/src/overlays/countdown.tsx +0 -6
- package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +56 -46
- package/corpus/templates/clips/desktop/src/styles.css +33 -5
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +113 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +3 -0
- package/corpus/templates/clips/server/lib/media-permissions.ts +5 -1
- package/corpus/templates/clips/shared/recording-audio.ts +62 -0
- package/corpus/templates/clips/shared/recording-core.ts +94 -0
- package/dist/action.d.ts +31 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +45 -1
- package/dist/action.js.map +1 -1
- package/dist/agent/context-xray/schema.d.ts +1 -1
- package/dist/agent/durable-background.d.ts +60 -0
- package/dist/agent/durable-background.d.ts.map +1 -0
- package/dist/agent/durable-background.js +144 -0
- package/dist/agent/durable-background.js.map +1 -0
- package/dist/agent/observational-memory/schema.d.ts +1 -1
- package/dist/agent/production-agent.d.ts +20 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +320 -14
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +48 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +45 -3
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +30 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +138 -24
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/types.d.ts +20 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +5 -0
- package/dist/application-state/store.js.map +1 -1
- package/dist/audit/actions/get-audit-event.d.ts +11 -0
- package/dist/audit/actions/get-audit-event.d.ts.map +1 -0
- package/dist/audit/actions/get-audit-event.js +22 -0
- package/dist/audit/actions/get-audit-event.js.map +1 -0
- package/dist/audit/actions/list-audit-events.d.ts +22 -0
- package/dist/audit/actions/list-audit-events.d.ts.map +1 -0
- package/dist/audit/actions/list-audit-events.js +61 -0
- package/dist/audit/actions/list-audit-events.js.map +1 -0
- package/dist/audit/cleanup-job.d.ts +12 -0
- package/dist/audit/cleanup-job.d.ts.map +1 -0
- package/dist/audit/cleanup-job.js +93 -0
- package/dist/audit/cleanup-job.js.map +1 -0
- package/dist/audit/config.d.ts +30 -0
- package/dist/audit/config.d.ts.map +1 -0
- package/dist/audit/config.js +65 -0
- package/dist/audit/config.js.map +1 -0
- package/dist/audit/index.d.ts +13 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +6 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/audit/record.d.ts +24 -0
- package/dist/audit/record.d.ts.map +1 -0
- package/dist/audit/record.js +106 -0
- package/dist/audit/record.js.map +1 -0
- package/dist/audit/redact.d.ts +28 -0
- package/dist/audit/redact.d.ts.map +1 -0
- package/dist/audit/redact.js +109 -0
- package/dist/audit/redact.js.map +1 -0
- package/dist/audit/store.d.ts +14 -0
- package/dist/audit/store.d.ts.map +1 -0
- package/dist/audit/store.js +219 -0
- package/dist/audit/store.js.map +1 -0
- package/dist/audit/types.d.ts +114 -0
- package/dist/audit/types.d.ts.map +1 -0
- package/dist/audit/types.js +15 -0
- package/dist/audit/types.js.map +1 -0
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +10 -0
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +2 -2
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +6 -1
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/blocks/library/FileTreeBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/FileTreeBlock.js +37 -4
- package/dist/client/blocks/library/FileTreeBlock.js.map +1 -1
- package/dist/client/chat/repo-helpers.d.ts +16 -0
- package/dist/client/chat/repo-helpers.d.ts.map +1 -1
- package/dist/client/chat/repo-helpers.js +40 -0
- package/dist/client/chat/repo-helpers.js.map +1 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +3 -0
- package/dist/db/client.js.map +1 -1
- package/dist/db/widen-columns.d.ts +39 -0
- package/dist/db/widen-columns.d.ts.map +1 -0
- package/dist/db/widen-columns.js +73 -0
- package/dist/db/widen-columns.js.map +1 -0
- package/dist/deploy/build.d.ts +29 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +85 -0
- package/dist/deploy/build.js.map +1 -1
- package/dist/deploy/workspace-deploy.d.ts.map +1 -1
- package/dist/deploy/workspace-deploy.js +108 -0
- package/dist/deploy/workspace-deploy.js.map +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +24 -9
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/oauth-tokens/store.d.ts.map +1 -1
- package/dist/oauth-tokens/store.js +6 -0
- package/dist/oauth-tokens/store.js.map +1 -1
- package/dist/provider-api/custom-registry.d.ts.map +1 -1
- package/dist/provider-api/custom-registry.js +8 -0
- package/dist/provider-api/custom-registry.js.map +1 -1
- package/dist/resources/store.d.ts.map +1 -1
- package/dist/resources/store.js +9 -0
- package/dist/resources/store.js.map +1 -1
- package/dist/scripts/runner.js +4 -3
- package/dist/scripts/runner.js.map +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +6 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +1 -0
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +68 -13
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +5 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +13 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/security-headers.d.ts +8 -5
- package/dist/server/security-headers.d.ts.map +1 -1
- package/dist/server/security-headers.js +9 -6
- package/dist/server/security-headers.js.map +1 -1
- package/dist/settings/store.d.ts.map +1 -1
- package/dist/settings/store.js +6 -0
- package/dist/settings/store.js.map +1 -1
- package/dist/templates/workspace-core/.agents/skills/audit-log/SKILL.md +93 -0
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +5 -0
- package/dist/usage/store.js.map +1 -1
- package/docs/content/actions.md +23 -0
- package/docs/content/audit-log.md +111 -0
- package/docs/content/multi-app-workspace.md +2 -2
- package/docs/design/durable-agent-runs.md +458 -4
- package/package.json +2 -1
- package/src/templates/workspace-core/.agents/skills/audit-log/SKILL.md +93 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileTreeBlock.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/FileTreeBlock.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAEV,YAAY,EAEb,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"FileTreeBlock.d.ts","sourceRoot":"","sources":["../../../../src/client/blocks/library/FileTreeBlock.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAEV,YAAY,EAEb,MAAM,uBAAuB,CAAC;AA0R/B;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,OAAO,EACP,KAAK,EACL,OAAO,EACP,GAAG,GACJ,EAAE,cAAc,CAAC,YAAY,CAAC,2CA6S9B;AAMD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,QAAQ,EACR,QAAQ,GACT,EAAE,cAAc,CAAC,YAAY,CAAC,2CAgI9B"}
|
|
@@ -93,8 +93,14 @@ function buildTree(entries) {
|
|
|
93
93
|
const segments = entry.path.split("/").filter(Boolean);
|
|
94
94
|
if (segments.length === 0)
|
|
95
95
|
return;
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
// A trailing slash marks a DIRECTORY entry: every segment is a folder and
|
|
97
|
+
// the entry's note/metadata attaches to the deepest folder (the last segment
|
|
98
|
+
// does NOT become a file leaf). Because folders are keyed by name, this also
|
|
99
|
+
// merges with any folder the sibling file paths already implied — so
|
|
100
|
+
// `packages/shared/` and `packages/shared/src/…` collapse onto one `shared`
|
|
101
|
+
// folder instead of a duplicate folder + file pair.
|
|
102
|
+
const isDir = /\/\s*$/.test(entry.path);
|
|
103
|
+
const folderSegments = isDir ? segments : segments.slice(0, -1);
|
|
98
104
|
let cursor = root;
|
|
99
105
|
let prefix = "";
|
|
100
106
|
for (const segment of folderSegments) {
|
|
@@ -107,6 +113,16 @@ function buildTree(entries) {
|
|
|
107
113
|
}
|
|
108
114
|
cursor = next;
|
|
109
115
|
}
|
|
116
|
+
if (isDir) {
|
|
117
|
+
// Attach metadata to the deepest folder (first declaration wins, so an
|
|
118
|
+
// explicit note isn't clobbered by a later bare prefix re-declaration).
|
|
119
|
+
if (!cursor.entry) {
|
|
120
|
+
cursor.entry = entry;
|
|
121
|
+
cursor.index = index;
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const fileName = segments[segments.length - 1];
|
|
110
126
|
cursor.files.push({
|
|
111
127
|
kind: "file",
|
|
112
128
|
name: fileName,
|
|
@@ -128,6 +144,8 @@ function buildTree(entries) {
|
|
|
128
144
|
name: child.name,
|
|
129
145
|
path: child.path,
|
|
130
146
|
children: materialize(child),
|
|
147
|
+
entry: child.entry,
|
|
148
|
+
index: child.index,
|
|
131
149
|
});
|
|
132
150
|
}
|
|
133
151
|
else {
|
|
@@ -148,7 +166,14 @@ function compactFolderNode(folder) {
|
|
|
148
166
|
const names = [folder.name];
|
|
149
167
|
let path = folder.path;
|
|
150
168
|
let children = folder.children;
|
|
151
|
-
|
|
169
|
+
// Collapse single-child folder chains (a/b/c) into one row — but never across
|
|
170
|
+
// a folder that carries its own directory note, so an explicitly authored
|
|
171
|
+
// directory (e.g. `packages/shared/` with a note) keeps its own row instead of
|
|
172
|
+
// being folded into its parent or child and losing the note.
|
|
173
|
+
while (!folder.entry &&
|
|
174
|
+
children.length === 1 &&
|
|
175
|
+
children[0]?.kind === "folder" &&
|
|
176
|
+
!children[0].entry) {
|
|
152
177
|
const child = children[0];
|
|
153
178
|
names.push(child.name);
|
|
154
179
|
path = child.path;
|
|
@@ -158,6 +183,8 @@ function compactFolderNode(folder) {
|
|
|
158
183
|
kind: "folder",
|
|
159
184
|
name: names.join("/"),
|
|
160
185
|
path,
|
|
186
|
+
entry: folder.entry,
|
|
187
|
+
index: folder.index,
|
|
161
188
|
children: compactTree(children),
|
|
162
189
|
};
|
|
163
190
|
}
|
|
@@ -247,7 +274,13 @@ export function FileTreeRead({ data, blockId, title, summary, ctx, }) {
|
|
|
247
274
|
const indent = depth * INDENT_STEP;
|
|
248
275
|
if (node.kind === "folder") {
|
|
249
276
|
const collapsed = collapsedFolders[node.path] ?? false;
|
|
250
|
-
|
|
277
|
+
// An explicit directory entry (trailing-slash path) can be a leaf folder
|
|
278
|
+
// with no children — e.g. `apps/mail/`. It still renders as a folder, just
|
|
279
|
+
// without a toggle chevron. Its authored note shows inline like a file's.
|
|
280
|
+
const expandable = node.children.length > 0;
|
|
281
|
+
const note = node.entry?.note?.trim();
|
|
282
|
+
const open = expandable && !collapsed;
|
|
283
|
+
return (_jsx("div", { children: _jsxs("button", { type: "button", "data-plan-interactive": true, disabled: !expandable, "aria-expanded": expandable ? !collapsed : undefined, onClick: expandable ? () => toggleFolder(node.path) : undefined, style: { paddingLeft: indent + 8 }, className: cn("flex w-full items-center gap-1.5 rounded-md py-1 pr-2 text-left text-[13px] transition-colors", expandable ? "hover:bg-accent/40" : "cursor-default"), children: [expandable ? (_jsx(IconChevronRight, { className: cn("size-3.5 shrink-0 text-plan-muted transition-transform", !collapsed && "rotate-90") })) : (_jsx("span", { className: "size-3.5 shrink-0", "aria-hidden": true })), open ? (_jsx(IconFolderOpen, { className: "size-4 shrink-0 text-plan-muted" })) : (_jsx(IconFolder, { className: "size-4 shrink-0 text-plan-muted" })), _jsx("span", { className: "min-w-0 truncate font-medium text-plan-text", children: node.name }), note && (_jsx("span", { className: "ml-1 min-w-0 flex-1 truncate text-xs text-plan-muted", children: note }))] }) }, `d:${node.path}`));
|
|
251
284
|
}
|
|
252
285
|
const { entry } = node;
|
|
253
286
|
const change = entry.change;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileTreeBlock.js","sourceRoot":"","sources":["../../../../src/client/blocks/library/FileTreeBlock.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n IconChevronRight,\n IconFile,\n IconFolder,\n IconFolderOpen,\n IconPlus,\n IconTrash,\n} from \"@tabler/icons-react\";\nimport { cn } from \"../../utils.js\";\nimport { ltrCodeBlockProps } from \"../code-block-direction.js\";\nimport type { BlockEditProps, BlockReadProps } from \"../types.js\";\nimport type {\n FileTreeChange,\n FileTreeData,\n FileTreeEntry,\n} from \"./file-tree.config.js\";\nimport { FILE_TREE_CHANGES } from \"./file-tree.config.js\";\nimport { DevInput, DevTextarea, DevSelect } from \"./dev-doc-ui.js\";\n\n/**\n * Read + Edit renderers for a `file-tree` block — a VS Code / GitHub-explorer\n * file and change tree. Lives in core so any app can register the dev-doc block\n * (no shadcn import; the editor's enum picker is the core `DevSelect`).\n */\n\n/* ── Theme-aware change tokens ─────────────────────────────────────────────── */\n\n/**\n * Change-badge palette. Tinted background + saturated text in BOTH the `.dark`\n * plan theme and light mode (never a dark-only palette). Each entry keeps legible\n * contrast against the plan surface via Tailwind `dark:` variants.\n */\nconst CHANGE_BADGE: Record<FileTreeChange, string> = {\n added:\n \"bg-emerald-100 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300\",\n modified: \"bg-blue-100 text-blue-700 dark:bg-blue-500/15 dark:text-blue-300\",\n removed: \"bg-red-100 text-red-700 dark:bg-red-500/15 dark:text-red-300\",\n renamed:\n \"bg-violet-100 text-violet-700 dark:bg-violet-500/15 dark:text-violet-300\",\n};\n\n/** Single-letter glyph shown in the change badge (VS Code gutter convention). */\nconst CHANGE_GLYPH: Record<FileTreeChange, string> = {\n added: \"A\",\n modified: \"M\",\n removed: \"D\",\n renamed: \"R\",\n};\n\n/** Accent ink for the file name itself, echoing its change color. */\nconst CHANGE_NAME_INK: Record<FileTreeChange, string> = {\n added: \"text-emerald-700 dark:text-emerald-300\",\n modified: \"text-blue-700 dark:text-blue-300\",\n removed: \"text-red-600 line-through dark:text-red-300\",\n renamed: \"text-violet-700 dark:text-violet-300\",\n};\n\nconst CHANGE_LABEL: Record<FileTreeChange, string> = {\n added: \"Added\",\n modified: \"Modified\",\n removed: \"Removed\",\n renamed: \"Renamed\",\n};\n\n/** Infer a fence language for a file's snippet from its `language` or extension. */\nfunction fenceLanguage(entry: FileTreeEntry): string {\n if (entry.language?.trim()) return entry.language.trim();\n const ext = entry.path.split(\".\").pop()?.toLowerCase() ?? \"\";\n const byExt: Record<string, string> = {\n ts: \"ts\",\n tsx: \"tsx\",\n js: \"js\",\n jsx: \"jsx\",\n mjs: \"js\",\n cjs: \"js\",\n json: \"json\",\n css: \"css\",\n scss: \"scss\",\n html: \"html\",\n md: \"md\",\n mdx: \"md\",\n py: \"python\",\n rb: \"ruby\",\n go: \"go\",\n rs: \"rust\",\n sql: \"sql\",\n sh: \"bash\",\n yml: \"yaml\",\n yaml: \"yaml\",\n toml: \"toml\",\n };\n return byExt[ext] ?? \"text\";\n}\n\n/** Wrap a raw snippet in a fenced code block for `ctx.renderMarkdown`. */\nfunction fence(snippet: string, language: string): string {\n // Never let the snippet's own content break out of the fence.\n const safe = snippet.replace(/```/g, \"ʼʼʼ\");\n return `\\`\\`\\`${language}\\n${safe.replace(/\\s+$/, \"\")}\\n\\`\\`\\``;\n}\n\n/* ── Tree construction (flat paths → nested folders) ───────────────────────── */\n\ninterface FileLeaf {\n kind: \"file\";\n /** Last path segment. */\n name: string;\n /** Full slash path, used as a stable key + anchor. */\n path: string;\n entry: FileTreeEntry;\n /** Index in the original flat `entries` (stable per-file disclosure key). */\n index: number;\n}\n\ninterface FolderNode {\n kind: \"folder\";\n name: string;\n /** Full slash path of the folder, used as a stable key. */\n path: string;\n children: TreeNode[];\n}\n\ntype TreeNode = FolderNode | FileLeaf;\n\ninterface VisibleTreeRow {\n node: TreeNode;\n depth: number;\n}\n\n/** A folder being assembled while we walk the paths (children keyed by name). */\ninterface FolderBuild {\n name: string;\n path: string;\n folders: Map<string, FolderBuild>;\n files: FileLeaf[];\n /** Insertion order of child names (folders + files) for stable rendering. */\n order: string[];\n}\n\nfunction makeFolder(name: string, path: string): FolderBuild {\n return { name, path, folders: new Map(), files: [], order: [] };\n}\n\n/**\n * Build a nested folder tree from the flat `entries`. Folders are derived purely\n * from the slash segments of each `path`; a single-segment path is a root file.\n * Insertion order is preserved within each folder, with folders sorted before\n * files at each level (the conventional explorer ordering).\n */\nfunction buildTree(entries: FileTreeEntry[]): TreeNode[] {\n const root = makeFolder(\"\", \"\");\n\n entries.forEach((entry, index) => {\n const segments = entry.path.split(\"/\").filter(Boolean);\n if (segments.length === 0) return;\n const fileName = segments[segments.length - 1] as string;\n const folderSegments = segments.slice(0, -1);\n\n let cursor = root;\n let prefix = \"\";\n for (const segment of folderSegments) {\n prefix = prefix ? `${prefix}/${segment}` : segment;\n let next = cursor.folders.get(segment);\n if (!next) {\n next = makeFolder(segment, prefix);\n cursor.folders.set(segment, next);\n cursor.order.push(`d:${segment}`);\n }\n cursor = next;\n }\n\n cursor.files.push({\n kind: \"file\",\n name: fileName,\n path: entry.path,\n entry,\n index,\n });\n cursor.order.push(`f:${cursor.files.length - 1}`);\n });\n\n const materialize = (folder: FolderBuild): TreeNode[] => {\n const nodes: TreeNode[] = [];\n for (const key of folder.order) {\n if (key.startsWith(\"d:\")) {\n const child = folder.folders.get(key.slice(2));\n if (!child) continue;\n nodes.push({\n kind: \"folder\",\n name: child.name,\n path: child.path,\n children: materialize(child),\n });\n } else {\n const file = folder.files[Number(key.slice(2))];\n if (file) nodes.push(file);\n }\n }\n // Folders before files at this level (standard explorer ordering).\n return [\n ...nodes.filter((node) => node.kind === \"folder\"),\n ...nodes.filter((node) => node.kind === \"file\"),\n ];\n };\n\n return materialize(root);\n}\n\nfunction compactFolderNode(folder: FolderNode): FolderNode {\n const names = [folder.name];\n let path = folder.path;\n let children = folder.children;\n\n while (children.length === 1 && children[0]?.kind === \"folder\") {\n const child = children[0];\n names.push(child.name);\n path = child.path;\n children = child.children;\n }\n\n return {\n kind: \"folder\",\n name: names.join(\"/\"),\n path,\n children: compactTree(children),\n };\n}\n\nfunction compactTree(nodes: TreeNode[]): TreeNode[] {\n return nodes.map((node) =>\n node.kind === \"folder\" ? compactFolderNode(node) : node,\n );\n}\n\nfunction flattenVisibleRows(\n nodes: TreeNode[],\n collapsedFolders: Record<string, boolean>,\n depth = 0,\n): VisibleTreeRow[] {\n const rows: VisibleTreeRow[] = [];\n\n for (const node of nodes) {\n rows.push({ node, depth });\n if (node.kind === \"folder\" && !(collapsedFolders[node.path] ?? false)) {\n rows.push(\n ...flattenVisibleRows(node.children, collapsedFolders, depth + 1),\n );\n }\n }\n\n return rows;\n}\n\n/* ── Read (IDE explorer) ───────────────────────────────────────────────────── */\n\nconst INDENT_STEP = 14; // px per nesting level — the explorer guide spacing.\nconst DEFAULT_VISIBLE_TREE_ROWS = 10;\n\n/**\n * Read-only renderer for a `file-tree` block — a VS Code / GitHub-explorer file\n * and change tree. The flat `entries` are folded into a nested tree of\n * collapsible folders (IconFolder + chevron) and files (IconFile) carrying a\n * single-letter change badge (A/M/D/R). A file with a `note` or `snippet` is\n * itself clickable and expands to show the note plus the snippet rendered as a\n * fenced code block via `ctx.renderMarkdown`. A summary header tallies the change\n * counts (\"+N · ~M · −K\"). Every color is theme-aware via Tailwind `dark:`\n * variants / plan CSS vars, so the tree reads correctly in both modes.\n */\nexport function FileTreeRead({\n data,\n blockId,\n title,\n summary,\n ctx,\n}: BlockReadProps<FileTreeData>) {\n const entries = data.entries ?? [];\n const tree = useMemo(() => compactTree(buildTree(entries)), [entries]);\n\n // Folders default to fully expanded so the tree is useful at a glance.\n const [collapsedFolders, setCollapsedFolders] = useState<\n Record<string, boolean>\n >({});\n const [showAllRows, setShowAllRows] = useState(false);\n // Files with a note/snippet collapse their detail by default (progressive\n // disclosure) — keyed by the flat entry index so duplicate names never clash.\n const [openFiles, setOpenFiles] = useState<Record<number, boolean>>({});\n\n const toggleFolder = (path: string) =>\n setCollapsedFolders((current) => ({\n ...current,\n [path]: !current[path],\n }));\n const toggleFile = (index: number) =>\n setOpenFiles((current) => ({ ...current, [index]: !current[index] }));\n\n // The recap \"Files touched\" left rail (only) widens into the document as a\n // flyout while the tree is the reader's active focus AND a file's detail is\n // open, then collapses back to a slim rail when they click elsewhere or close\n // the last open file. `data-files-expanded` on the root drives the rail width\n // via CSS (`:has()`); it is inert anywhere the tree renders inline.\n const rootRef = useRef<HTMLElement>(null);\n const [active, setActive] = useState(false);\n const anyFileOpen = useMemo(\n () => Object.values(openFiles).some(Boolean),\n [openFiles],\n );\n const railExpanded = active && anyFileOpen;\n\n useEffect(() => {\n if (!railExpanded) return;\n const onPointerDown = (event: PointerEvent) => {\n const root = rootRef.current;\n if (\n root &&\n event.target instanceof Node &&\n !root.contains(event.target)\n ) {\n // Clicking away collapses the rail and closes any open file detail, so\n // it returns to the clean slim state rather than leaving notes open in\n // the cramped width.\n setActive(false);\n setOpenFiles({});\n }\n };\n document.addEventListener(\"pointerdown\", onPointerDown, true);\n return () =>\n document.removeEventListener(\"pointerdown\", onPointerDown, true);\n }, [railExpanded]);\n\n // Change tally for the summary header.\n const counts = useMemo(() => {\n const tally = { added: 0, modified: 0, removed: 0, renamed: 0 };\n for (const entry of entries) {\n if (entry.change) tally[entry.change] += 1;\n }\n return tally;\n }, [entries]);\n const changeTotal =\n counts.added + counts.modified + counts.removed + counts.renamed;\n\n const visibleRows = useMemo(\n () => flattenVisibleRows(tree, collapsedFolders),\n [collapsedFolders, tree],\n );\n const shouldLimitRows = visibleRows.length > DEFAULT_VISIBLE_TREE_ROWS;\n const displayedRows =\n showAllRows || !shouldLimitRows\n ? visibleRows\n : visibleRows.slice(0, DEFAULT_VISIBLE_TREE_ROWS);\n\n const renderRow = ({ node, depth }: VisibleTreeRow): React.ReactNode => {\n const indent = depth * INDENT_STEP;\n if (node.kind === \"folder\") {\n const collapsed = collapsedFolders[node.path] ?? false;\n return (\n <div key={`d:${node.path}`}>\n <button\n type=\"button\"\n data-plan-interactive\n aria-expanded={!collapsed}\n onClick={() => toggleFolder(node.path)}\n style={{ paddingLeft: indent + 8 }}\n className=\"flex w-full items-center gap-1.5 rounded-md py-1 pr-2 text-left text-[13px] transition-colors hover:bg-accent/40\"\n >\n <IconChevronRight\n className={cn(\n \"size-3.5 shrink-0 text-plan-muted transition-transform\",\n !collapsed && \"rotate-90\",\n )}\n />\n {collapsed ? (\n <IconFolder className=\"size-4 shrink-0 text-plan-muted\" />\n ) : (\n <IconFolderOpen className=\"size-4 shrink-0 text-plan-muted\" />\n )}\n <span className=\"min-w-0 truncate font-medium text-plan-text\">\n {node.name}\n </span>\n </button>\n </div>\n );\n }\n\n const { entry } = node;\n const change = entry.change;\n const hasDetail = Boolean(entry.note?.trim() || entry.snippet?.trim());\n const isOpen = openFiles[node.index] ?? false;\n\n return (\n <div key={`f:${node.index}`}>\n <button\n type=\"button\"\n data-plan-interactive\n data-file-path={node.path}\n disabled={!hasDetail}\n aria-expanded={hasDetail ? isOpen : undefined}\n onClick={hasDetail ? () => toggleFile(node.index) : undefined}\n style={{ paddingLeft: indent + 8 }}\n className={cn(\n \"group flex w-full items-center gap-1.5 rounded-md py-1 pr-2 text-left text-[13px] transition-colors\",\n hasDetail ? \"hover:bg-accent/40\" : \"cursor-default\",\n )}\n >\n {/* Chevron slot — present only for files with expandable detail so\n everything stays aligned with the folder rows above. */}\n {hasDetail ? (\n <IconChevronRight\n className={cn(\n \"size-3.5 shrink-0 text-plan-muted transition-transform\",\n isOpen && \"rotate-90\",\n )}\n />\n ) : (\n <span className=\"size-3.5 shrink-0\" aria-hidden />\n )}\n <IconFile\n className={cn(\n \"size-4 shrink-0\",\n change === \"removed\" ? \"text-plan-muted\" : \"text-plan-muted/80\",\n )}\n />\n <span\n className={cn(\n \"min-w-0 truncate font-medium\",\n change ? CHANGE_NAME_INK[change] : \"text-plan-text\",\n )}\n >\n {node.name}\n </span>\n {change && (\n <span\n title={CHANGE_LABEL[change]}\n aria-label={CHANGE_LABEL[change]}\n className={cn(\n \"ml-1 flex size-4 shrink-0 items-center justify-center rounded text-[10px] font-bold leading-none\",\n CHANGE_BADGE[change],\n )}\n >\n {CHANGE_GLYPH[change]}\n </span>\n )}\n {entry.note?.trim() && !isOpen && (\n <span className=\"ml-1 min-w-0 flex-1 truncate text-xs text-plan-muted\">\n {entry.note}\n </span>\n )}\n </button>\n\n {/* Expanded file detail: the note + a fenced snippet. */}\n {hasDetail && isOpen && (\n <div\n style={{ paddingLeft: indent + 8 + 20 }}\n className=\"pb-2 pr-2 pt-0.5\"\n >\n {entry.note?.trim() && (\n <p className=\"text-xs leading-relaxed text-plan-muted\">\n {entry.note}\n </p>\n )}\n {entry.snippet?.trim() && (\n <div className=\"mt-2 an-file-tree-snippet\">\n {ctx.renderMarkdown?.(\n fence(entry.snippet, fenceLanguage(entry)),\n )}\n </div>\n )}\n </div>\n )}\n </div>\n );\n };\n\n return (\n <section\n {...ltrCodeBlockProps}\n ref={rootRef}\n className=\"plan-block\"\n data-block-id={blockId}\n data-files-expanded={railExpanded ? \"\" : undefined}\n onPointerDown={() => setActive(true)}\n >\n {title && <div className=\"plan-block-label\">{title}</div>}\n\n <div className=\"overflow-hidden rounded-xl border border-plan-line bg-plan-block\">\n {/* Summary header: file count + change tally. */}\n <div className=\"flex flex-wrap items-center gap-2 border-b border-plan-line bg-accent/20 px-3 py-2\">\n {data.title && (\n <span className=\"text-[13px] font-semibold text-plan-text\">\n {data.title}\n </span>\n )}\n <span className=\"text-xs text-plan-muted\">\n {entries.length} {entries.length === 1 ? \"file\" : \"files\"}\n </span>\n {changeTotal > 0 && (\n <span className=\"ml-auto flex items-center gap-2 font-mono text-xs\">\n {counts.added > 0 && (\n <span className=\"text-emerald-600 dark:text-emerald-300\">\n +{counts.added}\n </span>\n )}\n {counts.modified > 0 && (\n <span className=\"text-blue-600 dark:text-blue-300\">\n ~{counts.modified}\n </span>\n )}\n {counts.removed > 0 && (\n <span className=\"text-red-600 dark:text-red-300\">\n −{counts.removed}\n </span>\n )}\n {counts.renamed > 0 && (\n <span className=\"text-violet-600 dark:text-violet-300\">\n »{counts.renamed}\n </span>\n )}\n </span>\n )}\n </div>\n\n {/* The tree itself. */}\n <div className=\"py-1.5\">\n {tree.length > 0 ? (\n <>\n {displayedRows.map(renderRow)}\n {shouldLimitRows && (\n <div className=\"px-2 pt-1\">\n <button\n type=\"button\"\n data-plan-interactive\n aria-expanded={showAllRows}\n onClick={() => setShowAllRows((current) => !current)}\n className=\"flex h-8 w-full items-center justify-center gap-1.5 rounded-md text-xs font-medium text-plan-muted transition-colors hover:bg-accent/40 hover:text-plan-text\"\n >\n <IconChevronRight\n className={cn(\n \"size-3.5 shrink-0 transition-transform\",\n showAllRows ? \"-rotate-90\" : \"rotate-90\",\n )}\n />\n {showAllRows\n ? \"Show fewer\"\n : `Show all ${visibleRows.length} rows`}\n </button>\n </div>\n )}\n </>\n ) : (\n <p className=\"px-3 py-2 text-xs text-plan-muted\">No files yet.</p>\n )}\n </div>\n </div>\n\n {summary && <p className=\"mt-5 text-plan-muted\">{summary}</p>}\n </section>\n );\n}\n\n/* ── Edit (panel form) ─────────────────────────────────────────────────────── */\n\nconst fieldLabelClass = \"text-xs font-medium text-muted-foreground\";\n\n/**\n * Panel editor for a `file-tree` block. A structured form: an optional title\n * Input plus a list of file rows (add/remove), each carrying a path Input, a\n * change Select, a note Input, an optional language Input, and a snippet\n * Textarea. The folder tree is derived from the paths in the Read render, so the\n * form stays flat and quick to edit. Renders BARE content (no `<section>`); the\n * registry's panel surface supplies the popover chrome.\n */\nexport function FileTreeEdit({\n data,\n onChange,\n editable,\n}: BlockEditProps<FileTreeData>) {\n const entries = data.entries ?? [];\n\n const patchEntries = (next: FileTreeEntry[]) =>\n onChange({ ...data, entries: next });\n\n const updateEntry = (index: number, next: Partial<FileTreeEntry>) =>\n patchEntries(\n entries.map((entry, i) => (i === index ? { ...entry, ...next } : entry)),\n );\n\n const removeEntry = (index: number) =>\n patchEntries(entries.filter((_, i) => i !== index));\n\n const addEntry = () =>\n patchEntries([...entries, { path: \"src/new-file.ts\", change: \"added\" }]);\n\n return (\n <div className=\"flex flex-col gap-4\" data-plan-interactive>\n <label className=\"flex flex-col gap-1.5\">\n <span className={fieldLabelClass}>Title (optional)</span>\n <DevInput\n className=\"h-9\"\n value={data.title ?? \"\"}\n disabled={!editable}\n placeholder=\"e.g. Files touched\"\n onChange={(event) =>\n onChange({ ...data, title: event.target.value || undefined })\n }\n />\n </label>\n\n <div className=\"flex flex-col gap-3\">\n {entries.map((entry, index) => (\n <div\n key={index}\n className=\"flex flex-col gap-2 rounded-lg border border-input p-3\"\n >\n <div className=\"grid grid-cols-[minmax(0,1fr)_120px_auto] gap-2\">\n <DevInput\n className=\"h-8 font-mono text-xs\"\n value={entry.path}\n disabled={!editable}\n placeholder=\"src/routes/git.ts\"\n onChange={(event) =>\n updateEntry(index, { path: event.target.value })\n }\n />\n <DevSelect\n className=\"h-8\"\n value={entry.change ?? \"none\"}\n disabled={!editable}\n onValueChange={(value) =>\n updateEntry(index, {\n change:\n value === \"none\" ? undefined : (value as FileTreeChange),\n })\n }\n options={[\n { value: \"none\", label: \"No change\" },\n ...FILE_TREE_CHANGES.map((change) => ({\n value: change,\n label: CHANGE_LABEL[change],\n })),\n ]}\n />\n {editable && (\n <button\n type=\"button\"\n data-plan-interactive\n aria-label=\"Remove file\"\n className=\"flex size-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent/60 hover:text-foreground\"\n onClick={() => removeEntry(index)}\n >\n <IconTrash className=\"size-4\" />\n </button>\n )}\n </div>\n <DevInput\n className=\"h-8 text-xs\"\n value={entry.note ?? \"\"}\n disabled={!editable}\n placeholder=\"Why this file changes\"\n onChange={(event) =>\n updateEntry(index, { note: event.target.value || undefined })\n }\n />\n <div className=\"grid grid-cols-[minmax(0,1fr)_120px] gap-2\">\n <DevTextarea\n className=\"min-h-[64px] font-mono text-xs\"\n value={entry.snippet ?? \"\"}\n disabled={!editable}\n placeholder=\"Optional code snippet\"\n onChange={(event) =>\n updateEntry(index, {\n snippet: event.target.value || undefined,\n })\n }\n />\n <DevInput\n className=\"h-8 self-start font-mono text-xs\"\n value={entry.language ?? \"\"}\n disabled={!editable}\n placeholder=\"language\"\n onChange={(event) =>\n updateEntry(index, {\n language: event.target.value || undefined,\n })\n }\n />\n </div>\n </div>\n ))}\n </div>\n\n {editable && (\n <button\n type=\"button\"\n data-plan-interactive\n className=\"flex items-center justify-center gap-1.5 rounded-md border border-dashed border-input py-2 text-sm text-muted-foreground hover:bg-accent/40 hover:text-foreground\"\n onClick={addEntry}\n >\n <IconPlus className=\"size-4\" />\n Add file\n </button>\n )}\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"FileTreeBlock.js","sourceRoot":"","sources":["../../../../src/client/blocks/library/FileTreeBlock.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n IconChevronRight,\n IconFile,\n IconFolder,\n IconFolderOpen,\n IconPlus,\n IconTrash,\n} from \"@tabler/icons-react\";\nimport { cn } from \"../../utils.js\";\nimport { ltrCodeBlockProps } from \"../code-block-direction.js\";\nimport type { BlockEditProps, BlockReadProps } from \"../types.js\";\nimport type {\n FileTreeChange,\n FileTreeData,\n FileTreeEntry,\n} from \"./file-tree.config.js\";\nimport { FILE_TREE_CHANGES } from \"./file-tree.config.js\";\nimport { DevInput, DevTextarea, DevSelect } from \"./dev-doc-ui.js\";\n\n/**\n * Read + Edit renderers for a `file-tree` block — a VS Code / GitHub-explorer\n * file and change tree. Lives in core so any app can register the dev-doc block\n * (no shadcn import; the editor's enum picker is the core `DevSelect`).\n */\n\n/* ── Theme-aware change tokens ─────────────────────────────────────────────── */\n\n/**\n * Change-badge palette. Tinted background + saturated text in BOTH the `.dark`\n * plan theme and light mode (never a dark-only palette). Each entry keeps legible\n * contrast against the plan surface via Tailwind `dark:` variants.\n */\nconst CHANGE_BADGE: Record<FileTreeChange, string> = {\n added:\n \"bg-emerald-100 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300\",\n modified: \"bg-blue-100 text-blue-700 dark:bg-blue-500/15 dark:text-blue-300\",\n removed: \"bg-red-100 text-red-700 dark:bg-red-500/15 dark:text-red-300\",\n renamed:\n \"bg-violet-100 text-violet-700 dark:bg-violet-500/15 dark:text-violet-300\",\n};\n\n/** Single-letter glyph shown in the change badge (VS Code gutter convention). */\nconst CHANGE_GLYPH: Record<FileTreeChange, string> = {\n added: \"A\",\n modified: \"M\",\n removed: \"D\",\n renamed: \"R\",\n};\n\n/** Accent ink for the file name itself, echoing its change color. */\nconst CHANGE_NAME_INK: Record<FileTreeChange, string> = {\n added: \"text-emerald-700 dark:text-emerald-300\",\n modified: \"text-blue-700 dark:text-blue-300\",\n removed: \"text-red-600 line-through dark:text-red-300\",\n renamed: \"text-violet-700 dark:text-violet-300\",\n};\n\nconst CHANGE_LABEL: Record<FileTreeChange, string> = {\n added: \"Added\",\n modified: \"Modified\",\n removed: \"Removed\",\n renamed: \"Renamed\",\n};\n\n/** Infer a fence language for a file's snippet from its `language` or extension. */\nfunction fenceLanguage(entry: FileTreeEntry): string {\n if (entry.language?.trim()) return entry.language.trim();\n const ext = entry.path.split(\".\").pop()?.toLowerCase() ?? \"\";\n const byExt: Record<string, string> = {\n ts: \"ts\",\n tsx: \"tsx\",\n js: \"js\",\n jsx: \"jsx\",\n mjs: \"js\",\n cjs: \"js\",\n json: \"json\",\n css: \"css\",\n scss: \"scss\",\n html: \"html\",\n md: \"md\",\n mdx: \"md\",\n py: \"python\",\n rb: \"ruby\",\n go: \"go\",\n rs: \"rust\",\n sql: \"sql\",\n sh: \"bash\",\n yml: \"yaml\",\n yaml: \"yaml\",\n toml: \"toml\",\n };\n return byExt[ext] ?? \"text\";\n}\n\n/** Wrap a raw snippet in a fenced code block for `ctx.renderMarkdown`. */\nfunction fence(snippet: string, language: string): string {\n // Never let the snippet's own content break out of the fence.\n const safe = snippet.replace(/```/g, \"ʼʼʼ\");\n return `\\`\\`\\`${language}\\n${safe.replace(/\\s+$/, \"\")}\\n\\`\\`\\``;\n}\n\n/* ── Tree construction (flat paths → nested folders) ───────────────────────── */\n\ninterface FileLeaf {\n kind: \"file\";\n /** Last path segment. */\n name: string;\n /** Full slash path, used as a stable key + anchor. */\n path: string;\n entry: FileTreeEntry;\n /** Index in the original flat `entries` (stable per-file disclosure key). */\n index: number;\n}\n\ninterface FolderNode {\n kind: \"folder\";\n name: string;\n /** Full slash path of the folder, used as a stable key. */\n path: string;\n children: TreeNode[];\n /** Present when the folder was authored as an explicit directory entry (a\n * `path` ending in `/`), carrying its note/metadata + flat `entries` index. */\n entry?: FileTreeEntry;\n index?: number;\n}\n\ntype TreeNode = FolderNode | FileLeaf;\n\ninterface VisibleTreeRow {\n node: TreeNode;\n depth: number;\n}\n\n/** A folder being assembled while we walk the paths (children keyed by name). */\ninterface FolderBuild {\n name: string;\n path: string;\n folders: Map<string, FolderBuild>;\n files: FileLeaf[];\n /** Insertion order of child names (folders + files) for stable rendering. */\n order: string[];\n /** Set when an explicit directory entry (trailing slash) targets this folder,\n * carrying its note/metadata + flat `entries` index. */\n entry?: FileTreeEntry;\n index?: number;\n}\n\nfunction makeFolder(name: string, path: string): FolderBuild {\n return { name, path, folders: new Map(), files: [], order: [] };\n}\n\n/**\n * Build a nested folder tree from the flat `entries`. Folders are derived purely\n * from the slash segments of each `path`; a single-segment path is a root file.\n * Insertion order is preserved within each folder, with folders sorted before\n * files at each level (the conventional explorer ordering).\n */\nfunction buildTree(entries: FileTreeEntry[]): TreeNode[] {\n const root = makeFolder(\"\", \"\");\n\n entries.forEach((entry, index) => {\n const segments = entry.path.split(\"/\").filter(Boolean);\n if (segments.length === 0) return;\n\n // A trailing slash marks a DIRECTORY entry: every segment is a folder and\n // the entry's note/metadata attaches to the deepest folder (the last segment\n // does NOT become a file leaf). Because folders are keyed by name, this also\n // merges with any folder the sibling file paths already implied — so\n // `packages/shared/` and `packages/shared/src/…` collapse onto one `shared`\n // folder instead of a duplicate folder + file pair.\n const isDir = /\\/\\s*$/.test(entry.path);\n const folderSegments = isDir ? segments : segments.slice(0, -1);\n\n let cursor = root;\n let prefix = \"\";\n for (const segment of folderSegments) {\n prefix = prefix ? `${prefix}/${segment}` : segment;\n let next = cursor.folders.get(segment);\n if (!next) {\n next = makeFolder(segment, prefix);\n cursor.folders.set(segment, next);\n cursor.order.push(`d:${segment}`);\n }\n cursor = next;\n }\n\n if (isDir) {\n // Attach metadata to the deepest folder (first declaration wins, so an\n // explicit note isn't clobbered by a later bare prefix re-declaration).\n if (!cursor.entry) {\n cursor.entry = entry;\n cursor.index = index;\n }\n return;\n }\n\n const fileName = segments[segments.length - 1] as string;\n cursor.files.push({\n kind: \"file\",\n name: fileName,\n path: entry.path,\n entry,\n index,\n });\n cursor.order.push(`f:${cursor.files.length - 1}`);\n });\n\n const materialize = (folder: FolderBuild): TreeNode[] => {\n const nodes: TreeNode[] = [];\n for (const key of folder.order) {\n if (key.startsWith(\"d:\")) {\n const child = folder.folders.get(key.slice(2));\n if (!child) continue;\n nodes.push({\n kind: \"folder\",\n name: child.name,\n path: child.path,\n children: materialize(child),\n entry: child.entry,\n index: child.index,\n });\n } else {\n const file = folder.files[Number(key.slice(2))];\n if (file) nodes.push(file);\n }\n }\n // Folders before files at this level (standard explorer ordering).\n return [\n ...nodes.filter((node) => node.kind === \"folder\"),\n ...nodes.filter((node) => node.kind === \"file\"),\n ];\n };\n\n return materialize(root);\n}\n\nfunction compactFolderNode(folder: FolderNode): FolderNode {\n const names = [folder.name];\n let path = folder.path;\n let children = folder.children;\n\n // Collapse single-child folder chains (a/b/c) into one row — but never across\n // a folder that carries its own directory note, so an explicitly authored\n // directory (e.g. `packages/shared/` with a note) keeps its own row instead of\n // being folded into its parent or child and losing the note.\n while (\n !folder.entry &&\n children.length === 1 &&\n children[0]?.kind === \"folder\" &&\n !children[0].entry\n ) {\n const child = children[0];\n names.push(child.name);\n path = child.path;\n children = child.children;\n }\n\n return {\n kind: \"folder\",\n name: names.join(\"/\"),\n path,\n entry: folder.entry,\n index: folder.index,\n children: compactTree(children),\n };\n}\n\nfunction compactTree(nodes: TreeNode[]): TreeNode[] {\n return nodes.map((node) =>\n node.kind === \"folder\" ? compactFolderNode(node) : node,\n );\n}\n\nfunction flattenVisibleRows(\n nodes: TreeNode[],\n collapsedFolders: Record<string, boolean>,\n depth = 0,\n): VisibleTreeRow[] {\n const rows: VisibleTreeRow[] = [];\n\n for (const node of nodes) {\n rows.push({ node, depth });\n if (node.kind === \"folder\" && !(collapsedFolders[node.path] ?? false)) {\n rows.push(\n ...flattenVisibleRows(node.children, collapsedFolders, depth + 1),\n );\n }\n }\n\n return rows;\n}\n\n/* ── Read (IDE explorer) ───────────────────────────────────────────────────── */\n\nconst INDENT_STEP = 14; // px per nesting level — the explorer guide spacing.\nconst DEFAULT_VISIBLE_TREE_ROWS = 10;\n\n/**\n * Read-only renderer for a `file-tree` block — a VS Code / GitHub-explorer file\n * and change tree. The flat `entries` are folded into a nested tree of\n * collapsible folders (IconFolder + chevron) and files (IconFile) carrying a\n * single-letter change badge (A/M/D/R). A file with a `note` or `snippet` is\n * itself clickable and expands to show the note plus the snippet rendered as a\n * fenced code block via `ctx.renderMarkdown`. A summary header tallies the change\n * counts (\"+N · ~M · −K\"). Every color is theme-aware via Tailwind `dark:`\n * variants / plan CSS vars, so the tree reads correctly in both modes.\n */\nexport function FileTreeRead({\n data,\n blockId,\n title,\n summary,\n ctx,\n}: BlockReadProps<FileTreeData>) {\n const entries = data.entries ?? [];\n const tree = useMemo(() => compactTree(buildTree(entries)), [entries]);\n\n // Folders default to fully expanded so the tree is useful at a glance.\n const [collapsedFolders, setCollapsedFolders] = useState<\n Record<string, boolean>\n >({});\n const [showAllRows, setShowAllRows] = useState(false);\n // Files with a note/snippet collapse their detail by default (progressive\n // disclosure) — keyed by the flat entry index so duplicate names never clash.\n const [openFiles, setOpenFiles] = useState<Record<number, boolean>>({});\n\n const toggleFolder = (path: string) =>\n setCollapsedFolders((current) => ({\n ...current,\n [path]: !current[path],\n }));\n const toggleFile = (index: number) =>\n setOpenFiles((current) => ({ ...current, [index]: !current[index] }));\n\n // The recap \"Files touched\" left rail (only) widens into the document as a\n // flyout while the tree is the reader's active focus AND a file's detail is\n // open, then collapses back to a slim rail when they click elsewhere or close\n // the last open file. `data-files-expanded` on the root drives the rail width\n // via CSS (`:has()`); it is inert anywhere the tree renders inline.\n const rootRef = useRef<HTMLElement>(null);\n const [active, setActive] = useState(false);\n const anyFileOpen = useMemo(\n () => Object.values(openFiles).some(Boolean),\n [openFiles],\n );\n const railExpanded = active && anyFileOpen;\n\n useEffect(() => {\n if (!railExpanded) return;\n const onPointerDown = (event: PointerEvent) => {\n const root = rootRef.current;\n if (\n root &&\n event.target instanceof Node &&\n !root.contains(event.target)\n ) {\n // Clicking away collapses the rail and closes any open file detail, so\n // it returns to the clean slim state rather than leaving notes open in\n // the cramped width.\n setActive(false);\n setOpenFiles({});\n }\n };\n document.addEventListener(\"pointerdown\", onPointerDown, true);\n return () =>\n document.removeEventListener(\"pointerdown\", onPointerDown, true);\n }, [railExpanded]);\n\n // Change tally for the summary header.\n const counts = useMemo(() => {\n const tally = { added: 0, modified: 0, removed: 0, renamed: 0 };\n for (const entry of entries) {\n if (entry.change) tally[entry.change] += 1;\n }\n return tally;\n }, [entries]);\n const changeTotal =\n counts.added + counts.modified + counts.removed + counts.renamed;\n\n const visibleRows = useMemo(\n () => flattenVisibleRows(tree, collapsedFolders),\n [collapsedFolders, tree],\n );\n const shouldLimitRows = visibleRows.length > DEFAULT_VISIBLE_TREE_ROWS;\n const displayedRows =\n showAllRows || !shouldLimitRows\n ? visibleRows\n : visibleRows.slice(0, DEFAULT_VISIBLE_TREE_ROWS);\n\n const renderRow = ({ node, depth }: VisibleTreeRow): React.ReactNode => {\n const indent = depth * INDENT_STEP;\n if (node.kind === \"folder\") {\n const collapsed = collapsedFolders[node.path] ?? false;\n // An explicit directory entry (trailing-slash path) can be a leaf folder\n // with no children — e.g. `apps/mail/`. It still renders as a folder, just\n // without a toggle chevron. Its authored note shows inline like a file's.\n const expandable = node.children.length > 0;\n const note = node.entry?.note?.trim();\n const open = expandable && !collapsed;\n return (\n <div key={`d:${node.path}`}>\n <button\n type=\"button\"\n data-plan-interactive\n disabled={!expandable}\n aria-expanded={expandable ? !collapsed : undefined}\n onClick={expandable ? () => toggleFolder(node.path) : undefined}\n style={{ paddingLeft: indent + 8 }}\n className={cn(\n \"flex w-full items-center gap-1.5 rounded-md py-1 pr-2 text-left text-[13px] transition-colors\",\n expandable ? \"hover:bg-accent/40\" : \"cursor-default\",\n )}\n >\n {expandable ? (\n <IconChevronRight\n className={cn(\n \"size-3.5 shrink-0 text-plan-muted transition-transform\",\n !collapsed && \"rotate-90\",\n )}\n />\n ) : (\n <span className=\"size-3.5 shrink-0\" aria-hidden />\n )}\n {open ? (\n <IconFolderOpen className=\"size-4 shrink-0 text-plan-muted\" />\n ) : (\n <IconFolder className=\"size-4 shrink-0 text-plan-muted\" />\n )}\n <span className=\"min-w-0 truncate font-medium text-plan-text\">\n {node.name}\n </span>\n {note && (\n <span className=\"ml-1 min-w-0 flex-1 truncate text-xs text-plan-muted\">\n {note}\n </span>\n )}\n </button>\n </div>\n );\n }\n\n const { entry } = node;\n const change = entry.change;\n const hasDetail = Boolean(entry.note?.trim() || entry.snippet?.trim());\n const isOpen = openFiles[node.index] ?? false;\n\n return (\n <div key={`f:${node.index}`}>\n <button\n type=\"button\"\n data-plan-interactive\n data-file-path={node.path}\n disabled={!hasDetail}\n aria-expanded={hasDetail ? isOpen : undefined}\n onClick={hasDetail ? () => toggleFile(node.index) : undefined}\n style={{ paddingLeft: indent + 8 }}\n className={cn(\n \"group flex w-full items-center gap-1.5 rounded-md py-1 pr-2 text-left text-[13px] transition-colors\",\n hasDetail ? \"hover:bg-accent/40\" : \"cursor-default\",\n )}\n >\n {/* Chevron slot — present only for files with expandable detail so\n everything stays aligned with the folder rows above. */}\n {hasDetail ? (\n <IconChevronRight\n className={cn(\n \"size-3.5 shrink-0 text-plan-muted transition-transform\",\n isOpen && \"rotate-90\",\n )}\n />\n ) : (\n <span className=\"size-3.5 shrink-0\" aria-hidden />\n )}\n <IconFile\n className={cn(\n \"size-4 shrink-0\",\n change === \"removed\" ? \"text-plan-muted\" : \"text-plan-muted/80\",\n )}\n />\n <span\n className={cn(\n \"min-w-0 truncate font-medium\",\n change ? CHANGE_NAME_INK[change] : \"text-plan-text\",\n )}\n >\n {node.name}\n </span>\n {change && (\n <span\n title={CHANGE_LABEL[change]}\n aria-label={CHANGE_LABEL[change]}\n className={cn(\n \"ml-1 flex size-4 shrink-0 items-center justify-center rounded text-[10px] font-bold leading-none\",\n CHANGE_BADGE[change],\n )}\n >\n {CHANGE_GLYPH[change]}\n </span>\n )}\n {entry.note?.trim() && !isOpen && (\n <span className=\"ml-1 min-w-0 flex-1 truncate text-xs text-plan-muted\">\n {entry.note}\n </span>\n )}\n </button>\n\n {/* Expanded file detail: the note + a fenced snippet. */}\n {hasDetail && isOpen && (\n <div\n style={{ paddingLeft: indent + 8 + 20 }}\n className=\"pb-2 pr-2 pt-0.5\"\n >\n {entry.note?.trim() && (\n <p className=\"text-xs leading-relaxed text-plan-muted\">\n {entry.note}\n </p>\n )}\n {entry.snippet?.trim() && (\n <div className=\"mt-2 an-file-tree-snippet\">\n {ctx.renderMarkdown?.(\n fence(entry.snippet, fenceLanguage(entry)),\n )}\n </div>\n )}\n </div>\n )}\n </div>\n );\n };\n\n return (\n <section\n {...ltrCodeBlockProps}\n ref={rootRef}\n className=\"plan-block\"\n data-block-id={blockId}\n data-files-expanded={railExpanded ? \"\" : undefined}\n onPointerDown={() => setActive(true)}\n >\n {title && <div className=\"plan-block-label\">{title}</div>}\n\n <div className=\"overflow-hidden rounded-xl border border-plan-line bg-plan-block\">\n {/* Summary header: file count + change tally. */}\n <div className=\"flex flex-wrap items-center gap-2 border-b border-plan-line bg-accent/20 px-3 py-2\">\n {data.title && (\n <span className=\"text-[13px] font-semibold text-plan-text\">\n {data.title}\n </span>\n )}\n <span className=\"text-xs text-plan-muted\">\n {entries.length} {entries.length === 1 ? \"file\" : \"files\"}\n </span>\n {changeTotal > 0 && (\n <span className=\"ml-auto flex items-center gap-2 font-mono text-xs\">\n {counts.added > 0 && (\n <span className=\"text-emerald-600 dark:text-emerald-300\">\n +{counts.added}\n </span>\n )}\n {counts.modified > 0 && (\n <span className=\"text-blue-600 dark:text-blue-300\">\n ~{counts.modified}\n </span>\n )}\n {counts.removed > 0 && (\n <span className=\"text-red-600 dark:text-red-300\">\n −{counts.removed}\n </span>\n )}\n {counts.renamed > 0 && (\n <span className=\"text-violet-600 dark:text-violet-300\">\n »{counts.renamed}\n </span>\n )}\n </span>\n )}\n </div>\n\n {/* The tree itself. */}\n <div className=\"py-1.5\">\n {tree.length > 0 ? (\n <>\n {displayedRows.map(renderRow)}\n {shouldLimitRows && (\n <div className=\"px-2 pt-1\">\n <button\n type=\"button\"\n data-plan-interactive\n aria-expanded={showAllRows}\n onClick={() => setShowAllRows((current) => !current)}\n className=\"flex h-8 w-full items-center justify-center gap-1.5 rounded-md text-xs font-medium text-plan-muted transition-colors hover:bg-accent/40 hover:text-plan-text\"\n >\n <IconChevronRight\n className={cn(\n \"size-3.5 shrink-0 transition-transform\",\n showAllRows ? \"-rotate-90\" : \"rotate-90\",\n )}\n />\n {showAllRows\n ? \"Show fewer\"\n : `Show all ${visibleRows.length} rows`}\n </button>\n </div>\n )}\n </>\n ) : (\n <p className=\"px-3 py-2 text-xs text-plan-muted\">No files yet.</p>\n )}\n </div>\n </div>\n\n {summary && <p className=\"mt-5 text-plan-muted\">{summary}</p>}\n </section>\n );\n}\n\n/* ── Edit (panel form) ─────────────────────────────────────────────────────── */\n\nconst fieldLabelClass = \"text-xs font-medium text-muted-foreground\";\n\n/**\n * Panel editor for a `file-tree` block. A structured form: an optional title\n * Input plus a list of file rows (add/remove), each carrying a path Input, a\n * change Select, a note Input, an optional language Input, and a snippet\n * Textarea. The folder tree is derived from the paths in the Read render, so the\n * form stays flat and quick to edit. Renders BARE content (no `<section>`); the\n * registry's panel surface supplies the popover chrome.\n */\nexport function FileTreeEdit({\n data,\n onChange,\n editable,\n}: BlockEditProps<FileTreeData>) {\n const entries = data.entries ?? [];\n\n const patchEntries = (next: FileTreeEntry[]) =>\n onChange({ ...data, entries: next });\n\n const updateEntry = (index: number, next: Partial<FileTreeEntry>) =>\n patchEntries(\n entries.map((entry, i) => (i === index ? { ...entry, ...next } : entry)),\n );\n\n const removeEntry = (index: number) =>\n patchEntries(entries.filter((_, i) => i !== index));\n\n const addEntry = () =>\n patchEntries([...entries, { path: \"src/new-file.ts\", change: \"added\" }]);\n\n return (\n <div className=\"flex flex-col gap-4\" data-plan-interactive>\n <label className=\"flex flex-col gap-1.5\">\n <span className={fieldLabelClass}>Title (optional)</span>\n <DevInput\n className=\"h-9\"\n value={data.title ?? \"\"}\n disabled={!editable}\n placeholder=\"e.g. Files touched\"\n onChange={(event) =>\n onChange({ ...data, title: event.target.value || undefined })\n }\n />\n </label>\n\n <div className=\"flex flex-col gap-3\">\n {entries.map((entry, index) => (\n <div\n key={index}\n className=\"flex flex-col gap-2 rounded-lg border border-input p-3\"\n >\n <div className=\"grid grid-cols-[minmax(0,1fr)_120px_auto] gap-2\">\n <DevInput\n className=\"h-8 font-mono text-xs\"\n value={entry.path}\n disabled={!editable}\n placeholder=\"src/routes/git.ts\"\n onChange={(event) =>\n updateEntry(index, { path: event.target.value })\n }\n />\n <DevSelect\n className=\"h-8\"\n value={entry.change ?? \"none\"}\n disabled={!editable}\n onValueChange={(value) =>\n updateEntry(index, {\n change:\n value === \"none\" ? undefined : (value as FileTreeChange),\n })\n }\n options={[\n { value: \"none\", label: \"No change\" },\n ...FILE_TREE_CHANGES.map((change) => ({\n value: change,\n label: CHANGE_LABEL[change],\n })),\n ]}\n />\n {editable && (\n <button\n type=\"button\"\n data-plan-interactive\n aria-label=\"Remove file\"\n className=\"flex size-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent/60 hover:text-foreground\"\n onClick={() => removeEntry(index)}\n >\n <IconTrash className=\"size-4\" />\n </button>\n )}\n </div>\n <DevInput\n className=\"h-8 text-xs\"\n value={entry.note ?? \"\"}\n disabled={!editable}\n placeholder=\"Why this file changes\"\n onChange={(event) =>\n updateEntry(index, { note: event.target.value || undefined })\n }\n />\n <div className=\"grid grid-cols-[minmax(0,1fr)_120px] gap-2\">\n <DevTextarea\n className=\"min-h-[64px] font-mono text-xs\"\n value={entry.snippet ?? \"\"}\n disabled={!editable}\n placeholder=\"Optional code snippet\"\n onChange={(event) =>\n updateEntry(index, {\n snippet: event.target.value || undefined,\n })\n }\n />\n <DevInput\n className=\"h-8 self-start font-mono text-xs\"\n value={entry.language ?? \"\"}\n disabled={!editable}\n placeholder=\"language\"\n onChange={(event) =>\n updateEntry(index, {\n language: event.target.value || undefined,\n })\n }\n />\n </div>\n </div>\n ))}\n </div>\n\n {editable && (\n <button\n type=\"button\"\n data-plan-interactive\n className=\"flex items-center justify-center gap-1.5 rounded-md border border-dashed border-input py-2 text-sm text-muted-foreground hover:bg-accent/40 hover:text-foreground\"\n onClick={addEntry}\n >\n <IconPlus className=\"size-4\" />\n Add file\n </button>\n )}\n </div>\n );\n}\n"]}
|
|
@@ -35,6 +35,22 @@ export interface NormalizedRepo {
|
|
|
35
35
|
}
|
|
36
36
|
export declare function getRepoMessages(repo: NormalizedRepo | null | undefined): RepoEntry[];
|
|
37
37
|
export declare function getRepoMessage(entry: RepoEntry): RepoMessage | null;
|
|
38
|
+
/**
|
|
39
|
+
* Collapse duplicate message ids before a repository is handed to
|
|
40
|
+
* `threadRuntime.import()`. assistant-ui's `MessageRepository` throws
|
|
41
|
+
* "MessageRepository(performOp/link): A message with the same id already exists
|
|
42
|
+
* in the parent tree" when the imported messages contain the same id more than
|
|
43
|
+
* once (Sentry AGENT-NATIVE-BROWSER-2Q). Duplicate ids are never valid thread
|
|
44
|
+
* data — they come from optimistic+echo races, streaming reconnect replays, or
|
|
45
|
+
* multi-tab merges — so keep only the LAST occurrence of each id (the most
|
|
46
|
+
* recent, most complete copy). parentId references stay valid because the
|
|
47
|
+
* surviving entry keeps the same id.
|
|
48
|
+
*
|
|
49
|
+
* Returns the input unchanged (same reference) when there are no duplicates, so
|
|
50
|
+
* the overwhelmingly common no-dupe case is a cheap no-op with zero behavioural
|
|
51
|
+
* change for normal threads.
|
|
52
|
+
*/
|
|
53
|
+
export declare function dedupeRepoMessagesById<T extends NormalizedRepo>(repo: T | null | undefined): T | null | undefined;
|
|
38
54
|
export declare function isAssistantMessageTerminal(message: RepoMessage | null): boolean;
|
|
39
55
|
export declare function repoHasAssistantMessage(repo: NormalizedRepo | null | undefined): boolean;
|
|
40
56
|
export declare function shouldImportServerThreadData(currentRepo: NormalizedRepo | null | undefined, incomingRepo: NormalizedRepo | null | undefined): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo-helpers.d.ts","sourceRoot":"","sources":["../../../src/client/chat/repo-helpers.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IACnC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,oFAAoF;AACpF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,WAAW,CAAC;IAEtB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAAC;IACxC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,mFAAmF;AACnF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,GACtC,SAAS,EAAE,CAEb;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,GAAG,WAAW,GAAG,IAAI,CAEnE;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,WAAW,GAAG,IAAI,GAC1B,OAAO,CAGT;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,GACtC,OAAO,CAIT;AA6BD,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,EAC9C,YAAY,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,GAC9C,OAAO,CAuBT"}
|
|
1
|
+
{"version":3,"file":"repo-helpers.d.ts","sourceRoot":"","sources":["../../../src/client/chat/repo-helpers.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IACnC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,oFAAoF;AACpF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,WAAW,CAAC;IAEtB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAAC;IACxC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,mFAAmF;AACnF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,GACtC,SAAS,EAAE,CAEb;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,GAAG,WAAW,GAAG,IAAI,CAEnE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,cAAc,EAC7D,IAAI,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,GACzB,CAAC,GAAG,IAAI,GAAG,SAAS,CAmBtB;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,WAAW,GAAG,IAAI,GAC1B,OAAO,CAGT;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,GACtC,OAAO,CAIT;AA6BD,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,EAC9C,YAAY,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,GAC9C,OAAO,CAuBT"}
|
|
@@ -5,6 +5,46 @@ export function getRepoMessages(repo) {
|
|
|
5
5
|
export function getRepoMessage(entry) {
|
|
6
6
|
return (entry?.message ?? entry);
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Collapse duplicate message ids before a repository is handed to
|
|
10
|
+
* `threadRuntime.import()`. assistant-ui's `MessageRepository` throws
|
|
11
|
+
* "MessageRepository(performOp/link): A message with the same id already exists
|
|
12
|
+
* in the parent tree" when the imported messages contain the same id more than
|
|
13
|
+
* once (Sentry AGENT-NATIVE-BROWSER-2Q). Duplicate ids are never valid thread
|
|
14
|
+
* data — they come from optimistic+echo races, streaming reconnect replays, or
|
|
15
|
+
* multi-tab merges — so keep only the LAST occurrence of each id (the most
|
|
16
|
+
* recent, most complete copy). parentId references stay valid because the
|
|
17
|
+
* surviving entry keeps the same id.
|
|
18
|
+
*
|
|
19
|
+
* Returns the input unchanged (same reference) when there are no duplicates, so
|
|
20
|
+
* the overwhelmingly common no-dupe case is a cheap no-op with zero behavioural
|
|
21
|
+
* change for normal threads.
|
|
22
|
+
*/
|
|
23
|
+
export function dedupeRepoMessagesById(repo) {
|
|
24
|
+
if (!repo || !Array.isArray(repo.messages))
|
|
25
|
+
return repo;
|
|
26
|
+
const entries = repo.messages;
|
|
27
|
+
const lastIndexById = new Map();
|
|
28
|
+
let hasDuplicate = false;
|
|
29
|
+
entries.forEach((entry, index) => {
|
|
30
|
+
const id = getRepoMessage(entry)?.id;
|
|
31
|
+
if (typeof id !== "string" || !id)
|
|
32
|
+
return;
|
|
33
|
+
if (lastIndexById.has(id))
|
|
34
|
+
hasDuplicate = true;
|
|
35
|
+
lastIndexById.set(id, index);
|
|
36
|
+
});
|
|
37
|
+
if (!hasDuplicate)
|
|
38
|
+
return repo;
|
|
39
|
+
const deduped = entries.filter((entry, index) => {
|
|
40
|
+
const id = getRepoMessage(entry)?.id;
|
|
41
|
+
// Keep id-less entries untouched; for duplicated ids keep only the last.
|
|
42
|
+
if (typeof id !== "string" || !id)
|
|
43
|
+
return true;
|
|
44
|
+
return lastIndexById.get(id) === index;
|
|
45
|
+
});
|
|
46
|
+
return { ...repo, messages: deduped };
|
|
47
|
+
}
|
|
8
48
|
export function isAssistantMessageTerminal(message) {
|
|
9
49
|
const statusType = message?.status?.type;
|
|
10
50
|
return statusType === "complete" || statusType === "incomplete";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo-helpers.js","sourceRoot":"","sources":["../../../src/client/chat/repo-helpers.ts"],"names":[],"mappings":"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","sourcesContent":["// Owns: typed helpers for reading/comparing the assistant-ui message repository shape.\n\n// The assistant-ui ExportedMessageRepository type is structurally complex and\n// the normalised internal form diverges slightly. We define a structural\n// interface covering the shapes both `threadRuntime.export()` and\n// `normalizeThreadRepository()` produce, and use it everywhere instead of `any`.\n\nexport interface RepoMessageStatus {\n type?: string;\n reason?: string;\n}\n\nexport interface RepoMessageContent {\n type: string;\n text?: string;\n [key: string]: unknown;\n}\n\nexport interface RepoMessage {\n id?: string;\n role?: string;\n status?: RepoMessageStatus;\n content?: string | RepoMessageContent[];\n metadata?: Record<string, unknown>;\n createdAt?: Date | string | number;\n [key: string]: unknown;\n}\n\n/** Entry in `repo.messages` — may be flat or wrapped `{ message: RepoMessage }`. */\nexport interface RepoEntry {\n parentId?: string | null;\n message?: RepoMessage;\n // Flat (unwrapped) fields are also legal:\n id?: string;\n role?: string;\n status?: RepoMessageStatus;\n content?: string | RepoMessageContent[];\n [key: string]: unknown;\n}\n\n/** Minimal structure of the normalised thread repository used by AssistantChat. */\nexport interface NormalizedRepo {\n messages?: RepoEntry[];\n headId?: string;\n queuedMessages?: unknown[];\n [key: string]: unknown;\n}\n\nexport function getRepoMessages(\n repo: NormalizedRepo | null | undefined,\n): RepoEntry[] {\n return Array.isArray(repo?.messages) ? repo.messages : [];\n}\n\nexport function getRepoMessage(entry: RepoEntry): RepoMessage | null {\n return (entry?.message ?? entry) as RepoMessage | null;\n}\n\nexport function isAssistantMessageTerminal(\n message: RepoMessage | null,\n): boolean {\n const statusType = message?.status?.type;\n return statusType === \"complete\" || statusType === \"incomplete\";\n}\n\nexport function repoHasAssistantMessage(\n repo: NormalizedRepo | null | undefined,\n): boolean {\n return getRepoMessages(repo).some(\n (m) => getRepoMessage(m)?.role === \"assistant\",\n );\n}\n\nfunction repoTextLength(repo: NormalizedRepo | null | undefined): number {\n let length = 0;\n for (const entry of getRepoMessages(repo)) {\n const message = getRepoMessage(entry);\n const content = message?.content;\n if (typeof content === \"string\") {\n length += content.length;\n } else if (Array.isArray(content)) {\n for (const part of content) {\n if (part?.type === \"text\" && typeof part.text === \"string\") {\n length += part.text.length;\n }\n }\n }\n }\n return length;\n}\n\nfunction repoTerminalAssistantCount(\n repo: NormalizedRepo | null | undefined,\n): number {\n return getRepoMessages(repo).filter((entry) => {\n const message = getRepoMessage(entry);\n return message?.role === \"assistant\" && isAssistantMessageTerminal(message);\n }).length;\n}\n\nexport function shouldImportServerThreadData(\n currentRepo: NormalizedRepo | null | undefined,\n incomingRepo: NormalizedRepo | null | undefined,\n): boolean {\n const incomingCount = getRepoMessages(incomingRepo).length;\n if (incomingCount === 0) return false;\n\n const currentCount = getRepoMessages(currentRepo).length;\n if (currentCount === 0) return true;\n if (incomingCount < currentCount) return false;\n\n if (incomingCount === currentCount) {\n const currentTerminalAssistants = repoTerminalAssistantCount(currentRepo);\n const incomingTerminalAssistants = repoTerminalAssistantCount(incomingRepo);\n if (incomingTerminalAssistants < currentTerminalAssistants) {\n return false;\n }\n if (\n incomingTerminalAssistants <= currentTerminalAssistants &&\n repoTextLength(incomingRepo) < repoTextLength(currentRepo)\n ) {\n return false;\n }\n }\n\n return true;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"repo-helpers.js","sourceRoot":"","sources":["../../../src/client/chat/repo-helpers.ts"],"names":[],"mappings":"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","sourcesContent":["// Owns: typed helpers for reading/comparing the assistant-ui message repository shape.\n\n// The assistant-ui ExportedMessageRepository type is structurally complex and\n// the normalised internal form diverges slightly. We define a structural\n// interface covering the shapes both `threadRuntime.export()` and\n// `normalizeThreadRepository()` produce, and use it everywhere instead of `any`.\n\nexport interface RepoMessageStatus {\n type?: string;\n reason?: string;\n}\n\nexport interface RepoMessageContent {\n type: string;\n text?: string;\n [key: string]: unknown;\n}\n\nexport interface RepoMessage {\n id?: string;\n role?: string;\n status?: RepoMessageStatus;\n content?: string | RepoMessageContent[];\n metadata?: Record<string, unknown>;\n createdAt?: Date | string | number;\n [key: string]: unknown;\n}\n\n/** Entry in `repo.messages` — may be flat or wrapped `{ message: RepoMessage }`. */\nexport interface RepoEntry {\n parentId?: string | null;\n message?: RepoMessage;\n // Flat (unwrapped) fields are also legal:\n id?: string;\n role?: string;\n status?: RepoMessageStatus;\n content?: string | RepoMessageContent[];\n [key: string]: unknown;\n}\n\n/** Minimal structure of the normalised thread repository used by AssistantChat. */\nexport interface NormalizedRepo {\n messages?: RepoEntry[];\n headId?: string;\n queuedMessages?: unknown[];\n [key: string]: unknown;\n}\n\nexport function getRepoMessages(\n repo: NormalizedRepo | null | undefined,\n): RepoEntry[] {\n return Array.isArray(repo?.messages) ? repo.messages : [];\n}\n\nexport function getRepoMessage(entry: RepoEntry): RepoMessage | null {\n return (entry?.message ?? entry) as RepoMessage | null;\n}\n\n/**\n * Collapse duplicate message ids before a repository is handed to\n * `threadRuntime.import()`. assistant-ui's `MessageRepository` throws\n * \"MessageRepository(performOp/link): A message with the same id already exists\n * in the parent tree\" when the imported messages contain the same id more than\n * once (Sentry AGENT-NATIVE-BROWSER-2Q). Duplicate ids are never valid thread\n * data — they come from optimistic+echo races, streaming reconnect replays, or\n * multi-tab merges — so keep only the LAST occurrence of each id (the most\n * recent, most complete copy). parentId references stay valid because the\n * surviving entry keeps the same id.\n *\n * Returns the input unchanged (same reference) when there are no duplicates, so\n * the overwhelmingly common no-dupe case is a cheap no-op with zero behavioural\n * change for normal threads.\n */\nexport function dedupeRepoMessagesById<T extends NormalizedRepo>(\n repo: T | null | undefined,\n): T | null | undefined {\n if (!repo || !Array.isArray(repo.messages)) return repo;\n const entries = repo.messages;\n const lastIndexById = new Map<string, number>();\n let hasDuplicate = false;\n entries.forEach((entry, index) => {\n const id = getRepoMessage(entry)?.id;\n if (typeof id !== \"string\" || !id) return;\n if (lastIndexById.has(id)) hasDuplicate = true;\n lastIndexById.set(id, index);\n });\n if (!hasDuplicate) return repo;\n const deduped = entries.filter((entry, index) => {\n const id = getRepoMessage(entry)?.id;\n // Keep id-less entries untouched; for duplicated ids keep only the last.\n if (typeof id !== \"string\" || !id) return true;\n return lastIndexById.get(id) === index;\n });\n return { ...repo, messages: deduped };\n}\n\nexport function isAssistantMessageTerminal(\n message: RepoMessage | null,\n): boolean {\n const statusType = message?.status?.type;\n return statusType === \"complete\" || statusType === \"incomplete\";\n}\n\nexport function repoHasAssistantMessage(\n repo: NormalizedRepo | null | undefined,\n): boolean {\n return getRepoMessages(repo).some(\n (m) => getRepoMessage(m)?.role === \"assistant\",\n );\n}\n\nfunction repoTextLength(repo: NormalizedRepo | null | undefined): number {\n let length = 0;\n for (const entry of getRepoMessages(repo)) {\n const message = getRepoMessage(entry);\n const content = message?.content;\n if (typeof content === \"string\") {\n length += content.length;\n } else if (Array.isArray(content)) {\n for (const part of content) {\n if (part?.type === \"text\" && typeof part.text === \"string\") {\n length += part.text.length;\n }\n }\n }\n }\n return length;\n}\n\nfunction repoTerminalAssistantCount(\n repo: NormalizedRepo | null | undefined,\n): number {\n return getRepoMessages(repo).filter((entry) => {\n const message = getRepoMessage(entry);\n return message?.role === \"assistant\" && isAssistantMessageTerminal(message);\n }).length;\n}\n\nexport function shouldImportServerThreadData(\n currentRepo: NormalizedRepo | null | undefined,\n incomingRepo: NormalizedRepo | null | undefined,\n): boolean {\n const incomingCount = getRepoMessages(incomingRepo).length;\n if (incomingCount === 0) return false;\n\n const currentCount = getRepoMessages(currentRepo).length;\n if (currentCount === 0) return true;\n if (incomingCount < currentCount) return false;\n\n if (incomingCount === currentCount) {\n const currentTerminalAssistants = repoTerminalAssistantCount(currentRepo);\n const incomingTerminalAssistants = repoTerminalAssistantCount(incomingRepo);\n if (incomingTerminalAssistants < currentTerminalAssistants) {\n return false;\n }\n if (\n incomingTerminalAssistants <= currentTerminalAssistants &&\n repoTextLength(incomingRepo) < repoTextLength(currentRepo)\n ) {\n return false;\n }\n }\n\n return true;\n}\n"]}
|
package/dist/db/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/db/client.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,IAAI,CAAC;AAEnD,MAAM,WAAW,MAAM;IACrB,OAAO,CACL,GAAG,EAAE,MAAM,GAAG;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAA;KAAE,GAC9C,OAAO,CAAC;QAAE,IAAI,EAAE,GAAG,EAAE,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5D;;;;;OAKG;IACH,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,GAAG,CAAC;CACjB;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,QAAQ,SAAK,GAAG,MAAM,CASpD;AAED,8EAA8E;AAC9E,wBAAgB,oBAAoB,IAAI,MAAM,GAAG,SAAS,CASzD;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAShD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBxE;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQzD;AAMD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,CAO/D;AAMD;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,CAAC,EACrC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAC3E,OAAO,CAAC,CAAC,CAAC,CAkBZ;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAOxE;AAqBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,GAAG,GAAG,OAAO,CAejD;AAQD,wBAAgB,UAAU,IAAI,OAAO,CAyBpC;AAED,wBAAgB,UAAU,IAAI,OAAO,CAEpC;AAgBD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAIzC;AAED,iFAAiF;AACjF,wBAAgB,OAAO,IAAI,MAAM,CAEhC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/db/client.ts"],"names":[],"mappings":"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"}
|
package/dist/db/client.js
CHANGED
|
@@ -268,6 +268,9 @@ export function isLocalDatabase() {
|
|
|
268
268
|
export function intType() {
|
|
269
269
|
return isPostgres() ? "BIGINT" : "INTEGER";
|
|
270
270
|
}
|
|
271
|
+
// `widenIntColumnsToBigInt` lives in `./widen-columns.js` (it depends only on
|
|
272
|
+
// `isPostgres`/`getDbExec` from here) so stores can import it without every
|
|
273
|
+
// `vi.mock("./client.js")` test having to stub the export.
|
|
271
274
|
// ---------------------------------------------------------------------------
|
|
272
275
|
// Parameter conversion: ? -> $1, $2, $3
|
|
273
276
|
// ---------------------------------------------------------------------------
|