@co0ontty/wand 4.83.1 → 4.84.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 (188) hide show
  1. package/README.md +4 -0
  2. package/dist/agent-dispatch.js +5 -1
  3. package/dist/ai-team-availability.d.ts +1 -1
  4. package/dist/ai-team-prompts.js +13 -4
  5. package/dist/ai-team-runner.d.ts +80 -5
  6. package/dist/ai-team-runner.js +332 -55
  7. package/dist/ai-team-types.d.ts +92 -5
  8. package/dist/ai-team-types.js +9 -2
  9. package/dist/build-info.json +3 -3
  10. package/dist/cli-api.d.ts +1 -0
  11. package/dist/cli-api.js +3 -0
  12. package/dist/cli.js +18 -1
  13. package/dist/commit-task-archive.d.ts +3 -1
  14. package/dist/commit-task-archive.js +21 -10
  15. package/dist/config.d.ts +1 -1
  16. package/dist/config.js +73 -0
  17. package/dist/conversation-mentions.d.ts +16 -0
  18. package/dist/conversation-mentions.js +32 -0
  19. package/dist/conversation-service.d.ts +90 -0
  20. package/dist/conversation-service.js +759 -0
  21. package/dist/conversation-session-preview.d.ts +9 -0
  22. package/dist/conversation-session-preview.js +47 -0
  23. package/dist/conversation-task-preview.d.ts +5 -0
  24. package/dist/conversation-task-preview.js +8 -0
  25. package/dist/conversation-types.d.ts +80 -0
  26. package/dist/conversation-types.js +7 -0
  27. package/dist/core-extension-host.d.ts +27 -0
  28. package/dist/core-extension-host.js +138 -0
  29. package/dist/core-runner.d.ts +86 -0
  30. package/dist/core-runner.js +757 -0
  31. package/dist/core-status-cli.d.ts +9 -0
  32. package/dist/core-status-cli.js +280 -0
  33. package/dist/core-turn-tracker.d.ts +12 -0
  34. package/dist/core-turn-tracker.js +36 -0
  35. package/dist/decision-runner.d.ts +6 -0
  36. package/dist/decision-runner.js +3 -1
  37. package/dist/decision-tool.d.ts +2 -0
  38. package/dist/decision-tool.js +49 -22
  39. package/dist/env-utils.d.ts +7 -3
  40. package/dist/env-utils.js +30 -5
  41. package/dist/git-quick-commit.d.ts +5 -0
  42. package/dist/git-quick-commit.js +9 -3
  43. package/dist/git-utils.js +2 -1
  44. package/dist/harness-engine.d.ts +110 -0
  45. package/dist/harness-engine.js +331 -0
  46. package/dist/mission-diff.js +2 -1
  47. package/dist/model-auto-assign.d.ts +50 -0
  48. package/dist/model-auto-assign.js +163 -0
  49. package/dist/model-group-runner.d.ts +4 -0
  50. package/dist/model-group-runner.js +72 -0
  51. package/dist/model-groups.d.ts +38 -0
  52. package/dist/model-groups.js +138 -0
  53. package/dist/models.d.ts +12 -0
  54. package/dist/models.js +102 -8
  55. package/dist/npm-update-utils.js +2 -1
  56. package/dist/openrouter-free-cli.d.ts +25 -0
  57. package/dist/openrouter-free-cli.js +109 -0
  58. package/dist/openrouter-free-models.d.ts +85 -0
  59. package/dist/openrouter-free-models.js +458 -0
  60. package/dist/openrouter-free-selection.d.ts +5 -0
  61. package/dist/openrouter-free-selection.js +7 -0
  62. package/dist/path-repair.d.ts +10 -0
  63. package/dist/path-repair.js +50 -5
  64. package/dist/pi-auto-resources.d.ts +12 -0
  65. package/dist/pi-auto-resources.js +151 -0
  66. package/dist/pi-execution-types.d.ts +48 -0
  67. package/dist/pi-execution-types.js +1 -0
  68. package/dist/pi-execution.d.ts +9 -0
  69. package/dist/pi-execution.js +187 -0
  70. package/dist/pi-extension-resources.d.ts +13 -0
  71. package/dist/pi-extension-resources.js +71 -0
  72. package/dist/pi-openrouter-free-bridge.d.ts +2 -0
  73. package/dist/pi-openrouter-free-bridge.js +80 -0
  74. package/dist/pi-resource-bridge.d.ts +2 -0
  75. package/dist/pi-resource-bridge.js +73 -0
  76. package/dist/pi-resource-catalog.d.ts +18 -0
  77. package/dist/pi-resource-catalog.js +82 -0
  78. package/dist/pi-resource-recommendation.d.ts +29 -0
  79. package/dist/pi-resource-recommendation.js +169 -0
  80. package/dist/pi-resource-run.d.ts +8 -0
  81. package/dist/pi-resource-run.js +28 -0
  82. package/dist/pi-session-settings.d.ts +157 -0
  83. package/dist/pi-session-settings.js +166 -0
  84. package/dist/platform-action-catalog.d.ts +24 -0
  85. package/dist/platform-action-catalog.js +141 -0
  86. package/dist/platform-action-service.d.ts +37 -0
  87. package/dist/platform-action-service.js +138 -0
  88. package/dist/platform-action-types.d.ts +125 -0
  89. package/dist/platform-action-types.js +7 -0
  90. package/dist/process-manager.d.ts +21 -1
  91. package/dist/process-manager.js +168 -15
  92. package/dist/provider-catalog.js +1 -1
  93. package/dist/provider-cli-updater.js +29 -8
  94. package/dist/provider-history-scanner.d.ts +9 -0
  95. package/dist/provider-history-scanner.js +107 -1
  96. package/dist/retention.d.ts +19 -7
  97. package/dist/retention.js +171 -28
  98. package/dist/server-ai-team-routes.js +6 -2
  99. package/dist/server-conversation-routes.d.ts +5 -0
  100. package/dist/server-conversation-routes.js +91 -0
  101. package/dist/server-employee-routes.d.ts +2 -0
  102. package/dist/server-employee-routes.js +85 -11
  103. package/dist/server-pi-recommendation-routes.d.ts +13 -0
  104. package/dist/server-pi-recommendation-routes.js +67 -0
  105. package/dist/server-resume-routes.js +1 -1
  106. package/dist/server-session-routes.d.ts +4 -3
  107. package/dist/server-session-routes.js +182 -37
  108. package/dist/server-settings-routes.d.ts +5 -0
  109. package/dist/server-settings-routes.js +46 -0
  110. package/dist/server-task-routes.js +49 -4
  111. package/dist/server-team-dispatch-routes.d.ts +19 -0
  112. package/dist/server-team-dispatch-routes.js +71 -0
  113. package/dist/server-update-routes.d.ts +1 -1
  114. package/dist/server-update-routes.js +10 -5
  115. package/dist/server-workspace-routes.js +44 -24
  116. package/dist/server.d.ts +1 -0
  117. package/dist/server.js +148 -30
  118. package/dist/session-ai-context.d.ts +5 -1
  119. package/dist/session-ai-context.js +9 -4
  120. package/dist/session-registry.d.ts +4 -0
  121. package/dist/session-registry.js +47 -13
  122. package/dist/session-transport.js +8 -1
  123. package/dist/settings-web-access.d.ts +15 -0
  124. package/dist/settings-web-access.js +37 -0
  125. package/dist/silicon-employee-defaults.d.ts +29 -0
  126. package/dist/silicon-employee-defaults.js +41 -0
  127. package/dist/silicon-employee-dispatch.d.ts +5 -1
  128. package/dist/silicon-employee-dispatch.js +10 -4
  129. package/dist/silicon-employee-draft.d.ts +21 -4
  130. package/dist/silicon-employee-draft.js +103 -16
  131. package/dist/storage.d.ts +73 -2
  132. package/dist/storage.js +362 -26
  133. package/dist/structured-claude-protocol.js +1 -1
  134. package/dist/structured-client-protocol.js +11 -33
  135. package/dist/structured-content.d.ts +23 -0
  136. package/dist/structured-content.js +72 -0
  137. package/dist/structured-failure.d.ts +16 -0
  138. package/dist/structured-failure.js +36 -0
  139. package/dist/structured-pi-adapter.d.ts +8 -3
  140. package/dist/structured-pi-adapter.js +131 -30
  141. package/dist/structured-provider-common.d.ts +6 -0
  142. package/dist/structured-provider-common.js +23 -1
  143. package/dist/structured-runner.d.ts +45 -0
  144. package/dist/structured-session-manager.d.ts +78 -4
  145. package/dist/structured-session-manager.js +513 -129
  146. package/dist/structured-thinking.d.ts +15 -0
  147. package/dist/structured-thinking.js +46 -0
  148. package/dist/subagent-dispatch.d.ts +56 -0
  149. package/dist/subagent-dispatch.js +102 -0
  150. package/dist/system-employee.d.ts +4 -3
  151. package/dist/system-employee.js +7 -4
  152. package/dist/task-retention.d.ts +23 -0
  153. package/dist/task-retention.js +69 -0
  154. package/dist/task-types.d.ts +4 -0
  155. package/dist/task-types.js +3 -0
  156. package/dist/team-dispatch.d.ts +101 -0
  157. package/dist/team-dispatch.js +269 -0
  158. package/dist/tool-preview.js +7 -1
  159. package/dist/tui/commands.js +50 -1
  160. package/dist/tui/ipc-protocol.d.ts +1 -1
  161. package/dist/tui/ipc-server.d.ts +4 -0
  162. package/dist/tui/ipc-server.js +23 -0
  163. package/dist/turn-heartbeat.d.ts +35 -0
  164. package/dist/turn-heartbeat.js +64 -0
  165. package/dist/types.d.ts +147 -1
  166. package/dist/user-profile.d.ts +33 -0
  167. package/dist/user-profile.js +70 -0
  168. package/dist/web-ui/chat-message-meta.d.ts +11 -0
  169. package/dist/web-ui/chat-message-meta.js +47 -0
  170. package/dist/web-ui/content/ai-teams.js +13 -13
  171. package/dist/web-ui/content/scripts.js +795 -95
  172. package/dist/web-ui/content/styles.css +1 -1
  173. package/dist/web-ui/content/tailwind.css +1 -6808
  174. package/dist/web-ui/content/vendor/qrcode/qrcode.bundle.js +1 -1
  175. package/dist/web-ui/embedded-assets.d.ts +1 -1
  176. package/dist/web-ui/embedded-assets.js +5 -5
  177. package/dist/web-ui/index.d.ts +1 -1
  178. package/dist/web-ui/index.js +3 -3
  179. package/dist/web-ui/markdown.d.ts +2 -0
  180. package/dist/web-ui/markdown.js +111 -0
  181. package/dist/web-ui/page.d.ts +4 -0
  182. package/dist/web-ui/page.js +8 -0
  183. package/dist/web-ui/react/local-preview/controller.d.ts +30 -0
  184. package/dist/web-ui/react/local-preview/controller.js +159 -0
  185. package/dist/web-ui/running-activity.d.ts +63 -0
  186. package/dist/web-ui/running-activity.js +134 -0
  187. package/dist/web-ui/styles.js +2 -11
  188. package/package.json +7 -2
@@ -1 +1 @@
1
- export declare function renderApp(configPath: string): string;
1
+ export declare function renderApp(configPath: string, page?: "console" | "settings", settingsAuth?: "browser" | "client"): string;
@@ -6,18 +6,18 @@ import { WAND_FAVICON_URL } from "./brand-identity.js";
6
6
  function vendorAssetUrl(relPath) {
7
7
  return `${relPath}?v=${EMBEDDED_WEB_ASSETS.vendor[relPath].hash}`;
8
8
  }
9
- export function renderApp(configPath) {
9
+ export function renderApp(configPath, page = "console", settingsAuth = "browser") {
10
10
  const stylesHref = `/assets/app.css?v=${getStylesAsset().hash}`;
11
11
  const scriptSrc = `/assets/app.js?v=${getScriptAsset(configPath).hash}`;
12
12
  const xtermSrc = vendorAssetUrl("/vendor/xterm/xterm.bundle.js");
13
13
  const qrcodeSrc = vendorAssetUrl("/vendor/qrcode/qrcode.bundle.js");
14
14
  const xtermCssHref = vendorAssetUrl("/vendor/xterm/xterm.css");
15
15
  return `<!doctype html>
16
- <html lang="zh-CN">
16
+ <html lang="zh-CN"${page === "settings" ? ` data-wand-page="settings" data-wand-settings-auth="${settingsAuth}"` : ""}>
17
17
  <head>
18
18
  <meta charset="utf-8" />
19
19
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, interactive-widget=resizes-content" />
20
- <title>Wand Console</title>
20
+ <title>${page === "settings" ? "Wand 设置" : "Wand Console"}</title>
21
21
  <link rel="icon" type="image/svg+xml" href="${WAND_FAVICON_URL}" />
22
22
  <meta name="description" content="Local CLI Console for Vibe Coding - Manage terminal sessions from your browser" />
23
23
  <meta name="theme-color" content="#f1eadf" media="(prefers-color-scheme: light)" />
@@ -0,0 +1,2 @@
1
+ /** Shared parser with only Wand's presentation and local-link adaptations. */
2
+ export declare function renderChatMarkdown(text: string): string;
@@ -0,0 +1,111 @@
1
+ import { Marked, Renderer } from "marked";
2
+ import { localFilePreviewHref, localHttpPreviewHref } from "./react/local-preview/controller.js";
3
+ function escapeHtml(value) {
4
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
5
+ .replace(/"/g, "&quot;").replace(/'/g, "&#39;");
6
+ }
7
+ function serverFilePath(target) {
8
+ let value = target.trim();
9
+ if (/^file:/i.test(value)) {
10
+ try {
11
+ const url = new URL(value);
12
+ if (url.hostname && url.hostname !== "localhost")
13
+ return null;
14
+ value = decodeURIComponent(url.pathname);
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ }
20
+ else {
21
+ if (!value.startsWith("/") || value.startsWith("//"))
22
+ return null;
23
+ try {
24
+ value = decodeURIComponent(value);
25
+ }
26
+ catch { /* Keep literal percent signs. */ }
27
+ }
28
+ if (/^\/(?:api|android|macos)(?:\/|$)/.test(value))
29
+ return null;
30
+ return value.replace(/#L\d+(?:C\d+)?$/i, "").replace(/:\d+(?::\d+)?$/, "");
31
+ }
32
+ function previewAttributes(value, href) {
33
+ return [
34
+ ["href", href],
35
+ ["class", "local-preview-link"],
36
+ ["data-local-preview-url", value],
37
+ ["onclick", "if(window.__openLocalPreview){event.preventDefault();window.__openLocalPreview(this.getAttribute('data-local-preview-url'));}"],
38
+ ];
39
+ }
40
+ function linkAttributes(target) {
41
+ const path = serverFilePath(target);
42
+ if (path) {
43
+ const preview = /(?:\/$|\.(?:html?|)$)/i.test(path) ? localFilePreviewHref(path) : null;
44
+ if (preview)
45
+ return previewAttributes(path, preview);
46
+ return [
47
+ ["href", "/api/file-raw?download=1&path=" + encodeURIComponent(path)],
48
+ ["class", "server-file-link"],
49
+ ["data-server-file-path", path],
50
+ ["title", "打开或下载服务端文件"],
51
+ ["onclick", "if(window.__openFilePreview){event.preventDefault();window.__openFilePreview(this.getAttribute('data-server-file-path'));}"],
52
+ ];
53
+ }
54
+ const preview = localHttpPreviewHref(target);
55
+ if (preview)
56
+ return previewAttributes(target, preview);
57
+ if (/^https?:\/\//i.test(target)) {
58
+ return [["href", target], ["target", "_blank"], ["rel", "noopener noreferrer"]];
59
+ }
60
+ if (/^(?:mailto:|#)/i.test(target))
61
+ return [["href", target]];
62
+ return null;
63
+ }
64
+ // Marked parses Markdown; this renderer owns only Wand's output contracts.
65
+ // Raw HTML is text, and links/images use explicit protocol allowlists. Never
66
+ // enable raw HTML or accept renderer extensions from message content.
67
+ const renderer = new Renderer();
68
+ renderer.html = ({ text }) => escapeHtml(text);
69
+ renderer.link = function ({ href, tokens }) {
70
+ const label = this.parser.parseInline(tokens);
71
+ const attributes = linkAttributes(href);
72
+ if (!attributes)
73
+ return "<span>" + label + "</span>";
74
+ return "<a" + attributes.map(([name, value]) => " " + name + '="' + escapeHtml(value) + '"').join("") + ">" + label + "</a>";
75
+ };
76
+ renderer.image = ({ href, text, title }) => {
77
+ if (!/^https?:\/\//i.test(href) && (!href.startsWith("/") || href.startsWith("//")))
78
+ return escapeHtml(text);
79
+ return '<img src="' + escapeHtml(href) + '" alt="' + escapeHtml(text) + '"' +
80
+ (title ? ' title="' + escapeHtml(title) + '"' : "") + ' loading="lazy" decoding="async">';
81
+ };
82
+ renderer.heading = function ({ depth, tokens }) {
83
+ const tag = "h" + Math.min(depth + 1, 6);
84
+ return "<" + tag + ">" + this.parser.parseInline(tokens) + "</" + tag + ">\n";
85
+ };
86
+ renderer.codespan = ({ text }) => '<code class="code-inline">' + escapeHtml(text) + "</code>";
87
+ renderer.code = ({ text, lang }) => {
88
+ const language = (lang || "").trim().split(/\s+/)[0];
89
+ const content = text.endsWith("\n") ? text : text + "\n";
90
+ return '<div class="code-block"><div class="code-block-header">' +
91
+ '<span class="code-lang">' + escapeHtml(language) + "</span>" +
92
+ '<button type="button" data-antd-control class="code-copy">复制</button></div>' +
93
+ "<pre><code>" + escapeHtml(content) + "</code></pre></div>\n";
94
+ };
95
+ renderer.tablecell = function ({ header, align, tokens }) {
96
+ const tag = header ? "th" : "td";
97
+ const alignment = align ? ' style="text-align:' + align + '"' : "";
98
+ return "<" + tag + alignment + ">" + this.parser.parseInline(tokens) + "</" + tag + ">\n";
99
+ };
100
+ const renderTable = renderer.table;
101
+ renderer.table = function (token) {
102
+ return '<div class="md-table-wrap">' + renderTable.call(this, token)
103
+ .replace("<table>", '<table class="md-table">') + "</div>\n";
104
+ };
105
+ const markdown = new Marked({ renderer, breaks: true, gfm: true, async: false });
106
+ /** Shared parser with only Wand's presentation and local-link adaptations. */
107
+ export function renderChatMarkdown(text) {
108
+ if (!text)
109
+ return "";
110
+ return '<div class="markdown-content">' + markdown.parse(text, { async: false }) + "</div>";
111
+ }
@@ -0,0 +1,4 @@
1
+ /** The HTML entry declares its page before browser modules execute. */
2
+ export declare function isStandaloneSettingsPage(): boolean;
3
+ /** Presentation only; the server still validates the live client session and WebView proof. */
4
+ export declare function isClientSettingsPage(): boolean;
@@ -0,0 +1,8 @@
1
+ /** The HTML entry declares its page before browser modules execute. */
2
+ export function isStandaloneSettingsPage() {
3
+ return typeof document !== "undefined" && document.documentElement?.dataset.wandPage === "settings";
4
+ }
5
+ /** Presentation only; the server still validates the live client session and WebView proof. */
6
+ export function isClientSettingsPage() {
7
+ return isStandaloneSettingsPage() && document.documentElement.dataset.wandSettingsAuth === "client";
8
+ }
@@ -0,0 +1,30 @@
1
+ export type LocalPreviewMode = "url" | "file";
2
+ export interface LocalPreviewSnapshot {
3
+ open: boolean;
4
+ mode: LocalPreviewMode;
5
+ value: string;
6
+ previewUrl: string | null;
7
+ sourceLabel: string;
8
+ error: string | null;
9
+ }
10
+ type Listener = () => void;
11
+ /** Convert an HTTP loopback URL to its Wand proxy URL, or return null. */
12
+ export declare function localHttpPreviewHref(input: string): string | null;
13
+ /** Convert a server-side HTML file or directory to its local-file preview URL. */
14
+ export declare function localFilePreviewHref(input: string): string | null;
15
+ declare function openFile(value: string): boolean;
16
+ declare function openUrl(value: string): boolean;
17
+ export declare const localPreviewController: {
18
+ subscribe(listener: Listener): () => void;
19
+ getSnapshot(): LocalPreviewSnapshot;
20
+ isOpen(): boolean;
21
+ closeIfOpen(): boolean;
22
+ show(value?: string): void;
23
+ close(): void;
24
+ setMode(mode: LocalPreviewMode): void;
25
+ setValue(value: string): void;
26
+ openUrl: typeof openUrl;
27
+ openFile: typeof openFile;
28
+ submit(): void;
29
+ };
30
+ export {};
@@ -0,0 +1,159 @@
1
+ let snapshot = {
2
+ open: false,
3
+ mode: "url",
4
+ value: "",
5
+ previewUrl: null,
6
+ sourceLabel: "",
7
+ error: null,
8
+ };
9
+ const listeners = new Set();
10
+ function publish(patch = {}) {
11
+ snapshot = { ...snapshot, ...patch };
12
+ for (const listener of listeners)
13
+ listener();
14
+ }
15
+ function isLoopbackHostname(hostname) {
16
+ return ["localhost", "127.0.0.1", "[::1]", "::1"].includes(hostname.toLowerCase());
17
+ }
18
+ /** Convert an HTTP loopback URL to its Wand proxy URL, or return null. */
19
+ export function localHttpPreviewHref(input) {
20
+ try {
21
+ const url = new URL(input.trim());
22
+ if (url.protocol !== "http:" || !isLoopbackHostname(url.hostname))
23
+ return null;
24
+ const port = url.port || "80";
25
+ return `/api/local-preview/127.0.0.1/${port}${url.pathname === "/" ? "/" : url.pathname}${url.search}`;
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
31
+ function normalizeLocalUrl(input) {
32
+ const trimmed = input.trim();
33
+ if (!trimmed)
34
+ throw new Error("请输入本地端口或地址。");
35
+ if (/^\d{1,5}$/.test(trimmed)) {
36
+ const port = Number(trimmed);
37
+ if (port < 1 || port > 65535)
38
+ throw new Error("端口号必须是 1–65535。");
39
+ return `/api/local-preview/127.0.0.1/${port}/`;
40
+ }
41
+ const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `http://${trimmed}`;
42
+ const previewUrl = localHttpPreviewHref(withScheme);
43
+ if (!previewUrl)
44
+ throw new Error("只允许预览本机(localhost)HTTP 服务。");
45
+ return previewUrl;
46
+ }
47
+ function base64UrlPath(input) {
48
+ const bytes = new TextEncoder().encode(input);
49
+ let binary = "";
50
+ for (const byte of bytes)
51
+ binary += String.fromCharCode(byte);
52
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
53
+ }
54
+ /** Convert a server-side HTML file or directory to its local-file preview URL. */
55
+ export function localFilePreviewHref(input) {
56
+ const value = input.trim();
57
+ if (!value.startsWith("/"))
58
+ return null;
59
+ return `/api/local-file/${base64UrlPath(value)}/`;
60
+ }
61
+ function normalizeLocalFile(input) {
62
+ const trimmed = input.trim();
63
+ if (!trimmed)
64
+ throw new Error("请输入文件或目录路径。");
65
+ return localFilePreviewHref(trimmed) ?? (() => { throw new Error("请输入服务端文件路径。"); })();
66
+ }
67
+ function openFile(value) {
68
+ try {
69
+ publish({
70
+ open: true,
71
+ mode: "file",
72
+ value,
73
+ previewUrl: normalizeLocalFile(value),
74
+ sourceLabel: value.trim(),
75
+ error: null,
76
+ });
77
+ return true;
78
+ }
79
+ catch (error) {
80
+ publish({
81
+ open: true,
82
+ mode: "file",
83
+ value,
84
+ error: error instanceof Error ? error.message : "无法打开本地文件预览。",
85
+ });
86
+ return false;
87
+ }
88
+ }
89
+ function openUrl(value) {
90
+ try {
91
+ publish({
92
+ open: true,
93
+ mode: "url",
94
+ value,
95
+ previewUrl: normalizeLocalUrl(value),
96
+ sourceLabel: value.trim(),
97
+ error: null,
98
+ });
99
+ return true;
100
+ }
101
+ catch (error) {
102
+ publish({
103
+ open: true,
104
+ mode: "url",
105
+ value,
106
+ error: error instanceof Error ? error.message : "无法打开本地预览。",
107
+ });
108
+ return false;
109
+ }
110
+ }
111
+ export const localPreviewController = {
112
+ subscribe(listener) {
113
+ listeners.add(listener);
114
+ return () => listeners.delete(listener);
115
+ },
116
+ getSnapshot() {
117
+ return snapshot;
118
+ },
119
+ isOpen() {
120
+ return snapshot.open;
121
+ },
122
+ closeIfOpen() {
123
+ if (!snapshot.open)
124
+ return false;
125
+ this.close();
126
+ return true;
127
+ },
128
+ show(value = "") {
129
+ publish({ open: true, value, error: null });
130
+ },
131
+ close() {
132
+ publish({ open: false, error: null });
133
+ },
134
+ setMode(mode) {
135
+ if (mode === snapshot.mode)
136
+ return;
137
+ publish({ mode, error: null });
138
+ },
139
+ setValue(value) {
140
+ publish({ value, error: null });
141
+ },
142
+ openUrl,
143
+ openFile,
144
+ submit() {
145
+ try {
146
+ const previewUrl = snapshot.mode === "url"
147
+ ? normalizeLocalUrl(snapshot.value)
148
+ : normalizeLocalFile(snapshot.value);
149
+ publish({
150
+ previewUrl,
151
+ sourceLabel: snapshot.value.trim(),
152
+ error: null,
153
+ });
154
+ }
155
+ catch (error) {
156
+ publish({ error: error instanceof Error ? error.message : "无法打开本地预览。" });
157
+ }
158
+ },
159
+ };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * 「正在执行」的客户端推导:只允许对服务端下发的运行事实与锚点时刻做展示差分。
3
+ * 锚点(turnStartedAt / lastActivityAt)缺失时一律不显示时长,前端不本地起表伪造运行中。
4
+ */
5
+ /** 服务端会话快照里本模块关心的字段(structured 与 PTY 共用同一形状)。 */
6
+ export interface RunningActivityShape {
7
+ readonly status?: string;
8
+ readonly archived?: boolean;
9
+ readonly permissionBlocked?: boolean;
10
+ readonly pendingEscalation?: unknown;
11
+ readonly queuedMessages?: readonly unknown[] | null;
12
+ /**
13
+ * PTY 本轮是否真在跑(computeRunningSignal().ptyRunning)。
14
+ * 裸 shell 的 status 一直是 running,不带上这个判定会把空闲终端读成「正在执行」。
15
+ */
16
+ readonly ptyRunning?: boolean;
17
+ readonly structuredState?: {
18
+ readonly inFlight?: boolean;
19
+ readonly phase?: string;
20
+ readonly turnStartedAt?: string | null;
21
+ readonly lastActivityAt?: string | null;
22
+ } | null;
23
+ readonly turnStartedAt?: string | null;
24
+ readonly lastActivityAt?: string | null;
25
+ }
26
+ /** 团队 run / 步骤的对应字段(与 run detail、/live 同语义)。 */
27
+ export interface RunningRunShape {
28
+ readonly status?: string;
29
+ readonly startedAt?: string | null;
30
+ readonly turnStartedAt?: string | null;
31
+ readonly lastActivityAt?: string | null;
32
+ }
33
+ export type RunningPhase = "received" | "executing" | "waiting" | "idle";
34
+ /** 静默起显阈值:超过后从「正在执行」升级为「仍在运行 · 已 N 分钟无新消息」。 */
35
+ export declare const SILENCE_NOTICE_MS = 60000;
36
+ /** 本轮开始时刻:只取服务端锚点,缺失返回 null(调用方不得回落到本地挂载时刻)。 */
37
+ export declare function turnStartedAtMs(source: RunningActivityShape | null | undefined): number | null;
38
+ /** 最近一次服务端观测到活动的时刻。 */
39
+ export declare function lastActivityAtMs(source: RunningActivityShape | RunningRunShape | null | undefined): number | null;
40
+ export declare function isRunningNow(source: RunningActivityShape | RunningRunShape | null | undefined): boolean;
41
+ /** 会话侧运行事实:structured 看本轮 inFlight,PTY 看 ptyRunning,两者都不看本地计时。 */
42
+ export declare function sessionRunning(source: RunningActivityShape | null | undefined): boolean;
43
+ /**
44
+ * 分阶段:等待审批/输入 > 已接收排队 > 正在执行 > 结束。
45
+ * 非运行中一律 idle,调用方必须据此立即收敛状态条,不留残留。
46
+ * `runMode` 给团队 run:那里的 status 就是运行事实本身。会话侧不行——裸 shell 的
47
+ * status 恒为 running,必须走 sessionRunning(structured inFlight / PTY ptyRunning)。
48
+ */
49
+ export declare function computeRunningPhase(source: RunningActivityShape | null | undefined, runMode?: boolean): RunningPhase;
50
+ /** 距最近一次活动已静默多久;锚点缺失或不在运行中时为 0。 */
51
+ export declare function silenceDurationMs(source: RunningActivityShape | RunningRunShape | null | undefined, nowMs: number): number;
52
+ /** 静默超阈值时的提示文案;不满足条件返回 null。 */
53
+ export declare function silenceNotice(source: RunningActivityShape | RunningRunShape | null | undefined, nowMs: number, thresholdMs?: number): string | null;
54
+ /** 与 formatElapsedShort 不同:面向「已 N 分钟」这类自然语言读数。 */
55
+ export declare function formatMinutes(ms: number): string;
56
+ export declare const PHASE_LABELS: Readonly<Record<RunningPhase, string>>;
57
+ /**
58
+ * 状态条的完整文案。运行中恒有文字(阶段 + 已运行时长),静默超阈值时追加无新消息提示,
59
+ * 这样「还在跑」和「已经停了」在界面上一眼可分。
60
+ */
61
+ export declare function runningStatusText(source: RunningActivityShape | null | undefined, nowMs: number, runMode?: boolean): string;
62
+ /** 团队侧文案:run 级锚点,语义与会话侧一致。 */
63
+ export declare function runStatusText(source: RunningRunShape | null | undefined, nowMs: number): string;
@@ -0,0 +1,134 @@
1
+ /**
2
+ * 「正在执行」的客户端推导:只允许对服务端下发的运行事实与锚点时刻做展示差分。
3
+ * 锚点(turnStartedAt / lastActivityAt)缺失时一律不显示时长,前端不本地起表伪造运行中。
4
+ */
5
+ /** 静默起显阈值:超过后从「正在执行」升级为「仍在运行 · 已 N 分钟无新消息」。 */
6
+ export const SILENCE_NOTICE_MS = 60_000;
7
+ const RUNNING_STATUSES = new Set([
8
+ "running", "thinking", "waiting-input", "waiting_input",
9
+ // 团队 run 的等待相位仍是「这一轮没结束」,收敛只发生在 done/failed/stopped。
10
+ "awaiting_approval", "waiting_user",
11
+ ]);
12
+ function parseTime(value) {
13
+ if (!value)
14
+ return null;
15
+ const parsed = new Date(value).getTime();
16
+ return Number.isFinite(parsed) ? parsed : null;
17
+ }
18
+ /** 本轮开始时刻:只取服务端锚点,缺失返回 null(调用方不得回落到本地挂载时刻)。 */
19
+ export function turnStartedAtMs(source) {
20
+ if (!source)
21
+ return null;
22
+ return parseTime(source.structuredState?.turnStartedAt) ?? parseTime(source.turnStartedAt);
23
+ }
24
+ /** 最近一次服务端观测到活动的时刻。 */
25
+ export function lastActivityAtMs(source) {
26
+ if (!source)
27
+ return null;
28
+ const structured = source.structuredState?.lastActivityAt;
29
+ return parseTime(structured)
30
+ ?? parseTime(source.lastActivityAt)
31
+ ?? parseTime(source.lastActivityAt);
32
+ }
33
+ export function isRunningNow(source) {
34
+ if (!source)
35
+ return false;
36
+ if (source.archived)
37
+ return false;
38
+ const activity = source;
39
+ if (activity.permissionBlocked || activity.pendingEscalation)
40
+ return true;
41
+ if (activity.structuredState?.inFlight)
42
+ return true;
43
+ const status = source.status;
44
+ return typeof status === "string" && RUNNING_STATUSES.has(status);
45
+ }
46
+ /** 会话侧运行事实:structured 看本轮 inFlight,PTY 看 ptyRunning,两者都不看本地计时。 */
47
+ export function sessionRunning(source) {
48
+ if (!source || source.archived)
49
+ return false;
50
+ return Boolean(source.structuredState?.inFlight) || source.ptyRunning === true;
51
+ }
52
+ /**
53
+ * 分阶段:等待审批/输入 > 已接收排队 > 正在执行 > 结束。
54
+ * 非运行中一律 idle,调用方必须据此立即收敛状态条,不留残留。
55
+ * `runMode` 给团队 run:那里的 status 就是运行事实本身。会话侧不行——裸 shell 的
56
+ * status 恒为 running,必须走 sessionRunning(structured inFlight / PTY ptyRunning)。
57
+ */
58
+ export function computeRunningPhase(source, runMode = false) {
59
+ if (!source)
60
+ return "idle";
61
+ // 排队未空 = 服务端还欠这一轮回复,属于「已接收」;不能被会话级 idle 吞掉。
62
+ if ((source.queuedMessages?.length ?? 0) > 0)
63
+ return "received";
64
+ if (!(runMode ? isRunningNow(source) : sessionRunning(source)))
65
+ return "idle";
66
+ if (source.permissionBlocked || source.pendingEscalation)
67
+ return "waiting";
68
+ return "executing";
69
+ }
70
+ /** 距最近一次活动已静默多久;锚点缺失或不在运行中时为 0。 */
71
+ export function silenceDurationMs(source, nowMs) {
72
+ if (!isRunningNow(source) && !sessionRunning(source))
73
+ return 0;
74
+ const last = lastActivityAtMs(source);
75
+ if (last === null)
76
+ return 0;
77
+ return Math.max(0, nowMs - last);
78
+ }
79
+ /** 静默超阈值时的提示文案;不满足条件返回 null。 */
80
+ export function silenceNotice(source, nowMs, thresholdMs = SILENCE_NOTICE_MS) {
81
+ const silence = silenceDurationMs(source, nowMs);
82
+ if (silence < thresholdMs)
83
+ return null;
84
+ return `仍在运行 · 已 ${formatMinutes(silence)}无新消息`;
85
+ }
86
+ /** 与 formatElapsedShort 不同:面向「已 N 分钟」这类自然语言读数。 */
87
+ export function formatMinutes(ms) {
88
+ const totalSeconds = Math.max(0, Math.floor(ms / 1000));
89
+ if (totalSeconds < 60)
90
+ return `${totalSeconds} 秒`;
91
+ const minutes = Math.floor(totalSeconds / 60);
92
+ if (minutes < 60)
93
+ return `${minutes} 分钟`;
94
+ const hours = Math.floor(minutes / 60);
95
+ const remainingMinutes = minutes % 60;
96
+ return remainingMinutes ? `${hours} 小时 ${remainingMinutes} 分钟` : `${hours} 小时`;
97
+ }
98
+ export const PHASE_LABELS = {
99
+ received: "已接收",
100
+ executing: "正在执行",
101
+ waiting: "等待你的输入",
102
+ idle: "",
103
+ };
104
+ /**
105
+ * 状态条的完整文案。运行中恒有文字(阶段 + 已运行时长),静默超阈值时追加无新消息提示,
106
+ * 这样「还在跑」和「已经停了」在界面上一眼可分。
107
+ */
108
+ export function runningStatusText(source, nowMs, runMode = false) {
109
+ const phase = computeRunningPhase(source, runMode);
110
+ if (phase === "idle")
111
+ return "";
112
+ const parts = [PHASE_LABELS[phase]];
113
+ const started = turnStartedAtMs(source);
114
+ if (started !== null)
115
+ parts.push(`已运行 ${formatMinutes(Math.max(0, nowMs - started))}`);
116
+ const silence = silenceNotice(source, nowMs);
117
+ if (silence)
118
+ parts.push(silence);
119
+ return parts.join(" · ");
120
+ }
121
+ /** 团队侧文案:run 级锚点,语义与会话侧一致。 */
122
+ export function runStatusText(source, nowMs) {
123
+ if (!isRunningNow(source))
124
+ return "";
125
+ const waiting = source?.status === "awaiting_approval" || source?.status === "waiting_user";
126
+ const parts = [waiting ? PHASE_LABELS.waiting : PHASE_LABELS.executing];
127
+ const started = parseTime(source?.turnStartedAt) ?? parseTime(source?.startedAt);
128
+ if (started !== null)
129
+ parts.push(`已运行 ${formatMinutes(Math.max(0, nowMs - started))}`);
130
+ const silence = silenceNotice(source, nowMs);
131
+ if (silence)
132
+ parts.push(silence);
133
+ return parts.join(" · ");
134
+ }
@@ -4,17 +4,8 @@ import { fileURLToPath } from "node:url";
4
4
  import { EMBEDDED_WEB_ASSETS } from "./embedded-assets.js";
5
5
  import { versionWebAsset } from "./asset-version.js";
6
6
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
- // 顺序即层叠顺序:Tailwind/Appica 编译产物在前,手写 styles.css 在后。
8
- //
9
- // 两个原因决定这个顺序不能反过来:
10
- //
11
- // 1. Tailwind 的 @layer 规则永远输给无层规则,所以 styles.css 里的手写规则
12
- // 仍然稳压 Utilities 一层(两个文件里 `*` 重置的分工见
13
- // src/web-ui/css/appica.css 顶部说明)。这一条与先后无关。
14
- // 2. Appica 在自己的无层 `:root` 里也声明了一批与 Wand 同名的 token
15
- // (--success / --warning / --info / --border-strong / --radius-md /
16
- // --shadow-sm …)。无层对无层只能靠先后裁决,styles.css 必须排在后面,
17
- // 否则 Appica 的中性灰会悄无声息地覆盖掉整套暖色调。
7
+ // Tailwind utilities/preflight precede the retained business CSS. Ant Design/X
8
+ // install their own component styles through the shared provider.
18
9
  const CSS_FILES = ["tailwind.css", "styles.css"];
19
10
  // 用 mtime+size 做缓存键,磁盘上 CSS 一变(npm run build / 手工 edit dist/)下次请求
20
11
  // 就会自动 re-read。否则进程启动时缓存的 CSS 会粘住整个生命周期,UI 改动看不到效果,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@co0ontty/wand",
3
- "version": "4.83.1",
3
+ "version": "4.84.0",
4
4
  "description": "A web console for Claude Code, Codex, OpenCode, and other local CLI tools.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -49,18 +49,22 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@anthropic-ai/sdk": "^0.81.0",
52
+ "@earendil-works/pi-agent-core": "1.0.2",
53
+ "@earendil-works/pi-ai": "1.0.2",
54
+ "@earendil-works/pi-coding-agent": "1.0.2",
52
55
  "@xterm/addon-serialize": "^0.14.0",
53
56
  "@xterm/addon-unicode11": "^0.9.0",
54
57
  "@xterm/headless": "^6.0.0",
55
58
  "compression": "^1.8.1",
56
59
  "express": "^4.21.2",
60
+ "marked": "^18.0.14",
57
61
  "multer": "^2.1.1",
58
62
  "neo-blessed": "^0.2.0",
59
63
  "node-pty": "^1.1.0",
60
64
  "ws": "^8.19.0"
61
65
  },
62
66
  "devDependencies": {
63
- "@appica/ui-react": "^1.1.0",
67
+ "@ant-design/x": "2.9.0",
64
68
  "@tailwindcss/cli": "^4.3.3",
65
69
  "@types/compression": "^1.8.1",
66
70
  "@types/express": "^4.17.21",
@@ -71,6 +75,7 @@
71
75
  "@types/ws": "^8.18.1",
72
76
  "@xterm/addon-fit": "^0.11.0",
73
77
  "@xterm/xterm": "^6.0.0",
78
+ "antd": "6.6.5",
74
79
  "esbuild": "^0.28.0",
75
80
  "qrcode": "^1.5.4",
76
81
  "react": "^19.2.7",