@agent-native/core 0.84.32 → 0.84.35

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 (81) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +20 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/a2a/response-text.ts +10 -1
  5. package/corpus/core/src/agent/production-agent.ts +23 -20
  6. package/corpus/core/src/agent/run-manager.ts +46 -1
  7. package/corpus/core/src/agent/thread-data-builder.ts +15 -2
  8. package/corpus/core/src/client/code-agent-chat-adapter.ts +8 -1
  9. package/corpus/core/src/client/sse-event-processor.ts +21 -3
  10. package/corpus/core/src/client/use-action.ts +5 -1
  11. package/corpus/core/src/code-agents/transcript-normalizer.ts +31 -0
  12. package/corpus/core/src/server/agent-chat-plugin.ts +8 -2
  13. package/corpus/core/src/server/agent-teams.ts +24 -2
  14. package/corpus/core/src/server/ssr-handler.ts +12 -0
  15. package/corpus/core/src/vite/action-types-plugin.ts +6 -2
  16. package/corpus/core/src/vite/client.ts +152 -17
  17. package/corpus/templates/analytics/changelog/2026-07-01-smoother-chat-streaming.md +6 -0
  18. package/corpus/templates/design/actions/import-design-source.ts +158 -0
  19. package/corpus/templates/design/actions/navigate.ts +3 -2
  20. package/corpus/templates/design/actions/view-screen.ts +1 -1
  21. package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +519 -0
  22. package/corpus/templates/design/app/hooks/use-navigation-state.ts +28 -5
  23. package/corpus/templates/design/app/i18n/zh-TW.ts +40 -0
  24. package/corpus/templates/design/app/i18n-data.ts +508 -0
  25. package/corpus/templates/design/app/pages/DesignEditor.tsx +27 -0
  26. package/corpus/templates/design/changelog/2026-07-01-design-can-import-figma-paste-fig-files-and-standalone-html.md +6 -0
  27. package/corpus/templates/design/package.json +3 -0
  28. package/corpus/templates/design/server/handlers/import-design-file.ts +168 -0
  29. package/corpus/templates/design/server/lib/figma-import/clipboard.ts +122 -0
  30. package/corpus/templates/design/server/lib/figma-import/decode.ts +455 -0
  31. package/corpus/templates/design/server/lib/figma-import/processor.ts +116 -0
  32. package/corpus/templates/design/server/lib/figma-import/render-html.ts +397 -0
  33. package/corpus/templates/design/server/lib/figma-import/types.ts +60 -0
  34. package/corpus/templates/design/server/lib/import-design-files.ts +314 -0
  35. package/corpus/templates/design/server/routes/api/import-design-file.post.ts +1 -0
  36. package/dist/a2a/response-text.d.ts +1 -0
  37. package/dist/a2a/response-text.d.ts.map +1 -1
  38. package/dist/a2a/response-text.js +8 -2
  39. package/dist/a2a/response-text.js.map +1 -1
  40. package/dist/agent/production-agent.d.ts.map +1 -1
  41. package/dist/agent/production-agent.js +31 -27
  42. package/dist/agent/production-agent.js.map +1 -1
  43. package/dist/agent/run-manager.d.ts +12 -0
  44. package/dist/agent/run-manager.d.ts.map +1 -1
  45. package/dist/agent/run-manager.js +32 -1
  46. package/dist/agent/run-manager.js.map +1 -1
  47. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  48. package/dist/agent/thread-data-builder.js +15 -2
  49. package/dist/agent/thread-data-builder.js.map +1 -1
  50. package/dist/client/code-agent-chat-adapter.d.ts.map +1 -1
  51. package/dist/client/code-agent-chat-adapter.js +5 -1
  52. package/dist/client/code-agent-chat-adapter.js.map +1 -1
  53. package/dist/client/sse-event-processor.d.ts.map +1 -1
  54. package/dist/client/sse-event-processor.js +21 -3
  55. package/dist/client/sse-event-processor.js.map +1 -1
  56. package/dist/client/use-action.d.ts +5 -1
  57. package/dist/client/use-action.d.ts.map +1 -1
  58. package/dist/client/use-action.js.map +1 -1
  59. package/dist/code-agents/transcript-normalizer.d.ts.map +1 -1
  60. package/dist/code-agents/transcript-normalizer.js +24 -0
  61. package/dist/code-agents/transcript-normalizer.js.map +1 -1
  62. package/dist/collab/routes.d.ts +1 -1
  63. package/dist/observability/routes.d.ts +3 -3
  64. package/dist/resources/handlers.d.ts +2 -2
  65. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  66. package/dist/server/agent-chat-plugin.js +2 -3
  67. package/dist/server/agent-chat-plugin.js.map +1 -1
  68. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  69. package/dist/server/agent-teams.d.ts.map +1 -1
  70. package/dist/server/agent-teams.js +17 -2
  71. package/dist/server/agent-teams.js.map +1 -1
  72. package/dist/server/ssr-handler.d.ts.map +1 -1
  73. package/dist/server/ssr-handler.js +7 -0
  74. package/dist/server/ssr-handler.js.map +1 -1
  75. package/dist/vite/action-types-plugin.d.ts.map +1 -1
  76. package/dist/vite/action-types-plugin.js +6 -2
  77. package/dist/vite/action-types-plugin.js.map +1 -1
  78. package/dist/vite/client.d.ts.map +1 -1
  79. package/dist/vite/client.js +132 -11
  80. package/dist/vite/client.js.map +1 -1
  81. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { EventEmitter } from "events";
1
2
  import fs from "fs";
2
3
  import type { IncomingMessage, ServerResponse } from "http";
3
4
  import { createRequire, syncBuiltinESMExports } from "module";
@@ -36,6 +37,137 @@ const require = createRequire(import.meta.url);
36
37
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
37
38
  let nitroFsWatchGuardInstalled = false;
38
39
 
40
+ type FsWatchArgs = [fs.PathLike, ...any[]];
41
+
42
+ function isFileWatchLimitError(
43
+ error: NodeJS.ErrnoException | undefined,
44
+ ): boolean {
45
+ return error?.code === "EMFILE" || error?.code === "ENOSPC";
46
+ }
47
+
48
+ function watchPollingIntervalMs(): number {
49
+ const raw = Number(process.env.CHOKIDAR_INTERVAL ?? 1000);
50
+ return Number.isFinite(raw) && raw > 0 ? raw : 1000;
51
+ }
52
+
53
+ function fsWatchListener(args: FsWatchArgs): fs.WatchListener<string> | null {
54
+ const maybeOptionsOrListener = args[1];
55
+ const maybeListener = args[2];
56
+ if (typeof maybeOptionsOrListener === "function")
57
+ return maybeOptionsOrListener as fs.WatchListener<string>;
58
+ if (typeof maybeListener === "function")
59
+ return maybeListener as fs.WatchListener<string>;
60
+ return null;
61
+ }
62
+
63
+ function fsWatchPersistent(args: FsWatchArgs): boolean {
64
+ const options = args[1];
65
+ if (!options || typeof options === "function") return true;
66
+ if (typeof options === "string" || Buffer.isBuffer(options)) return true;
67
+ return options.persistent !== false;
68
+ }
69
+
70
+ function directEntrySnapshot(
71
+ target: string,
72
+ ): Map<string, { mtimeMs: number; size: number; directory: boolean }> {
73
+ const snapshot = new Map<
74
+ string,
75
+ { mtimeMs: number; size: number; directory: boolean }
76
+ >();
77
+ const stat = fs.statSync(target);
78
+ if (!stat.isDirectory()) {
79
+ snapshot.set(path.basename(target), {
80
+ mtimeMs: stat.mtimeMs,
81
+ size: stat.size,
82
+ directory: false,
83
+ });
84
+ return snapshot;
85
+ }
86
+ for (const entry of fs.readdirSync(target, { withFileTypes: true })) {
87
+ const entryPath = path.join(target, entry.name);
88
+ try {
89
+ const entryStat = fs.statSync(entryPath);
90
+ snapshot.set(entry.name, {
91
+ mtimeMs: entryStat.mtimeMs,
92
+ size: entryStat.size,
93
+ directory: entryStat.isDirectory(),
94
+ });
95
+ } catch {
96
+ // The entry may have disappeared between readdir and stat.
97
+ }
98
+ }
99
+ return snapshot;
100
+ }
101
+
102
+ function createPollingFsWatcher(args: FsWatchArgs): fs.FSWatcher {
103
+ const target = String(args[0]);
104
+ const listener = fsWatchListener(args);
105
+ const emitter = new EventEmitter() as fs.FSWatcher;
106
+ let closed = false;
107
+ let previous = directEntrySnapshot(target);
108
+
109
+ if (listener) emitter.on("change", listener);
110
+
111
+ const emitChange = (eventName: "change" | "rename", filename: string) => {
112
+ emitter.emit("change", eventName, filename);
113
+ };
114
+
115
+ const timer = setInterval(() => {
116
+ if (closed) return;
117
+ let next: typeof previous;
118
+ try {
119
+ next = directEntrySnapshot(target);
120
+ } catch (error) {
121
+ emitter.emit("error", error);
122
+ return;
123
+ }
124
+
125
+ for (const [filename, current] of next) {
126
+ const old = previous.get(filename);
127
+ if (!old) {
128
+ emitChange("rename", filename);
129
+ continue;
130
+ }
131
+ if (
132
+ old.mtimeMs !== current.mtimeMs ||
133
+ old.size !== current.size ||
134
+ old.directory !== current.directory
135
+ ) {
136
+ emitChange("change", filename);
137
+ }
138
+ }
139
+ for (const filename of previous.keys()) {
140
+ if (!next.has(filename)) emitChange("rename", filename);
141
+ }
142
+ previous = next;
143
+ }, watchPollingIntervalMs());
144
+
145
+ if (!fsWatchPersistent(args)) timer.unref();
146
+
147
+ emitter.close = () => {
148
+ if (closed) return;
149
+ closed = true;
150
+ clearInterval(timer);
151
+ emitter.removeAllListeners();
152
+ };
153
+ emitter.ref = () => {
154
+ timer.ref();
155
+ return emitter;
156
+ };
157
+ emitter.unref = () => {
158
+ timer.unref();
159
+ return emitter;
160
+ };
161
+
162
+ return emitter;
163
+ }
164
+
165
+ function warnNitroFsWatchFallback(target: unknown, err: NodeJS.ErrnoException) {
166
+ console.warn(
167
+ `[agent-native] Falling back to polling Nitro fs.watch for ${String(target)}: ${err.message}`,
168
+ );
169
+ }
170
+
39
171
  function installNitroFsWatchGuard(): void {
40
172
  if (nitroFsWatchGuardInstalled) return;
41
173
  nitroFsWatchGuardInstalled = true;
@@ -49,29 +181,32 @@ function installNitroFsWatchGuard(): void {
49
181
  watcher = originalWatch(...args);
50
182
  } catch (error) {
51
183
  const err = error as NodeJS.ErrnoException;
52
- if (err.code !== "EMFILE" && err.code !== "ENOSPC") throw error;
53
- console.warn(
54
- `[agent-native] Disabled Nitro fs.watch for ${String(args[0])}: ${err.message}`,
55
- );
56
- return {
57
- close() {},
58
- on() {
59
- return this as fs.FSWatcher;
60
- },
61
- } as unknown as fs.FSWatcher;
184
+ if (!isFileWatchLimitError(err)) throw error;
185
+ warnNitroFsWatchFallback(args[0], err);
186
+ return createPollingFsWatcher(args as FsWatchArgs);
62
187
  }
63
188
 
64
189
  const originalEmit = watcher.emit.bind(watcher);
190
+ const originalClose = watcher.close.bind(watcher);
191
+ let pollingFallback: fs.FSWatcher | undefined;
192
+
193
+ watcher.close = (() => {
194
+ pollingFallback?.close();
195
+ return originalClose();
196
+ }) as fs.FSWatcher["close"];
197
+
65
198
  watcher.emit = ((eventName: string | symbol, ...eventArgs: any[]) => {
66
199
  const err = eventArgs[0] as NodeJS.ErrnoException | undefined;
67
- if (
68
- eventName === "error" &&
69
- (err?.code === "EMFILE" || err?.code === "ENOSPC")
70
- ) {
71
- console.warn(
72
- `[agent-native] Disabled Nitro fs.watch for ${String(args[0])}: ${err.message}`,
73
- );
200
+ if (eventName === "error" && isFileWatchLimitError(err) && err) {
201
+ warnNitroFsWatchFallback(args[0], err);
74
202
  watcher.close();
203
+ pollingFallback = createPollingFsWatcher(args as FsWatchArgs);
204
+ pollingFallback.on("change", (changeEvent, filename) => {
205
+ originalEmit("change", changeEvent, filename);
206
+ });
207
+ pollingFallback.on("error", (pollingError) => {
208
+ originalEmit("error", pollingError);
209
+ });
75
210
  return false;
76
211
  }
77
212
  return originalEmit(eventName, ...eventArgs);
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-01
4
+ ---
5
+
6
+ Analytics chat responses stream more smoothly during long background runs and guarded final answers.
@@ -0,0 +1,158 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { assertAccess } from "@agent-native/core/sharing";
3
+ import { z } from "zod";
4
+
5
+ import { parseFigmaClipboardHtml } from "../server/lib/figma-import/clipboard.js";
6
+ import { importFigmaBuffer } from "../server/lib/figma-import/processor.js";
7
+ import {
8
+ normalizeImportedHtmlDocument,
9
+ resolveImportDesignId,
10
+ saveImportedDesignFiles,
11
+ type ImportedDesignFile,
12
+ } from "../server/lib/import-design-files.js";
13
+
14
+ const MAX_HTML_IMPORT_BYTES = 2 * 1024 * 1024;
15
+
16
+ function ensureHtmlSize(content: string) {
17
+ if (Buffer.byteLength(content, "utf8") > MAX_HTML_IMPORT_BYTES) {
18
+ throw new Error("HTML import content is too large (max 2 MB).");
19
+ }
20
+ }
21
+
22
+ function baseFilename(originalName: string | undefined, fallback: string) {
23
+ return (originalName?.trim() || fallback).replace(/\.[^.]+$/, "") + ".html";
24
+ }
25
+
26
+ export default defineAction({
27
+ description:
28
+ "Import Figma clipboard HTML or standalone HTML into the current Design project as one or more editable screens.",
29
+ schema: z.object({
30
+ designId: z
31
+ .string()
32
+ .optional()
33
+ .describe("Design id. Defaults to the active editor navigation state."),
34
+ sourceType: z.enum(["figma-paste-html", "html-string"]),
35
+ content: z
36
+ .string()
37
+ .max(
38
+ MAX_HTML_IMPORT_BYTES,
39
+ "HTML import content is too large (max 2 MB).",
40
+ ),
41
+ originalName: z.string().optional(),
42
+ }),
43
+ run: async ({ designId, sourceType, content, originalName }) => {
44
+ ensureHtmlSize(content);
45
+ const resolvedDesignId = await resolveImportDesignId(designId);
46
+ await assertAccess("design", resolvedDesignId, "editor");
47
+
48
+ if (sourceType === "html-string") {
49
+ const saved = await saveImportedDesignFiles({
50
+ designId: resolvedDesignId,
51
+ sourceType: "html-import",
52
+ files: [
53
+ {
54
+ filename: baseFilename(originalName, "imported-html"),
55
+ fileType: "html",
56
+ content: normalizeImportedHtmlDocument(content, "HTML source"),
57
+ source: { sourceType: "html-string", originalName },
58
+ },
59
+ ],
60
+ });
61
+ return {
62
+ ...saved,
63
+ stats: { sourceKind: "html-string", frameCount: saved.files.length },
64
+ };
65
+ }
66
+
67
+ const parsed = parseFigmaClipboardHtml(content);
68
+ const warnings: string[] = [];
69
+ if (parsed.buffer && parsed.hasFigmaBuffer) {
70
+ try {
71
+ const imported = await importFigmaBuffer({
72
+ buffer: parsed.buffer,
73
+ filename: originalName ?? "figma-paste.fig",
74
+ sourceKind: "figma-paste",
75
+ selection: { nodeId: parsed.meta?.selectedNodeId },
76
+ meta: parsed.meta,
77
+ });
78
+ const files: ImportedDesignFile[] = imported.files.map((file) => ({
79
+ filename: file.filename,
80
+ fileType: "html",
81
+ content: file.content,
82
+ source: file.source,
83
+ preferredFrame: {
84
+ title:
85
+ typeof file.source?.frameName === "string"
86
+ ? file.source.frameName
87
+ : undefined,
88
+ width: file.width,
89
+ height: file.height,
90
+ },
91
+ }));
92
+ const saved = await saveImportedDesignFiles({
93
+ designId: resolvedDesignId,
94
+ sourceType: "figma-paste",
95
+ files,
96
+ warnings: [...warnings, ...imported.warnings],
97
+ });
98
+ return { ...saved, stats: imported.stats };
99
+ } catch (error) {
100
+ warnings.push(
101
+ error instanceof Error
102
+ ? `Figma binary payload could not be decoded: ${error.message}`
103
+ : "Figma binary payload could not be decoded.",
104
+ );
105
+ }
106
+ } else if (parsed.buffer) {
107
+ warnings.push(
108
+ "The clipboard included a Figma buffer, but it was not a supported fig-kiwi payload.",
109
+ );
110
+ }
111
+
112
+ if (!parsed.fallbackHtml) {
113
+ throw new Error(
114
+ "No importable Figma frame data or visible HTML was found in the clipboard.",
115
+ );
116
+ }
117
+ const saved = await saveImportedDesignFiles({
118
+ designId: resolvedDesignId,
119
+ sourceType: "figma-paste-fallback",
120
+ files: [
121
+ {
122
+ filename: baseFilename(originalName, "figma-paste"),
123
+ fileType: "html",
124
+ content: normalizeImportedHtmlDocument(
125
+ parsed.fallbackHtml,
126
+ "Figma clipboard fallback HTML",
127
+ ),
128
+ source: {
129
+ sourceType: "figma-paste-fallback",
130
+ selectedNodeId: parsed.meta?.selectedNodeId,
131
+ fileKey: parsed.meta?.fileKey,
132
+ },
133
+ },
134
+ ],
135
+ warnings,
136
+ });
137
+ return {
138
+ ...saved,
139
+ stats: {
140
+ sourceKind: "figma-paste",
141
+ format: "html-fallback",
142
+ frameCount: saved.files.length,
143
+ imageCount: 0,
144
+ selectedNodeId: parsed.meta?.selectedNodeId,
145
+ },
146
+ };
147
+ },
148
+ link: ({ result }) => {
149
+ if (!result || typeof result !== "object") return null;
150
+ const designId = (result as { designId?: string }).designId;
151
+ if (!designId) return null;
152
+ return {
153
+ url: `/design/${designId}`,
154
+ label: "Open overview",
155
+ view: "editor",
156
+ };
157
+ },
158
+ });
@@ -18,7 +18,7 @@
18
18
  * --designId Design ID (for editor/present views)
19
19
  * --editorView Editor mode for designs: single or overview
20
20
  * --inspectorTab Inspector tab for designs: design or tweaks (extensions opens Tools for compatibility)
21
- * --leftPanel Left editor panel: file, agent, assets, tools, tokens, or code
21
+ * --leftPanel Left editor panel: file, agent, assets, import, tools, tokens, or code
22
22
  * --fileId Screen/file id to focus in the design editor
23
23
  * --filename Screen filename to focus in the design editor
24
24
  * --tool Design editor tool to activate
@@ -51,6 +51,7 @@ const designLeftPanelSchema = z.enum([
51
51
  "file",
52
52
  "agent",
53
53
  "assets",
54
+ "import",
54
55
  "tools",
55
56
  "tokens",
56
57
  "code",
@@ -58,7 +59,7 @@ const designLeftPanelSchema = z.enum([
58
59
 
59
60
  export default defineAction({
60
61
  description:
61
- "Navigate the UI to a specific view or path. Views: list, editor, design-systems, present, settings. Use --designId with editor/present views and --designSystemId with design-systems. For designs, use editorView=overview to show the infinite screens canvas, or editorView=single with fileId/filename/screen to focus a screen. Use leftPanel=file|agent|assets|tools|tokens|code to focus the left rail, including the wide Code workspace. Legacy inspectorTab=extensions opens Tools. Use tool to activate a design editor tool.",
62
+ "Navigate the UI to a specific view or path. Views: list, editor, design-systems, present, settings. Use --designId with editor/present views and --designSystemId with design-systems. For designs, use editorView=overview to show the infinite screens canvas, or editorView=single with fileId/filename/screen to focus a screen. Use leftPanel=file|agent|assets|import|tools|tokens|code to focus the left rail, including Import and the wide Code workspace. Legacy inspectorTab=extensions opens Tools. Use tool to activate a design editor tool.",
62
63
  schema: z
63
64
  .object({
64
65
  view: z
@@ -136,7 +136,7 @@ function resolveActiveCodeFile(
136
136
 
137
137
  export default defineAction({
138
138
  description:
139
- "See what the user is currently looking at on screen. Returns the current navigation state including which design is open, which view they are on (list, editor, design-systems, present, settings), active/focused design screen, selected element, active inspector tab (design or tweaks), active left rail panel (file, agent, assets, tools, tokens, or code), active code file metadata, overview canvas state, plus any pending question overlay. Always call this first before taking any action.",
139
+ "See what the user is currently looking at on screen. Returns the current navigation state including which design is open, which view they are on (list, editor, design-systems, present, settings), active/focused design screen, selected element, active inspector tab (design or tweaks), active left rail panel (file, agent, assets, import, tools, tokens, or code), active code file metadata, overview canvas state, plus any pending question overlay. Always call this first before taking any action.",
140
140
  schema: z.object({}),
141
141
  http: false,
142
142
  run: async () => {