dominds 0.1.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/LICENSE +157 -0
- package/README.md +250 -0
- package/README.zh.md +161 -0
- package/dist/access-control.js +253 -0
- package/dist/cli/create.js +263 -0
- package/dist/cli/read.js +84 -0
- package/dist/cli/tui.js +199 -0
- package/dist/cli/webui.js +169 -0
- package/dist/cli.js +227 -0
- package/dist/dialog-factory.js +53 -0
- package/dist/dialog-global-registry.js +68 -0
- package/dist/dialog-instance-registry.js +78 -0
- package/dist/dialog-run-state.js +198 -0
- package/dist/dialog.js +1024 -0
- package/dist/evt-registry.js +103 -0
- package/dist/index.js +8 -0
- package/dist/llm/client.js +69 -0
- package/dist/llm/defaults.yaml +386 -0
- package/dist/llm/driver.js +3214 -0
- package/dist/llm/gen/anthropic.js +611 -0
- package/dist/llm/gen/codex.js +375 -0
- package/dist/llm/gen/mock.js +326 -0
- package/dist/llm/gen/openai.js +470 -0
- package/dist/llm/gen/registry.js +26 -0
- package/dist/llm/gen.js +2 -0
- package/dist/llm/tools-projection.js +37 -0
- package/dist/log.js +228 -0
- package/dist/mcp/config.js +230 -0
- package/dist/mcp/sdk-client.js +129 -0
- package/dist/mcp/server-runtime.js +57 -0
- package/dist/mcp/stdio-client.js +280 -0
- package/dist/mcp/supervisor.js +979 -0
- package/dist/mcp/tool-names.js +109 -0
- package/dist/minds/builtin/cmdr/persona.md +3 -0
- package/dist/minds/builtin/dijiang/knowledge.md +287 -0
- package/dist/minds/builtin/dijiang/persona.md +7 -0
- package/dist/minds/builtin/fuxi/persona.en.md +59 -0
- package/dist/minds/builtin/fuxi/persona.zh.md +49 -0
- package/dist/minds/builtin/pangu/persona.en.md +78 -0
- package/dist/minds/builtin/pangu/persona.zh.md +71 -0
- package/dist/minds/load.js +617 -0
- package/dist/minds/minds-i18n.js +131 -0
- package/dist/minds/system-prompt.js +281 -0
- package/dist/persistence.js +3128 -0
- package/dist/problems.js +109 -0
- package/dist/server/api-routes.js +1031 -0
- package/dist/server/auth.js +180 -0
- package/dist/server/mime-types.js +32 -0
- package/dist/server/prompts-routes.js +543 -0
- package/dist/server/server-core.js +235 -0
- package/dist/server/setup-routes.js +697 -0
- package/dist/server/static-server.js +132 -0
- package/dist/server/websocket-handler.js +1011 -0
- package/dist/server.js +164 -0
- package/dist/shared/async-fifo-mutex.js +36 -0
- package/dist/shared/diligence.js +20 -0
- package/dist/shared/dotenv.js +144 -0
- package/dist/shared/evt.js +195 -0
- package/dist/shared/i18n/driver-messages.js +267 -0
- package/dist/shared/i18n/text.js +9 -0
- package/dist/shared/i18n/tool-result-messages.js +51 -0
- package/dist/shared/rtws-cli.js +73 -0
- package/dist/shared/runtime-language.js +47 -0
- package/dist/shared/team-mgmt-manual.js +116 -0
- package/dist/shared/types/context-health.js +2 -0
- package/dist/shared/types/dialog.js +11 -0
- package/dist/shared/types/i18n.js +2 -0
- package/dist/shared/types/index.js +26 -0
- package/dist/shared/types/language.js +40 -0
- package/dist/shared/types/problems.js +2 -0
- package/dist/shared/types/prompts.js +2 -0
- package/dist/shared/types/q4h.js +7 -0
- package/dist/shared/types/run-state.js +8 -0
- package/dist/shared/types/setup.js +2 -0
- package/dist/shared/types/storage.js +10 -0
- package/dist/shared/types/tellask.js +8 -0
- package/dist/shared/types/tools-registry.js +2 -0
- package/dist/shared/types/wire.js +12 -0
- package/dist/shared/utils/fmt.js +9 -0
- package/dist/shared/utils/html.js +20 -0
- package/dist/shared/utils/id.js +18 -0
- package/dist/shared/utils/inter-dialog-format.js +101 -0
- package/dist/shared/utils/time.js +13 -0
- package/dist/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/static/assets/_baseUniq-Crfl3d5Y.js +661 -0
- package/dist/static/assets/_baseUniq-Crfl3d5Y.js.map +1 -0
- package/dist/static/assets/arc-CbA_x9GD.js +132 -0
- package/dist/static/assets/arc-CbA_x9GD.js.map +1 -0
- package/dist/static/assets/architectureDiagram-VXUJARFQ-lcFS8ZQJ.js +8685 -0
- package/dist/static/assets/architectureDiagram-VXUJARFQ-lcFS8ZQJ.js.map +1 -0
- package/dist/static/assets/blockDiagram-VD42YOAC-B3Q36qRc.js +3608 -0
- package/dist/static/assets/blockDiagram-VD42YOAC-B3Q36qRc.js.map +1 -0
- package/dist/static/assets/c4Diagram-YG6GDRKO-Mt-aq3VH.js +2482 -0
- package/dist/static/assets/c4Diagram-YG6GDRKO-Mt-aq3VH.js.map +1 -0
- package/dist/static/assets/channel-BVr1Yke-.js +8 -0
- package/dist/static/assets/channel-BVr1Yke-.js.map +1 -0
- package/dist/static/assets/chunk-4BX2VUAB-qCIn5Iic.js +17 -0
- package/dist/static/assets/chunk-4BX2VUAB-qCIn5Iic.js.map +1 -0
- package/dist/static/assets/chunk-55IACEB6-q172NeCV.js +14 -0
- package/dist/static/assets/chunk-55IACEB6-q172NeCV.js.map +1 -0
- package/dist/static/assets/chunk-B4BG7PRW-CMJmtYzq.js +1827 -0
- package/dist/static/assets/chunk-B4BG7PRW-CMJmtYzq.js.map +1 -0
- package/dist/static/assets/chunk-DI55MBZ5-DiuwwZPL.js +1916 -0
- package/dist/static/assets/chunk-DI55MBZ5-DiuwwZPL.js.map +1 -0
- package/dist/static/assets/chunk-FMBD7UC4-06sqZTTn.js +20 -0
- package/dist/static/assets/chunk-FMBD7UC4-06sqZTTn.js.map +1 -0
- package/dist/static/assets/chunk-QN33PNHL-CnpBNkpP.js +25 -0
- package/dist/static/assets/chunk-QN33PNHL-CnpBNkpP.js.map +1 -0
- package/dist/static/assets/chunk-QZHKN3VN-CNgjMR-e.js +18 -0
- package/dist/static/assets/chunk-QZHKN3VN-CNgjMR-e.js.map +1 -0
- package/dist/static/assets/chunk-TZMSLE5B-BxtzW6--.js +109 -0
- package/dist/static/assets/chunk-TZMSLE5B-BxtzW6--.js.map +1 -0
- package/dist/static/assets/classDiagram-2ON5EDUG-29huvmn-.js +23 -0
- package/dist/static/assets/classDiagram-2ON5EDUG-29huvmn-.js.map +1 -0
- package/dist/static/assets/classDiagram-v2-WZHVMYZB-29huvmn-.js +23 -0
- package/dist/static/assets/classDiagram-v2-WZHVMYZB-29huvmn-.js.map +1 -0
- package/dist/static/assets/clone-D2OgLSSn.js +9 -0
- package/dist/static/assets/clone-D2OgLSSn.js.map +1 -0
- package/dist/static/assets/cose-bilkent-S5V4N54A-BNegDCxl.js +4943 -0
- package/dist/static/assets/cose-bilkent-S5V4N54A-BNegDCxl.js.map +1 -0
- package/dist/static/assets/cytoscape.esm-Bm8DJGmZ.js +30240 -0
- package/dist/static/assets/cytoscape.esm-Bm8DJGmZ.js.map +1 -0
- package/dist/static/assets/dagre-6UL2VRFP-f1XrTRSn.js +695 -0
- package/dist/static/assets/dagre-6UL2VRFP-f1XrTRSn.js.map +1 -0
- package/dist/static/assets/defaultLocale-DVr69WTU.js +207 -0
- package/dist/static/assets/defaultLocale-DVr69WTU.js.map +1 -0
- package/dist/static/assets/diagram-PSM6KHXK-8w1WbeDi.js +849 -0
- package/dist/static/assets/diagram-PSM6KHXK-8w1WbeDi.js.map +1 -0
- package/dist/static/assets/diagram-QEK2KX5R-CF4wtMmR.js +303 -0
- package/dist/static/assets/diagram-QEK2KX5R-CF4wtMmR.js.map +1 -0
- package/dist/static/assets/diagram-S2PKOQOG-8p3Avgn2.js +213 -0
- package/dist/static/assets/diagram-S2PKOQOG-8p3Avgn2.js.map +1 -0
- package/dist/static/assets/erDiagram-Q2GNP2WA-BMKLxlM9.js +1159 -0
- package/dist/static/assets/erDiagram-Q2GNP2WA-BMKLxlM9.js.map +1 -0
- package/dist/static/assets/favicon-Cmg5RbCj.svg +8 -0
- package/dist/static/assets/flowDiagram-NV44I4VS-CgEuPNK2.js +2332 -0
- package/dist/static/assets/flowDiagram-NV44I4VS-CgEuPNK2.js.map +1 -0
- package/dist/static/assets/ganttDiagram-JELNMOA3-bJkDCf-9.js +3681 -0
- package/dist/static/assets/ganttDiagram-JELNMOA3-bJkDCf-9.js.map +1 -0
- package/dist/static/assets/gitGraphDiagram-NY62KEGX-4QE9kesp.js +1206 -0
- package/dist/static/assets/gitGraphDiagram-NY62KEGX-4QE9kesp.js.map +1 -0
- package/dist/static/assets/graph-CS0Pmm7c.js +597 -0
- package/dist/static/assets/graph-CS0Pmm7c.js.map +1 -0
- package/dist/static/assets/index-BS6HnGzC.js +112303 -0
- package/dist/static/assets/index-BS6HnGzC.js.map +1 -0
- package/dist/static/assets/index-DaIsSzC_.css +483 -0
- package/dist/static/assets/infoDiagram-WHAUD3N6-ypBcKfUs.js +34 -0
- package/dist/static/assets/infoDiagram-WHAUD3N6-ypBcKfUs.js.map +1 -0
- package/dist/static/assets/init-ZxktEp_H.js +17 -0
- package/dist/static/assets/init-ZxktEp_H.js.map +1 -0
- package/dist/static/assets/journeyDiagram-XKPGCS4Q-QnrxDowJ.js +1255 -0
- package/dist/static/assets/journeyDiagram-XKPGCS4Q-QnrxDowJ.js.map +1 -0
- package/dist/static/assets/kanban-definition-3W4ZIXB7-CfvEc4z5.js +1048 -0
- package/dist/static/assets/kanban-definition-3W4ZIXB7-CfvEc4z5.js.map +1 -0
- package/dist/static/assets/layout-8TGxpm23.js +2218 -0
- package/dist/static/assets/layout-8TGxpm23.js.map +1 -0
- package/dist/static/assets/linear-BATBPQQv.js +341 -0
- package/dist/static/assets/linear-BATBPQQv.js.map +1 -0
- package/dist/static/assets/min-B3oVH3AC.js +42 -0
- package/dist/static/assets/min-B3oVH3AC.js.map +1 -0
- package/dist/static/assets/mindmap-definition-VGOIOE7T-L7VLwwF8.js +1127 -0
- package/dist/static/assets/mindmap-definition-VGOIOE7T-L7VLwwF8.js.map +1 -0
- package/dist/static/assets/ordinal-CxptdPJm.js +77 -0
- package/dist/static/assets/ordinal-CxptdPJm.js.map +1 -0
- package/dist/static/assets/pieDiagram-ADFJNKIX-CFW3zIhM.js +241 -0
- package/dist/static/assets/pieDiagram-ADFJNKIX-CFW3zIhM.js.map +1 -0
- package/dist/static/assets/quadrantDiagram-AYHSOK5B-B7ssen3E.js +1338 -0
- package/dist/static/assets/quadrantDiagram-AYHSOK5B-B7ssen3E.js.map +1 -0
- package/dist/static/assets/requirementDiagram-UZGBJVZJ-D0v5BArv.js +1162 -0
- package/dist/static/assets/requirementDiagram-UZGBJVZJ-D0v5BArv.js.map +1 -0
- package/dist/static/assets/sankeyDiagram-TZEHDZUN-B7slncJe.js +1195 -0
- package/dist/static/assets/sankeyDiagram-TZEHDZUN-B7slncJe.js.map +1 -0
- package/dist/static/assets/sequenceDiagram-WL72ISMW-oXU2lRh_.js +3875 -0
- package/dist/static/assets/sequenceDiagram-WL72ISMW-oXU2lRh_.js.map +1 -0
- package/dist/static/assets/stateDiagram-FKZM4ZOC-CFYsEd0x.js +452 -0
- package/dist/static/assets/stateDiagram-FKZM4ZOC-CFYsEd0x.js.map +1 -0
- package/dist/static/assets/stateDiagram-v2-4FDKWEC3-C0UWaNA7.js +22 -0
- package/dist/static/assets/stateDiagram-v2-4FDKWEC3-C0UWaNA7.js.map +1 -0
- package/dist/static/assets/timeline-definition-IT6M3QCI-C3KODUrh.js +1223 -0
- package/dist/static/assets/timeline-definition-IT6M3QCI-C3KODUrh.js.map +1 -0
- package/dist/static/assets/treemap-KMMF4GRG-DAGDLhj2.js +18753 -0
- package/dist/static/assets/treemap-KMMF4GRG-DAGDLhj2.js.map +1 -0
- package/dist/static/assets/xychartDiagram-PRI3JC2R-C0J9iwTO.js +1888 -0
- package/dist/static/assets/xychartDiagram-PRI3JC2R-C0J9iwTO.js.map +1 -0
- package/dist/static/index.html +71 -0
- package/dist/static/testing/dom-observation-utils.js +425 -0
- package/dist/static/testing/e2e-test-helper.js +3119 -0
- package/dist/team.js +1160 -0
- package/dist/tellask.js +431 -0
- package/dist/tool.js +150 -0
- package/dist/tools/apply-patch.js +542 -0
- package/dist/tools/builtins.js +196 -0
- package/dist/tools/context-health.js +177 -0
- package/dist/tools/ctrl.js +478 -0
- package/dist/tools/diag.js +583 -0
- package/dist/tools/env.js +184 -0
- package/dist/tools/fs.js +818 -0
- package/dist/tools/mcp.js +138 -0
- package/dist/tools/mem.js +349 -0
- package/dist/tools/os.js +751 -0
- package/dist/tools/prompts/team_mgmt.en.md +70 -0
- package/dist/tools/prompts/team_mgmt.zh.md +70 -0
- package/dist/tools/prompts/ws_mod.en.md +86 -0
- package/dist/tools/prompts/ws_mod.zh.md +87 -0
- package/dist/tools/registry-snapshot.js +31 -0
- package/dist/tools/registry.js +121 -0
- package/dist/tools/ripgrep.js +678 -0
- package/dist/tools/team-mgmt.js +3300 -0
- package/dist/tools/txt.js +3178 -0
- package/dist/utils/id.js +72 -0
- package/dist/utils/task-doc.js +236 -0
- package/dist/utils/task-package.js +522 -0
- package/dist/utils/taskdoc-search.js +280 -0
- package/dist/utils/taskdoc.js +400 -0
- package/package.json +69 -0
package/dist/tools/os.js
ADDED
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Module: tools/os
|
|
4
|
+
*
|
|
5
|
+
* Operating system interaction tools for shell command execution.
|
|
6
|
+
* Provides shell_cmd and stop_daemon FuncTools with advanced process management.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.getDaemonOutputTool = exports.stopDaemonTool = exports.readonlyShellTool = exports.shellCmdTool = exports.shellCmdReminderOwner = void 0;
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const runtime_language_1 = require("../shared/runtime-language");
|
|
12
|
+
const time_1 = require("../shared/utils/time");
|
|
13
|
+
// Scrolling buffer that maintains a fixed number of lines like a terminal
|
|
14
|
+
class ScrollingBuffer {
|
|
15
|
+
constructor(maxLines) {
|
|
16
|
+
this.maxLines = maxLines;
|
|
17
|
+
this.lines = [];
|
|
18
|
+
this.linesScrolledOut = 0;
|
|
19
|
+
}
|
|
20
|
+
addLine(line) {
|
|
21
|
+
this.lines.push(line);
|
|
22
|
+
if (this.lines.length > this.maxLines) {
|
|
23
|
+
this.lines.shift();
|
|
24
|
+
this.linesScrolledOut++;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
addText(text) {
|
|
28
|
+
const newLines = text.split('\n');
|
|
29
|
+
// Don't add empty line at the end if text ends with newline
|
|
30
|
+
if (newLines[newLines.length - 1] === '') {
|
|
31
|
+
newLines.pop();
|
|
32
|
+
}
|
|
33
|
+
for (const line of newLines) {
|
|
34
|
+
this.addLine(line);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
getContent() {
|
|
38
|
+
return this.lines.join('\n');
|
|
39
|
+
}
|
|
40
|
+
getScrollInfo() {
|
|
41
|
+
return {
|
|
42
|
+
linesScrolledOut: this.linesScrolledOut,
|
|
43
|
+
hasScrolledContent: this.linesScrolledOut > 0,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
isEmpty() {
|
|
47
|
+
return this.lines.length === 0;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
class HeadTailByteBuffer {
|
|
51
|
+
constructor(maxBytes) {
|
|
52
|
+
this.head = [];
|
|
53
|
+
this.tail = [];
|
|
54
|
+
this.headBytes = 0;
|
|
55
|
+
this.tailBytes = 0;
|
|
56
|
+
this.omittedBytes = 0;
|
|
57
|
+
this.maxBytes = Math.max(0, Math.floor(maxBytes));
|
|
58
|
+
this.headBudget = Math.floor(this.maxBytes / 2);
|
|
59
|
+
this.tailBudget = this.maxBytes - this.headBudget;
|
|
60
|
+
}
|
|
61
|
+
addBytes(chunk) {
|
|
62
|
+
if (this.maxBytes === 0) {
|
|
63
|
+
this.omittedBytes += chunk.length;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (this.headBytes < this.headBudget) {
|
|
67
|
+
const remainingHead = this.headBudget - this.headBytes;
|
|
68
|
+
if (chunk.length <= remainingHead) {
|
|
69
|
+
this.head.push(chunk);
|
|
70
|
+
this.headBytes += chunk.length;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const headPart = chunk.subarray(0, remainingHead);
|
|
74
|
+
const tailPart = chunk.subarray(remainingHead);
|
|
75
|
+
if (headPart.length > 0) {
|
|
76
|
+
this.head.push(headPart);
|
|
77
|
+
this.headBytes += headPart.length;
|
|
78
|
+
}
|
|
79
|
+
this.pushToTail(tailPart);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
this.pushToTail(chunk);
|
|
83
|
+
}
|
|
84
|
+
addText(text) {
|
|
85
|
+
this.addBytes(Buffer.from(text));
|
|
86
|
+
}
|
|
87
|
+
pushToTail(chunk) {
|
|
88
|
+
if (this.tailBudget === 0) {
|
|
89
|
+
this.omittedBytes += chunk.length;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (chunk.length >= this.tailBudget) {
|
|
93
|
+
this.omittedBytes += this.tailBytes;
|
|
94
|
+
this.tail.length = 0;
|
|
95
|
+
this.tailBytes = 0;
|
|
96
|
+
const kept = chunk.subarray(chunk.length - this.tailBudget);
|
|
97
|
+
const omitted = chunk.length - kept.length;
|
|
98
|
+
this.omittedBytes += omitted;
|
|
99
|
+
if (kept.length > 0) {
|
|
100
|
+
this.tail.push(kept);
|
|
101
|
+
this.tailBytes = kept.length;
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
this.tail.push(chunk);
|
|
106
|
+
this.tailBytes += chunk.length;
|
|
107
|
+
while (this.tailBytes > this.tailBudget && this.tail.length > 0) {
|
|
108
|
+
const dropped = this.tail.shift();
|
|
109
|
+
if (!dropped)
|
|
110
|
+
break;
|
|
111
|
+
this.tailBytes -= dropped.length;
|
|
112
|
+
this.omittedBytes += dropped.length;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
getOmittedBytes() {
|
|
116
|
+
return this.omittedBytes;
|
|
117
|
+
}
|
|
118
|
+
isEmpty() {
|
|
119
|
+
return this.headBytes === 0 && this.tailBytes === 0;
|
|
120
|
+
}
|
|
121
|
+
getContent() {
|
|
122
|
+
const chunks = [];
|
|
123
|
+
chunks.push(...this.head);
|
|
124
|
+
chunks.push(...this.tail);
|
|
125
|
+
return Buffer.concat(chunks).toString();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Global registry for daemon processes
|
|
129
|
+
const daemonProcesses = new Map();
|
|
130
|
+
function getOsToolMessages(language) {
|
|
131
|
+
if (language === 'zh') {
|
|
132
|
+
return {
|
|
133
|
+
daemonStarted: (pid, timeoutSeconds, _command) => `🔄 命令已作为守护进程启动(PID: ${pid})\n该进程在 ${timeoutSeconds} 秒内未完成,已在后台继续运行。\n已添加提醒以跟踪其进度。\n\n需要时可使用 stop_daemon({"pid": ${pid}}) 终止该进程。`,
|
|
134
|
+
commandCompleted: (exitCode, scrollNotice) => `✅ 命令已完成(退出码:${exitCode ?? 'unknown'})${scrollNotice}\n\n`,
|
|
135
|
+
scrolledLinesNotice: (lines) => `\n⚠️ 执行期间有 ${lines} 行已滚出可视范围`,
|
|
136
|
+
stdoutLabel: '📤 stdout:',
|
|
137
|
+
stderrLabel: '📤 stderr:',
|
|
138
|
+
failedToExecute: (msg) => `❌ 执行命令失败:${msg}`,
|
|
139
|
+
noDaemonFound: (pid) => `❌ 未找到 PID 为 ${pid} 的守护进程`,
|
|
140
|
+
daemonStopped: (pid, command) => `✅ 守护进程 ${pid}(${command})已停止`,
|
|
141
|
+
failedToStop: (pid, msg) => `❌ 停止守护进程 ${pid} 失败:${msg}`,
|
|
142
|
+
daemonOutputHeader: (pid, streamLabel) => `📤 守护进程 ${pid} ${streamLabel} 输出:\n`,
|
|
143
|
+
noOutput: '(无输出)',
|
|
144
|
+
scrolledOutNotice: (lines) => `\n\n⚠️ 有 ${lines} 行已滚出可视范围`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
daemonStarted: (pid, timeoutSeconds, _command) => `🔄 Command started as daemon process (PID: ${pid})\nThe process didn't complete within ${timeoutSeconds} seconds and is now running in the background.\nA reminder has been added to track its progress.\n\nUse stop_daemon({"pid": ${pid}}) to terminate it when needed.`,
|
|
149
|
+
commandCompleted: (exitCode, scrollNotice) => `✅ Command completed (exit code: ${exitCode ?? 'unknown'})${scrollNotice}\n\n`,
|
|
150
|
+
scrolledLinesNotice: (lines) => `\n⚠️ ${lines} lines scrolled out of view during execution`,
|
|
151
|
+
stdoutLabel: '📤 stdout:',
|
|
152
|
+
stderrLabel: '📤 stderr:',
|
|
153
|
+
failedToExecute: (msg) => `❌ Failed to execute command: ${msg}`,
|
|
154
|
+
noDaemonFound: (pid) => `❌ No daemon process found with PID ${pid}`,
|
|
155
|
+
daemonStopped: (pid, command) => `✅ Daemon process ${pid} (${command}) stopped successfully`,
|
|
156
|
+
failedToStop: (pid, msg) => `❌ Failed to stop daemon process ${pid}: ${msg}`,
|
|
157
|
+
daemonOutputHeader: (pid, streamLabel) => `📤 Daemon ${pid} ${streamLabel} output:\n`,
|
|
158
|
+
noOutput: '(no output)',
|
|
159
|
+
scrolledOutNotice: (lines) => `\n\n⚠️ ${lines} lines have scrolled out of view`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function isJsonObject(value) {
|
|
163
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
164
|
+
}
|
|
165
|
+
function isShellCmdReminderMeta(meta) {
|
|
166
|
+
return isJsonObject(meta) && typeof meta.pid === 'number';
|
|
167
|
+
}
|
|
168
|
+
function parseShellCmdArgs(args) {
|
|
169
|
+
const command = args.command;
|
|
170
|
+
if (typeof command !== 'string' || command.trim() === '') {
|
|
171
|
+
throw new Error('shell_cmd.command must be a string');
|
|
172
|
+
}
|
|
173
|
+
const shell = args.shell;
|
|
174
|
+
if (shell !== undefined && typeof shell !== 'string') {
|
|
175
|
+
throw new Error('shell_cmd.shell must be a string if provided');
|
|
176
|
+
}
|
|
177
|
+
const bufferSize = args.bufferSize;
|
|
178
|
+
if (bufferSize !== undefined && typeof bufferSize !== 'number') {
|
|
179
|
+
throw new Error('shell_cmd.bufferSize must be a number if provided');
|
|
180
|
+
}
|
|
181
|
+
const timeoutSeconds = args.timeoutSeconds;
|
|
182
|
+
if (timeoutSeconds !== undefined && typeof timeoutSeconds !== 'number') {
|
|
183
|
+
throw new Error('shell_cmd.timeoutSeconds must be a number if provided');
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
command,
|
|
187
|
+
shell: typeof shell === 'string' && shell.trim() !== '' ? shell : undefined,
|
|
188
|
+
bufferSize: bufferSize === 0
|
|
189
|
+
? undefined
|
|
190
|
+
: bufferSize === undefined
|
|
191
|
+
? undefined
|
|
192
|
+
: Number.isInteger(bufferSize) && bufferSize > 0
|
|
193
|
+
? bufferSize
|
|
194
|
+
: (() => {
|
|
195
|
+
throw new Error('shell_cmd.bufferSize must be a positive integer (or 0 for default)');
|
|
196
|
+
})(),
|
|
197
|
+
timeoutSeconds: timeoutSeconds === 0
|
|
198
|
+
? undefined
|
|
199
|
+
: timeoutSeconds === undefined
|
|
200
|
+
? undefined
|
|
201
|
+
: Number.isInteger(timeoutSeconds) && timeoutSeconds > 0
|
|
202
|
+
? timeoutSeconds
|
|
203
|
+
: (() => {
|
|
204
|
+
throw new Error('shell_cmd.timeoutSeconds must be a positive integer (or 0 for default)');
|
|
205
|
+
})(),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function parseReadonlyShellArgs(args) {
|
|
209
|
+
const command = args.command;
|
|
210
|
+
if (typeof command !== 'string' || command.trim() === '') {
|
|
211
|
+
throw new Error('readonly_shell.command must be a string');
|
|
212
|
+
}
|
|
213
|
+
const timeoutAlias = args.timeout;
|
|
214
|
+
if (timeoutAlias !== undefined && typeof timeoutAlias !== 'number') {
|
|
215
|
+
throw new Error('readonly_shell.timeout must be a number if provided');
|
|
216
|
+
}
|
|
217
|
+
const timeoutMs = args.timeout_ms;
|
|
218
|
+
if (timeoutMs !== undefined && typeof timeoutMs !== 'number') {
|
|
219
|
+
throw new Error('readonly_shell.timeout_ms must be a number if provided');
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
command,
|
|
223
|
+
timeoutMs: timeoutMs === undefined
|
|
224
|
+
? timeoutAlias === undefined
|
|
225
|
+
? undefined
|
|
226
|
+
: timeoutAlias === 0
|
|
227
|
+
? undefined
|
|
228
|
+
: Number.isInteger(timeoutAlias) && timeoutAlias > 0
|
|
229
|
+
? timeoutAlias
|
|
230
|
+
: (() => {
|
|
231
|
+
throw new Error('readonly_shell.timeout must be a positive integer (or 0 for default)');
|
|
232
|
+
})()
|
|
233
|
+
: timeoutMs === 0
|
|
234
|
+
? undefined
|
|
235
|
+
: Number.isInteger(timeoutMs) && timeoutMs > 0
|
|
236
|
+
? timeoutMs
|
|
237
|
+
: (() => {
|
|
238
|
+
throw new Error('readonly_shell.timeout_ms must be a positive integer (or 0 for default)');
|
|
239
|
+
})(),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function parseStopDaemonArgs(args) {
|
|
243
|
+
const pid = args.pid;
|
|
244
|
+
if (typeof pid !== 'number') {
|
|
245
|
+
throw new Error('stop_daemon.pid must be a number');
|
|
246
|
+
}
|
|
247
|
+
return { pid };
|
|
248
|
+
}
|
|
249
|
+
function parseGetDaemonOutputArgs(args) {
|
|
250
|
+
const pid = args.pid;
|
|
251
|
+
if (typeof pid !== 'number') {
|
|
252
|
+
throw new Error('get_daemon_output.pid must be a number');
|
|
253
|
+
}
|
|
254
|
+
const stream = args.stream;
|
|
255
|
+
if (stream !== undefined && stream !== '' && stream !== 'stdout' && stream !== 'stderr') {
|
|
256
|
+
throw new Error('get_daemon_output.stream must be "stdout" or "stderr" if provided');
|
|
257
|
+
}
|
|
258
|
+
return { pid, stream: stream === '' ? undefined : stream };
|
|
259
|
+
}
|
|
260
|
+
// JSON Schema for shell_cmd parameters
|
|
261
|
+
const shellCmdSchema = {
|
|
262
|
+
type: 'object',
|
|
263
|
+
properties: {
|
|
264
|
+
command: {
|
|
265
|
+
type: 'string',
|
|
266
|
+
description: 'The shell command to execute',
|
|
267
|
+
},
|
|
268
|
+
shell: {
|
|
269
|
+
type: 'string',
|
|
270
|
+
description: 'Shell to use for execution (default: bash)',
|
|
271
|
+
},
|
|
272
|
+
bufferSize: {
|
|
273
|
+
type: 'number',
|
|
274
|
+
description: 'Maximum number of lines to keep in scrolling buffer (default: 500)',
|
|
275
|
+
},
|
|
276
|
+
timeoutSeconds: {
|
|
277
|
+
type: 'number',
|
|
278
|
+
description: 'Timeout in seconds to wait for process completion (default: 5)',
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
required: ['command'],
|
|
282
|
+
additionalProperties: false,
|
|
283
|
+
};
|
|
284
|
+
const readonlyShellSchema = {
|
|
285
|
+
type: 'object',
|
|
286
|
+
properties: {
|
|
287
|
+
command: {
|
|
288
|
+
type: 'string',
|
|
289
|
+
description: 'Read-only shell command (allowed prefixes: cat, rg, sed, ls, nl, wc, git show)',
|
|
290
|
+
},
|
|
291
|
+
timeout_ms: {
|
|
292
|
+
type: 'number',
|
|
293
|
+
description: 'Maximum time in milliseconds the command is allowed to run (default: 10000)',
|
|
294
|
+
},
|
|
295
|
+
timeout: {
|
|
296
|
+
type: 'number',
|
|
297
|
+
description: 'Alias for timeout_ms',
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
required: ['command'],
|
|
301
|
+
additionalProperties: false,
|
|
302
|
+
};
|
|
303
|
+
// JSON Schema for stop_daemon parameters
|
|
304
|
+
const stopDaemonSchema = {
|
|
305
|
+
type: 'object',
|
|
306
|
+
properties: {
|
|
307
|
+
pid: {
|
|
308
|
+
type: 'number',
|
|
309
|
+
description: 'Process ID of the daemon to stop',
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
required: ['pid'],
|
|
313
|
+
additionalProperties: false,
|
|
314
|
+
};
|
|
315
|
+
// JSON Schema for get_daemon_output parameters
|
|
316
|
+
const getDaemonOutputSchema = {
|
|
317
|
+
type: 'object',
|
|
318
|
+
properties: {
|
|
319
|
+
pid: {
|
|
320
|
+
type: 'number',
|
|
321
|
+
description: 'Process ID of the daemon to query',
|
|
322
|
+
},
|
|
323
|
+
stream: {
|
|
324
|
+
type: 'string',
|
|
325
|
+
description: 'Output stream to retrieve - "stdout" or "stderr" (default: stdout)',
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
required: ['pid'],
|
|
329
|
+
additionalProperties: false,
|
|
330
|
+
};
|
|
331
|
+
// Format daemon status for reminder display
|
|
332
|
+
function formatDaemonStatus(daemon) {
|
|
333
|
+
const uptime = Math.floor((Date.now() - daemon.startTime.getTime()) / 1000);
|
|
334
|
+
const status = daemon.isRunning
|
|
335
|
+
? 'running'
|
|
336
|
+
: `exited (code: ${daemon.exitCode}, signal: ${daemon.exitSignal})`;
|
|
337
|
+
const stdoutInfo = daemon.stdoutBuffer.getScrollInfo();
|
|
338
|
+
const stderrInfo = daemon.stderrBuffer.getScrollInfo();
|
|
339
|
+
let scrollNotice = '';
|
|
340
|
+
if (stdoutInfo.hasScrolledContent || stderrInfo.hasScrolledContent) {
|
|
341
|
+
const scrolledLines = stdoutInfo.linesScrolledOut + stderrInfo.linesScrolledOut;
|
|
342
|
+
scrollNotice = `\n⚠️ ${scrolledLines} lines have scrolled out of view`;
|
|
343
|
+
}
|
|
344
|
+
const stdoutContent = daemon.stdoutBuffer.isEmpty()
|
|
345
|
+
? '(no output)'
|
|
346
|
+
: daemon.stdoutBuffer.getContent();
|
|
347
|
+
const stderrContent = daemon.stderrBuffer.isEmpty()
|
|
348
|
+
? '(no errors)'
|
|
349
|
+
: daemon.stderrBuffer.getContent();
|
|
350
|
+
const fenceConsole = '```console';
|
|
351
|
+
const fenceEnd = '```';
|
|
352
|
+
return `🔄 Daemon Process ${daemon.pid}
|
|
353
|
+
Command: ${daemon.command}
|
|
354
|
+
Shell: ${daemon.shell}
|
|
355
|
+
Status: ${status}
|
|
356
|
+
Uptime: ${uptime}s
|
|
357
|
+
Started: ${(0, time_1.formatUnifiedTimestamp)(daemon.startTime)}${scrollNotice}
|
|
358
|
+
|
|
359
|
+
📤 Latest stdout:
|
|
360
|
+
${fenceConsole}
|
|
361
|
+
${stdoutContent}
|
|
362
|
+
${fenceEnd}
|
|
363
|
+
|
|
364
|
+
📤 Latest stderr:
|
|
365
|
+
${fenceConsole}
|
|
366
|
+
${stderrContent}
|
|
367
|
+
${fenceEnd}
|
|
368
|
+
|
|
369
|
+
💡 Use stop_daemon({"pid": ${daemon.pid}}) to terminate this process`;
|
|
370
|
+
}
|
|
371
|
+
// ReminderOwner implementation for shell command tool
|
|
372
|
+
exports.shellCmdReminderOwner = {
|
|
373
|
+
name: 'shellCmd',
|
|
374
|
+
async updateReminder(dlg, reminder) {
|
|
375
|
+
if (reminder.owner !== exports.shellCmdReminderOwner || !isShellCmdReminderMeta(reminder.meta)) {
|
|
376
|
+
return { treatment: 'keep' };
|
|
377
|
+
}
|
|
378
|
+
const pid = reminder.meta.pid;
|
|
379
|
+
const daemon = daemonProcesses.get(pid);
|
|
380
|
+
if (!daemon) {
|
|
381
|
+
// Daemon process no longer exists
|
|
382
|
+
return { treatment: 'drop' };
|
|
383
|
+
}
|
|
384
|
+
// Check if process has exited
|
|
385
|
+
if (!daemon.isRunning) {
|
|
386
|
+
// Process has exited, provide final status and drop reminder
|
|
387
|
+
const finalStatus = formatDaemonStatus(daemon);
|
|
388
|
+
daemonProcesses.delete(pid);
|
|
389
|
+
return {
|
|
390
|
+
treatment: 'update',
|
|
391
|
+
updatedContent: `🏁 Process ${pid} has completed:\n\n${finalStatus}`,
|
|
392
|
+
updatedMeta: {
|
|
393
|
+
...reminder.meta,
|
|
394
|
+
completed: true,
|
|
395
|
+
lastUpdated: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
// Update daemon status
|
|
400
|
+
daemon.lastUpdateTime = new Date();
|
|
401
|
+
// Check if process is still actually running
|
|
402
|
+
try {
|
|
403
|
+
process.kill(pid, 0); // Signal 0 checks if process exists
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
// Process no longer exists
|
|
407
|
+
daemon.isRunning = false;
|
|
408
|
+
daemon.exitCode = -1;
|
|
409
|
+
daemon.exitSignal = 'UNKNOWN';
|
|
410
|
+
}
|
|
411
|
+
// Update the reminder with current daemon status
|
|
412
|
+
const updatedContent = formatDaemonStatus(daemon);
|
|
413
|
+
return {
|
|
414
|
+
treatment: 'update',
|
|
415
|
+
updatedContent,
|
|
416
|
+
updatedMeta: {
|
|
417
|
+
...reminder.meta,
|
|
418
|
+
lastUpdated: (0, time_1.formatUnifiedTimestamp)(daemon.lastUpdateTime),
|
|
419
|
+
},
|
|
420
|
+
};
|
|
421
|
+
},
|
|
422
|
+
async renderReminder(dlg, reminder, index) {
|
|
423
|
+
if (reminder.owner !== exports.shellCmdReminderOwner || !isShellCmdReminderMeta(reminder.meta)) {
|
|
424
|
+
// Fallback to default rendering if this reminder doesn't belong to this tool
|
|
425
|
+
return {
|
|
426
|
+
type: 'transient_guide_msg',
|
|
427
|
+
role: 'assistant',
|
|
428
|
+
content: `🔔 **System-managed reminder item #${index + 1}** - Process Management
|
|
429
|
+
This reminder is system-managed and should update/drop automatically based on the underlying process lifecycle.
|
|
430
|
+
---
|
|
431
|
+
${reminder.content}`,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
const pid = reminder.meta.pid;
|
|
435
|
+
const daemon = daemonProcesses.get(pid);
|
|
436
|
+
if (!daemon) {
|
|
437
|
+
// Daemon no longer exists, render as completed
|
|
438
|
+
return {
|
|
439
|
+
type: 'transient_guide_msg',
|
|
440
|
+
role: 'assistant',
|
|
441
|
+
content: `⚰️ **Process Lifecycle Alert #${index + 1}** - Daemon Terminated (PID ${pid})
|
|
442
|
+
This daemon process has completed its lifecycle and is no longer running. This reminder should auto-drop (or can be ignored).`,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
// Render with current daemon status - fully dynamic
|
|
446
|
+
const uptime = Math.floor((Date.now() - daemon.startTime.getTime()) / 1000);
|
|
447
|
+
const uptimeStr = uptime < 60
|
|
448
|
+
? `${uptime}s`
|
|
449
|
+
: uptime < 3600
|
|
450
|
+
? `${Math.floor(uptime / 60)}m ${uptime % 60}s`
|
|
451
|
+
: `${Math.floor(uptime / 3600)}h ${Math.floor((uptime % 3600) / 60)}m`;
|
|
452
|
+
const statusInfo = formatDaemonStatus(daemon);
|
|
453
|
+
return {
|
|
454
|
+
type: 'transient_guide_msg',
|
|
455
|
+
role: 'assistant',
|
|
456
|
+
content: `🔄 **Active Daemon Monitor #${index + 1}** - PID ${pid} (Uptime: ${uptimeStr})
|
|
457
|
+
This daemon process is actively running and requires periodic assessment. I should check its health, resource usage, and operational status. This reminder is system-managed and will update/drop automatically.
|
|
458
|
+
|
|
459
|
+
**Current Status:**
|
|
460
|
+
${statusInfo}`,
|
|
461
|
+
};
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
// Shell command tool implementation
|
|
465
|
+
exports.shellCmdTool = {
|
|
466
|
+
type: 'func',
|
|
467
|
+
name: 'shell_cmd',
|
|
468
|
+
description: 'Execute shell commands with optional timeout. If timeoutSeconds > 0 and command runs longer, it becomes a tracked daemon process. Daemons persist across messages and require explicit stop_daemon or get_daemon_output calls.',
|
|
469
|
+
descriptionI18n: {
|
|
470
|
+
en: 'Execute shell commands with optional timeout. If timeoutSeconds > 0 and command runs longer, it becomes a tracked daemon process. Daemons persist across messages and require explicit stop_daemon or get_daemon_output calls.',
|
|
471
|
+
zh: '执行 shell 命令(支持超时)。如果 timeoutSeconds > 0 且命令运行时间超过超时,将转为可追踪的后台守护进程。守护进程会跨消息持续存在,需要显式调用 stop_daemon 或 get_daemon_output 来管理与查看输出。',
|
|
472
|
+
},
|
|
473
|
+
parameters: shellCmdSchema,
|
|
474
|
+
async call(dlg, caller, args) {
|
|
475
|
+
const language = (0, runtime_language_1.getWorkLanguage)();
|
|
476
|
+
const t = getOsToolMessages(language);
|
|
477
|
+
const parsedArgs = parseShellCmdArgs(args);
|
|
478
|
+
const { command, shell = 'bash', bufferSize = 500, timeoutSeconds = 5 } = parsedArgs;
|
|
479
|
+
const stdoutBuffer = new ScrollingBuffer(bufferSize);
|
|
480
|
+
const stderrBuffer = new ScrollingBuffer(bufferSize);
|
|
481
|
+
return new Promise((resolve) => {
|
|
482
|
+
const childProcess = (0, child_process_1.spawn)(shell, ['-c', command], {
|
|
483
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
484
|
+
});
|
|
485
|
+
const pid = childProcess.pid;
|
|
486
|
+
const startTime = new Date();
|
|
487
|
+
// Set up data handlers
|
|
488
|
+
childProcess.stdout?.on('data', (data) => {
|
|
489
|
+
stdoutBuffer.addText(data.toString());
|
|
490
|
+
});
|
|
491
|
+
childProcess.stderr?.on('data', (data) => {
|
|
492
|
+
stderrBuffer.addText(data.toString());
|
|
493
|
+
});
|
|
494
|
+
// Set up timeout
|
|
495
|
+
const timeoutHandle = setTimeout(() => {
|
|
496
|
+
// Process didn't exit within timeout - treat as daemon
|
|
497
|
+
const daemon = {
|
|
498
|
+
pid,
|
|
499
|
+
command,
|
|
500
|
+
shell,
|
|
501
|
+
process: childProcess,
|
|
502
|
+
startTime,
|
|
503
|
+
stdoutBuffer,
|
|
504
|
+
stderrBuffer,
|
|
505
|
+
isRunning: true,
|
|
506
|
+
lastUpdateTime: new Date(),
|
|
507
|
+
};
|
|
508
|
+
daemonProcesses.set(pid, daemon);
|
|
509
|
+
// Add reminder for daemon process
|
|
510
|
+
const reminderContent = `[Daemon PID ${pid} - This content should not be visible, check dynamic rendering]`;
|
|
511
|
+
dlg.addReminder(reminderContent, exports.shellCmdReminderOwner, {
|
|
512
|
+
type: 'daemon',
|
|
513
|
+
pid,
|
|
514
|
+
command,
|
|
515
|
+
shell,
|
|
516
|
+
startTime: (0, time_1.formatUnifiedTimestamp)(startTime),
|
|
517
|
+
});
|
|
518
|
+
resolve(t.daemonStarted(pid, timeoutSeconds, command));
|
|
519
|
+
}, timeoutSeconds * 1000);
|
|
520
|
+
// Handle process completion
|
|
521
|
+
childProcess.on('close', (code, signal) => {
|
|
522
|
+
clearTimeout(timeoutHandle);
|
|
523
|
+
// Process completed within timeout - return full output
|
|
524
|
+
const stdoutInfo = stdoutBuffer.getScrollInfo();
|
|
525
|
+
const stderrInfo = stderrBuffer.getScrollInfo();
|
|
526
|
+
let scrollNotice = '';
|
|
527
|
+
if (stdoutInfo.hasScrolledContent || stderrInfo.hasScrolledContent) {
|
|
528
|
+
const scrolledLines = stdoutInfo.linesScrolledOut + stderrInfo.linesScrolledOut;
|
|
529
|
+
scrollNotice = t.scrolledLinesNotice(scrolledLines);
|
|
530
|
+
}
|
|
531
|
+
const stdoutContent = stdoutBuffer.getContent();
|
|
532
|
+
const stderrContent = stderrBuffer.getContent();
|
|
533
|
+
const fenceConsole = '```console';
|
|
534
|
+
const fenceEnd = '```';
|
|
535
|
+
let result = t.commandCompleted(code, scrollNotice);
|
|
536
|
+
if (stdoutContent) {
|
|
537
|
+
result += `${t.stdoutLabel}\n${fenceConsole}\n${stdoutContent}\n${fenceEnd}\n\n`;
|
|
538
|
+
}
|
|
539
|
+
if (stderrContent) {
|
|
540
|
+
result += `${t.stderrLabel}\n${fenceConsole}\n${stderrContent}\n${fenceEnd}`;
|
|
541
|
+
}
|
|
542
|
+
resolve(result.trim());
|
|
543
|
+
});
|
|
544
|
+
childProcess.on('error', (error) => {
|
|
545
|
+
clearTimeout(timeoutHandle);
|
|
546
|
+
resolve(t.failedToExecute(error.message));
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
},
|
|
550
|
+
};
|
|
551
|
+
const readonlyShellAllowedPrefixes = ['cat', 'rg', 'sed', 'ls', 'nl', 'wc', 'git show'];
|
|
552
|
+
function isAllowedReadonlyShellCommand(command) {
|
|
553
|
+
const trimmed = command.trimStart();
|
|
554
|
+
for (const prefix of readonlyShellAllowedPrefixes) {
|
|
555
|
+
if (trimmed === prefix || trimmed.startsWith(`${prefix} `)) {
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
exports.readonlyShellTool = {
|
|
562
|
+
type: 'func',
|
|
563
|
+
name: 'readonly_shell',
|
|
564
|
+
description: 'Execute a read-only shell command from a small allowlist (cat, rg, sed, ls, nl, wc, git show). Commands outside the allowlist are rejected.',
|
|
565
|
+
descriptionI18n: {
|
|
566
|
+
en: 'Execute a read-only shell command from a small allowlist (cat, rg, sed, ls, nl, wc, git show). You are explicitly authorized to call this tool yourself (no delegation). Commands outside the allowlist are rejected.',
|
|
567
|
+
zh: '执行只读 shell 命令(仅允许:cat、rg、sed、ls、nl、wc、git show)。你已被明确授权自行调用该工具(无需委派)。不在允许列表内的命令会被拒绝。',
|
|
568
|
+
},
|
|
569
|
+
parameters: readonlyShellSchema,
|
|
570
|
+
async call(dlg, caller, args) {
|
|
571
|
+
const language = (0, runtime_language_1.getWorkLanguage)();
|
|
572
|
+
const t = getOsToolMessages(language);
|
|
573
|
+
const parsedArgs = parseReadonlyShellArgs(args);
|
|
574
|
+
const { command, timeoutMs = 10000 } = parsedArgs;
|
|
575
|
+
if (!isAllowedReadonlyShellCommand(command)) {
|
|
576
|
+
const allowedList = readonlyShellAllowedPrefixes.join(', ');
|
|
577
|
+
return language === 'zh'
|
|
578
|
+
? `❌ readonly_shell 仅允许以下命令前缀:${allowedList}\n收到:${command}`
|
|
579
|
+
: `❌ readonly_shell only allows these command prefixes: ${allowedList}\nGot: ${command}`;
|
|
580
|
+
}
|
|
581
|
+
const stdoutBuffer = new HeadTailByteBuffer(1024 * 1024);
|
|
582
|
+
const stderrBuffer = new HeadTailByteBuffer(1024 * 1024);
|
|
583
|
+
return new Promise((resolve) => {
|
|
584
|
+
let settled = false;
|
|
585
|
+
const finish = (content) => {
|
|
586
|
+
if (settled)
|
|
587
|
+
return;
|
|
588
|
+
settled = true;
|
|
589
|
+
resolve(content.trim());
|
|
590
|
+
};
|
|
591
|
+
const childProcess = (0, child_process_1.spawn)('bash', ['-c', command], {
|
|
592
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
593
|
+
});
|
|
594
|
+
childProcess.stdout?.on('data', (data) => {
|
|
595
|
+
stdoutBuffer.addBytes(data);
|
|
596
|
+
});
|
|
597
|
+
childProcess.stderr?.on('data', (data) => {
|
|
598
|
+
stderrBuffer.addBytes(data);
|
|
599
|
+
});
|
|
600
|
+
const timeoutHandle = setTimeout(() => {
|
|
601
|
+
try {
|
|
602
|
+
childProcess.kill('SIGTERM');
|
|
603
|
+
}
|
|
604
|
+
catch {
|
|
605
|
+
// ignore
|
|
606
|
+
}
|
|
607
|
+
const omittedBytes = stdoutBuffer.getOmittedBytes() + stderrBuffer.getOmittedBytes();
|
|
608
|
+
const fenceConsole = '```console';
|
|
609
|
+
const fenceEnd = '```';
|
|
610
|
+
const timeoutMsg = language === 'zh'
|
|
611
|
+
? `⏱️ 命令超时(${timeoutMs}ms),已发送 SIGTERM。\n`
|
|
612
|
+
: `⏱️ Command timed out (${timeoutMs}ms); SIGTERM sent.\n`;
|
|
613
|
+
const truncationNotice = omittedBytes > 0
|
|
614
|
+
? language === 'zh'
|
|
615
|
+
? `⚠️ 输出已截断,约省略 ${omittedBytes} 字节\n`
|
|
616
|
+
: `⚠️ Output truncated; ~${omittedBytes} bytes omitted\n`
|
|
617
|
+
: '';
|
|
618
|
+
let result = `${timeoutMsg}${truncationNotice}`.trimEnd();
|
|
619
|
+
const stdoutContent = stdoutBuffer.getContent();
|
|
620
|
+
const stderrContent = stderrBuffer.getContent();
|
|
621
|
+
if (stdoutContent) {
|
|
622
|
+
result += `\n\n${t.stdoutLabel}\n${fenceConsole}\n${stdoutContent}\n${fenceEnd}`;
|
|
623
|
+
}
|
|
624
|
+
if (stderrContent) {
|
|
625
|
+
result += `\n\n${t.stderrLabel}\n${fenceConsole}\n${stderrContent}\n${fenceEnd}`;
|
|
626
|
+
}
|
|
627
|
+
finish(result);
|
|
628
|
+
}, timeoutMs);
|
|
629
|
+
childProcess.on('close', (code) => {
|
|
630
|
+
clearTimeout(timeoutHandle);
|
|
631
|
+
const omittedBytes = stdoutBuffer.getOmittedBytes() + stderrBuffer.getOmittedBytes();
|
|
632
|
+
const truncationNotice = omittedBytes > 0
|
|
633
|
+
? language === 'zh'
|
|
634
|
+
? `\n⚠️ 输出已截断,约省略 ${omittedBytes} 字节`
|
|
635
|
+
: `\n⚠️ Output truncated; ~${omittedBytes} bytes omitted`
|
|
636
|
+
: '';
|
|
637
|
+
const stdoutContent = stdoutBuffer.getContent();
|
|
638
|
+
const stderrContent = stderrBuffer.getContent();
|
|
639
|
+
const fenceConsole = '```console';
|
|
640
|
+
const fenceEnd = '```';
|
|
641
|
+
let result = t.commandCompleted(code, truncationNotice);
|
|
642
|
+
if (stdoutContent) {
|
|
643
|
+
result += `${t.stdoutLabel}\n${fenceConsole}\n${stdoutContent}\n${fenceEnd}\n\n`;
|
|
644
|
+
}
|
|
645
|
+
if (stderrContent) {
|
|
646
|
+
result += `${t.stderrLabel}\n${fenceConsole}\n${stderrContent}\n${fenceEnd}`;
|
|
647
|
+
}
|
|
648
|
+
finish(result);
|
|
649
|
+
});
|
|
650
|
+
childProcess.on('error', (error) => {
|
|
651
|
+
clearTimeout(timeoutHandle);
|
|
652
|
+
finish(t.failedToExecute(error.message));
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
},
|
|
656
|
+
};
|
|
657
|
+
// Stop daemon tool implementation
|
|
658
|
+
exports.stopDaemonTool = {
|
|
659
|
+
type: 'func',
|
|
660
|
+
name: 'stop_daemon',
|
|
661
|
+
description: 'Terminate a running daemon process by PID. Use this after checking daemon output with get_daemon_output when monitoring is complete. Removes the daemon from tracking.',
|
|
662
|
+
descriptionI18n: {
|
|
663
|
+
en: 'Terminate a running daemon process by PID. Use this after checking daemon output with get_daemon_output when monitoring is complete. Removes the daemon from tracking.',
|
|
664
|
+
zh: '根据 PID 终止正在运行的守护进程。通常在使用 get_daemon_output 检查输出并确认无需继续监控后调用。该操作会停止进程并移除追踪。',
|
|
665
|
+
},
|
|
666
|
+
parameters: stopDaemonSchema,
|
|
667
|
+
async call(dlg, caller, args) {
|
|
668
|
+
const language = (0, runtime_language_1.getWorkLanguage)();
|
|
669
|
+
const t = getOsToolMessages(language);
|
|
670
|
+
const { pid } = parseStopDaemonArgs(args);
|
|
671
|
+
const daemon = daemonProcesses.get(pid);
|
|
672
|
+
if (!daemon) {
|
|
673
|
+
return t.noDaemonFound(pid);
|
|
674
|
+
}
|
|
675
|
+
try {
|
|
676
|
+
// Kill the process
|
|
677
|
+
process.kill(pid, 'SIGTERM');
|
|
678
|
+
// Wait a bit for graceful shutdown
|
|
679
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
680
|
+
// Force kill if still running
|
|
681
|
+
try {
|
|
682
|
+
process.kill(pid, 'SIGKILL');
|
|
683
|
+
}
|
|
684
|
+
catch (e) {
|
|
685
|
+
// Process already terminated
|
|
686
|
+
}
|
|
687
|
+
// Update daemon status
|
|
688
|
+
daemon.isRunning = false;
|
|
689
|
+
daemon.exitCode = -1;
|
|
690
|
+
daemon.exitSignal = 'SIGTERM';
|
|
691
|
+
// Remove associated reminders
|
|
692
|
+
const indicesToRemove = [];
|
|
693
|
+
for (let i = 0; i < dlg.reminders.length; i++) {
|
|
694
|
+
const reminder = dlg.reminders[i];
|
|
695
|
+
if (reminder.owner === exports.shellCmdReminderOwner &&
|
|
696
|
+
isShellCmdReminderMeta(reminder.meta) &&
|
|
697
|
+
reminder.meta.pid === pid) {
|
|
698
|
+
indicesToRemove.push(i);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
// Remove reminders in reverse order to maintain indices
|
|
702
|
+
for (let i = indicesToRemove.length - 1; i >= 0; i--) {
|
|
703
|
+
dlg.deleteReminder(indicesToRemove[i]);
|
|
704
|
+
}
|
|
705
|
+
// Clean up daemon tracking
|
|
706
|
+
daemonProcesses.delete(pid);
|
|
707
|
+
return t.daemonStopped(pid, daemon.command);
|
|
708
|
+
}
|
|
709
|
+
catch (error) {
|
|
710
|
+
daemonProcesses.delete(pid); // Clean up tracking even if kill failed
|
|
711
|
+
return t.failedToStop(pid, error instanceof Error ? error.message : String(error));
|
|
712
|
+
}
|
|
713
|
+
},
|
|
714
|
+
};
|
|
715
|
+
// Get daemon output tool implementation
|
|
716
|
+
exports.getDaemonOutputTool = {
|
|
717
|
+
type: 'func',
|
|
718
|
+
name: 'get_daemon_output',
|
|
719
|
+
description: 'Retrieve captured stdout/stderr output from a tracked daemon process by PID. Call this to check what a daemon has logged since it started. Returns (no output) if nothing has been written yet.',
|
|
720
|
+
descriptionI18n: {
|
|
721
|
+
en: 'Retrieve captured stdout/stderr output from a tracked daemon process by PID. Call this to check what a daemon has logged since it started. Returns (no output) if nothing has been written yet.',
|
|
722
|
+
zh: '根据 PID 获取已追踪守护进程的 stdout/stderr 输出。用于查看守护进程自启动以来产生的日志;如果尚无输出则返回 (no output)。',
|
|
723
|
+
},
|
|
724
|
+
parameters: getDaemonOutputSchema,
|
|
725
|
+
async call(dlg, caller, args) {
|
|
726
|
+
const language = (0, runtime_language_1.getWorkLanguage)();
|
|
727
|
+
const t = getOsToolMessages(language);
|
|
728
|
+
const { pid, stream = 'stdout' } = parseGetDaemonOutputArgs(args);
|
|
729
|
+
const daemon = daemonProcesses.get(pid);
|
|
730
|
+
if (!daemon) {
|
|
731
|
+
return t.noDaemonFound(pid);
|
|
732
|
+
}
|
|
733
|
+
const buffer = stream === 'stdout' ? daemon.stdoutBuffer : daemon.stderrBuffer;
|
|
734
|
+
const scrollInfo = buffer.getScrollInfo();
|
|
735
|
+
const content = buffer.getContent();
|
|
736
|
+
const streamLabel = stream === 'stdout' ? 'stdout' : 'stderr';
|
|
737
|
+
const fenceConsole = '```console';
|
|
738
|
+
const fenceEnd = '```';
|
|
739
|
+
let result = t.daemonOutputHeader(pid, streamLabel);
|
|
740
|
+
if (content) {
|
|
741
|
+
result += `${fenceConsole}\n${content}\n${fenceEnd}`;
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
result += t.noOutput;
|
|
745
|
+
}
|
|
746
|
+
if (scrollInfo.hasScrolledContent) {
|
|
747
|
+
result += t.scrolledOutNotice(scrollInfo.linesScrolledOut);
|
|
748
|
+
}
|
|
749
|
+
return result;
|
|
750
|
+
},
|
|
751
|
+
};
|