@ai-matrx/chat 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,13 +20,14 @@ const ChatHostContext = createContext(null);
20
20
  function ChatProvider({ host, store, children }) {
21
21
  const resolved = typeof window === "undefined" ? resolveChatHost(host) : configureChat(host);
22
22
  const [privateStore] = useState(() => {
23
- if (store) return null;
24
- const created = createChatStore();
23
+ const created = store ? null : createChatStore();
24
+ const chipStore = store ?? created;
25
25
  if (typeof window !== "undefined" && !isRemarkDurabilityRegistered()) {
26
26
  registerRemarkDurability(
27
27
  createDbRemarkDurability({
28
28
  db: resolved.db,
29
- dispatch: created.dispatch,
29
+ dispatch: chipStore.dispatch,
30
+ getState: chipStore.getState,
30
31
  onFailure: (message, error) => {
31
32
  resolved.notify.error(message);
32
33
  console.error("[remarks] write failed:", error);
@@ -34,6 +35,7 @@ function ChatProvider({ host, store, children }) {
34
35
  })
35
36
  );
36
37
  }
38
+ if (!created) return null;
37
39
  return { store: created, snapshot: created.getState() };
38
40
  });
39
41
  const chatStore = store ?? privateStore.store;
@@ -3,7 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { logFailure } from "../../../ui/expectedRefusal.js";
4
4
  import { useCallback, useEffect, useRef, useState } from "react";
5
5
  import { AlertTriangle, Brain, Loader2, RotateCw, SquarePen } from "lucide-react";
6
- import { Button } from "@ai-matrx/design-system/controls";
6
+ import { Button, SegmentedControl } from "@ai-matrx/design-system/controls";
7
7
  import { WindowPanel } from "../../../host/ui-slots.js";
8
8
  import { useAppDispatch, useAppSelector } from "../../../store/hooks.js";
9
9
  import { toast } from "../../../host/notify.js";
@@ -20,7 +20,7 @@ import {
20
20
  ConversationHistorySidebar
21
21
  } from "../../../agents/components/conversation-history/ConversationHistorySidebar.js";
22
22
  import { selectHasMessages } from "../../../agents/redux/execution-system/messages/messages.selectors.js";
23
- import { selectConversationListItemById } from "../../../agents/redux/conversation-list/conversation-list.selectors.js";
23
+ import { findConversationRow } from "../../../agents/redux/conversation-list/conversation-list.selectors.js";
24
24
  import { selectLatestConversationId } from "../../../agents/redux/execution-system/selectors/aggregate.selectors.js";
25
25
  import { selectFocusedConversation } from "../../../agents/redux/execution-system/conversation-focus/conversation-focus.selectors.js";
26
26
  import { useAgentLauncher } from "../../../agents/hooks/useAgentLauncher.js";
@@ -73,7 +73,7 @@ function SessionChatRow({
73
73
  resolveHref
74
74
  }) {
75
75
  const listItem = useAppSelector(
76
- selectConversationListItemById(chat.conversationId)
76
+ (state) => findConversationRow(state, chat.conversationId)
77
77
  );
78
78
  const localTitle = useAppSelector(
79
79
  (state) => state.messages.byConversationId[chat.conversationId]?.title ?? null
@@ -134,30 +134,43 @@ function AgentRunWindowSidebar({
134
134
  },
135
135
  chat.conversationId
136
136
  )) }) : null;
137
- if (!agentId || !canonicalAgentId) {
138
- return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [
139
- sessionSection,
140
- /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-2 px-3 py-10 text-center", children: [
141
- /* @__PURE__ */ jsx(Brain, { className: "h-6 w-6 text-muted-foreground opacity-25" }),
142
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Select an agent from the header to see its history." })
143
- ] })
144
- ] });
145
- }
137
+ const [listScope, setListScope] = useState("all");
138
+ const agentOnly = listScope === "agent" && !!canonicalAgentId;
139
+ const scopeToggle = /* @__PURE__ */ jsx("div", { className: "shrink-0 px-2 pt-2 pb-1", children: /* @__PURE__ */ jsx(
140
+ SegmentedControl,
141
+ {
142
+ "aria-label": "Which chats to list",
143
+ fill: true,
144
+ value: agentOnly ? "agent" : "all",
145
+ onValueChange: (next) => setListScope(next),
146
+ data: [
147
+ { value: "all", label: "All agents" },
148
+ {
149
+ value: "agent",
150
+ label: "This agent",
151
+ title: canonicalAgentId ? "Only chats with the agent in the title bar" : "Pick an agent in the title bar first",
152
+ disabled: !canonicalAgentId
153
+ }
154
+ ]
155
+ }
156
+ ) });
146
157
  return /* @__PURE__ */ jsx(
147
158
  ConversationHistorySidebar,
148
159
  {
149
160
  variant: "consumer",
150
- scopeId: `${surfaceKey ?? "agent-run-window"}:history:${canonicalAgentId}`,
151
- agentIds: [canonicalAgentId],
161
+ scopeId: agentOnly ? `${surfaceKey ?? "agent-run-window"}:history:${canonicalAgentId}` : `${surfaceKey ?? "agent-run-window"}:history:all`,
162
+ agentIds: agentOnly && canonicalAgentId ? [canonicalAgentId] : [],
152
163
  surfaceId: "agent-runner",
153
164
  activeConversationId: null,
154
165
  onOpenConversation: handleOpenConversation,
155
166
  openInPlace: true,
156
167
  surfaceKey: surfaceKey ?? void 0,
157
168
  getConversationHref,
169
+ headerSlot: scopeToggle,
158
170
  topSlot: sessionSection,
159
171
  className: "bg-transparent"
160
- }
172
+ },
173
+ agentOnly ? `agent:${canonicalAgentId}` : "all"
161
174
  );
162
175
  }
163
176
  function AgentRunWindowNewRunButton({
@@ -197,7 +210,7 @@ function WindowTitleContent({
197
210
  onAgentSelect
198
211
  }) {
199
212
  const listTitle = useAppSelector(
200
- (state) => conversationId ? selectConversationListItemById(conversationId)(state)?.title ?? null : null
213
+ (state) => conversationId ? findConversationRow(state, conversationId)?.title ?? null : null
201
214
  );
202
215
  const localTitle = useAppSelector(
203
216
  (state) => conversationId ? state.messages.byConversationId[conversationId]?.title ?? null : null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/chat",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "THE one AI Matrx person-to-agent chat system: the full chat (running agents, conversations, tool calls, streaming, context, surfaces, voice) as explicit per-module subpaths @ai-matrx/chat/<domain>/<module> (no wildcards: scripts/public-modules.json) over a host contract (/host, /host/react) whose only required value is a db. One chat, one renderer: the old kernel entries (., /external-store, /matrx, /react, /tools, styles.css, tokens.css) are retired.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -480,6 +480,11 @@
480
480
  "import": "./dist/agents/components/conversation-actions/rename/ConversationRenameDialogHost.js",
481
481
  "default": "./dist/agents/components/conversation-actions/rename/ConversationRenameDialogHost.js"
482
482
  },
483
+ "./agents/components/conversation-history/AgentConversationList": {
484
+ "types": "./dist/agents/components/conversation-history/AgentConversationList.d.ts",
485
+ "import": "./dist/agents/components/conversation-history/AgentConversationList.js",
486
+ "default": "./dist/agents/components/conversation-history/AgentConversationList.js"
487
+ },
483
488
  "./agents/components/conversation-history/ConversationHistorySidebar": {
484
489
  "types": "./dist/agents/components/conversation-history/ConversationHistorySidebar.d.ts",
485
490
  "import": "./dist/agents/components/conversation-history/ConversationHistorySidebar.js",
@@ -3831,6 +3836,41 @@
3831
3836
  "import": "./dist/tool-call-visualization/components/toolCardUiSession.js",
3832
3837
  "default": "./dist/tool-call-visualization/components/toolCardUiSession.js"
3833
3838
  },
3839
+ "./tool-call-visualization/configured-rows/ConfiguredToolRow": {
3840
+ "types": "./dist/tool-call-visualization/configured-rows/ConfiguredToolRow.d.ts",
3841
+ "import": "./dist/tool-call-visualization/configured-rows/ConfiguredToolRow.js",
3842
+ "default": "./dist/tool-call-visualization/configured-rows/ConfiguredToolRow.js"
3843
+ },
3844
+ "./tool-call-visualization/configured-rows/CopyToolButton": {
3845
+ "types": "./dist/tool-call-visualization/configured-rows/CopyToolButton.d.ts",
3846
+ "import": "./dist/tool-call-visualization/configured-rows/CopyToolButton.js",
3847
+ "default": "./dist/tool-call-visualization/configured-rows/CopyToolButton.js"
3848
+ },
3849
+ "./tool-call-visualization/configured-rows/ToolProgressView": {
3850
+ "types": "./dist/tool-call-visualization/configured-rows/ToolProgressView.d.ts",
3851
+ "import": "./dist/tool-call-visualization/configured-rows/ToolProgressView.js",
3852
+ "default": "./dist/tool-call-visualization/configured-rows/ToolProgressView.js"
3853
+ },
3854
+ "./tool-call-visualization/configured-rows/register": {
3855
+ "types": "./dist/tool-call-visualization/configured-rows/register.d.ts",
3856
+ "import": "./dist/tool-call-visualization/configured-rows/register.js",
3857
+ "default": "./dist/tool-call-visualization/configured-rows/register.js"
3858
+ },
3859
+ "./tool-call-visualization/configured-rows/registries": {
3860
+ "types": "./dist/tool-call-visualization/configured-rows/registries.d.ts",
3861
+ "import": "./dist/tool-call-visualization/configured-rows/registries.js",
3862
+ "default": "./dist/tool-call-visualization/configured-rows/registries.js"
3863
+ },
3864
+ "./tool-call-visualization/configured-rows/transforms": {
3865
+ "types": "./dist/tool-call-visualization/configured-rows/transforms.d.ts",
3866
+ "import": "./dist/tool-call-visualization/configured-rows/transforms.js",
3867
+ "default": "./dist/tool-call-visualization/configured-rows/transforms.js"
3868
+ },
3869
+ "./tool-call-visualization/configured-rows/types": {
3870
+ "types": "./dist/tool-call-visualization/configured-rows/types.d.ts",
3871
+ "import": "./dist/tool-call-visualization/configured-rows/types.js",
3872
+ "default": "./dist/tool-call-visualization/configured-rows/types.js"
3873
+ },
3834
3874
  "./tool-call-visualization/db-renderer/fetchToolRendererRow": {
3835
3875
  "types": "./dist/tool-call-visualization/db-renderer/fetchToolRendererRow.d.ts",
3836
3876
  "import": "./dist/tool-call-visualization/db-renderer/fetchToolRendererRow.js",
@@ -4630,21 +4670,21 @@
4630
4670
  "@ai-matrx/alchemy": "latest",
4631
4671
  "@ai-matrx/associations": "latest",
4632
4672
  "@ai-matrx/browser-audio": "latest",
4633
- "@ai-matrx/canvas": "latest",
4634
4673
  "@ai-matrx/content-ir": "latest",
4635
4674
  "@ai-matrx/content-ir-react": "latest",
4636
4675
  "@ai-matrx/data": "latest",
4637
- "@ai-matrx/design-system": "latest",
4676
+ "@ai-matrx/canvas": "latest",
4638
4677
  "@ai-matrx/diff": "latest",
4639
4678
  "@ai-matrx/icons": "latest",
4640
4679
  "@ai-matrx/kit": "latest",
4641
4680
  "@ai-matrx/media": "latest",
4642
4681
  "@ai-matrx/print": "latest",
4643
- "@ai-matrx/records": "latest",
4644
4682
  "@ai-matrx/realtime": "latest",
4683
+ "@ai-matrx/records": "latest",
4645
4684
  "@ai-matrx/records-ui": "latest",
4646
4685
  "@ai-matrx/rich-content": "latest",
4647
- "@ai-matrx/rich-editor": "latest"
4686
+ "@ai-matrx/rich-editor": "latest",
4687
+ "@ai-matrx/design-system": "latest"
4648
4688
  },
4649
4689
  "devDependencies": {
4650
4690
  "@arethetypeswrong/cli": "^0.18.5",
@@ -4762,5 +4802,5 @@
4762
4802
  "check:side-effects": "node ../scripts/check-side-effects-listed.mjs . && node ../scripts/check-side-effects-listed.mjs --self-test",
4763
4803
  "check:public-surface": "node ./scripts/sync-public-surface.mjs --check && node ./scripts/sync-public-surface.mjs --self-test"
4764
4804
  },
4765
- "gitHead": "5c8ccea93390e2e507cc848eb9a00c84e4e695e8"
4805
+ "gitHead": "45dfe27953371f12f06886920aedd16aedfd960f"
4766
4806
  }
@@ -1,50 +0,0 @@
1
- /**
2
- * Content Pre-Filter — Fast-reject layers for splitContentIntoBlocksV2
3
- *
4
- * Designed to run BEFORE any regex, JSON parsing, or XML extraction.
5
- * Returns a set of candidate categories for a given line so the main
6
- * parser can skip entire detection branches.
7
- *
8
- * Layer 0: Single-character scan (nanoseconds)
9
- * Layer 1: Cheap secondary validation (tens of nanoseconds)
10
- *
11
- * If no candidates are found → the line is guaranteed to be plain text.
12
- */
13
- /**
14
- * Bit flags — a line can be a candidate for multiple categories simultaneously.
15
- * Using bits so the main parser can do `if (candidates & Candidate.CODE)`.
16
- */
17
- export declare const Candidate: {
18
- readonly NONE: 0;
19
- readonly TEXT: 0;
20
- readonly CODE: number;
21
- readonly XML_SIMPLE: number;
22
- readonly XML_ATTR: number;
23
- readonly TABLE: number;
24
- readonly IMAGE: number;
25
- readonly VIDEO: number;
26
- readonly DIVIDER: number;
27
- readonly TREE: number;
28
- readonly BARE_JSON: number;
29
- readonly AUDIO: number;
30
- readonly MATRX_FILE: number;
31
- };
32
- export type CandidateFlags = number;
33
- /**
34
- * Scans a line and returns a bitmask of candidate categories.
35
- *
36
- * This scans the FULL line (not just the start) to support inline blocks
37
- * like "Hello <decision prompt="...">". The scan is still cheap because
38
- * we only look at individual characters — no regex, no string splitting.
39
- *
40
- * @param line The raw line (before trimming)
41
- * @param trimmed The normalized+trimmed line (caller already has this)
42
- * @returns Bitmask of Candidate flags
43
- */
44
- export declare function classifyLine(line: string, trimmed: string): CandidateFlags;
45
- export declare function isPlainText(flags: CandidateFlags): boolean;
46
- /**
47
- * Quick check for whether flags include a specific candidate.
48
- * Usage: `if (hasCandidate(flags, Candidate.CODE)) { ... }`
49
- */
50
- export declare function hasCandidate(flags: CandidateFlags, candidate: number): boolean;
@@ -1,243 +0,0 @@
1
- import { OUR_FILE_URL_MARKERS } from "@ai-matrx/media/files/engine";
2
- const Candidate = {
3
- NONE: 0,
4
- TEXT: 0,
5
- // No special block detected — plain text
6
- CODE: 1 << 0,
7
- // ``` or ~~~ fenced code block
8
- XML_SIMPLE: 1 << 1,
9
- // <thinking>, <flashcards>, etc.
10
- XML_ATTR: 1 << 2,
11
- // <decision prompt="...">, <artifact ...>
12
- TABLE: 1 << 3,
13
- // | col | col |
14
- IMAGE: 1 << 4,
15
- // ![alt](url) or [Image URL: ...]
16
- VIDEO: 1 << 5,
17
- // [Video URL: ...]
18
- DIVIDER: 1 << 7,
19
- // *** or # ===
20
- TREE: 1 << 8,
21
- // Box-drawing / ASCII tree characters
22
- BARE_JSON: 1 << 9,
23
- // { "key": ... } JSON object without code fences
24
- AUDIO: 1 << 10,
25
- // [Audio URL: ...] or a standalone …/clip.mp3 link
26
- MATRX_FILE: 1 << 11
27
- // link/URL to one of OUR files (signed S3 / CDN / public / share)
28
- };
29
- const AUDIO_EXT_RE = /\.(mp3|wav|m4a|aac|ogg|oga|opus|flac|weba|webm)(\?[^\s)]*)?$/i;
30
- function isStandaloneAudioLink(trimmed) {
31
- const link = trimmed.match(/^!?\[.*?\]\((https?:\/\/[^\s)]+)\)$/);
32
- if (link) return AUDIO_EXT_RE.test(link[1]);
33
- if (/^https?:\/\/[^\s)]+$/.test(trimmed)) return AUDIO_EXT_RE.test(trimmed);
34
- return false;
35
- }
36
- function mightBeOurFile(trimmed) {
37
- const markers = OUR_FILE_URL_MARKERS;
38
- for (let mi = 0; mi < markers.length; mi++) {
39
- if (trimmed.includes(markers[mi])) return true;
40
- }
41
- return false;
42
- }
43
- const SIMPLE_XML_TAGS = /* @__PURE__ */ new Set([
44
- "thinking",
45
- "think",
46
- "reasoning",
47
- "info",
48
- "task",
49
- "database",
50
- "private",
51
- "plan",
52
- "event",
53
- "tool",
54
- "questionnaire",
55
- "flashcards",
56
- "cooking_recipe",
57
- "timeline",
58
- "progress_tracker",
59
- "troubleshooting",
60
- "resources",
61
- "research"
62
- ]);
63
- const ATTR_XML_TAGS = /* @__PURE__ */ new Set([
64
- "decision",
65
- "artifact",
66
- // Editor pill tags — carry attributes (file, line, severity, language, …)
67
- // so they're classified as ATTR. The chat-markdown renderer parses them
68
- // back into chips on load.
69
- "editor_error",
70
- "editor_code_snippet"
71
- ]);
72
- const ALL_XML_TAGS = /* @__PURE__ */ new Set([...SIMPLE_XML_TAGS, ...ATTR_XML_TAGS]);
73
- const TREE_CHARS = /* @__PURE__ */ new Set([
74
- "\u251C",
75
- "\u2514",
76
- "\u2502",
77
- "\u250C",
78
- "\u2510",
79
- "\u2518",
80
- "\u252C",
81
- "\u2534",
82
- "\u2524",
83
- "\u253C",
84
- "\u2500"
85
- ]);
86
- function classifyLine(line, trimmed) {
87
- let flags = Candidate.NONE;
88
- const len = trimmed.length;
89
- if (len === 0) return flags;
90
- const first = trimmed[0];
91
- if (first === "`" || first === "~") {
92
- if (len >= 3 && trimmed[1] === first && trimmed[2] === first) {
93
- flags |= Candidate.CODE;
94
- }
95
- }
96
- if (first === "{") {
97
- flags |= Candidate.BARE_JSON;
98
- }
99
- if (first === "|") {
100
- if (trimmed.indexOf("|", 1) !== -1) {
101
- flags |= Candidate.TABLE;
102
- }
103
- }
104
- if (first === "!" && len >= 2 && trimmed[1] === "[") {
105
- flags |= Candidate.IMAGE;
106
- }
107
- if (first === "*" || first === "#") {
108
- if (first === "*") {
109
- if (isStarDivider(trimmed)) {
110
- flags |= Candidate.DIVIDER;
111
- }
112
- } else {
113
- if (len >= 4 && trimmed.indexOf("=") !== -1) {
114
- flags |= Candidate.DIVIDER;
115
- }
116
- }
117
- }
118
- if (TREE_CHARS.has(first)) {
119
- flags |= Candidate.TREE;
120
- }
121
- let searchFrom = 0;
122
- while (searchFrom < len) {
123
- const ltPos = trimmed.indexOf("<", searchFrom);
124
- if (ltPos === -1) break;
125
- if (ltPos + 2 < len && trimmed[ltPos + 1] === "<" && trimmed[ltPos + 2] === "<") {
126
- searchFrom = ltPos + 3;
127
- continue;
128
- }
129
- if (ltPos + 1 < len) {
130
- const next = trimmed[ltPos + 1];
131
- if (isLetter(next)) {
132
- const tagName = extractTagName(trimmed, ltPos + 1);
133
- if (tagName) {
134
- if (SIMPLE_XML_TAGS.has(tagName)) {
135
- flags |= Candidate.XML_SIMPLE;
136
- }
137
- if (ATTR_XML_TAGS.has(tagName)) {
138
- flags |= Candidate.XML_ATTR;
139
- }
140
- }
141
- }
142
- }
143
- searchFrom = ltPos + 1;
144
- }
145
- if (!(flags & Candidate.IMAGE) || !(flags & Candidate.VIDEO) || !(flags & Candidate.AUDIO)) {
146
- const bracketIdx = trimmed.indexOf("[");
147
- if (bracketIdx !== -1) {
148
- if (bracketIdx + 10 < len) {
149
- const after = trimmed[bracketIdx + 1];
150
- if (after === "I" && trimmed.substring(bracketIdx + 1, bracketIdx + 11) === "Image URL:") {
151
- flags |= Candidate.IMAGE;
152
- } else if (after === "V" && trimmed.substring(bracketIdx + 1, bracketIdx + 11) === "Video URL:") {
153
- flags |= Candidate.VIDEO;
154
- } else if (after === "A" && trimmed.substring(bracketIdx + 1, bracketIdx + 11) === "Audio URL:") {
155
- flags |= Candidate.AUDIO;
156
- }
157
- }
158
- }
159
- }
160
- if (!(flags & Candidate.AUDIO) && (first === "[" || first === "!" || first === "h") && isStandaloneAudioLink(trimmed)) {
161
- flags |= Candidate.AUDIO;
162
- }
163
- if (!(flags & Candidate.TABLE) && first !== "|") {
164
- if (trimmed.indexOf("|") !== -1) {
165
- const firstPipe = trimmed.indexOf("|");
166
- if (firstPipe !== -1 && trimmed.indexOf("|", firstPipe + 1) !== -1) {
167
- flags |= Candidate.TABLE;
168
- }
169
- }
170
- }
171
- if (!(flags & Candidate.TREE)) {
172
- for (let ci = 1; ci < len; ci++) {
173
- if (TREE_CHARS.has(trimmed[ci])) {
174
- flags |= Candidate.TREE;
175
- break;
176
- }
177
- }
178
- }
179
- if (!(flags & Candidate.TREE) && (first === " " || first === " " || first === "\u2502" || first === "|" || first === "+")) {
180
- if (looksLikeAsciiTree(trimmed)) {
181
- flags |= Candidate.TREE;
182
- }
183
- }
184
- if (!(flags & Candidate.MATRX_FILE) && mightBeOurFile(trimmed)) {
185
- flags |= Candidate.MATRX_FILE;
186
- }
187
- return flags;
188
- }
189
- function isLetter(ch) {
190
- const c = ch.charCodeAt(0);
191
- return c >= 65 && c <= 90 || c >= 97 && c <= 122;
192
- }
193
- function extractTagName(str, start) {
194
- let end = start;
195
- while (end < str.length) {
196
- const c = str.charCodeAt(end);
197
- if (c >= 97 && c <= 122 || c >= 65 && c <= 90 || c >= 48 && c <= 57 || c === 95) {
198
- end++;
199
- } else {
200
- break;
201
- }
202
- }
203
- if (end === start) return null;
204
- return str.substring(start, end).toLowerCase();
205
- }
206
- function isStarDivider(trimmed) {
207
- let starCount = 0;
208
- for (let i = 0; i < trimmed.length; i++) {
209
- const ch = trimmed[i];
210
- if (ch === "*") {
211
- starCount++;
212
- } else if (ch !== " ") {
213
- return false;
214
- }
215
- }
216
- return starCount >= 3;
217
- }
218
- function looksLikeAsciiTree(trimmed) {
219
- let i = 0;
220
- while (i < trimmed.length) {
221
- const ch2 = trimmed[i];
222
- if (ch2 === " " || ch2 === " " || ch2 === "\u2502" || ch2 === "|") {
223
- i++;
224
- } else {
225
- break;
226
- }
227
- }
228
- if (i >= trimmed.length) return false;
229
- const ch = trimmed[i];
230
- return ch === "\u251C" || ch === "\u2514" || ch === "+";
231
- }
232
- function isPlainText(flags) {
233
- return flags === Candidate.NONE;
234
- }
235
- function hasCandidate(flags, candidate) {
236
- return (flags & candidate) !== 0;
237
- }
238
- export {
239
- Candidate,
240
- classifyLine,
241
- hasCandidate,
242
- isPlainText
243
- };