@celljs/ta-web 4.6.1 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +90 -0
- package/README.md +13 -7
- package/esm/browser/chat/chat-protocol.d.ts +4 -0
- package/esm/browser/chat/chat-view.js +4 -1
- package/esm/browser/chat/chat-view.js.map +1 -1
- package/esm/browser/chat/chat.d.ts +8 -1
- package/esm/browser/chat/chat.js +34 -15
- package/esm/browser/chat/chat.js.map +1 -1
- package/esm/browser/chat/draft-model.d.ts +15 -0
- package/esm/browser/chat/draft-model.js +53 -0
- package/esm/browser/chat/draft-model.js.map +1 -0
- package/esm/browser/components/account-menu.d.ts +3 -2
- package/esm/browser/components/account-menu.js +21 -16
- package/esm/browser/components/account-menu.js.map +1 -1
- package/esm/browser/components/app-view.js +13 -4
- package/esm/browser/components/app-view.js.map +1 -1
- package/esm/browser/components/chat-list.d.ts +3 -1
- package/esm/browser/components/chat-list.js +6 -1
- package/esm/browser/components/chat-list.js.map +1 -1
- package/esm/browser/components/composer-box.d.ts +6 -0
- package/esm/browser/components/composer-box.js +50 -13
- package/esm/browser/components/composer-box.js.map +1 -1
- package/esm/browser/components/composer.js +1 -1
- package/esm/browser/components/composer.js.map +1 -1
- package/esm/browser/components/dialog-view.d.ts +4 -1
- package/esm/browser/components/dialog-view.js +11 -7
- package/esm/browser/components/dialog-view.js.map +1 -1
- package/esm/browser/components/folder-picker.d.ts +37 -6
- package/esm/browser/components/folder-picker.js +155 -33
- package/esm/browser/components/folder-picker.js.map +1 -1
- package/esm/browser/components/notice-center.js +3 -1
- package/esm/browser/components/notice-center.js.map +1 -1
- package/esm/browser/components/remote-hosts.d.ts +7 -0
- package/esm/browser/components/remote-hosts.js +94 -0
- package/esm/browser/components/remote-hosts.js.map +1 -0
- package/esm/browser/components/remote-prompts.d.ts +7 -0
- package/esm/browser/components/remote-prompts.js +72 -0
- package/esm/browser/components/remote-prompts.js.map +1 -0
- package/esm/browser/components/session-view.js +48 -4
- package/esm/browser/components/session-view.js.map +1 -1
- package/esm/browser/components/settings-view.js +8 -1
- package/esm/browser/components/settings-view.js.map +1 -1
- package/esm/browser/components/terminal-art.d.ts +7 -0
- package/esm/browser/components/terminal-art.js +12 -0
- package/esm/browser/components/terminal-art.js.map +1 -0
- package/esm/browser/components/tool-view.js +1 -1
- package/esm/browser/components/tool-view.js.map +1 -1
- package/esm/browser/components/web-composer.d.ts +4 -1
- package/esm/browser/components/web-composer.js +2 -2
- package/esm/browser/components/web-composer.js.map +1 -1
- package/esm/browser/components/working-directory.js +1 -1
- package/esm/browser/components/working-directory.js.map +1 -1
- package/esm/browser/extension/icons.js +6 -1
- package/esm/browser/extension/icons.js.map +1 -1
- package/esm/browser/extension/web-extension-api.d.ts +120 -2
- package/esm/browser/extension/web-extensions.d.ts +3 -0
- package/esm/browser/extension/web-extensions.js +33 -1
- package/esm/browser/extension/web-extensions.js.map +1 -1
- package/esm/browser/i18n/locales.d.ts +44 -1
- package/esm/browser/i18n/messages.d.ts +43 -0
- package/esm/browser/i18n/messages.js +99 -13
- package/esm/browser/i18n/messages.js.map +1 -1
- package/esm/browser/notices/notices-protocol.d.ts +3 -0
- package/esm/browser/notices/notices.d.ts +3 -0
- package/esm/browser/notices/notices.js +15 -0
- package/esm/browser/notices/notices.js.map +1 -1
- package/esm/browser/style/base.d.ts +1 -1
- package/esm/browser/style/base.js +6 -1
- package/esm/browser/style/base.js.map +1 -1
- package/esm/browser/style/conversation.d.ts +1 -1
- package/esm/browser/style/conversation.js +16 -1
- package/esm/browser/style/conversation.js.map +1 -1
- package/esm/browser/style/layout.d.ts +1 -1
- package/esm/browser/style/layout.js +53 -15
- package/esm/browser/style/layout.js.map +1 -1
- package/esm/browser/style/shell.d.ts +1 -1
- package/esm/browser/style/shell.js +1 -0
- package/esm/browser/style/shell.js.map +1 -1
- package/esm/browser/terminal/canvas-font.d.ts +10 -0
- package/esm/browser/terminal/canvas-font.js +34 -0
- package/esm/browser/terminal/canvas-font.js.map +1 -0
- package/esm/browser/terminal/terminal-links.d.ts +18 -0
- package/esm/browser/terminal/terminal-links.js +72 -0
- package/esm/browser/terminal/terminal-links.js.map +1 -0
- package/esm/browser/terminal/terminal-theme.d.ts +8 -0
- package/esm/browser/terminal/terminal-theme.js +65 -0
- package/esm/browser/terminal/terminal-theme.js.map +1 -0
- package/esm/browser/terminal/web-terminal.d.ts +74 -0
- package/esm/browser/terminal/web-terminal.js +300 -0
- package/esm/browser/terminal/web-terminal.js.map +1 -0
- package/esm/browser/terminal/xterm.d.ts +5 -0
- package/esm/browser/terminal/xterm.js +9 -0
- package/esm/browser/terminal/xterm.js.map +1 -0
- package/esm/browser/util/desktop.d.ts +43 -0
- package/esm/browser/util/desktop.js +55 -0
- package/esm/browser/util/desktop.js.map +1 -0
- package/esm/browser/util/file-drag.d.ts +26 -0
- package/esm/browser/util/file-drag.js +73 -0
- package/esm/browser/util/file-drag.js.map +1 -0
- package/esm/browser/util/message.d.ts +1 -0
- package/esm/browser/util/message.js +7 -5
- package/esm/browser/util/message.js.map +1 -1
- package/esm/browser/util/terminal-text.d.ts +33 -0
- package/esm/browser/util/terminal-text.js +209 -0
- package/esm/browser/util/terminal-text.js.map +1 -0
- package/esm/browser/util/tool.js +16 -0
- package/esm/browser/util/tool.js.map +1 -1
- package/esm/browser/workbench-protocol.d.ts +7 -1
- package/esm/browser/workbench-protocol.js.map +1 -1
- package/esm/browser/workbench.d.ts +7 -1
- package/esm/browser/workbench.js +23 -0
- package/esm/browser/workbench.js.map +1 -1
- package/lib/browser/chat/chat-protocol.d.ts +4 -0
- package/lib/browser/chat/chat-protocol.d.ts.map +1 -1
- package/lib/browser/chat/chat-view.d.ts.map +1 -1
- package/lib/browser/chat/chat-view.js +4 -1
- package/lib/browser/chat/chat-view.js.map +1 -1
- package/lib/browser/chat/chat.d.ts +8 -1
- package/lib/browser/chat/chat.d.ts.map +1 -1
- package/lib/browser/chat/chat.js +34 -15
- package/lib/browser/chat/chat.js.map +1 -1
- package/lib/browser/chat/chat.spec.js +30 -0
- package/lib/browser/chat/chat.spec.js.map +1 -1
- package/lib/browser/chat/draft-model.d.ts +16 -0
- package/lib/browser/chat/draft-model.d.ts.map +1 -0
- package/lib/browser/chat/draft-model.js +90 -0
- package/lib/browser/chat/draft-model.js.map +1 -0
- package/lib/browser/chat/draft-model.spec.d.ts +2 -0
- package/lib/browser/chat/draft-model.spec.d.ts.map +1 -0
- package/lib/browser/chat/draft-model.spec.js +31 -0
- package/lib/browser/chat/draft-model.spec.js.map +1 -0
- package/lib/browser/components/account-menu.d.ts +3 -2
- package/lib/browser/components/account-menu.d.ts.map +1 -1
- package/lib/browser/components/account-menu.js +21 -16
- package/lib/browser/components/account-menu.js.map +1 -1
- package/lib/browser/components/app-view.d.ts.map +1 -1
- package/lib/browser/components/app-view.js +13 -4
- package/lib/browser/components/app-view.js.map +1 -1
- package/lib/browser/components/chat-list.d.ts +3 -1
- package/lib/browser/components/chat-list.d.ts.map +1 -1
- package/lib/browser/components/chat-list.js +6 -1
- package/lib/browser/components/chat-list.js.map +1 -1
- package/lib/browser/components/composer-box.d.ts +6 -0
- package/lib/browser/components/composer-box.d.ts.map +1 -1
- package/lib/browser/components/composer-box.js +51 -14
- package/lib/browser/components/composer-box.js.map +1 -1
- package/lib/browser/components/composer.d.ts.map +1 -1
- package/lib/browser/components/composer.js +1 -1
- package/lib/browser/components/composer.js.map +1 -1
- package/lib/browser/components/dialog-view.d.ts +4 -1
- package/lib/browser/components/dialog-view.d.ts.map +1 -1
- package/lib/browser/components/dialog-view.js +11 -7
- package/lib/browser/components/dialog-view.js.map +1 -1
- package/lib/browser/components/folder-picker.d.ts +37 -6
- package/lib/browser/components/folder-picker.d.ts.map +1 -1
- package/lib/browser/components/folder-picker.js +156 -33
- package/lib/browser/components/folder-picker.js.map +1 -1
- package/lib/browser/components/folder-picker.spec.js +15 -0
- package/lib/browser/components/folder-picker.spec.js.map +1 -1
- package/lib/browser/components/notice-center.d.ts.map +1 -1
- package/lib/browser/components/notice-center.js +3 -1
- package/lib/browser/components/notice-center.js.map +1 -1
- package/lib/browser/components/plus-menu.spec.d.ts +2 -0
- package/lib/browser/components/plus-menu.spec.d.ts.map +1 -0
- package/lib/browser/components/plus-menu.spec.js +18 -0
- package/lib/browser/components/plus-menu.spec.js.map +1 -0
- package/lib/browser/components/remote-hosts.d.ts +8 -0
- package/lib/browser/components/remote-hosts.d.ts.map +1 -0
- package/lib/browser/components/remote-hosts.js +130 -0
- package/lib/browser/components/remote-hosts.js.map +1 -0
- package/lib/browser/components/remote-prompts.d.ts +8 -0
- package/lib/browser/components/remote-prompts.d.ts.map +1 -0
- package/lib/browser/components/remote-prompts.js +108 -0
- package/lib/browser/components/remote-prompts.js.map +1 -0
- package/lib/browser/components/session-view.d.ts.map +1 -1
- package/lib/browser/components/session-view.js +48 -4
- package/lib/browser/components/session-view.js.map +1 -1
- package/lib/browser/components/settings-view.d.ts.map +1 -1
- package/lib/browser/components/settings-view.js +7 -0
- package/lib/browser/components/settings-view.js.map +1 -1
- package/lib/browser/components/terminal-art.d.ts +8 -0
- package/lib/browser/components/terminal-art.d.ts.map +1 -0
- package/lib/browser/components/terminal-art.js +48 -0
- package/lib/browser/components/terminal-art.js.map +1 -0
- package/lib/browser/components/tool-view.js +1 -1
- package/lib/browser/components/tool-view.js.map +1 -1
- package/lib/browser/components/web-composer.d.ts +4 -1
- package/lib/browser/components/web-composer.d.ts.map +1 -1
- package/lib/browser/components/web-composer.js +2 -2
- package/lib/browser/components/web-composer.js.map +1 -1
- package/lib/browser/components/working-directory.js +1 -1
- package/lib/browser/components/working-directory.js.map +1 -1
- package/lib/browser/extension/icons.d.ts.map +1 -1
- package/lib/browser/extension/icons.js +5 -0
- package/lib/browser/extension/icons.js.map +1 -1
- package/lib/browser/extension/web-extension-api.d.ts +120 -2
- package/lib/browser/extension/web-extension-api.d.ts.map +1 -1
- package/lib/browser/extension/web-extensions.d.ts +3 -0
- package/lib/browser/extension/web-extensions.d.ts.map +1 -1
- package/lib/browser/extension/web-extensions.js +33 -1
- package/lib/browser/extension/web-extensions.js.map +1 -1
- package/lib/browser/i18n/locales.d.ts +44 -1
- package/lib/browser/i18n/locales.d.ts.map +1 -1
- package/lib/browser/i18n/messages.d.ts +43 -0
- package/lib/browser/i18n/messages.d.ts.map +1 -1
- package/lib/browser/i18n/messages.js +99 -13
- package/lib/browser/i18n/messages.js.map +1 -1
- package/lib/browser/notices/notices-protocol.d.ts +3 -0
- package/lib/browser/notices/notices-protocol.d.ts.map +1 -1
- package/lib/browser/notices/notices.d.ts +3 -0
- package/lib/browser/notices/notices.d.ts.map +1 -1
- package/lib/browser/notices/notices.js +15 -0
- package/lib/browser/notices/notices.js.map +1 -1
- package/lib/browser/notices/notices.spec.js +8 -3
- package/lib/browser/notices/notices.spec.js.map +1 -1
- package/lib/browser/style/base.d.ts +1 -1
- package/lib/browser/style/base.d.ts.map +1 -1
- package/lib/browser/style/base.js +6 -1
- package/lib/browser/style/base.js.map +1 -1
- package/lib/browser/style/conversation.d.ts +1 -1
- package/lib/browser/style/conversation.d.ts.map +1 -1
- package/lib/browser/style/conversation.js +16 -1
- package/lib/browser/style/conversation.js.map +1 -1
- package/lib/browser/style/layout.d.ts +1 -1
- package/lib/browser/style/layout.d.ts.map +1 -1
- package/lib/browser/style/layout.js +53 -15
- package/lib/browser/style/layout.js.map +1 -1
- package/lib/browser/style/shell.d.ts +1 -1
- package/lib/browser/style/shell.d.ts.map +1 -1
- package/lib/browser/style/shell.js +1 -0
- package/lib/browser/style/shell.js.map +1 -1
- package/lib/browser/terminal/canvas-font.d.ts +11 -0
- package/lib/browser/terminal/canvas-font.d.ts.map +1 -0
- package/lib/browser/terminal/canvas-font.js +37 -0
- package/lib/browser/terminal/canvas-font.js.map +1 -0
- package/lib/browser/terminal/canvas-font.spec.d.ts +2 -0
- package/lib/browser/terminal/canvas-font.spec.d.ts.map +1 -0
- package/lib/browser/terminal/canvas-font.spec.js +32 -0
- package/lib/browser/terminal/canvas-font.spec.js.map +1 -0
- package/lib/browser/terminal/terminal-links.d.ts +19 -0
- package/lib/browser/terminal/terminal-links.d.ts.map +1 -0
- package/lib/browser/terminal/terminal-links.js +75 -0
- package/lib/browser/terminal/terminal-links.js.map +1 -0
- package/lib/browser/terminal/terminal-links.spec.d.ts +2 -0
- package/lib/browser/terminal/terminal-links.spec.d.ts.map +1 -0
- package/lib/browser/terminal/terminal-links.spec.js +30 -0
- package/lib/browser/terminal/terminal-links.spec.js.map +1 -0
- package/lib/browser/terminal/terminal-theme.d.ts +9 -0
- package/lib/browser/terminal/terminal-theme.d.ts.map +1 -0
- package/lib/browser/terminal/terminal-theme.js +68 -0
- package/lib/browser/terminal/terminal-theme.js.map +1 -0
- package/lib/browser/terminal/terminal-theme.spec.d.ts +2 -0
- package/lib/browser/terminal/terminal-theme.spec.d.ts.map +1 -0
- package/lib/browser/terminal/terminal-theme.spec.js +24 -0
- package/lib/browser/terminal/terminal-theme.spec.js.map +1 -0
- package/lib/browser/terminal/web-terminal.d.ts +75 -0
- package/lib/browser/terminal/web-terminal.d.ts.map +1 -0
- package/lib/browser/terminal/web-terminal.js +304 -0
- package/lib/browser/terminal/web-terminal.js.map +1 -0
- package/lib/browser/terminal/xterm.d.ts +6 -0
- package/lib/browser/terminal/xterm.d.ts.map +1 -0
- package/lib/browser/terminal/xterm.js +16 -0
- package/lib/browser/terminal/xterm.js.map +1 -0
- package/lib/browser/util/desktop.d.ts +44 -0
- package/lib/browser/util/desktop.d.ts.map +1 -0
- package/lib/browser/util/desktop.js +58 -0
- package/lib/browser/util/desktop.js.map +1 -0
- package/lib/browser/util/desktop.spec.d.ts +2 -0
- package/lib/browser/util/desktop.spec.d.ts.map +1 -0
- package/lib/browser/util/desktop.spec.js +67 -0
- package/lib/browser/util/desktop.spec.js.map +1 -0
- package/lib/browser/util/file-drag.d.ts +27 -0
- package/lib/browser/util/file-drag.d.ts.map +1 -0
- package/lib/browser/util/file-drag.js +76 -0
- package/lib/browser/util/file-drag.js.map +1 -0
- package/lib/browser/util/file-drag.spec.d.ts +2 -0
- package/lib/browser/util/file-drag.spec.d.ts.map +1 -0
- package/lib/browser/util/file-drag.spec.js +46 -0
- package/lib/browser/util/file-drag.spec.js.map +1 -0
- package/lib/browser/util/message.d.ts +1 -0
- package/lib/browser/util/message.d.ts.map +1 -1
- package/lib/browser/util/message.js +7 -5
- package/lib/browser/util/message.js.map +1 -1
- package/lib/browser/util/message.spec.js +2 -0
- package/lib/browser/util/message.spec.js.map +1 -1
- package/lib/browser/util/terminal-text.d.ts +34 -0
- package/lib/browser/util/terminal-text.d.ts.map +1 -0
- package/lib/browser/util/terminal-text.js +212 -0
- package/lib/browser/util/terminal-text.js.map +1 -0
- package/lib/browser/util/terminal-text.spec.d.ts +2 -0
- package/lib/browser/util/terminal-text.spec.d.ts.map +1 -0
- package/lib/browser/util/terminal-text.spec.js +54 -0
- package/lib/browser/util/terminal-text.spec.js.map +1 -0
- package/lib/browser/util/tool.d.ts.map +1 -1
- package/lib/browser/util/tool.js +16 -0
- package/lib/browser/util/tool.js.map +1 -1
- package/lib/browser/util/tool.spec.js +12 -0
- package/lib/browser/util/tool.spec.js.map +1 -1
- package/lib/browser/workbench-protocol.d.ts +7 -1
- package/lib/browser/workbench-protocol.d.ts.map +1 -1
- package/lib/browser/workbench-protocol.js.map +1 -1
- package/lib/browser/workbench.d.ts +7 -1
- package/lib/browser/workbench.d.ts.map +1 -1
- package/lib/browser/workbench.js +23 -0
- package/lib/browser/workbench.js.map +1 -1
- package/package.json +11 -7
- package/src/browser/chat/chat-protocol.ts +4 -0
- package/src/browser/chat/chat-view.tsx +5 -1
- package/src/browser/chat/chat.spec.ts +31 -0
- package/src/browser/chat/chat.ts +36 -14
- package/src/browser/chat/draft-model.spec.tsx +31 -0
- package/src/browser/chat/draft-model.tsx +63 -0
- package/src/browser/components/account-menu.tsx +23 -20
- package/src/browser/components/app-view.tsx +12 -3
- package/src/browser/components/chat-list.tsx +9 -2
- package/src/browser/components/composer-box.tsx +52 -6
- package/src/browser/components/composer.tsx +1 -0
- package/src/browser/components/dialog-view.tsx +16 -7
- package/src/browser/components/folder-picker.spec.ts +17 -0
- package/src/browser/components/folder-picker.tsx +221 -39
- package/src/browser/components/notice-center.tsx +7 -1
- package/src/browser/components/plus-menu.spec.ts +16 -0
- package/src/browser/components/remote-hosts.tsx +149 -0
- package/src/browser/components/remote-prompts.tsx +111 -0
- package/src/browser/components/session-view.tsx +65 -4
- package/src/browser/components/settings-view.tsx +8 -0
- package/src/browser/components/terminal-art.tsx +21 -0
- package/src/browser/components/tool-view.tsx +1 -1
- package/src/browser/components/web-composer.tsx +18 -3
- package/src/browser/components/working-directory.tsx +1 -1
- package/src/browser/extension/icons.ts +10 -0
- package/src/browser/extension/web-extension-api.ts +134 -2
- package/src/browser/extension/web-extensions.ts +47 -2
- package/src/browser/i18n/messages.ts +99 -13
- package/src/browser/notices/notices-protocol.ts +3 -0
- package/src/browser/notices/notices.spec.ts +9 -3
- package/src/browser/notices/notices.ts +17 -1
- package/src/browser/style/base.ts +6 -1
- package/src/browser/style/conversation.ts +16 -1
- package/src/browser/style/layout.ts +53 -15
- package/src/browser/style/shell.ts +1 -0
- package/src/browser/terminal/canvas-font.spec.ts +29 -0
- package/src/browser/terminal/canvas-font.ts +32 -0
- package/src/browser/terminal/terminal-links.spec.ts +32 -0
- package/src/browser/terminal/terminal-links.ts +75 -0
- package/src/browser/terminal/terminal-theme.spec.ts +23 -0
- package/src/browser/terminal/terminal-theme.ts +66 -0
- package/src/browser/terminal/web-terminal.ts +330 -0
- package/src/browser/terminal/xterm.ts +10 -0
- package/src/browser/util/desktop.spec.ts +68 -0
- package/src/browser/util/desktop.ts +96 -0
- package/src/browser/util/file-drag.spec.ts +48 -0
- package/src/browser/util/file-drag.ts +78 -0
- package/src/browser/util/message.spec.ts +2 -0
- package/src/browser/util/message.ts +7 -4
- package/src/browser/util/terminal-text.spec.ts +60 -0
- package/src/browser/util/terminal-text.ts +223 -0
- package/src/browser/util/tool.spec.ts +12 -0
- package/src/browser/util/tool.ts +17 -0
- package/src/browser/workbench-protocol.ts +8 -0
- package/src/browser/workbench.ts +29 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** What Ta Desktop can do for the page beyond a tab of a browser. */
|
|
2
|
+
export type DesktopFeature = 'webview' | 'debugWebview' | 'showItemInFolder' | 'browserData';
|
|
3
|
+
|
|
4
|
+
/** The file manager of the system, from Ta Desktop: {@link WebDesktop} of the extension API. */
|
|
5
|
+
export interface DesktopFileManager {
|
|
6
|
+
/** Node's `process.platform` of the machine: what the file manager is called there. */
|
|
7
|
+
readonly platform: string;
|
|
8
|
+
showItemInFolder(path: string): Promise<void>;
|
|
9
|
+
readonly browserData?: DesktopBrowserData;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** A profile of the user's Chrome, Edge…, whose sign-ins Ta Desktop can take. */
|
|
13
|
+
export interface DesktopBrowserProfile {
|
|
14
|
+
id: string;
|
|
15
|
+
browser: string;
|
|
16
|
+
name: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** What an import took; `appBound`: cookies only Chrome itself can read (Windows). */
|
|
20
|
+
export interface DesktopBrowserImport {
|
|
21
|
+
imported: number;
|
|
22
|
+
skipped: number;
|
|
23
|
+
appBound: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The sign-ins of the user's browsers in the browser of Ta: {@link WebBrowserData} of the extension API. */
|
|
27
|
+
export interface DesktopBrowserData {
|
|
28
|
+
profiles(): Promise<DesktopBrowserProfile[]>;
|
|
29
|
+
/** After the user said yes in a dialog of the app; `undefined` when they said no. */
|
|
30
|
+
import(profileId: string): Promise<DesktopBrowserImport | undefined>;
|
|
31
|
+
clear(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A command of the DevTools protocol to the page of a `<webview>` of this page, by its getWebContentsId(). */
|
|
35
|
+
export type DebugWebview = (webContentsId: number, method: string, params?: Record<string, unknown>) => Promise<unknown>;
|
|
36
|
+
|
|
37
|
+
interface DesktopScope {
|
|
38
|
+
taDesktop?: {
|
|
39
|
+
features?: unknown;
|
|
40
|
+
platform?: unknown;
|
|
41
|
+
showItemInFolder?: unknown;
|
|
42
|
+
debugWebview?: DebugWebview;
|
|
43
|
+
browserData?: { profiles?: unknown; import?: unknown; clear?: unknown };
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Ta Desktop gives its window's page `taDesktop` (its preload, apps/ta-desktop): the features it adds to
|
|
49
|
+
* what a browser can do. A browser, an installed web app and the mobile app have none.
|
|
50
|
+
*/
|
|
51
|
+
export namespace DesktopUtil {
|
|
52
|
+
export function has(feature: DesktopFeature, scope: object = globalThis): boolean {
|
|
53
|
+
const features = (scope as DesktopScope).taDesktop?.features;
|
|
54
|
+
return Array.isArray(features) && features.includes(feature);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The file manager of the system, when the page is in Ta Desktop. */
|
|
58
|
+
export function fileManager(scope: object = globalThis): DesktopFileManager | undefined {
|
|
59
|
+
const desktop = (scope as DesktopScope).taDesktop;
|
|
60
|
+
const show = desktop?.showItemInFolder;
|
|
61
|
+
if (!has('showItemInFolder', scope) || typeof show !== 'function') {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
return Object.freeze({
|
|
65
|
+
platform: typeof desktop?.platform === 'string' ? desktop.platform : 'unknown',
|
|
66
|
+
showItemInFolder: (path: string) => Promise.resolve(show(path)).then(() => undefined),
|
|
67
|
+
browserData: browserData(scope),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The sign-ins of the user's browsers, where Ta Desktop can read them (macOS, Windows). */
|
|
72
|
+
export function browserData(scope: object = globalThis): DesktopBrowserData | undefined {
|
|
73
|
+
const data = (scope as DesktopScope).taDesktop?.browserData;
|
|
74
|
+
const { profiles, import: take, clear } = data ?? {};
|
|
75
|
+
if (!has('browserData', scope) || typeof profiles !== 'function' || typeof take !== 'function' || typeof clear !== 'function') {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
return Object.freeze({
|
|
79
|
+
profiles: async () => ((await profiles()) as DesktopBrowserProfile[]) ?? [],
|
|
80
|
+
import: async (profileId: string) => {
|
|
81
|
+
const report = (await take(profileId)) as DesktopBrowserImport | { cancelled: true };
|
|
82
|
+
return 'cancelled' in report ? undefined : report;
|
|
83
|
+
},
|
|
84
|
+
clear: async () => {
|
|
85
|
+
await clear();
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** `debugWebview` of Ta Desktop; `undefined` elsewhere. */
|
|
91
|
+
export function debugWebview(scope: object = globalThis): DebugWebview | undefined {
|
|
92
|
+
const desktop = (scope as DesktopScope).taDesktop;
|
|
93
|
+
const debug = desktop?.debugWebview;
|
|
94
|
+
return has('debugWebview', scope) && typeof debug === 'function' ? (webContentsId, method, params) => debug(webContentsId, method, params) : undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { FileDragUtil } from './file-drag';
|
|
3
|
+
|
|
4
|
+
/** A DataTransfer as the browser keeps one during a drag. */
|
|
5
|
+
function transfer(): DataTransfer {
|
|
6
|
+
const data = new Map<string, string>();
|
|
7
|
+
return {
|
|
8
|
+
get types() {
|
|
9
|
+
return [...data.keys()];
|
|
10
|
+
},
|
|
11
|
+
setData: (type: string, value: string) => void data.set(type, value),
|
|
12
|
+
getData: (type: string) => data.get(type) ?? '',
|
|
13
|
+
} as unknown as DataTransfer;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('FileDragUtil', () => {
|
|
17
|
+
it('puts the files on a drag, and their paths as text', () => {
|
|
18
|
+
const dataTransfer = transfer();
|
|
19
|
+
FileDragUtil.set(dataTransfer, { cwd: '/work/app', paths: ['src', 'README.md'] });
|
|
20
|
+
expect(FileDragUtil.has(dataTransfer)).toBe(true);
|
|
21
|
+
expect(FileDragUtil.get(dataTransfer)).toEqual({ cwd: '/work/app', paths: ['src', 'README.md'] });
|
|
22
|
+
expect(dataTransfer.getData('text/plain')).toBe('/work/app/src\n/work/app/README.md');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('reads nothing from other drags', () => {
|
|
26
|
+
const dataTransfer = transfer();
|
|
27
|
+
expect(FileDragUtil.has(dataTransfer)).toBe(false);
|
|
28
|
+
expect(FileDragUtil.get(dataTransfer)).toBeUndefined();
|
|
29
|
+
dataTransfer.setData(FileDragUtil.TYPE, '{"cwd":1}');
|
|
30
|
+
expect(FileDragUtil.get(dataTransfer)).toBeUndefined();
|
|
31
|
+
dataTransfer.setData(FileDragUtil.TYPE, 'not json');
|
|
32
|
+
expect(FileDragUtil.get(dataTransfer)).toBeUndefined();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('gives the paths inside their machine', () => {
|
|
36
|
+
expect(FileDragUtil.paths({ cwd: 'ssh://dev/home/me/app', paths: ['src/a.ts', ''] })).toEqual(['/home/me/app/src/a.ts', '/home/me/app']);
|
|
37
|
+
expect(FileDragUtil.paths({ cwd: 'C:\\work\\app', paths: ['src/a.ts'] })).toEqual(['C:\\work\\app\\src\\a.ts']);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('makes references relative to the conversation when they are inside it', () => {
|
|
41
|
+
const files = { cwd: '/work/app', paths: ['src/a.ts', 'my notes.md'] };
|
|
42
|
+
expect(FileDragUtil.references(files, '/work/app')).toBe('@src/a.ts @"my notes.md"');
|
|
43
|
+
expect(FileDragUtil.references({ cwd: '/work/app/src', paths: ['a.ts'] }, '/work/app')).toBe('@src/a.ts');
|
|
44
|
+
expect(FileDragUtil.references(files, '/other')).toBe('@/work/app/src/a.ts @"/work/app/my notes.md"');
|
|
45
|
+
expect(FileDragUtil.references({ cwd: 'ssh://dev/work/app', paths: ['a.ts'] }, '/work/app')).toBe('@/work/app/a.ts');
|
|
46
|
+
expect(FileDragUtil.references(files, undefined)).toBe('@/work/app/src/a.ts @"/work/app/my notes.md"');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { TaLocation } from '@celljs/ta-protocol';
|
|
2
|
+
import { ComposerUtil } from './composer';
|
|
3
|
+
|
|
4
|
+
/** Files of a directory dragged from a view (the explorer): {@link WebFileDrag} of the extension API. */
|
|
5
|
+
export interface DraggedFiles {
|
|
6
|
+
/** The directory they are of: a path of the machine of ta serve, or `ssh://` (TaLocation). */
|
|
7
|
+
cwd: string;
|
|
8
|
+
/** Relative to `cwd`, with `/`; '' for `cwd` itself. */
|
|
9
|
+
paths: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Files on a drag between the views of the page: their own type, read on a drop; and their paths as text,
|
|
14
|
+
* for what is not of the page (a native field, another app).
|
|
15
|
+
*/
|
|
16
|
+
export namespace FileDragUtil {
|
|
17
|
+
export const TYPE = 'application/x-ta-files';
|
|
18
|
+
|
|
19
|
+
export function set(dataTransfer: DataTransfer, files: DraggedFiles): void {
|
|
20
|
+
const dragged: DraggedFiles = { cwd: files.cwd, paths: [...files.paths] };
|
|
21
|
+
dataTransfer.setData(TYPE, JSON.stringify(dragged));
|
|
22
|
+
dataTransfer.setData('text/plain', paths(dragged).join('\n'));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Whether a drag holds files: all that `dragover` can tell. */
|
|
26
|
+
export function has(dataTransfer: Pick<DataTransfer, 'types'>): boolean {
|
|
27
|
+
return Array.from(dataTransfer.types ?? []).includes(TYPE);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The files of a drop; undefined when it holds none. */
|
|
31
|
+
export function get(dataTransfer: Pick<DataTransfer, 'getData'>): DraggedFiles | undefined {
|
|
32
|
+
try {
|
|
33
|
+
const value = JSON.parse(dataTransfer.getData(TYPE) || 'null') as Partial<DraggedFiles> | null;
|
|
34
|
+
if (!value || typeof value.cwd !== 'string' || !Array.isArray(value.paths)) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
const list = value.paths.filter((path): path is string => typeof path === 'string');
|
|
38
|
+
return list.length ? { cwd: value.cwd, paths: list } : undefined;
|
|
39
|
+
} catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The paths inside their machine, as a terminal there takes them. */
|
|
45
|
+
export function paths(files: DraggedFiles): string[] {
|
|
46
|
+
const base = TaLocation.path(files.cwd);
|
|
47
|
+
const windows = /^[a-zA-Z]:[\\/]|^\\\\/.test(base) && !base.includes('/');
|
|
48
|
+
const trimmed = base.replace(/[\\/]+$/, '');
|
|
49
|
+
return files.paths.map(path => {
|
|
50
|
+
if (!path) {
|
|
51
|
+
return base;
|
|
52
|
+
}
|
|
53
|
+
return windows ? `${trimmed}\\${path.replace(/\//g, '\\')}` : `${trimmed}/${path}`;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The files as `@` references of a conversation in `cwd`: relative to it when they are inside it, on the
|
|
59
|
+
* same machine; else their paths there.
|
|
60
|
+
*/
|
|
61
|
+
export function references(files: DraggedFiles, cwd: string | undefined): string {
|
|
62
|
+
const absolute = paths(files);
|
|
63
|
+
const sameMachine = cwd !== undefined && TaLocation.host(cwd) === TaLocation.host(files.cwd);
|
|
64
|
+
const root = cwd === undefined ? '' : TaLocation.path(cwd).replace(/[\\/]+$/, '');
|
|
65
|
+
return absolute
|
|
66
|
+
.map((path, index) => {
|
|
67
|
+
if (files.cwd === cwd) {
|
|
68
|
+
return files.paths[index] || '.';
|
|
69
|
+
}
|
|
70
|
+
if (sameMachine && root && path.startsWith(`${root}/`)) {
|
|
71
|
+
return path.slice(root.length + 1);
|
|
72
|
+
}
|
|
73
|
+
return path;
|
|
74
|
+
})
|
|
75
|
+
.map(ComposerUtil.reference)
|
|
76
|
+
.join(' ');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -26,6 +26,8 @@ describe('MessageUtil', () => {
|
|
|
26
26
|
expect(MessageUtil.basename('/home/me/project/')).toBe('project');
|
|
27
27
|
expect(MessageUtil.shortPath('/home/me/work/project')).toBe('…/work/project');
|
|
28
28
|
expect(MessageUtil.shortPath('/srv')).toBe('/srv');
|
|
29
|
+
expect(MessageUtil.shortPath('ssh://dev/data/my/workspace')).toBe('dev:…/my/workspace');
|
|
30
|
+
expect(MessageUtil.shortPath('ssh://dev/srv')).toBe('dev:/srv');
|
|
29
31
|
});
|
|
30
32
|
|
|
31
33
|
it('finds the entry of a message by its role and time, the newest first', () => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SessionMessage, ToolRun } from '@celljs/ta-client';
|
|
2
|
+
import { TaLocation } from '@celljs/ta-protocol';
|
|
2
3
|
|
|
3
4
|
export interface ContentBlock {
|
|
4
5
|
type: string;
|
|
@@ -177,12 +178,14 @@ export namespace MessageUtil {
|
|
|
177
178
|
|
|
178
179
|
/** The last two parts of a path: enough to recognize a project. */
|
|
179
180
|
export function shortPath(path: string): string {
|
|
180
|
-
const
|
|
181
|
-
|
|
181
|
+
const remote = TaLocation.parse(path);
|
|
182
|
+
const parts = (remote ? remote.path : path).split(/[\\/]/).filter(Boolean);
|
|
183
|
+
const short = parts.length > 2 ? `…/${parts.slice(-2).join('/')}` : remote ? remote.path || '~' : path;
|
|
184
|
+
return remote ? `${remote.host}:${short}` : short;
|
|
182
185
|
}
|
|
183
186
|
|
|
187
|
+
/** The last name of a path, or of an `ssh://` location (TaLocation). */
|
|
184
188
|
export function basename(path: string): string {
|
|
185
|
-
|
|
186
|
-
return parts[parts.length - 1] ?? path;
|
|
189
|
+
return TaLocation.basename(path);
|
|
187
190
|
}
|
|
188
191
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { TerminalText } from './terminal-text';
|
|
3
|
+
|
|
4
|
+
const QR = ['▄▄▄▄▄▄▄▄', '█ ▄▄▄ ██', '█ ███ ▀█', '█▄▄▄▄▄██', '██▀ ▄▀▄█', '█▄▀▀▄ ▀█', '█▀▀▀▀▀▀█', '▀▀▀▀▀▀▀▀'];
|
|
5
|
+
|
|
6
|
+
describe('TerminalText', () => {
|
|
7
|
+
it('takes a picture of half blocks out of the text, dark cells true', () => {
|
|
8
|
+
const { text, art } = TerminalText.art(`Scan with WeChat:\n\n${QR.join('\n')}\n`);
|
|
9
|
+
expect(text).toBe('Scan with WeChat:');
|
|
10
|
+
// The dark row above the light margin and the one below it are the terminal's background.
|
|
11
|
+
expect(art).toHaveLength(QR.length * 2 - 2);
|
|
12
|
+
expect(art?.[0]).toEqual(new Array(8).fill(false));
|
|
13
|
+
expect(art?.[1].slice(0, 3)).toEqual([false, true, true]);
|
|
14
|
+
expect(art?.[2].slice(0, 3)).toEqual([false, true, false]);
|
|
15
|
+
expect(art?.at(-1)).toEqual(new Array(8).fill(false));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('leaves text without a picture alone, and a short bar is no picture', () => {
|
|
19
|
+
expect(TerminalText.art('Saved')).toEqual({ text: 'Saved' });
|
|
20
|
+
expect(TerminalText.art('progress\n████ ')).toEqual({ text: 'progress\n████ ' });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('reads a picture of spaces on black and white, two characters a cell, and drops the escapes of the rest', () => {
|
|
24
|
+
const dark = '\x1b[40m \x1b[0m';
|
|
25
|
+
const light = '\x1b[47m \x1b[0m';
|
|
26
|
+
const line = (cells: string) => [...cells].map(cell => (cell === '#' ? dark : light)).join('');
|
|
27
|
+
const rows = ['#.#.', '.#.#', '##..', '..##', '#..#', '.##.', '####', '....'];
|
|
28
|
+
const { text, art } = TerminalText.art(`\x1b[1mScan\x1b[22m:\n${rows.map(line).join('\n')}`);
|
|
29
|
+
expect(text).toBe('Scan:');
|
|
30
|
+
expect(art).toEqual(rows.map(row => [...row].map(cell => cell === '#')));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('drops the escapes of a terminal', () => {
|
|
34
|
+
expect(TerminalText.plain('\x1b[31mred\x1b[0m \x1b]8;;https://a.b\x07link\x1b]8;;\x07')).toBe('red link');
|
|
35
|
+
expect(TerminalText.status('\x1b[32m[git]\x1b[0m ✅ clean')).toEqual({ label: 'git', segments: [{ text: 'clean', tone: 'done' }] });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('draws the dark cells as runs of one row', () => {
|
|
39
|
+
expect(
|
|
40
|
+
TerminalText.path([
|
|
41
|
+
[true, true, false, true],
|
|
42
|
+
[false, false, false, false],
|
|
43
|
+
]),
|
|
44
|
+
).toBe('M0 0h2v1h-2zM3 0h1v1h-1z');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('splits a status into its label, segments and tones', () => {
|
|
48
|
+
expect(TerminalText.status('[微信] ⏳ 登录中...')).toEqual({ label: '微信', segments: [{ text: '登录中...', tone: 'running' }] });
|
|
49
|
+
expect(TerminalText.status('[微信] ✅ 已连接 | abcd1234... | 🔒')).toEqual({
|
|
50
|
+
label: '微信',
|
|
51
|
+
segments: [{ text: '已连接', tone: 'done' }, { text: 'abcd1234...' }, { text: '🔒' }],
|
|
52
|
+
});
|
|
53
|
+
expect(TerminalText.status('❌ 已断开 | ❌ 被占用').segments).toEqual([
|
|
54
|
+
{ text: '已断开', tone: 'error' },
|
|
55
|
+
{ text: '被占用', tone: 'error' },
|
|
56
|
+
]);
|
|
57
|
+
expect(TerminalText.status('main ✔')).toEqual({ segments: [{ text: 'main', tone: 'done' }] });
|
|
58
|
+
expect(TerminalText.status('indexing 3/10')).toEqual({ segments: [{ text: 'indexing 3/10' }] });
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/** The tone of a status, from the sign a terminal extension puts before it. */
|
|
2
|
+
export type StatusTone = 'running' | 'done' | 'error' | 'waiting';
|
|
3
|
+
|
|
4
|
+
export interface StatusSegment {
|
|
5
|
+
text: string;
|
|
6
|
+
tone?: StatusTone;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** A status of an extension (`setStatus`), in its parts: `[WeChat] ✅ connected | abc` is a label and two segments. */
|
|
10
|
+
export interface StatusParts {
|
|
11
|
+
label?: string;
|
|
12
|
+
segments: StatusSegment[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Text that pi extensions write for a terminal (pi's TUI), shown in a page: without the escapes of the
|
|
17
|
+
* terminal, with the pictures they draw with characters (a QR code to sign in) drawn, and the signs of their
|
|
18
|
+
* status line as tones.
|
|
19
|
+
*/
|
|
20
|
+
export namespace TerminalText {
|
|
21
|
+
/** Half blocks, as qrcode-terminal's small mode: the character is the light of two cells, one above the other. */
|
|
22
|
+
const CELLS: Record<string, [boolean, boolean]> = { '█': [true, true], '▀': [true, false], '▄': [false, true], ' ': [false, false] };
|
|
23
|
+
const HALF_BLOCKS = /^[█▀▄ ]+$/;
|
|
24
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: the escapes of a terminal are what is matched
|
|
25
|
+
const ESCAPES = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-Z\\-_]/g;
|
|
26
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: the colours of a terminal
|
|
27
|
+
const SGR = /\x1b\[([0-9;]*)m/g;
|
|
28
|
+
/** Fewer lines than this are not a picture (a bar, a rule). */
|
|
29
|
+
const MIN_ART_LINES = 8;
|
|
30
|
+
const TONES: [RegExp, StatusTone][] = [
|
|
31
|
+
[/^(?:⏳|⌛|🔄)\uFE0F?\s*/u, 'running'],
|
|
32
|
+
[/^(?:✅|✔|☑)\uFE0F?\s*/u, 'done'],
|
|
33
|
+
[/^(?:❌|✖|⛔|🚫)\uFE0F?\s*/u, 'error'],
|
|
34
|
+
[/^(?:⚠)\uFE0F?\s*/u, 'waiting'],
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
/** The text without the escapes of a terminal (colours, styles, links, titles): a page draws its own. */
|
|
38
|
+
export function plain(text: string): string {
|
|
39
|
+
return text.replace(ESCAPES, '');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A line of half blocks as its two rows of cells, dark ones true. */
|
|
43
|
+
function halfBlocks(line: string): boolean[][] | undefined {
|
|
44
|
+
const text = plain(line);
|
|
45
|
+
if (!HALF_BLOCKS.test(text) || !/[█▀▄]/.test(text)) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
const top: boolean[] = [];
|
|
49
|
+
const bottom: boolean[] = [];
|
|
50
|
+
for (const char of text) {
|
|
51
|
+
const [upper, lower] = CELLS[char];
|
|
52
|
+
top.push(!upper);
|
|
53
|
+
bottom.push(!lower);
|
|
54
|
+
}
|
|
55
|
+
return [top, bottom];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A line of spaces on black and white backgrounds, as qrcode-terminal's large mode, as its row of cells:
|
|
60
|
+
* dark ones true, a cell for each character.
|
|
61
|
+
*/
|
|
62
|
+
function backgrounds(line: string): boolean[][] | undefined {
|
|
63
|
+
if (!line.includes('\x1b[') || plain(line).trim()) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
const row: boolean[] = [];
|
|
67
|
+
let dark: boolean | undefined;
|
|
68
|
+
let at = 0;
|
|
69
|
+
const take = (until: number) => {
|
|
70
|
+
for (let i = at; i < until; i++) {
|
|
71
|
+
if (dark === undefined) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
row.push(dark);
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
};
|
|
78
|
+
for (const match of line.matchAll(SGR)) {
|
|
79
|
+
if (!take(match.index)) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
at = match.index + match[0].length;
|
|
83
|
+
for (const code of (match[1] || '0').split(';').map(Number)) {
|
|
84
|
+
if (code === 40 || code === 100) {
|
|
85
|
+
dark = true;
|
|
86
|
+
} else if (code === 47 || code === 107) {
|
|
87
|
+
dark = false;
|
|
88
|
+
} else if (code === 0 || code === 49) {
|
|
89
|
+
dark = undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (!take(line.length) || !row.length) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
return [row];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The longest run of lines that `cells` reads, as rows of cells, and where it is. */
|
|
100
|
+
function longest(lines: string[], cells: (line: string) => boolean[][] | undefined): { start: number; end: number; rows: boolean[][] } | undefined {
|
|
101
|
+
let best: { start: number; end: number; rows: boolean[][] } | undefined;
|
|
102
|
+
let run: { start: number; end: number; rows: boolean[][] } | undefined;
|
|
103
|
+
lines.forEach((line, index) => {
|
|
104
|
+
const read = cells(line);
|
|
105
|
+
if (!read) {
|
|
106
|
+
run = undefined;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
run = run ? { ...run, end: index, rows: [...run.rows, ...read] } : { start: index, end: index, rows: read };
|
|
110
|
+
if (!best || run.end - run.start > best.end - best.start) {
|
|
111
|
+
best = run;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
return best && best.end - best.start + 1 >= MIN_ART_LINES ? best : undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Two characters make a square cell on a terminal: rows whose characters all go in pairs are halved. */
|
|
118
|
+
function squared(rows: boolean[][]): boolean[][] {
|
|
119
|
+
const paired = rows.every(row => row.length % 2 === 0 && row.every((cell, x) => x % 2 === 0 || cell === row[x - 1]));
|
|
120
|
+
return paired ? rows.map(row => row.filter((_, x) => x % 2 === 0)) : rows;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Rows of one width: a shorter one is light at its end, as the background of a terminal. */
|
|
124
|
+
function even(rows: boolean[][]): boolean[][] {
|
|
125
|
+
const width = Math.max(...rows.map(row => row.length));
|
|
126
|
+
return rows.map(row => [...row, ...new Array<boolean>(width - row.length).fill(false)]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Without the rows and columns at its edges that are dark all along: the background of the terminal around
|
|
131
|
+
* the light margin a QR code draws, not part of the picture.
|
|
132
|
+
*/
|
|
133
|
+
function trimmed(rows: boolean[][]): boolean[][] {
|
|
134
|
+
let top = 0;
|
|
135
|
+
let bottom = rows.length;
|
|
136
|
+
while (top < bottom && rows[top].every(Boolean)) {
|
|
137
|
+
top++;
|
|
138
|
+
}
|
|
139
|
+
while (bottom > top && rows[bottom - 1].every(Boolean)) {
|
|
140
|
+
bottom--;
|
|
141
|
+
}
|
|
142
|
+
const kept = rows.slice(top, bottom);
|
|
143
|
+
const width = kept[0]?.length ?? 0;
|
|
144
|
+
const dark = (x: number) => kept.every(row => row[x]);
|
|
145
|
+
let left = 0;
|
|
146
|
+
let right = width;
|
|
147
|
+
while (left < right && dark(left)) {
|
|
148
|
+
left++;
|
|
149
|
+
}
|
|
150
|
+
while (right > left && dark(right - 1)) {
|
|
151
|
+
right--;
|
|
152
|
+
}
|
|
153
|
+
return kept.map(row => row.slice(left, right));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The text without the picture in it, and without escapes; and the picture as rows of cells, dark ones true.
|
|
158
|
+
* A picture is the longest run of lines of half blocks, or of spaces on black and white: how a terminal
|
|
159
|
+
* extension draws a QR code. Its light is what a dark terminal shows light.
|
|
160
|
+
*/
|
|
161
|
+
export function art(text: string): { text: string; art?: boolean[][] } {
|
|
162
|
+
const lines = text.split('\n');
|
|
163
|
+
const blocks = longest(lines, halfBlocks);
|
|
164
|
+
const colours = longest(lines, backgrounds);
|
|
165
|
+
const found = blocks && (!colours || blocks.end - blocks.start >= colours.end - colours.start) ? blocks : colours;
|
|
166
|
+
if (!found) {
|
|
167
|
+
return { text: plain(text) };
|
|
168
|
+
}
|
|
169
|
+
const rest = plain([...lines.slice(0, found.start), ...lines.slice(found.end + 1)].join('\n')).trim();
|
|
170
|
+
const art = trimmed(even(found === colours ? squared(found.rows) : found.rows));
|
|
171
|
+
return art.length ? { text: rest, art } : { text: plain(text) };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** The picture as an SVG path of its dark cells, one unit each. */
|
|
175
|
+
export function path(art: boolean[][]): string {
|
|
176
|
+
let d = '';
|
|
177
|
+
art.forEach((row, y) => {
|
|
178
|
+
let x = 0;
|
|
179
|
+
while (x < row.length) {
|
|
180
|
+
if (!row[x]) {
|
|
181
|
+
x++;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const from = x;
|
|
185
|
+
while (x < row.length && row[x]) {
|
|
186
|
+
x++;
|
|
187
|
+
}
|
|
188
|
+
d += `M${from} ${y}h${x - from}v1h-${x - from}z`;
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
return d;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** A status line in its parts: a leading `[label]`, segments split by ` | `, and a sign at the start of one as its tone. */
|
|
195
|
+
export function status(text: string): StatusParts {
|
|
196
|
+
let rest = plain(text).trim();
|
|
197
|
+
const labelled = /^\[([^\]]+)\]\s*/.exec(rest);
|
|
198
|
+
const label = labelled?.[1].trim();
|
|
199
|
+
if (labelled) {
|
|
200
|
+
rest = rest.slice(labelled[0].length);
|
|
201
|
+
}
|
|
202
|
+
const segments = rest
|
|
203
|
+
.split(/\s+\|\s+/)
|
|
204
|
+
.map(part => part.trim())
|
|
205
|
+
.filter(Boolean)
|
|
206
|
+
.map(part => {
|
|
207
|
+
for (const [sign, tone] of TONES) {
|
|
208
|
+
if (sign.test(part)) {
|
|
209
|
+
return { text: part.replace(sign, ''), tone };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Or after it, as `main ✔`; a sign alone stays, as it is all there is to read.
|
|
213
|
+
const signed = /\s+(\S+)$/u.exec(part);
|
|
214
|
+
for (const [sign, tone] of signed ? TONES : []) {
|
|
215
|
+
if (signed && sign.test(signed[1])) {
|
|
216
|
+
return { text: part.slice(0, signed.index), tone };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return { text: part };
|
|
220
|
+
});
|
|
221
|
+
return label ? { label, segments } : { segments };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -13,6 +13,18 @@ describe('ToolUtil', () => {
|
|
|
13
13
|
expect(ToolUtil.summary('read', { path: 'a.ts', offset: 10, limit: 5 }).target).toBe('a.ts:10-14');
|
|
14
14
|
expect(ToolUtil.summary('start_task', { title: 'Fix the build', instructions: 'long' }).target).toBe('Fix the build');
|
|
15
15
|
expect(ToolUtil.summary('mcp_search', { limit: 3, query: 'cats' })).toEqual({ verb: undefined, name: 'mcp_search', target: 'cats', code: false });
|
|
16
|
+
expect(ToolUtil.summary('agent_tool', { title: '`npm test`', kind: 'execute', target: 'npm test', tool: 'Bash' })).toEqual({
|
|
17
|
+
verb: 'ta.tool.verb.bash',
|
|
18
|
+
name: 'Bash',
|
|
19
|
+
target: 'npm test',
|
|
20
|
+
code: true,
|
|
21
|
+
});
|
|
22
|
+
expect(ToolUtil.summary('agent_tool', { title: 'Skill', kind: 'other', target: 'Skill', tool: 'Skill' })).toEqual({
|
|
23
|
+
verb: undefined,
|
|
24
|
+
name: 'Skill',
|
|
25
|
+
target: '',
|
|
26
|
+
code: false,
|
|
27
|
+
});
|
|
16
28
|
expect(ToolUtil.scriptTitle('// @options: {}\n// read the config\nawait tools.read({})')).toBe('read the config');
|
|
17
29
|
expect(ToolUtil.scriptTitle('const a = 1;\nreturn a')).toBe('const a = 1;');
|
|
18
30
|
});
|
package/src/browser/util/tool.ts
CHANGED
|
@@ -61,6 +61,16 @@ const VERBS: Record<string, MessageId> = {
|
|
|
61
61
|
stop_task: 'ta.tool.verb.stopTask',
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
+
/** The verbs of the kinds of calls ACP agents make (`agent_tool` of @celljs/ta-agents). */
|
|
65
|
+
const AGENT_VERBS: Record<string, MessageId> = {
|
|
66
|
+
execute: 'ta.tool.verb.bash',
|
|
67
|
+
read: 'ta.tool.verb.read',
|
|
68
|
+
edit: 'ta.tool.verb.edit',
|
|
69
|
+
delete: 'ta.tool.verb.edit',
|
|
70
|
+
move: 'ta.tool.verb.edit',
|
|
71
|
+
search: 'ta.tool.verb.grep',
|
|
72
|
+
};
|
|
73
|
+
|
|
64
74
|
const TARGET_CHARS = 160;
|
|
65
75
|
|
|
66
76
|
function str(value: unknown): string {
|
|
@@ -105,6 +115,13 @@ export namespace ToolUtil {
|
|
|
105
115
|
return { verb, name, target: scriptTitle(str(args.code)), code: false };
|
|
106
116
|
case 'start_task':
|
|
107
117
|
return { verb, name, target: str(args.title), code: false };
|
|
118
|
+
case 'agent_tool': {
|
|
119
|
+
// A call another agent made with its own tool: its kind, and what it was on.
|
|
120
|
+
const kind = str(args.kind);
|
|
121
|
+
const tool = str(args.tool) || str(args.title) || name;
|
|
122
|
+
const target = line(str(args.target) || str(args.title));
|
|
123
|
+
return { verb: AGENT_VERBS[kind], name: tool, target: target === tool ? '' : target, code: kind === 'execute' || kind === 'search' };
|
|
124
|
+
}
|
|
108
125
|
case 'get_task':
|
|
109
126
|
case 'stop_task':
|
|
110
127
|
case 'message_task':
|
|
@@ -4,6 +4,8 @@ import type {
|
|
|
4
4
|
TaAuthServer,
|
|
5
5
|
TaPackageServer,
|
|
6
6
|
TaRelayServer,
|
|
7
|
+
TaRemoteEvent,
|
|
8
|
+
TaRemoteServer,
|
|
7
9
|
TaServer,
|
|
8
10
|
TaServerInfo,
|
|
9
11
|
TaSessionInfo,
|
|
@@ -42,6 +44,7 @@ export interface TaWorkbench {
|
|
|
42
44
|
readonly packages: TaPackageServer;
|
|
43
45
|
readonly auth: TaAuthServer;
|
|
44
46
|
readonly relay: TaRelayServer;
|
|
47
|
+
readonly remote: TaRemoteServer;
|
|
45
48
|
subscribe(listener: (state: WorkbenchState) => void): () => void;
|
|
46
49
|
/** Signs in with the token of the URL fragment (`ta serve --web`) or the one this page used before. */
|
|
47
50
|
start(): Promise<void>;
|
|
@@ -63,4 +66,9 @@ export interface TaWorkbench {
|
|
|
63
66
|
onExtensionData(listener: (name: string, keys: string[], cwd?: string) => void): () => void;
|
|
64
67
|
/** What happens during the sign-ins of this page (TaAuthServer.login); the listener stays across reconnections. */
|
|
65
68
|
onAuthEvent(listener: (loginId: string, event: TaAuthEvent) => void): () => void;
|
|
69
|
+
/**
|
|
70
|
+
* What happens with the remote machines (TaRemoteServer.watch, which the page calls on each connection):
|
|
71
|
+
* the prompts that wait come again as `prompt` events after a reconnection. The listener stays.
|
|
72
|
+
*/
|
|
73
|
+
onRemoteEvent(listener: (event: TaRemoteEvent) => void): () => void;
|
|
66
74
|
}
|