shennian 0.3.86 → 0.4.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.
Files changed (98) hide show
  1. package/README.md +3 -2
  2. package/dist/bin/shennian.js +1 -1
  3. package/dist/publish-build-manifest.json +32 -27
  4. package/dist/src/agents/adapter.js +1 -19
  5. package/dist/src/agents/claude-managed-permissions.js +1 -248
  6. package/dist/src/agents/claude.js +8 -393
  7. package/dist/src/agents/codex-managed-permissions.js +1 -294
  8. package/dist/src/agents/codex-managed-room-tools.js +1 -249
  9. package/dist/src/agents/codex-utils.js +7 -200
  10. package/dist/src/agents/codex.d.ts +7 -1
  11. package/dist/src/agents/codex.js +17 -1352
  12. package/dist/src/agents/config-status.js +1 -226
  13. package/dist/src/agents/cursor-managed-permissions.js +2 -299
  14. package/dist/src/agents/cursor.js +1 -314
  15. package/dist/src/agents/custom.js +4 -418
  16. package/dist/src/agents/external-channel-instructions.js +11 -94
  17. package/dist/src/agents/gemini.js +1 -180
  18. package/dist/src/agents/manager.js +18 -250
  19. package/dist/src/agents/model-registry/cache.js +1 -37
  20. package/dist/src/agents/model-registry/discovery.js +2 -188
  21. package/dist/src/agents/model-registry/parsers.js +4 -521
  22. package/dist/src/agents/model-registry/runner.js +1 -30
  23. package/dist/src/agents/model-registry/service.js +1 -115
  24. package/dist/src/agents/model-registry/types.js +1 -8
  25. package/dist/src/agents/model-registry.js +1 -19
  26. package/dist/src/agents/native-capabilities.js +1 -237
  27. package/dist/src/agents/openclaw.js +2 -293
  28. package/dist/src/agents/opencode-managed-permissions.js +2 -187
  29. package/dist/src/agents/opencode.js +1 -298
  30. package/dist/src/agents/platform-instructions.js +17 -46
  31. package/dist/src/agents/runtime-compatibility.js +1 -44
  32. package/dist/src/agents/workbuddy-managed-permissions.js +2 -329
  33. package/dist/src/agents/workbuddy.js +3 -444
  34. package/dist/src/commands/logout.d.ts +3 -0
  35. package/dist/src/commands/logout.js +1 -0
  36. package/dist/src/daemon-ipc/paths.js +1 -1
  37. package/dist/src/fs/boundary.js +1 -185
  38. package/dist/src/fs/handler.js +1 -130
  39. package/dist/src/fs/security.js +1 -32
  40. package/dist/src/fs/text-decoder.js +1 -110
  41. package/dist/src/index.js +3 -587
  42. package/dist/src/manager/prompt.js +29 -34
  43. package/dist/src/manager/registry.js +1 -317
  44. package/dist/src/manager/retired-external-channel-compat.js +1 -40
  45. package/dist/src/manager/runtime.js +23 -1514
  46. package/dist/src/native-fusion/codex-parser.d.ts +3 -1
  47. package/dist/src/native-fusion/codex-parser.js +4 -660
  48. package/dist/src/native-fusion/codex-user-input.js +5 -102
  49. package/dist/src/native-fusion/config.js +1 -5
  50. package/dist/src/native-fusion/file-watcher.js +1 -191
  51. package/dist/src/native-fusion/local-repository.js +2 -569
  52. package/dist/src/native-fusion/opencode-parser.js +3 -130
  53. package/dist/src/native-fusion/parser-common.d.ts +6 -1
  54. package/dist/src/native-fusion/parser-common.js +4 -4
  55. package/dist/src/native-fusion/parsers.d.ts +4 -2
  56. package/dist/src/native-fusion/parsers.js +3 -140
  57. package/dist/src/native-fusion/service.js +2 -739
  58. package/dist/src/native-fusion/session-header.js +2 -101
  59. package/dist/src/native-fusion/state.js +2 -104
  60. package/dist/src/native-fusion/types.js +1 -1
  61. package/dist/src/personal-sync/machine-authority.js +1 -34
  62. package/dist/src/room/activation-audit.js +2 -45
  63. package/dist/src/room/activation-policy.js +1 -30
  64. package/dist/src/room/activation-service.js +8 -408
  65. package/dist/src/room/invite-bootstrap.js +1 -156
  66. package/dist/src/room/managed-prompt-composer.d.ts +1 -1
  67. package/dist/src/room/managed-prompt-composer.js +17 -16
  68. package/dist/src/room/managed-room-binding.js +1 -167
  69. package/dist/src/room/managed-room-ipc.d.ts +4 -0
  70. package/dist/src/room/managed-room-ipc.js +2 -2
  71. package/dist/src/room/mirror-sync-service.d.ts +1 -0
  72. package/dist/src/room/mirror-sync-service.js +1 -183
  73. package/dist/src/room/room-binding-client.js +1 -151
  74. package/dist/src/session/archive-zip.js +1 -220
  75. package/dist/src/session/deletion-sync-state.js +2 -136
  76. package/dist/src/session/handlers/agent-capabilities.js +1 -14
  77. package/dist/src/session/handlers/agent-config.js +1 -150
  78. package/dist/src/session/handlers/agents.js +1 -55
  79. package/dist/src/session/handlers/chat.js +2 -1030
  80. package/dist/src/session/handlers/control.js +1 -61
  81. package/dist/src/session/handlers/fs.js +1 -594
  82. package/dist/src/session/handlers/message-status.js +1 -15
  83. package/dist/src/session/handlers/remote-access.js +1 -43
  84. package/dist/src/session/handlers/room-managed.js +1 -101
  85. package/dist/src/session/handlers/session-refresh.js +1 -35
  86. package/dist/src/session/handlers/title.js +1 -79
  87. package/dist/src/session/handlers/tool-detail.js +1 -149
  88. package/dist/src/session/handlers/voice-window.js +1 -62
  89. package/dist/src/session/history-subscriptions.js +1 -470
  90. package/dist/src/session/manager.d.ts +1 -0
  91. package/dist/src/session/manager.js +1 -658
  92. package/dist/src/session/native-cleanup-outbox.js +2 -287
  93. package/dist/src/session/native-session-lifecycle.js +6 -327
  94. package/dist/src/session/queue.d.ts +8 -0
  95. package/dist/src/session/queue.js +6 -6
  96. package/dist/src/upgrade/scheduler.js +1 -126
  97. package/node_modules/@shennian/wire/dist/agent-workspace.js +2 -2
  98. package/package.json +1 -1
@@ -1,102 +1,5 @@
1
- // @arch docs/architecture/cli/native-session-fusion.md
2
- // @test src/__tests__/native-fusion-codex-delegation.test.ts
3
- // @test src/__tests__/native-fusion-codex-current-input.test.ts
4
- import { normalizeText } from './parser-common.js';
5
- export function parseCodexTextElements(value) {
6
- if (!Array.isArray(value))
7
- return '';
8
- return value
9
- .map((item) => {
10
- if (typeof item === 'string')
11
- return item;
12
- if (typeof item !== 'object' || item === null)
13
- return '';
14
- const record = item;
15
- return typeof record.text === 'string' ? record.text : '';
16
- })
17
- .map((text) => normalizeText(text))
18
- .filter(Boolean)
19
- .join('\n\n');
20
- }
21
- function stripCodexImagePlaceholders(text) {
22
- return normalizeText(text.replace(/<image>\s*<\/image>/gi, ''));
23
- }
24
- const CODEX_AMBIENT_BROWSER_REQUEST_RE = /^<in-app-browser-context\b(?=[^>]*\bsource=["']ambient-ui-state["'])[^>]*>[\s\S]*?<\/in-app-browser-context>\s*\n+##\s+My request(?: for Codex)?:\s*([\s\S]*)$/i;
25
- export function stripCodexUserMessageWrapper(text) {
26
- const normalized = normalizeText(text);
27
- if (!normalized)
28
- return { text: '', strippedWrapper: false };
29
- const ambientBrowserRequestMatch = normalized.match(CODEX_AMBIENT_BROWSER_REQUEST_RE);
30
- if (ambientBrowserRequestMatch?.[1]) {
31
- return {
32
- text: stripCodexImagePlaceholders(ambientBrowserRequestMatch[1]),
33
- strippedWrapper: true,
34
- };
35
- }
36
- const requestMatch = normalized.match(/My request for Codex:\s*([\s\S]*)$/i);
37
- if (requestMatch?.[1]) {
38
- return { text: stripCodexImagePlaceholders(requestMatch[1]), strippedWrapper: true };
39
- }
40
- const attachmentRequestMatch = normalized.match(/^# Files mentioned by the user:\s*[\s\S]*?\n+## My request:\s*([\s\S]*)$/i);
41
- if (attachmentRequestMatch?.[1]) {
42
- return { text: stripCodexImagePlaceholders(attachmentRequestMatch[1]), strippedWrapper: true };
43
- }
44
- return { text: normalized, strippedWrapper: false };
45
- }
46
- const CODEX_IMAGE_OPEN_TAG_RE = /^<image\b[^>]*\bpath="([^"]+)"[^>]*>\s*$/i;
47
- const CODEX_IMAGE_CLOSE_TAG_RE = /^<\/image>\s*$/i;
48
- export function parseCodexResponseUserInput(content) {
49
- if (!Array.isArray(content))
50
- return { text: '', imagePaths: [], strippedWrapper: false };
51
- const ignoredTextIndexes = new Set();
52
- const imagePaths = [];
53
- for (let index = 0; index + 2 < content.length; index += 1) {
54
- const opening = content[index];
55
- const image = content[index + 1];
56
- const closing = content[index + 2];
57
- if (typeof opening !== 'object' || opening === null ||
58
- typeof image !== 'object' || image === null ||
59
- typeof closing !== 'object' || closing === null)
60
- continue;
61
- const openingRecord = opening;
62
- const imageRecord = image;
63
- const closingRecord = closing;
64
- if (openingRecord.type !== 'input_text' || typeof openingRecord.text !== 'string' ||
65
- imageRecord.type !== 'input_image' ||
66
- closingRecord.type !== 'input_text' || typeof closingRecord.text !== 'string' ||
67
- !CODEX_IMAGE_CLOSE_TAG_RE.test(normalizeText(closingRecord.text)))
68
- continue;
69
- const imagePath = normalizeText(openingRecord.text).match(CODEX_IMAGE_OPEN_TAG_RE)?.[1];
70
- if (!imagePath)
71
- continue;
72
- imagePaths.push(imagePath);
73
- ignoredTextIndexes.add(index);
74
- ignoredTextIndexes.add(index + 2);
75
- }
76
- const rawText = content
77
- .map((part, index) => {
78
- if (ignoredTextIndexes.has(index))
79
- return '';
80
- if (typeof part === 'string')
81
- return part;
82
- if (typeof part !== 'object' || part === null)
83
- return '';
84
- const record = part;
85
- return record.type === 'input_text' && typeof record.text === 'string' ? record.text : '';
86
- })
87
- .map((text) => normalizeText(text))
88
- .filter(Boolean)
89
- .join('\n\n');
90
- const stripped = stripCodexUserMessageWrapper(rawText);
91
- return { ...stripped, imagePaths };
92
- }
93
- export function parseCodexDelegationInput(payload) {
94
- if (payload.namespace !== 'codex_app' || typeof payload.output !== 'string')
95
- return null;
96
- const normalized = normalizeText(payload.output);
97
- if (!normalized.startsWith('<codex_delegation>') || !normalized.endsWith('</codex_delegation>')) {
98
- return null;
99
- }
100
- const input = normalized.match(/<input>([\s\S]*?)<\/input>\s*<\/codex_delegation>$/)?.[1];
101
- return input ? normalizeText(input) : null;
102
- }
1
+ import{normalizeText as s}from"./parser-common.js";function E(t){return Array.isArray(t)?t.map(e=>{if(typeof e=="string")return e;if(typeof e!="object"||e===null)return"";const n=e;return typeof n.text=="string"?n.text:""}).map(e=>s(e)).filter(Boolean).join(`
2
+
3
+ `):""}function x(t){return s(t.replace(/<image>\s*<\/image>/gi,""))}const g=/^<in-app-browser-context\b(?=[^>]*\bsource=["']ambient-ui-state["'])[^>]*>[\s\S]*?<\/in-app-browser-context>\s*\n+##\s+My request(?: for Codex)?:\s*([\s\S]*)$/i;function m(t){const e=s(t);if(!e)return{text:"",strippedWrapper:!1};const n=e.match(g);if(n?.[1])return{text:x(n[1]),strippedWrapper:!0};const p=e.match(/My request for Codex:\s*([\s\S]*)$/i);if(p?.[1])return{text:x(p[1]),strippedWrapper:!0};const a=e.match(/^# Files mentioned by the user:\s*[\s\S]*?\n+## My request:\s*([\s\S]*)$/i);return a?.[1]?{text:x(a[1]),strippedWrapper:!0}:{text:e,strippedWrapper:!1}}const y=/^<image\b[^>]*\bpath="([^"]+)"[^>]*>\s*$/i,h=/^<\/image>\s*$/i;function b(t){if(!Array.isArray(t))return{text:"",imagePaths:[],strippedWrapper:!1};const e=new Set,n=[];for(let r=0;r+2<t.length;r+=1){const i=t[r],o=t[r+1],u=t[r+2];if(typeof i!="object"||i===null||typeof o!="object"||o===null||typeof u!="object"||u===null)continue;const c=i,l=o,f=u;if(c.type!=="input_text"||typeof c.text!="string"||l.type!=="input_image"||f.type!=="input_text"||typeof f.text!="string"||!h.test(s(f.text)))continue;const d=s(c.text).match(y)?.[1];d&&(n.push(d),e.add(r),e.add(r+2))}const p=t.map((r,i)=>{if(e.has(i))return"";if(typeof r=="string")return r;if(typeof r!="object"||r===null)return"";const o=r;return o.type==="input_text"&&typeof o.text=="string"?o.text:""}).map(r=>s(r)).filter(Boolean).join(`
4
+
5
+ `);return{...m(p),imagePaths:n}}function R(t){if(t.namespace!=="codex_app"||typeof t.output!="string")return null;const e=s(t.output);if(!e.startsWith("<codex_delegation>")||!e.endsWith("</codex_delegation>"))return null;const n=e.match(/<input>([\s\S]*?)<\/input>\s*<\/codex_delegation>$/)?.[1];return n?s(n):null}export{R as parseCodexDelegationInput,b as parseCodexResponseUserInput,E as parseCodexTextElements,m as stripCodexUserMessageWrapper};
@@ -1,5 +1 @@
1
- // @arch docs/architecture/cli/native-session-fusion.md
2
- // @test src/__tests__/native-fusion-config.test.ts
3
- export function isNativeFusionEnabled(env = process.env) {
4
- return env.SHENNIAN_NATIVE_FUSION_DISABLED !== '1';
5
- }
1
+ function n(e=process.env){return e.SHENNIAN_NATIVE_FUSION_DISABLED!=="1"}export{n as isNativeFusionEnabled};
@@ -1,191 +1 @@
1
- // @arch docs/architecture/cli/native-session-fusion.md#51-watcher-拓扑与事件规则
2
- // @test src/__tests__/native-fusion-file-watcher.test.ts
3
- import fs from 'node:fs';
4
- import path from 'node:path';
5
- const WATCH_DEBOUNCE_MS = 250;
6
- export class NativeFusionFileWatcher {
7
- onChanged;
8
- directoryWatchFactory;
9
- fileWatchFactory;
10
- directoryWatchers = new Map();
11
- fileWatchers = new Map();
12
- filesByDirectory = new Map();
13
- debounceTimers = new Map();
14
- constructor(onChanged, directoryWatchFactory = (directory, listener) => (fs.watch(directory, listener)), fileWatchFactory = (filePath, listener) => fs.watch(filePath, listener)) {
15
- this.onChanged = onChanged;
16
- this.directoryWatchFactory = directoryWatchFactory;
17
- this.fileWatchFactory = fileWatchFactory;
18
- }
19
- refresh(filePaths, rootDirectories = []) {
20
- const nextFiles = new Set(filePaths);
21
- const nextFilesByDirectory = new Map();
22
- for (const filePath of filePaths) {
23
- const directory = path.dirname(filePath);
24
- const files = nextFilesByDirectory.get(directory) ?? new Set();
25
- files.add(filePath);
26
- nextFilesByDirectory.set(directory, files);
27
- }
28
- const nextDirectories = new Set(nextFilesByDirectory.keys());
29
- for (const root of rootDirectories) {
30
- this.collectDirectories(root, nextDirectories);
31
- }
32
- for (const [directory, watcher] of this.directoryWatchers.entries()) {
33
- if (nextDirectories.has(directory))
34
- continue;
35
- watcher.close();
36
- this.directoryWatchers.delete(directory);
37
- }
38
- for (const [filePath, watcher] of this.fileWatchers.entries()) {
39
- if (nextFiles.has(filePath))
40
- continue;
41
- watcher.close();
42
- this.fileWatchers.delete(filePath);
43
- }
44
- this.filesByDirectory = nextFilesByDirectory;
45
- for (const directory of nextDirectories)
46
- this.trackDirectory(directory);
47
- // A directory watcher is sufficient for file creation/rename on all supported
48
- // platforms, but macOS does not reliably report content appends to an existing
49
- // child. Watch known transcript files directly so resumed native sessions stay
50
- // realtime; the periodic full scan remains the fallback when a handle fails.
51
- for (const filePath of nextFiles)
52
- this.trackFile(filePath);
53
- }
54
- /** Install the append watcher before parsing a newly discovered transcript. */
55
- trackFile(filePath) {
56
- const directory = path.dirname(filePath);
57
- const files = this.filesByDirectory.get(directory) ?? new Set();
58
- files.add(filePath);
59
- this.filesByDirectory.set(directory, files);
60
- this.trackDirectory(directory);
61
- if (this.fileWatchers.has(filePath))
62
- return;
63
- try {
64
- const watcher = this.fileWatchFactory(filePath, () => this.schedule(filePath));
65
- this.fileWatchers.set(filePath, watcher);
66
- }
67
- catch (error) {
68
- console.warn(`[native-fusion] unable to watch transcript ${filePath}`, error);
69
- }
70
- }
71
- untrackFile(filePath) {
72
- this.fileWatchers.get(filePath)?.close();
73
- this.fileWatchers.delete(filePath);
74
- const directory = path.dirname(filePath);
75
- const files = this.filesByDirectory.get(directory);
76
- files?.delete(filePath);
77
- if (files?.size === 0)
78
- this.filesByDirectory.delete(directory);
79
- }
80
- stop() {
81
- for (const watcher of this.directoryWatchers.values())
82
- watcher.close();
83
- for (const watcher of this.fileWatchers.values())
84
- watcher.close();
85
- this.directoryWatchers.clear();
86
- this.fileWatchers.clear();
87
- this.filesByDirectory.clear();
88
- for (const timer of this.debounceTimers.values())
89
- clearTimeout(timer);
90
- this.debounceTimers.clear();
91
- }
92
- handleChange(directory, filename) {
93
- const changedPath = filename ? path.join(directory, filename.toString()) : null;
94
- if (changedPath) {
95
- try {
96
- const stat = fs.statSync(changedPath);
97
- if (stat.isDirectory()) {
98
- this.trackDirectoryTree(changedPath, true);
99
- return;
100
- }
101
- if (stat.isFile() && isNativeTranscriptPath(changedPath)) {
102
- this.trackFile(changedPath);
103
- this.schedule(changedPath);
104
- return;
105
- }
106
- }
107
- catch {
108
- if (isNativeTranscriptPath(changedPath)) {
109
- this.untrackFile(changedPath);
110
- this.schedule(changedPath);
111
- return;
112
- }
113
- }
114
- }
115
- const targets = [...(this.filesByDirectory.get(directory) ?? [])];
116
- for (const target of targets)
117
- this.schedule(target);
118
- }
119
- trackDirectory(directory) {
120
- if (this.directoryWatchers.has(directory))
121
- return;
122
- try {
123
- const watcher = this.directoryWatchFactory(directory, (_eventType, filename) => {
124
- this.handleChange(directory, filename);
125
- });
126
- this.directoryWatchers.set(directory, watcher);
127
- }
128
- catch (error) {
129
- console.warn(`[native-fusion] unable to watch ${directory}`, error);
130
- }
131
- }
132
- collectDirectories(directory, output) {
133
- if (!fs.existsSync(directory))
134
- return;
135
- let stat;
136
- try {
137
- stat = fs.statSync(directory);
138
- }
139
- catch {
140
- return;
141
- }
142
- if (!stat.isDirectory())
143
- return;
144
- output.add(directory);
145
- let entries;
146
- try {
147
- entries = fs.readdirSync(directory, { withFileTypes: true });
148
- }
149
- catch {
150
- return;
151
- }
152
- for (const entry of entries) {
153
- if (entry.isDirectory())
154
- this.collectDirectories(path.join(directory, entry.name), output);
155
- }
156
- }
157
- trackDirectoryTree(directory, scheduleDiscoveredFiles) {
158
- this.trackDirectory(directory);
159
- let entries;
160
- try {
161
- entries = fs.readdirSync(directory, { withFileTypes: true });
162
- }
163
- catch {
164
- return;
165
- }
166
- for (const entry of entries) {
167
- const fullPath = path.join(directory, entry.name);
168
- if (entry.isDirectory()) {
169
- this.trackDirectoryTree(fullPath, scheduleDiscoveredFiles);
170
- }
171
- else if (entry.isFile() && isNativeTranscriptPath(fullPath)) {
172
- this.trackFile(fullPath);
173
- if (scheduleDiscoveredFiles)
174
- this.schedule(fullPath);
175
- }
176
- }
177
- }
178
- schedule(filePath) {
179
- const previous = this.debounceTimers.get(filePath);
180
- if (previous)
181
- clearTimeout(previous);
182
- const timer = setTimeout(() => {
183
- this.debounceTimers.delete(filePath);
184
- this.onChanged([filePath]);
185
- }, WATCH_DEBOUNCE_MS);
186
- this.debounceTimers.set(filePath, timer);
187
- }
188
- }
189
- function isNativeTranscriptPath(filePath) {
190
- return filePath.endsWith('.jsonl') || filePath.endsWith('.opencode-session.json');
191
- }
1
+ import o from"node:fs";import a from"node:path";const y=250;class W{onChanged;directoryWatchFactory;fileWatchFactory;directoryWatchers=new Map;fileWatchers=new Map;filesByDirectory=new Map;debounceTimers=new Map;constructor(t,r=(s,i)=>o.watch(s,i),e=(s,i)=>o.watch(s,i)){this.onChanged=t,this.directoryWatchFactory=r,this.fileWatchFactory=e}refresh(t,r=[]){const e=new Set(t),s=new Map;for(const c of t){const n=a.dirname(c),f=s.get(n)??new Set;f.add(c),s.set(n,f)}const i=new Set(s.keys());for(const c of r)this.collectDirectories(c,i);for(const[c,n]of this.directoryWatchers.entries())i.has(c)||(n.close(),this.directoryWatchers.delete(c));for(const[c,n]of this.fileWatchers.entries())e.has(c)||(n.close(),this.fileWatchers.delete(c));this.filesByDirectory=s;for(const c of i)this.trackDirectory(c);for(const c of e)this.trackFile(c)}trackFile(t){const r=a.dirname(t),e=this.filesByDirectory.get(r)??new Set;if(e.add(t),this.filesByDirectory.set(r,e),this.trackDirectory(r),!this.fileWatchers.has(t))try{const s=this.fileWatchFactory(t,()=>this.schedule(t));this.fileWatchers.set(t,s)}catch(s){console.warn(`[native-fusion] unable to watch transcript ${t}`,s)}}untrackFile(t){this.fileWatchers.get(t)?.close(),this.fileWatchers.delete(t);const r=a.dirname(t),e=this.filesByDirectory.get(r);e?.delete(t),e?.size===0&&this.filesByDirectory.delete(r)}stop(){for(const t of this.directoryWatchers.values())t.close();for(const t of this.fileWatchers.values())t.close();this.directoryWatchers.clear(),this.fileWatchers.clear(),this.filesByDirectory.clear();for(const t of this.debounceTimers.values())clearTimeout(t);this.debounceTimers.clear()}handleChange(t,r){const e=r?a.join(t,r.toString()):null;if(e)try{const i=o.statSync(e);if(i.isDirectory()){this.trackDirectoryTree(e,!0);return}if(i.isFile()&&l(e)){this.trackFile(e),this.schedule(e);return}}catch{if(l(e)){this.untrackFile(e),this.schedule(e);return}}const s=[...this.filesByDirectory.get(t)??[]];for(const i of s)this.schedule(i)}trackDirectory(t){if(!this.directoryWatchers.has(t))try{const r=this.directoryWatchFactory(t,(e,s)=>{this.handleChange(t,s)});this.directoryWatchers.set(t,r)}catch(r){console.warn(`[native-fusion] unable to watch ${t}`,r)}}collectDirectories(t,r){if(!o.existsSync(t))return;let e;try{e=o.statSync(t)}catch{return}if(!e.isDirectory())return;r.add(t);let s;try{s=o.readdirSync(t,{withFileTypes:!0})}catch{return}for(const i of s)i.isDirectory()&&this.collectDirectories(a.join(t,i.name),r)}trackDirectoryTree(t,r){this.trackDirectory(t);let e;try{e=o.readdirSync(t,{withFileTypes:!0})}catch{return}for(const s of e){const i=a.join(t,s.name);s.isDirectory()?this.trackDirectoryTree(i,r):s.isFile()&&l(i)&&(this.trackFile(i),r&&this.schedule(i))}}schedule(t){const r=this.debounceTimers.get(t);r&&clearTimeout(r);const e=setTimeout(()=>{this.debounceTimers.delete(t),this.onChanged([t])},y);this.debounceTimers.set(t,e)}}function l(h){return h.endsWith(".jsonl")||h.endsWith(".opencode-session.json")}export{W as NativeFusionFileWatcher};