mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-19 15:33:21 +08:00
feat(skills): merge unavailable tools into footnote, bind cache to token, prune stale caches, warn on remote plain HTTP
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
056f2d4ea3
commit
05bfd97c6e
19 changed files with 1100 additions and 496 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
"format": 1,
|
"format": 1,
|
||||||
"builds": {
|
"builds": {
|
||||||
"lib/agent-tools-biz-monitor.js": {
|
"lib/agent-tools-biz-monitor.js": {
|
||||||
"output": "11fd312eeb902daab190770bf9465fcbd18ff97e48ca2d4e86144cffa6691790",
|
"output": "b9ab81312e2443d43b283116ac7f981666f5673d9d550141f264a99702ea7398",
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"package.json": "5132d4dd66628c59e1b495b5a75fc6c29a30c220a6c112059b4c3b5983d3cbb9",
|
"package.json": "5132d4dd66628c59e1b495b5a75fc6c29a30c220a6c112059b4c3b5983d3cbb9",
|
||||||
"pnpm-lock.yaml": "f1209ac70e9c2294e0f300c0aad88b64aeecd6aa19dfc929c5766c7d033615a3",
|
"pnpm-lock.yaml": "f1209ac70e9c2294e0f300c0aad88b64aeecd6aa19dfc929c5766c7d033615a3",
|
||||||
|
|
@ -11,9 +11,9 @@
|
||||||
"src/agent-tools-biz-monitor.ts": "201bd447198d5ee7cb1aa25031a3f6054fe12a8a221edd4915752696cca1537d",
|
"src/agent-tools-biz-monitor.ts": "201bd447198d5ee7cb1aa25031a3f6054fe12a8a221edd4915752696cca1537d",
|
||||||
"src/netx/capability-groups.ts": "3b7ac48ec3bbef91f324c24156af783d741dc5aeeb4db95d52eeafadbcbbed51",
|
"src/netx/capability-groups.ts": "3b7ac48ec3bbef91f324c24156af783d741dc5aeeb4db95d52eeafadbcbbed51",
|
||||||
"src/netx/config-values.ts": "7e8ca894d0b09f5e52686d6b293e49b46dd2cc13d714b0e0b103c3d07433e117",
|
"src/netx/config-values.ts": "7e8ca894d0b09f5e52686d6b293e49b46dd2cc13d714b0e0b103c3d07433e117",
|
||||||
"src/netx/group-skills.ts": "84a35fe639b3670fd4e95e3e5801a78020551655fd66007a9f151e2f8619e9ac",
|
"src/netx/group-skills.ts": "3fa78686f4c035f056f3cb5ed2f0411ad52c043958b859ebfcc254a625554e3a",
|
||||||
"src/netx/group-tools-plugin.ts": "647e5258b46648705585cd5d3765556f391810434c6c7f04f3df9e2f44d8ba67",
|
"src/netx/group-tools-plugin.ts": "647e5258b46648705585cd5d3765556f391810434c6c7f04f3df9e2f44d8ba67",
|
||||||
"src/netx/http.ts": "aa0552d6087593924f448fc8efaadda92f8803147190741f5abc3030cf32c439",
|
"src/netx/http.ts": "982ca3e5da8fe36debd5a0c561578c8f2c5e2a9cc0527d7dbacdf4e5a4aa9edd",
|
||||||
"src/netx/json-safe.ts": "5fe610d5ec96d31096733f7e828b4b2266c19692535a232a644bb49784ee24d9",
|
"src/netx/json-safe.ts": "5fe610d5ec96d31096733f7e828b4b2266c19692535a232a644bb49784ee24d9",
|
||||||
"src/netx/kb-context-skill.ts": "e0375006ba1e505fa6e5fd50f1748d7b43bb8c34809d10a0549e934006eb3045",
|
"src/netx/kb-context-skill.ts": "e0375006ba1e505fa6e5fd50f1748d7b43bb8c34809d10a0549e934006eb3045",
|
||||||
"src/netx/kb-local-ops.ts": "2c28ff25c8abcb68e2b197767a3eea0e056bcc3aa4ccdd6561d069a7a0da2463",
|
"src/netx/kb-local-ops.ts": "2c28ff25c8abcb68e2b197767a3eea0e056bcc3aa4ccdd6561d069a7a0da2463",
|
||||||
|
|
@ -24,18 +24,18 @@
|
||||||
"src/netx/kb-read-ops.ts": "1359b370293b9f4afa51774ed729525d77756f1552cd46abf4d4d6c595eaeb86",
|
"src/netx/kb-read-ops.ts": "1359b370293b9f4afa51774ed729525d77756f1552cd46abf4d4d6c595eaeb86",
|
||||||
"src/netx/kb-runtime.ts": "ebf2fd36c407439f1a59344af18d9fe7275825925adbe08719595d06161d6d5b",
|
"src/netx/kb-runtime.ts": "ebf2fd36c407439f1a59344af18d9fe7275825925adbe08719595d06161d6d5b",
|
||||||
"src/netx/mcp-catalog.ts": "b79962da8f74f04595ed89b166b851d83cac41e8338b80b7629da2021a2c281d",
|
"src/netx/mcp-catalog.ts": "b79962da8f74f04595ed89b166b851d83cac41e8338b80b7629da2021a2c281d",
|
||||||
"src/netx/mcp-remote.ts": "4fb64d62c3947463d7ea174edb4ceed2a8bba090ad5bf8ede50654c423d7a91f",
|
"src/netx/mcp-remote.ts": "89576a02729f50b7f03ef5f689a5e9c8380fa6ad4e44c46e1c846eceadd04571",
|
||||||
"src/netx/mcp-schema.ts": "2bb2eccd001101bd97389578c627931e89fbab4e057dab500e8b7b8bd655366f",
|
"src/netx/mcp-schema.ts": "2bb2eccd001101bd97389578c627931e89fbab4e057dab500e8b7b8bd655366f",
|
||||||
"src/netx/mcp-store.ts": "002a5d432d4532ea0ed07f00801eea9ca8cb84d6ebcb347311967e13007fde32",
|
"src/netx/mcp-store.ts": "ea91f2171b93dc46c4d72cd811bbb03b16060db83abe21b6f71c68bbfb32d6ff",
|
||||||
"src/netx/mcp-types.ts": "4b30d6e8fba328b0e116c43a1a90d2b686ef4b9fc648b521063876426e9c2833",
|
"src/netx/mcp-types.ts": "4b30d6e8fba328b0e116c43a1a90d2b686ef4b9fc648b521063876426e9c2833",
|
||||||
"src/netx/runtime.ts": "238555c92c3972ee9de8ec062b14d6a86961ec4c64609565ba1164b1a460d386",
|
"src/netx/runtime.ts": "238555c92c3972ee9de8ec062b14d6a86961ec4c64609565ba1164b1a460d386",
|
||||||
"src/netx/skills-cache.ts": "10957061256cec6b381c5604745d905146abe5da1b418055414166879bb90094",
|
"src/netx/skills-cache.ts": "58383b578d7d3c5376016351b3f36466ac74faad810f835ea5af9733a729ef7f",
|
||||||
"src/netx/tool-mount-status.ts": "783981774d717ca5e7bf7c0ec7273b40787ef12a6b40749c74543b30fa390faf",
|
"src/netx/tool-mount-status.ts": "783981774d717ca5e7bf7c0ec7273b40787ef12a6b40749c74543b30fa390faf",
|
||||||
"src/netx/tools.ts": "88c925aba50a7d6b3fc800986789955fd77c7aca8387c399b1ef58edbf7b9139"
|
"src/netx/tools.ts": "88c925aba50a7d6b3fc800986789955fd77c7aca8387c399b1ef58edbf7b9139"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lib/agent-tools-common.js": {
|
"lib/agent-tools-common.js": {
|
||||||
"output": "9e40a55c9c823b76c8060aa5f328e89b1411091f1a23841e63505deb2de3e316",
|
"output": "6cf374696df26b8519e4e43b33dc00991e6ad5dbb1eae442c446d68849a1d440",
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"package.json": "5132d4dd66628c59e1b495b5a75fc6c29a30c220a6c112059b4c3b5983d3cbb9",
|
"package.json": "5132d4dd66628c59e1b495b5a75fc6c29a30c220a6c112059b4c3b5983d3cbb9",
|
||||||
"pnpm-lock.yaml": "f1209ac70e9c2294e0f300c0aad88b64aeecd6aa19dfc929c5766c7d033615a3",
|
"pnpm-lock.yaml": "f1209ac70e9c2294e0f300c0aad88b64aeecd6aa19dfc929c5766c7d033615a3",
|
||||||
|
|
@ -44,9 +44,9 @@
|
||||||
"src/agent-tools-common.ts": "cf463e940bc2042d7462502de93d45380550da54f1965fa684d72f71b16bc41b",
|
"src/agent-tools-common.ts": "cf463e940bc2042d7462502de93d45380550da54f1965fa684d72f71b16bc41b",
|
||||||
"src/netx/capability-groups.ts": "3b7ac48ec3bbef91f324c24156af783d741dc5aeeb4db95d52eeafadbcbbed51",
|
"src/netx/capability-groups.ts": "3b7ac48ec3bbef91f324c24156af783d741dc5aeeb4db95d52eeafadbcbbed51",
|
||||||
"src/netx/config-values.ts": "7e8ca894d0b09f5e52686d6b293e49b46dd2cc13d714b0e0b103c3d07433e117",
|
"src/netx/config-values.ts": "7e8ca894d0b09f5e52686d6b293e49b46dd2cc13d714b0e0b103c3d07433e117",
|
||||||
"src/netx/group-skills.ts": "84a35fe639b3670fd4e95e3e5801a78020551655fd66007a9f151e2f8619e9ac",
|
"src/netx/group-skills.ts": "3fa78686f4c035f056f3cb5ed2f0411ad52c043958b859ebfcc254a625554e3a",
|
||||||
"src/netx/group-tools-plugin.ts": "647e5258b46648705585cd5d3765556f391810434c6c7f04f3df9e2f44d8ba67",
|
"src/netx/group-tools-plugin.ts": "647e5258b46648705585cd5d3765556f391810434c6c7f04f3df9e2f44d8ba67",
|
||||||
"src/netx/http.ts": "aa0552d6087593924f448fc8efaadda92f8803147190741f5abc3030cf32c439",
|
"src/netx/http.ts": "982ca3e5da8fe36debd5a0c561578c8f2c5e2a9cc0527d7dbacdf4e5a4aa9edd",
|
||||||
"src/netx/json-safe.ts": "5fe610d5ec96d31096733f7e828b4b2266c19692535a232a644bb49784ee24d9",
|
"src/netx/json-safe.ts": "5fe610d5ec96d31096733f7e828b4b2266c19692535a232a644bb49784ee24d9",
|
||||||
"src/netx/kb-context-skill.ts": "e0375006ba1e505fa6e5fd50f1748d7b43bb8c34809d10a0549e934006eb3045",
|
"src/netx/kb-context-skill.ts": "e0375006ba1e505fa6e5fd50f1748d7b43bb8c34809d10a0549e934006eb3045",
|
||||||
"src/netx/kb-local-ops.ts": "2c28ff25c8abcb68e2b197767a3eea0e056bcc3aa4ccdd6561d069a7a0da2463",
|
"src/netx/kb-local-ops.ts": "2c28ff25c8abcb68e2b197767a3eea0e056bcc3aa4ccdd6561d069a7a0da2463",
|
||||||
|
|
@ -57,18 +57,18 @@
|
||||||
"src/netx/kb-read-ops.ts": "1359b370293b9f4afa51774ed729525d77756f1552cd46abf4d4d6c595eaeb86",
|
"src/netx/kb-read-ops.ts": "1359b370293b9f4afa51774ed729525d77756f1552cd46abf4d4d6c595eaeb86",
|
||||||
"src/netx/kb-runtime.ts": "ebf2fd36c407439f1a59344af18d9fe7275825925adbe08719595d06161d6d5b",
|
"src/netx/kb-runtime.ts": "ebf2fd36c407439f1a59344af18d9fe7275825925adbe08719595d06161d6d5b",
|
||||||
"src/netx/mcp-catalog.ts": "b79962da8f74f04595ed89b166b851d83cac41e8338b80b7629da2021a2c281d",
|
"src/netx/mcp-catalog.ts": "b79962da8f74f04595ed89b166b851d83cac41e8338b80b7629da2021a2c281d",
|
||||||
"src/netx/mcp-remote.ts": "4fb64d62c3947463d7ea174edb4ceed2a8bba090ad5bf8ede50654c423d7a91f",
|
"src/netx/mcp-remote.ts": "89576a02729f50b7f03ef5f689a5e9c8380fa6ad4e44c46e1c846eceadd04571",
|
||||||
"src/netx/mcp-schema.ts": "2bb2eccd001101bd97389578c627931e89fbab4e057dab500e8b7b8bd655366f",
|
"src/netx/mcp-schema.ts": "2bb2eccd001101bd97389578c627931e89fbab4e057dab500e8b7b8bd655366f",
|
||||||
"src/netx/mcp-store.ts": "002a5d432d4532ea0ed07f00801eea9ca8cb84d6ebcb347311967e13007fde32",
|
"src/netx/mcp-store.ts": "ea91f2171b93dc46c4d72cd811bbb03b16060db83abe21b6f71c68bbfb32d6ff",
|
||||||
"src/netx/mcp-types.ts": "4b30d6e8fba328b0e116c43a1a90d2b686ef4b9fc648b521063876426e9c2833",
|
"src/netx/mcp-types.ts": "4b30d6e8fba328b0e116c43a1a90d2b686ef4b9fc648b521063876426e9c2833",
|
||||||
"src/netx/runtime.ts": "238555c92c3972ee9de8ec062b14d6a86961ec4c64609565ba1164b1a460d386",
|
"src/netx/runtime.ts": "238555c92c3972ee9de8ec062b14d6a86961ec4c64609565ba1164b1a460d386",
|
||||||
"src/netx/skills-cache.ts": "10957061256cec6b381c5604745d905146abe5da1b418055414166879bb90094",
|
"src/netx/skills-cache.ts": "58383b578d7d3c5376016351b3f36466ac74faad810f835ea5af9733a729ef7f",
|
||||||
"src/netx/tool-mount-status.ts": "783981774d717ca5e7bf7c0ec7273b40787ef12a6b40749c74543b30fa390faf",
|
"src/netx/tool-mount-status.ts": "783981774d717ca5e7bf7c0ec7273b40787ef12a6b40749c74543b30fa390faf",
|
||||||
"src/netx/tools.ts": "88c925aba50a7d6b3fc800986789955fd77c7aca8387c399b1ef58edbf7b9139"
|
"src/netx/tools.ts": "88c925aba50a7d6b3fc800986789955fd77c7aca8387c399b1ef58edbf7b9139"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lib/agent-tools-nms.js": {
|
"lib/agent-tools-nms.js": {
|
||||||
"output": "126ef9bc22cbe2814442efd68a0ff83ce29fdac7f9baed9602a7a186ef29db0f",
|
"output": "f7fd09a4fb4fe79218c22567ffb806319271c47ad9de5fbcac3748562adbda60",
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"package.json": "5132d4dd66628c59e1b495b5a75fc6c29a30c220a6c112059b4c3b5983d3cbb9",
|
"package.json": "5132d4dd66628c59e1b495b5a75fc6c29a30c220a6c112059b4c3b5983d3cbb9",
|
||||||
"pnpm-lock.yaml": "f1209ac70e9c2294e0f300c0aad88b64aeecd6aa19dfc929c5766c7d033615a3",
|
"pnpm-lock.yaml": "f1209ac70e9c2294e0f300c0aad88b64aeecd6aa19dfc929c5766c7d033615a3",
|
||||||
|
|
@ -77,9 +77,9 @@
|
||||||
"src/agent-tools-nms.ts": "6505e51bfb2948756440b00724b19d907d410d34ef1dcbf08019774c0f9d7ef5",
|
"src/agent-tools-nms.ts": "6505e51bfb2948756440b00724b19d907d410d34ef1dcbf08019774c0f9d7ef5",
|
||||||
"src/netx/capability-groups.ts": "3b7ac48ec3bbef91f324c24156af783d741dc5aeeb4db95d52eeafadbcbbed51",
|
"src/netx/capability-groups.ts": "3b7ac48ec3bbef91f324c24156af783d741dc5aeeb4db95d52eeafadbcbbed51",
|
||||||
"src/netx/config-values.ts": "7e8ca894d0b09f5e52686d6b293e49b46dd2cc13d714b0e0b103c3d07433e117",
|
"src/netx/config-values.ts": "7e8ca894d0b09f5e52686d6b293e49b46dd2cc13d714b0e0b103c3d07433e117",
|
||||||
"src/netx/group-skills.ts": "84a35fe639b3670fd4e95e3e5801a78020551655fd66007a9f151e2f8619e9ac",
|
"src/netx/group-skills.ts": "3fa78686f4c035f056f3cb5ed2f0411ad52c043958b859ebfcc254a625554e3a",
|
||||||
"src/netx/group-tools-plugin.ts": "647e5258b46648705585cd5d3765556f391810434c6c7f04f3df9e2f44d8ba67",
|
"src/netx/group-tools-plugin.ts": "647e5258b46648705585cd5d3765556f391810434c6c7f04f3df9e2f44d8ba67",
|
||||||
"src/netx/http.ts": "aa0552d6087593924f448fc8efaadda92f8803147190741f5abc3030cf32c439",
|
"src/netx/http.ts": "982ca3e5da8fe36debd5a0c561578c8f2c5e2a9cc0527d7dbacdf4e5a4aa9edd",
|
||||||
"src/netx/json-safe.ts": "5fe610d5ec96d31096733f7e828b4b2266c19692535a232a644bb49784ee24d9",
|
"src/netx/json-safe.ts": "5fe610d5ec96d31096733f7e828b4b2266c19692535a232a644bb49784ee24d9",
|
||||||
"src/netx/kb-context-skill.ts": "e0375006ba1e505fa6e5fd50f1748d7b43bb8c34809d10a0549e934006eb3045",
|
"src/netx/kb-context-skill.ts": "e0375006ba1e505fa6e5fd50f1748d7b43bb8c34809d10a0549e934006eb3045",
|
||||||
"src/netx/kb-local-ops.ts": "2c28ff25c8abcb68e2b197767a3eea0e056bcc3aa4ccdd6561d069a7a0da2463",
|
"src/netx/kb-local-ops.ts": "2c28ff25c8abcb68e2b197767a3eea0e056bcc3aa4ccdd6561d069a7a0da2463",
|
||||||
|
|
@ -90,18 +90,18 @@
|
||||||
"src/netx/kb-read-ops.ts": "1359b370293b9f4afa51774ed729525d77756f1552cd46abf4d4d6c595eaeb86",
|
"src/netx/kb-read-ops.ts": "1359b370293b9f4afa51774ed729525d77756f1552cd46abf4d4d6c595eaeb86",
|
||||||
"src/netx/kb-runtime.ts": "ebf2fd36c407439f1a59344af18d9fe7275825925adbe08719595d06161d6d5b",
|
"src/netx/kb-runtime.ts": "ebf2fd36c407439f1a59344af18d9fe7275825925adbe08719595d06161d6d5b",
|
||||||
"src/netx/mcp-catalog.ts": "b79962da8f74f04595ed89b166b851d83cac41e8338b80b7629da2021a2c281d",
|
"src/netx/mcp-catalog.ts": "b79962da8f74f04595ed89b166b851d83cac41e8338b80b7629da2021a2c281d",
|
||||||
"src/netx/mcp-remote.ts": "4fb64d62c3947463d7ea174edb4ceed2a8bba090ad5bf8ede50654c423d7a91f",
|
"src/netx/mcp-remote.ts": "89576a02729f50b7f03ef5f689a5e9c8380fa6ad4e44c46e1c846eceadd04571",
|
||||||
"src/netx/mcp-schema.ts": "2bb2eccd001101bd97389578c627931e89fbab4e057dab500e8b7b8bd655366f",
|
"src/netx/mcp-schema.ts": "2bb2eccd001101bd97389578c627931e89fbab4e057dab500e8b7b8bd655366f",
|
||||||
"src/netx/mcp-store.ts": "002a5d432d4532ea0ed07f00801eea9ca8cb84d6ebcb347311967e13007fde32",
|
"src/netx/mcp-store.ts": "ea91f2171b93dc46c4d72cd811bbb03b16060db83abe21b6f71c68bbfb32d6ff",
|
||||||
"src/netx/mcp-types.ts": "4b30d6e8fba328b0e116c43a1a90d2b686ef4b9fc648b521063876426e9c2833",
|
"src/netx/mcp-types.ts": "4b30d6e8fba328b0e116c43a1a90d2b686ef4b9fc648b521063876426e9c2833",
|
||||||
"src/netx/runtime.ts": "238555c92c3972ee9de8ec062b14d6a86961ec4c64609565ba1164b1a460d386",
|
"src/netx/runtime.ts": "238555c92c3972ee9de8ec062b14d6a86961ec4c64609565ba1164b1a460d386",
|
||||||
"src/netx/skills-cache.ts": "10957061256cec6b381c5604745d905146abe5da1b418055414166879bb90094",
|
"src/netx/skills-cache.ts": "58383b578d7d3c5376016351b3f36466ac74faad810f835ea5af9733a729ef7f",
|
||||||
"src/netx/tool-mount-status.ts": "783981774d717ca5e7bf7c0ec7273b40787ef12a6b40749c74543b30fa390faf",
|
"src/netx/tool-mount-status.ts": "783981774d717ca5e7bf7c0ec7273b40787ef12a6b40749c74543b30fa390faf",
|
||||||
"src/netx/tools.ts": "88c925aba50a7d6b3fc800986789955fd77c7aca8387c399b1ef58edbf7b9139"
|
"src/netx/tools.ts": "88c925aba50a7d6b3fc800986789955fd77c7aca8387c399b1ef58edbf7b9139"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lib/agent-tools-ops.js": {
|
"lib/agent-tools-ops.js": {
|
||||||
"output": "56fd1c68147c6d48cca50535f67ce3807dab5f35db4ca9d675fe8db731d9f1d2",
|
"output": "59218d9c32d97a92cf90c4a6c6d5f184f04162a93aada26d408e7d90debc93ca",
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"package.json": "5132d4dd66628c59e1b495b5a75fc6c29a30c220a6c112059b4c3b5983d3cbb9",
|
"package.json": "5132d4dd66628c59e1b495b5a75fc6c29a30c220a6c112059b4c3b5983d3cbb9",
|
||||||
"pnpm-lock.yaml": "f1209ac70e9c2294e0f300c0aad88b64aeecd6aa19dfc929c5766c7d033615a3",
|
"pnpm-lock.yaml": "f1209ac70e9c2294e0f300c0aad88b64aeecd6aa19dfc929c5766c7d033615a3",
|
||||||
|
|
@ -110,9 +110,9 @@
|
||||||
"src/agent-tools-ops.ts": "2f0331106de53f3dcdd8d61ce2d9da363f0a9b854f1dd7d98e27997c82d98a77",
|
"src/agent-tools-ops.ts": "2f0331106de53f3dcdd8d61ce2d9da363f0a9b854f1dd7d98e27997c82d98a77",
|
||||||
"src/netx/capability-groups.ts": "3b7ac48ec3bbef91f324c24156af783d741dc5aeeb4db95d52eeafadbcbbed51",
|
"src/netx/capability-groups.ts": "3b7ac48ec3bbef91f324c24156af783d741dc5aeeb4db95d52eeafadbcbbed51",
|
||||||
"src/netx/config-values.ts": "7e8ca894d0b09f5e52686d6b293e49b46dd2cc13d714b0e0b103c3d07433e117",
|
"src/netx/config-values.ts": "7e8ca894d0b09f5e52686d6b293e49b46dd2cc13d714b0e0b103c3d07433e117",
|
||||||
"src/netx/group-skills.ts": "84a35fe639b3670fd4e95e3e5801a78020551655fd66007a9f151e2f8619e9ac",
|
"src/netx/group-skills.ts": "3fa78686f4c035f056f3cb5ed2f0411ad52c043958b859ebfcc254a625554e3a",
|
||||||
"src/netx/group-tools-plugin.ts": "647e5258b46648705585cd5d3765556f391810434c6c7f04f3df9e2f44d8ba67",
|
"src/netx/group-tools-plugin.ts": "647e5258b46648705585cd5d3765556f391810434c6c7f04f3df9e2f44d8ba67",
|
||||||
"src/netx/http.ts": "aa0552d6087593924f448fc8efaadda92f8803147190741f5abc3030cf32c439",
|
"src/netx/http.ts": "982ca3e5da8fe36debd5a0c561578c8f2c5e2a9cc0527d7dbacdf4e5a4aa9edd",
|
||||||
"src/netx/json-safe.ts": "5fe610d5ec96d31096733f7e828b4b2266c19692535a232a644bb49784ee24d9",
|
"src/netx/json-safe.ts": "5fe610d5ec96d31096733f7e828b4b2266c19692535a232a644bb49784ee24d9",
|
||||||
"src/netx/kb-context-skill.ts": "e0375006ba1e505fa6e5fd50f1748d7b43bb8c34809d10a0549e934006eb3045",
|
"src/netx/kb-context-skill.ts": "e0375006ba1e505fa6e5fd50f1748d7b43bb8c34809d10a0549e934006eb3045",
|
||||||
"src/netx/kb-local-ops.ts": "2c28ff25c8abcb68e2b197767a3eea0e056bcc3aa4ccdd6561d069a7a0da2463",
|
"src/netx/kb-local-ops.ts": "2c28ff25c8abcb68e2b197767a3eea0e056bcc3aa4ccdd6561d069a7a0da2463",
|
||||||
|
|
@ -123,18 +123,18 @@
|
||||||
"src/netx/kb-read-ops.ts": "1359b370293b9f4afa51774ed729525d77756f1552cd46abf4d4d6c595eaeb86",
|
"src/netx/kb-read-ops.ts": "1359b370293b9f4afa51774ed729525d77756f1552cd46abf4d4d6c595eaeb86",
|
||||||
"src/netx/kb-runtime.ts": "ebf2fd36c407439f1a59344af18d9fe7275825925adbe08719595d06161d6d5b",
|
"src/netx/kb-runtime.ts": "ebf2fd36c407439f1a59344af18d9fe7275825925adbe08719595d06161d6d5b",
|
||||||
"src/netx/mcp-catalog.ts": "b79962da8f74f04595ed89b166b851d83cac41e8338b80b7629da2021a2c281d",
|
"src/netx/mcp-catalog.ts": "b79962da8f74f04595ed89b166b851d83cac41e8338b80b7629da2021a2c281d",
|
||||||
"src/netx/mcp-remote.ts": "4fb64d62c3947463d7ea174edb4ceed2a8bba090ad5bf8ede50654c423d7a91f",
|
"src/netx/mcp-remote.ts": "89576a02729f50b7f03ef5f689a5e9c8380fa6ad4e44c46e1c846eceadd04571",
|
||||||
"src/netx/mcp-schema.ts": "2bb2eccd001101bd97389578c627931e89fbab4e057dab500e8b7b8bd655366f",
|
"src/netx/mcp-schema.ts": "2bb2eccd001101bd97389578c627931e89fbab4e057dab500e8b7b8bd655366f",
|
||||||
"src/netx/mcp-store.ts": "002a5d432d4532ea0ed07f00801eea9ca8cb84d6ebcb347311967e13007fde32",
|
"src/netx/mcp-store.ts": "ea91f2171b93dc46c4d72cd811bbb03b16060db83abe21b6f71c68bbfb32d6ff",
|
||||||
"src/netx/mcp-types.ts": "4b30d6e8fba328b0e116c43a1a90d2b686ef4b9fc648b521063876426e9c2833",
|
"src/netx/mcp-types.ts": "4b30d6e8fba328b0e116c43a1a90d2b686ef4b9fc648b521063876426e9c2833",
|
||||||
"src/netx/runtime.ts": "238555c92c3972ee9de8ec062b14d6a86961ec4c64609565ba1164b1a460d386",
|
"src/netx/runtime.ts": "238555c92c3972ee9de8ec062b14d6a86961ec4c64609565ba1164b1a460d386",
|
||||||
"src/netx/skills-cache.ts": "10957061256cec6b381c5604745d905146abe5da1b418055414166879bb90094",
|
"src/netx/skills-cache.ts": "58383b578d7d3c5376016351b3f36466ac74faad810f835ea5af9733a729ef7f",
|
||||||
"src/netx/tool-mount-status.ts": "783981774d717ca5e7bf7c0ec7273b40787ef12a6b40749c74543b30fa390faf",
|
"src/netx/tool-mount-status.ts": "783981774d717ca5e7bf7c0ec7273b40787ef12a6b40749c74543b30fa390faf",
|
||||||
"src/netx/tools.ts": "88c925aba50a7d6b3fc800986789955fd77c7aca8387c399b1ef58edbf7b9139"
|
"src/netx/tools.ts": "88c925aba50a7d6b3fc800986789955fd77c7aca8387c399b1ef58edbf7b9139"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lib/agent-tools-topology.js": {
|
"lib/agent-tools-topology.js": {
|
||||||
"output": "7b683d38bbab4764684d88e6f0989177e60789e6b8923438b1c860c75d54fc26",
|
"output": "b04b4ab6ed44cbb233f0d8c37d26d24d5336432d1ca8b950970a69aafdefdd5b",
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"package.json": "5132d4dd66628c59e1b495b5a75fc6c29a30c220a6c112059b4c3b5983d3cbb9",
|
"package.json": "5132d4dd66628c59e1b495b5a75fc6c29a30c220a6c112059b4c3b5983d3cbb9",
|
||||||
"pnpm-lock.yaml": "f1209ac70e9c2294e0f300c0aad88b64aeecd6aa19dfc929c5766c7d033615a3",
|
"pnpm-lock.yaml": "f1209ac70e9c2294e0f300c0aad88b64aeecd6aa19dfc929c5766c7d033615a3",
|
||||||
|
|
@ -143,9 +143,9 @@
|
||||||
"src/agent-tools-topology.ts": "22bf5f1c71a5b1d33bd3a894bca2c6e4c2fc72072c8a9d7bbc00e214dfafa9f8",
|
"src/agent-tools-topology.ts": "22bf5f1c71a5b1d33bd3a894bca2c6e4c2fc72072c8a9d7bbc00e214dfafa9f8",
|
||||||
"src/netx/capability-groups.ts": "3b7ac48ec3bbef91f324c24156af783d741dc5aeeb4db95d52eeafadbcbbed51",
|
"src/netx/capability-groups.ts": "3b7ac48ec3bbef91f324c24156af783d741dc5aeeb4db95d52eeafadbcbbed51",
|
||||||
"src/netx/config-values.ts": "7e8ca894d0b09f5e52686d6b293e49b46dd2cc13d714b0e0b103c3d07433e117",
|
"src/netx/config-values.ts": "7e8ca894d0b09f5e52686d6b293e49b46dd2cc13d714b0e0b103c3d07433e117",
|
||||||
"src/netx/group-skills.ts": "84a35fe639b3670fd4e95e3e5801a78020551655fd66007a9f151e2f8619e9ac",
|
"src/netx/group-skills.ts": "3fa78686f4c035f056f3cb5ed2f0411ad52c043958b859ebfcc254a625554e3a",
|
||||||
"src/netx/group-tools-plugin.ts": "647e5258b46648705585cd5d3765556f391810434c6c7f04f3df9e2f44d8ba67",
|
"src/netx/group-tools-plugin.ts": "647e5258b46648705585cd5d3765556f391810434c6c7f04f3df9e2f44d8ba67",
|
||||||
"src/netx/http.ts": "aa0552d6087593924f448fc8efaadda92f8803147190741f5abc3030cf32c439",
|
"src/netx/http.ts": "982ca3e5da8fe36debd5a0c561578c8f2c5e2a9cc0527d7dbacdf4e5a4aa9edd",
|
||||||
"src/netx/json-safe.ts": "5fe610d5ec96d31096733f7e828b4b2266c19692535a232a644bb49784ee24d9",
|
"src/netx/json-safe.ts": "5fe610d5ec96d31096733f7e828b4b2266c19692535a232a644bb49784ee24d9",
|
||||||
"src/netx/kb-context-skill.ts": "e0375006ba1e505fa6e5fd50f1748d7b43bb8c34809d10a0549e934006eb3045",
|
"src/netx/kb-context-skill.ts": "e0375006ba1e505fa6e5fd50f1748d7b43bb8c34809d10a0549e934006eb3045",
|
||||||
"src/netx/kb-local-ops.ts": "2c28ff25c8abcb68e2b197767a3eea0e056bcc3aa4ccdd6561d069a7a0da2463",
|
"src/netx/kb-local-ops.ts": "2c28ff25c8abcb68e2b197767a3eea0e056bcc3aa4ccdd6561d069a7a0da2463",
|
||||||
|
|
@ -156,18 +156,18 @@
|
||||||
"src/netx/kb-read-ops.ts": "1359b370293b9f4afa51774ed729525d77756f1552cd46abf4d4d6c595eaeb86",
|
"src/netx/kb-read-ops.ts": "1359b370293b9f4afa51774ed729525d77756f1552cd46abf4d4d6c595eaeb86",
|
||||||
"src/netx/kb-runtime.ts": "ebf2fd36c407439f1a59344af18d9fe7275825925adbe08719595d06161d6d5b",
|
"src/netx/kb-runtime.ts": "ebf2fd36c407439f1a59344af18d9fe7275825925adbe08719595d06161d6d5b",
|
||||||
"src/netx/mcp-catalog.ts": "b79962da8f74f04595ed89b166b851d83cac41e8338b80b7629da2021a2c281d",
|
"src/netx/mcp-catalog.ts": "b79962da8f74f04595ed89b166b851d83cac41e8338b80b7629da2021a2c281d",
|
||||||
"src/netx/mcp-remote.ts": "4fb64d62c3947463d7ea174edb4ceed2a8bba090ad5bf8ede50654c423d7a91f",
|
"src/netx/mcp-remote.ts": "89576a02729f50b7f03ef5f689a5e9c8380fa6ad4e44c46e1c846eceadd04571",
|
||||||
"src/netx/mcp-schema.ts": "2bb2eccd001101bd97389578c627931e89fbab4e057dab500e8b7b8bd655366f",
|
"src/netx/mcp-schema.ts": "2bb2eccd001101bd97389578c627931e89fbab4e057dab500e8b7b8bd655366f",
|
||||||
"src/netx/mcp-store.ts": "002a5d432d4532ea0ed07f00801eea9ca8cb84d6ebcb347311967e13007fde32",
|
"src/netx/mcp-store.ts": "ea91f2171b93dc46c4d72cd811bbb03b16060db83abe21b6f71c68bbfb32d6ff",
|
||||||
"src/netx/mcp-types.ts": "4b30d6e8fba328b0e116c43a1a90d2b686ef4b9fc648b521063876426e9c2833",
|
"src/netx/mcp-types.ts": "4b30d6e8fba328b0e116c43a1a90d2b686ef4b9fc648b521063876426e9c2833",
|
||||||
"src/netx/runtime.ts": "238555c92c3972ee9de8ec062b14d6a86961ec4c64609565ba1164b1a460d386",
|
"src/netx/runtime.ts": "238555c92c3972ee9de8ec062b14d6a86961ec4c64609565ba1164b1a460d386",
|
||||||
"src/netx/skills-cache.ts": "10957061256cec6b381c5604745d905146abe5da1b418055414166879bb90094",
|
"src/netx/skills-cache.ts": "58383b578d7d3c5376016351b3f36466ac74faad810f835ea5af9733a729ef7f",
|
||||||
"src/netx/tool-mount-status.ts": "783981774d717ca5e7bf7c0ec7273b40787ef12a6b40749c74543b30fa390faf",
|
"src/netx/tool-mount-status.ts": "783981774d717ca5e7bf7c0ec7273b40787ef12a6b40749c74543b30fa390faf",
|
||||||
"src/netx/tools.ts": "88c925aba50a7d6b3fc800986789955fd77c7aca8387c399b1ef58edbf7b9139"
|
"src/netx/tools.ts": "88c925aba50a7d6b3fc800986789955fd77c7aca8387c399b1ef58edbf7b9139"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lib/agent-tools.js": {
|
"lib/agent-tools.js": {
|
||||||
"output": "e831d2c6a671a9b4bcc2449a0e84772446848372925567704cdb0590a2bb7071",
|
"output": "89f59175ba51d39a0fbd48e825e83470eadd84ef5378c8c29c51ab0cde3845d6",
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"package.json": "5132d4dd66628c59e1b495b5a75fc6c29a30c220a6c112059b4c3b5983d3cbb9",
|
"package.json": "5132d4dd66628c59e1b495b5a75fc6c29a30c220a6c112059b4c3b5983d3cbb9",
|
||||||
"pnpm-lock.yaml": "f1209ac70e9c2294e0f300c0aad88b64aeecd6aa19dfc929c5766c7d033615a3",
|
"pnpm-lock.yaml": "f1209ac70e9c2294e0f300c0aad88b64aeecd6aa19dfc929c5766c7d033615a3",
|
||||||
|
|
@ -176,9 +176,9 @@
|
||||||
"src/agent-tools.ts": "b7e4f8476ba9c5f2754d859074c94129642d18883da293b80e310c09336bc5c5",
|
"src/agent-tools.ts": "b7e4f8476ba9c5f2754d859074c94129642d18883da293b80e310c09336bc5c5",
|
||||||
"src/netx/capability-groups.ts": "3b7ac48ec3bbef91f324c24156af783d741dc5aeeb4db95d52eeafadbcbbed51",
|
"src/netx/capability-groups.ts": "3b7ac48ec3bbef91f324c24156af783d741dc5aeeb4db95d52eeafadbcbbed51",
|
||||||
"src/netx/config-values.ts": "7e8ca894d0b09f5e52686d6b293e49b46dd2cc13d714b0e0b103c3d07433e117",
|
"src/netx/config-values.ts": "7e8ca894d0b09f5e52686d6b293e49b46dd2cc13d714b0e0b103c3d07433e117",
|
||||||
"src/netx/group-skills.ts": "84a35fe639b3670fd4e95e3e5801a78020551655fd66007a9f151e2f8619e9ac",
|
"src/netx/group-skills.ts": "3fa78686f4c035f056f3cb5ed2f0411ad52c043958b859ebfcc254a625554e3a",
|
||||||
"src/netx/group-tools-plugin.ts": "647e5258b46648705585cd5d3765556f391810434c6c7f04f3df9e2f44d8ba67",
|
"src/netx/group-tools-plugin.ts": "647e5258b46648705585cd5d3765556f391810434c6c7f04f3df9e2f44d8ba67",
|
||||||
"src/netx/http.ts": "aa0552d6087593924f448fc8efaadda92f8803147190741f5abc3030cf32c439",
|
"src/netx/http.ts": "982ca3e5da8fe36debd5a0c561578c8f2c5e2a9cc0527d7dbacdf4e5a4aa9edd",
|
||||||
"src/netx/json-safe.ts": "5fe610d5ec96d31096733f7e828b4b2266c19692535a232a644bb49784ee24d9",
|
"src/netx/json-safe.ts": "5fe610d5ec96d31096733f7e828b4b2266c19692535a232a644bb49784ee24d9",
|
||||||
"src/netx/kb-context-skill.ts": "e0375006ba1e505fa6e5fd50f1748d7b43bb8c34809d10a0549e934006eb3045",
|
"src/netx/kb-context-skill.ts": "e0375006ba1e505fa6e5fd50f1748d7b43bb8c34809d10a0549e934006eb3045",
|
||||||
"src/netx/kb-local-ops.ts": "2c28ff25c8abcb68e2b197767a3eea0e056bcc3aa4ccdd6561d069a7a0da2463",
|
"src/netx/kb-local-ops.ts": "2c28ff25c8abcb68e2b197767a3eea0e056bcc3aa4ccdd6561d069a7a0da2463",
|
||||||
|
|
@ -189,31 +189,31 @@
|
||||||
"src/netx/kb-read-ops.ts": "1359b370293b9f4afa51774ed729525d77756f1552cd46abf4d4d6c595eaeb86",
|
"src/netx/kb-read-ops.ts": "1359b370293b9f4afa51774ed729525d77756f1552cd46abf4d4d6c595eaeb86",
|
||||||
"src/netx/kb-runtime.ts": "ebf2fd36c407439f1a59344af18d9fe7275825925adbe08719595d06161d6d5b",
|
"src/netx/kb-runtime.ts": "ebf2fd36c407439f1a59344af18d9fe7275825925adbe08719595d06161d6d5b",
|
||||||
"src/netx/mcp-catalog.ts": "b79962da8f74f04595ed89b166b851d83cac41e8338b80b7629da2021a2c281d",
|
"src/netx/mcp-catalog.ts": "b79962da8f74f04595ed89b166b851d83cac41e8338b80b7629da2021a2c281d",
|
||||||
"src/netx/mcp-remote.ts": "4fb64d62c3947463d7ea174edb4ceed2a8bba090ad5bf8ede50654c423d7a91f",
|
"src/netx/mcp-remote.ts": "89576a02729f50b7f03ef5f689a5e9c8380fa6ad4e44c46e1c846eceadd04571",
|
||||||
"src/netx/mcp-schema.ts": "2bb2eccd001101bd97389578c627931e89fbab4e057dab500e8b7b8bd655366f",
|
"src/netx/mcp-schema.ts": "2bb2eccd001101bd97389578c627931e89fbab4e057dab500e8b7b8bd655366f",
|
||||||
"src/netx/mcp-store.ts": "002a5d432d4532ea0ed07f00801eea9ca8cb84d6ebcb347311967e13007fde32",
|
"src/netx/mcp-store.ts": "ea91f2171b93dc46c4d72cd811bbb03b16060db83abe21b6f71c68bbfb32d6ff",
|
||||||
"src/netx/mcp-types.ts": "4b30d6e8fba328b0e116c43a1a90d2b686ef4b9fc648b521063876426e9c2833",
|
"src/netx/mcp-types.ts": "4b30d6e8fba328b0e116c43a1a90d2b686ef4b9fc648b521063876426e9c2833",
|
||||||
"src/netx/runtime.ts": "238555c92c3972ee9de8ec062b14d6a86961ec4c64609565ba1164b1a460d386",
|
"src/netx/runtime.ts": "238555c92c3972ee9de8ec062b14d6a86961ec4c64609565ba1164b1a460d386",
|
||||||
"src/netx/skills-cache.ts": "10957061256cec6b381c5604745d905146abe5da1b418055414166879bb90094",
|
"src/netx/skills-cache.ts": "58383b578d7d3c5376016351b3f36466ac74faad810f835ea5af9733a729ef7f",
|
||||||
"src/netx/tool-mount-status.ts": "783981774d717ca5e7bf7c0ec7273b40787ef12a6b40749c74543b30fa390faf",
|
"src/netx/tool-mount-status.ts": "783981774d717ca5e7bf7c0ec7273b40787ef12a6b40749c74543b30fa390faf",
|
||||||
"src/netx/tools.ts": "88c925aba50a7d6b3fc800986789955fd77c7aca8387c399b1ef58edbf7b9139"
|
"src/netx/tools.ts": "88c925aba50a7d6b3fc800986789955fd77c7aca8387c399b1ef58edbf7b9139"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lib/client.js": {
|
"lib/client.js": {
|
||||||
"output": "76d73a9e8354e55c27d60c90d17206b7d9eb096829a64cbfc15ab1152c7db49b",
|
"output": "bec095ff7f28d3eb97152486275c93badc43fe687377c5d0e3267b355add2e2d",
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"package.json": "5132d4dd66628c59e1b495b5a75fc6c29a30c220a6c112059b4c3b5983d3cbb9",
|
"package.json": "5132d4dd66628c59e1b495b5a75fc6c29a30c220a6c112059b4c3b5983d3cbb9",
|
||||||
"pnpm-lock.yaml": "f1209ac70e9c2294e0f300c0aad88b64aeecd6aa19dfc929c5766c7d033615a3",
|
"pnpm-lock.yaml": "f1209ac70e9c2294e0f300c0aad88b64aeecd6aa19dfc929c5766c7d033615a3",
|
||||||
"scripts/build-client.mjs": "bbb37a7d5024772f35824a4a3b5b9bc75fc670da391151e35a3811a677e23fd1",
|
"scripts/build-client.mjs": "bbb37a7d5024772f35824a4a3b5b9bc75fc670da391151e35a3811a677e23fd1",
|
||||||
"scripts/build-integrity.mjs": "18e9e32c452ef6217d771675fa8b763464a27ac0cf192d5fbe5cb1682380b0d9",
|
"scripts/build-integrity.mjs": "18e9e32c452ef6217d771675fa8b763464a27ac0cf192d5fbe5cb1682380b0d9",
|
||||||
"src/client/NetxopsCard.tsx": "51d85f150fd13fc5377de29a4e80b992e860514e0700884218f9f9c7bdcd4cc1",
|
"src/client/NetxopsCard.tsx": "d4c4e795949496f5f913914c23c338bdbc81ea2b721a55d835815f2eae13f667",
|
||||||
"src/client/alarm-push-status-view.ts": "9ad84e4a691a6c22de4d85c45047251716929c63cada362205db98428a810a31",
|
"src/client/alarm-push-status-view.ts": "9ad84e4a691a6c22de4d85c45047251716929c63cada362205db98428a810a31",
|
||||||
"src/client/card-form.ts": "00ec10c453fffae8beac4a35a13299514c120c913100a6e4536e7a00d072b8d9",
|
"src/client/card-form.ts": "00ec10c453fffae8beac4a35a13299514c120c913100a6e4536e7a00d072b8d9",
|
||||||
"src/client/controller.ts": "d3353feda6f6b004dd094b1fc90d743003b55ce5a9d0b46c8fb9c1eb33925cc1",
|
"src/client/controller.ts": "d3353feda6f6b004dd094b1fc90d743003b55ce5a9d0b46c8fb9c1eb33925cc1",
|
||||||
"src/client/im-delivery-catalog.ts": "faf18d4a8fdcf30033b6d905f156c6fc953d214f8d734417f3bed783994d6117",
|
"src/client/im-delivery-catalog.ts": "faf18d4a8fdcf30033b6d905f156c6fc953d214f8d734417f3bed783994d6117",
|
||||||
"src/client/index.ts": "1f73ae8e8c02e13b47c5679869359444485b4621eca20a15c632289f22e5d760",
|
"src/client/index.ts": "1f73ae8e8c02e13b47c5679869359444485b4621eca20a15c632289f22e5d760",
|
||||||
"src/client/kb-status-view.ts": "458d1dfd9c0f93e6cdd06780c0ad813de54c3aa952fcdb63758a616ab43ffb10",
|
"src/client/kb-status-view.ts": "458d1dfd9c0f93e6cdd06780c0ad813de54c3aa952fcdb63758a616ab43ffb10",
|
||||||
"src/client/locales.ts": "b9aa9cac558119c3a81e773dc3b49378f0c37fb2220696a484a92bf19aabb5d6",
|
"src/client/locales.ts": "35106f934baaad22cf5e7afdebe415ef198af29f4831ad9dfa7ff2d0235cf4d9",
|
||||||
"src/client/mcp-catalog-view.ts": "3066ba1fd56b19e099e1f58ba9699a4d17de9f5733e27a4f4da0c4f58809a545",
|
"src/client/mcp-catalog-view.ts": "3066ba1fd56b19e099e1f58ba9699a4d17de9f5733e27a4f4da0c4f58809a545",
|
||||||
"src/client/sessions-export-view.ts": "3397b081318c23d9d816596566e19101df7234390a698a07481c7c1a019a66fd",
|
"src/client/sessions-export-view.ts": "3397b081318c23d9d816596566e19101df7234390a698a07481c7c1a019a66fd",
|
||||||
"src/client/settings-form.ts": "0318749c9afd049982a60eee7b2ca4a0a2cdcd4f574f9c87616f2ccbe39779eb",
|
"src/client/settings-form.ts": "0318749c9afd049982a60eee7b2ca4a0a2cdcd4f574f9c87616f2ccbe39779eb",
|
||||||
|
|
@ -222,7 +222,7 @@
|
||||||
"src/client/tools-status-view.ts": "4ff296641164124748356a24e0d38ec88f09ee9826bf1a2ab51159026a2b2ca8",
|
"src/client/tools-status-view.ts": "4ff296641164124748356a24e0d38ec88f09ee9826bf1a2ab51159026a2b2ca8",
|
||||||
"src/netx/capability-groups.ts": "3b7ac48ec3bbef91f324c24156af783d741dc5aeeb4db95d52eeafadbcbbed51",
|
"src/netx/capability-groups.ts": "3b7ac48ec3bbef91f324c24156af783d741dc5aeeb4db95d52eeafadbcbbed51",
|
||||||
"src/netx/config-values.ts": "7e8ca894d0b09f5e52686d6b293e49b46dd2cc13d714b0e0b103c3d07433e117",
|
"src/netx/config-values.ts": "7e8ca894d0b09f5e52686d6b293e49b46dd2cc13d714b0e0b103c3d07433e117",
|
||||||
"src/netx/http.ts": "aa0552d6087593924f448fc8efaadda92f8803147190741f5abc3030cf32c439",
|
"src/netx/http.ts": "982ca3e5da8fe36debd5a0c561578c8f2c5e2a9cc0527d7dbacdf4e5a4aa9edd",
|
||||||
"src/netx/im-targets.ts": "123613038dee8b14ce63ea46e88029287c1a8c527e6ba6cadab02f8c95b28917",
|
"src/netx/im-targets.ts": "123613038dee8b14ce63ea46e88029287c1a8c527e6ba6cadab02f8c95b28917",
|
||||||
"src/netx/kb-manifest.ts": "0389b1801aa2028b2a05044f5b4560273e9ea77f11145280d7620762df4cad02",
|
"src/netx/kb-manifest.ts": "0389b1801aa2028b2a05044f5b4560273e9ea77f11145280d7620762df4cad02",
|
||||||
"src/netx/mcp-catalog.ts": "b79962da8f74f04595ed89b166b851d83cac41e8338b80b7629da2021a2c281d",
|
"src/netx/mcp-catalog.ts": "b79962da8f74f04595ed89b166b851d83cac41e8338b80b7629da2021a2c281d",
|
||||||
|
|
@ -231,14 +231,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lib/index.js": {
|
"lib/index.js": {
|
||||||
"output": "515aafc5a26d8680cedbe970751b5f565e098c8f691014fb0f9a1fe9edd6f82f",
|
"output": "ffdddb5bd035a1ef3f268efd6590e472f32d99ad9105da056d9d00852c6adf17",
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"package.json": "5132d4dd66628c59e1b495b5a75fc6c29a30c220a6c112059b4c3b5983d3cbb9",
|
"package.json": "5132d4dd66628c59e1b495b5a75fc6c29a30c220a6c112059b4c3b5983d3cbb9",
|
||||||
"pnpm-lock.yaml": "f1209ac70e9c2294e0f300c0aad88b64aeecd6aa19dfc929c5766c7d033615a3",
|
"pnpm-lock.yaml": "f1209ac70e9c2294e0f300c0aad88b64aeecd6aa19dfc929c5766c7d033615a3",
|
||||||
"scripts/build-host.mjs": "1210c12b05a31f4ed64439fe5f08750cb75605b30327210cd97584b64aadf6a8",
|
"scripts/build-host.mjs": "1210c12b05a31f4ed64439fe5f08750cb75605b30327210cd97584b64aadf6a8",
|
||||||
"scripts/build-integrity.mjs": "18e9e32c452ef6217d771675fa8b763464a27ac0cf192d5fbe5cb1682380b0d9",
|
"scripts/build-integrity.mjs": "18e9e32c452ef6217d771675fa8b763464a27ac0cf192d5fbe5cb1682380b0d9",
|
||||||
"src/agent-preset-install.ts": "1f397bb8e1c9a3f13cb7e4c442c3d13c42fce94fbc4eb1d90b85a0485bda48bc",
|
"src/agent-preset-install.ts": "1f397bb8e1c9a3f13cb7e4c442c3d13c42fce94fbc4eb1d90b85a0485bda48bc",
|
||||||
"src/index.ts": "f583927e67045d83362930d8c416d540c1d8b08b18b2ad78e2a3d72ea03df559",
|
"src/index.ts": "86d6ab02fb50b59de89e8ae84bb8c65978d715968f4c721f779e86b60d23a13e",
|
||||||
"src/netx/alarm-dispatch.ts": "9f476f1c620e40d3e2017554ec2fa8a1848f75d7e77e7c9ee5ee0464e970ba62",
|
"src/netx/alarm-dispatch.ts": "9f476f1c620e40d3e2017554ec2fa8a1848f75d7e77e7c9ee5ee0464e970ba62",
|
||||||
"src/netx/alarm-im.ts": "a75b37e379500cbb90f1f4b81d448d45a97fd9e744e507608910f510480cffaf",
|
"src/netx/alarm-im.ts": "a75b37e379500cbb90f1f4b81d448d45a97fd9e744e507608910f510480cffaf",
|
||||||
"src/netx/alarm-push-status.ts": "21f0efa33ea6f21e3ede35b4accbc6c0c4c78fea23631f1c5b80e6e9aef8b82f",
|
"src/netx/alarm-push-status.ts": "21f0efa33ea6f21e3ede35b4accbc6c0c4c78fea23631f1c5b80e6e9aef8b82f",
|
||||||
|
|
@ -246,10 +246,10 @@
|
||||||
"src/netx/alarm-session.ts": "0df97efc3debb262573c19ade84d9d50584d58b21fa2c5ae5badd382915f18e3",
|
"src/netx/alarm-session.ts": "0df97efc3debb262573c19ade84d9d50584d58b21fa2c5ae5badd382915f18e3",
|
||||||
"src/netx/capability-groups.ts": "3b7ac48ec3bbef91f324c24156af783d741dc5aeeb4db95d52eeafadbcbbed51",
|
"src/netx/capability-groups.ts": "3b7ac48ec3bbef91f324c24156af783d741dc5aeeb4db95d52eeafadbcbbed51",
|
||||||
"src/netx/config-values.ts": "7e8ca894d0b09f5e52686d6b293e49b46dd2cc13d714b0e0b103c3d07433e117",
|
"src/netx/config-values.ts": "7e8ca894d0b09f5e52686d6b293e49b46dd2cc13d714b0e0b103c3d07433e117",
|
||||||
"src/netx/group-skills.ts": "84a35fe639b3670fd4e95e3e5801a78020551655fd66007a9f151e2f8619e9ac",
|
"src/netx/group-skills.ts": "3fa78686f4c035f056f3cb5ed2f0411ad52c043958b859ebfcc254a625554e3a",
|
||||||
"src/netx/group-tools-plugin.ts": "647e5258b46648705585cd5d3765556f391810434c6c7f04f3df9e2f44d8ba67",
|
"src/netx/group-tools-plugin.ts": "647e5258b46648705585cd5d3765556f391810434c6c7f04f3df9e2f44d8ba67",
|
||||||
"src/netx/heal-profile-entry.ts": "18c2b635f727bf41d38c7c38fcc0ce0c5391c9d98b2d662db354f1908defb6be",
|
"src/netx/heal-profile-entry.ts": "18c2b635f727bf41d38c7c38fcc0ce0c5391c9d98b2d662db354f1908defb6be",
|
||||||
"src/netx/http.ts": "aa0552d6087593924f448fc8efaadda92f8803147190741f5abc3030cf32c439",
|
"src/netx/http.ts": "982ca3e5da8fe36debd5a0c561578c8f2c5e2a9cc0527d7dbacdf4e5a4aa9edd",
|
||||||
"src/netx/i18n.ts": "9278e4adaa96cae413326f4dd8c796f2712d4749bcf92e5170967e38cee2d082",
|
"src/netx/i18n.ts": "9278e4adaa96cae413326f4dd8c796f2712d4749bcf92e5170967e38cee2d082",
|
||||||
"src/netx/im-targets.ts": "123613038dee8b14ce63ea46e88029287c1a8c527e6ba6cadab02f8c95b28917",
|
"src/netx/im-targets.ts": "123613038dee8b14ce63ea46e88029287c1a8c527e6ba6cadab02f8c95b28917",
|
||||||
"src/netx/json-safe.ts": "5fe610d5ec96d31096733f7e828b4b2266c19692535a232a644bb49784ee24d9",
|
"src/netx/json-safe.ts": "5fe610d5ec96d31096733f7e828b4b2266c19692535a232a644bb49784ee24d9",
|
||||||
|
|
@ -262,16 +262,16 @@
|
||||||
"src/netx/kb-read-ops.ts": "1359b370293b9f4afa51774ed729525d77756f1552cd46abf4d4d6c595eaeb86",
|
"src/netx/kb-read-ops.ts": "1359b370293b9f4afa51774ed729525d77756f1552cd46abf4d4d6c595eaeb86",
|
||||||
"src/netx/kb-runtime.ts": "ebf2fd36c407439f1a59344af18d9fe7275825925adbe08719595d06161d6d5b",
|
"src/netx/kb-runtime.ts": "ebf2fd36c407439f1a59344af18d9fe7275825925adbe08719595d06161d6d5b",
|
||||||
"src/netx/mcp-catalog.ts": "b79962da8f74f04595ed89b166b851d83cac41e8338b80b7629da2021a2c281d",
|
"src/netx/mcp-catalog.ts": "b79962da8f74f04595ed89b166b851d83cac41e8338b80b7629da2021a2c281d",
|
||||||
"src/netx/mcp-remote.ts": "4fb64d62c3947463d7ea174edb4ceed2a8bba090ad5bf8ede50654c423d7a91f",
|
"src/netx/mcp-remote.ts": "89576a02729f50b7f03ef5f689a5e9c8380fa6ad4e44c46e1c846eceadd04571",
|
||||||
"src/netx/mcp-schema.ts": "2bb2eccd001101bd97389578c627931e89fbab4e057dab500e8b7b8bd655366f",
|
"src/netx/mcp-schema.ts": "2bb2eccd001101bd97389578c627931e89fbab4e057dab500e8b7b8bd655366f",
|
||||||
"src/netx/mcp-store.ts": "002a5d432d4532ea0ed07f00801eea9ca8cb84d6ebcb347311967e13007fde32",
|
"src/netx/mcp-store.ts": "ea91f2171b93dc46c4d72cd811bbb03b16060db83abe21b6f71c68bbfb32d6ff",
|
||||||
"src/netx/mcp-types.ts": "4b30d6e8fba328b0e116c43a1a90d2b686ef4b9fc648b521063876426e9c2833",
|
"src/netx/mcp-types.ts": "4b30d6e8fba328b0e116c43a1a90d2b686ef4b9fc648b521063876426e9c2833",
|
||||||
"src/netx/model-language.ts": "07df448b407086237c184a71bfc78e7cb27e50d7f8c499995d8b368b14780c9e",
|
"src/netx/model-language.ts": "07df448b407086237c184a71bfc78e7cb27e50d7f8c499995d8b368b14780c9e",
|
||||||
"src/netx/netxops-web-rpc.ts": "c6981c91b0a300806d44f45708f07c5102ec40655e5d387394608e5472ad3214",
|
"src/netx/netxops-web-rpc.ts": "c6981c91b0a300806d44f45708f07c5102ec40655e5d387394608e5472ad3214",
|
||||||
"src/netx/preset-publication.ts": "22e357cb58bf72cb0d269246f9e3e083269feb1706a9c4f152eb042330c9e967",
|
"src/netx/preset-publication.ts": "22e357cb58bf72cb0d269246f9e3e083269feb1706a9c4f152eb042330c9e967",
|
||||||
"src/netx/runtime.ts": "238555c92c3972ee9de8ec062b14d6a86961ec4c64609565ba1164b1a460d386",
|
"src/netx/runtime.ts": "238555c92c3972ee9de8ec062b14d6a86961ec4c64609565ba1164b1a460d386",
|
||||||
"src/netx/session-export.ts": "8e6e9a3094045901563366fac3d6dc74d9bfd78bbff853358d6500955d3dcff6",
|
"src/netx/session-export.ts": "8e6e9a3094045901563366fac3d6dc74d9bfd78bbff853358d6500955d3dcff6",
|
||||||
"src/netx/skills-cache.ts": "10957061256cec6b381c5604745d905146abe5da1b418055414166879bb90094",
|
"src/netx/skills-cache.ts": "58383b578d7d3c5376016351b3f36466ac74faad810f835ea5af9733a729ef7f",
|
||||||
"src/netx/tool-mount-status.ts": "783981774d717ca5e7bf7c0ec7273b40787ef12a6b40749c74543b30fa390faf",
|
"src/netx/tool-mount-status.ts": "783981774d717ca5e7bf7c0ec7273b40787ef12a6b40749c74543b30fa390faf",
|
||||||
"src/netx/tools.ts": "88c925aba50a7d6b3fc800986789955fd77c7aca8387c399b1ef58edbf7b9139",
|
"src/netx/tools.ts": "88c925aba50a7d6b3fc800986789955fd77c7aca8387c399b1ef58edbf7b9139",
|
||||||
"src/session-export-shared.ts": "e30d884ec22a0ea2e016c1479f62a8bc8d7ddcce5369a36647d411d07a8620d5"
|
"src/session-export-shared.ts": "e30d884ec22a0ea2e016c1479f62a8bc8d7ddcce5369a36647d411d07a8620d5"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// src/netx/group-tools-plugin.ts
|
// src/netx/group-tools-plugin.ts
|
||||||
import { readFileSync as readFileSync6 } from "node:fs";
|
import { readFileSync as readFileSync7 } from "node:fs";
|
||||||
import { dirname as dirname6, join as join11 } from "node:path";
|
import { dirname as dirname6, join as join11 } from "node:path";
|
||||||
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
||||||
|
|
||||||
|
|
@ -267,6 +267,15 @@ function mcpContentText(content, fallback = "") {
|
||||||
|
|
||||||
// src/netx/skills-cache.ts
|
// src/netx/skills-cache.ts
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
|
import {
|
||||||
|
existsSync,
|
||||||
|
mkdirSync,
|
||||||
|
readdirSync,
|
||||||
|
readFileSync,
|
||||||
|
rmSync,
|
||||||
|
statSync,
|
||||||
|
writeFileSync
|
||||||
|
} from "node:fs";
|
||||||
import { homedir } from "node:os";
|
import { homedir } from "node:os";
|
||||||
import { dirname, join } from "node:path";
|
import { dirname, join } from "node:path";
|
||||||
|
|
||||||
|
|
@ -293,6 +302,7 @@ function normalizeApiUrl(raw) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/skills-cache.ts
|
// src/netx/skills-cache.ts
|
||||||
|
var STALE_CACHE_MS = 30 * 24 * 60 * 60 * 1000;
|
||||||
function dshHome() {
|
function dshHome() {
|
||||||
const fromEnv = process.env.DSH_HOME?.trim();
|
const fromEnv = process.env.DSH_HOME?.trim();
|
||||||
return fromEnv || join(homedir(), ".dsh");
|
return fromEnv || join(homedir(), ".dsh");
|
||||||
|
|
@ -305,9 +315,46 @@ function skillsCacheKey(apiUrl) {
|
||||||
const base = normalizeSkillsApiUrl(apiUrl);
|
const base = normalizeSkillsApiUrl(apiUrl);
|
||||||
return createHash("sha256").update(base).digest("hex").slice(0, 16);
|
return createHash("sha256").update(base).digest("hex").slice(0, 16);
|
||||||
}
|
}
|
||||||
function skillsCacheDir(apiUrl) {
|
function cacheParent() {
|
||||||
return join(dshHome(), "netxops-mcp-skills", skillsCacheKey(apiUrl));
|
return join(dshHome(), "netxops-mcp-skills");
|
||||||
}
|
}
|
||||||
|
function skillsCacheDir(apiUrl) {
|
||||||
|
return join(cacheParent(), skillsCacheKey(apiUrl));
|
||||||
|
}
|
||||||
|
function tokenFingerprint(token) {
|
||||||
|
const raw = token.trim();
|
||||||
|
return raw ? createHash("sha256").update(raw).digest("hex").slice(0, 16) : "";
|
||||||
|
}
|
||||||
|
function readMeta(root) {
|
||||||
|
try {
|
||||||
|
const meta = JSON.parse(readFileSync(join(root, ".meta.json"), "utf8"));
|
||||||
|
return meta && typeof meta === "object" && !Array.isArray(meta) ? meta : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function usableSkillsCacheDir(apiUrl, token) {
|
||||||
|
if (!apiUrl.trim())
|
||||||
|
return "";
|
||||||
|
const root = skillsCacheDir(apiUrl);
|
||||||
|
const pulledWith = readMeta(root)?.tokenFp;
|
||||||
|
if (pulledWith && pulledWith !== tokenFingerprint(token))
|
||||||
|
return "";
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
function cachedUnavailableTools(root) {
|
||||||
|
const raw = readMeta(root)?.unavailableTools;
|
||||||
|
const out = {};
|
||||||
|
if (!raw || typeof raw !== "object")
|
||||||
|
return out;
|
||||||
|
for (const group of CAPABILITY_GROUP_IDS) {
|
||||||
|
const names = raw[group];
|
||||||
|
if (Array.isArray(names))
|
||||||
|
out[group] = names.filter((n) => typeof n === "string");
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
var summaryMemo = new Map;
|
||||||
|
|
||||||
// src/netx/mcp-store.ts
|
// src/netx/mcp-store.ts
|
||||||
var STORE_KEY = Symbol.for("dsh-netxops.mcp-catalog-store");
|
var STORE_KEY = Symbol.for("dsh-netxops.mcp-catalog-store");
|
||||||
|
|
@ -512,12 +559,12 @@ function disabledFootnote(names, lang) {
|
||||||
const en = (lang ?? "").trim().toLowerCase().startsWith("en");
|
const en = (lang ?? "").trim().toLowerCase().startsWith("en");
|
||||||
return en ? `
|
return en ? `
|
||||||
|
|
||||||
## Disabled tools
|
## Unavailable tools
|
||||||
The following tools are disabled: ${list}. Do not call them.
|
The following tools are disabled or not granted to this token: ${list}. Do not call them; skip steps that need them.
|
||||||
` : `
|
` : `
|
||||||
|
|
||||||
## 已禁用工具
|
## 不可用工具
|
||||||
以下工具已禁用:${list},不要调用。
|
以下工具已禁用或当前 token 无权使用:${list},不要调用,涉及它们的步骤请跳过。
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
function resolveSkillsRoot(options) {
|
function resolveSkillsRoot(options) {
|
||||||
|
|
@ -527,7 +574,7 @@ function resolveSkillsRoot(options) {
|
||||||
const connection = getNetxConnection();
|
const connection = getNetxConnection();
|
||||||
if (!connection?.apiUrl?.trim())
|
if (!connection?.apiUrl?.trim())
|
||||||
return "";
|
return "";
|
||||||
return skillsCacheDir(connection.apiUrl);
|
return usableSkillsCacheDir(connection.apiUrl, connection.token ?? "");
|
||||||
}
|
}
|
||||||
async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions) {
|
async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions) {
|
||||||
const options = signalOrOptions instanceof AbortSignal || signalOrOptions === undefined ? { signal: signalOrOptions } : signalOrOptions;
|
const options = signalOrOptions instanceof AbortSignal || signalOrOptions === undefined ? { signal: signalOrOptions } : signalOrOptions;
|
||||||
|
|
@ -546,6 +593,7 @@ async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions
|
||||||
}
|
}
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
|
const unavailable = cachedUnavailableTools(root);
|
||||||
try {
|
try {
|
||||||
for (const groupId of CAPABILITY_GROUP_IDS) {
|
for (const groupId of CAPABILITY_GROUP_IDS) {
|
||||||
if (!enabled.has(groupId))
|
if (!enabled.has(groupId))
|
||||||
|
|
@ -559,7 +607,11 @@ async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions
|
||||||
if (skills.length === 0) {
|
if (skills.length === 0) {
|
||||||
log?.warn?.("%s: no skills loaded for group=%s from %s", providerLabel, groupId, join2(root, SKILL_DIR_BY_GROUP[groupId]));
|
log?.warn?.("%s: no skills loaded for group=%s from %s", providerLabel, groupId, join2(root, SKILL_DIR_BY_GROUP[groupId]));
|
||||||
}
|
}
|
||||||
const footnote = disabledFootnote(options.disabledByGroup?.[groupId] ?? [], options.lang);
|
const offNames = new Set([
|
||||||
|
...options.disabledByGroup?.[groupId] ?? [],
|
||||||
|
...(unavailable[groupId] ?? []).map(publicToolName)
|
||||||
|
]);
|
||||||
|
const footnote = disabledFootnote([...offNames].sort(), options.lang);
|
||||||
for (const skill of skills) {
|
for (const skill of skills) {
|
||||||
const base = groupId === "topology" ? `${skill.content}
|
const base = groupId === "topology" ? `${skill.content}
|
||||||
|
|
||||||
|
|
@ -593,11 +645,11 @@ Use only tools present in this session. Dual-unit/orbit/polish recipes require s
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-context-skill.ts
|
// src/netx/kb-context-skill.ts
|
||||||
import { readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync3 } from "node:fs";
|
import { readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync4 } from "node:fs";
|
||||||
import { join as join5 } from "node:path";
|
import { join as join5 } from "node:path";
|
||||||
|
|
||||||
// src/netx/kb-manifest.ts
|
// src/netx/kb-manifest.ts
|
||||||
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
||||||
import { basename, dirname as dirname2, join as join3, resolve } from "node:path";
|
import { basename, dirname as dirname2, join as join3, resolve } from "node:path";
|
||||||
var EMPTY_CONTENT = {
|
var EMPTY_CONTENT = {
|
||||||
hasRegions: false,
|
hasRegions: false,
|
||||||
|
|
@ -624,7 +676,7 @@ function resolveKbPaths(snapshot) {
|
||||||
const paths = { skills: "_skills", ...snapshot.paths };
|
const paths = { skills: "_skills", ...snapshot.paths };
|
||||||
return Object.entries(paths).filter((entry) => typeof entry[1] === "string" && entry[1].trim() !== "").map(([key, rel]) => {
|
return Object.entries(paths).filter((entry) => typeof entry[1] === "string" && entry[1].trim() !== "").map(([key, rel]) => {
|
||||||
const absolute = resolve(snapshot.realRoot, rel);
|
const absolute = resolve(snapshot.realRoot, rel);
|
||||||
return { key, relative: rel, absolute, exists: existsSync(absolute) };
|
return { key, relative: rel, absolute, exists: existsSync2(absolute) };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function renderKbPathTable(snapshot) {
|
function renderKbPathTable(snapshot) {
|
||||||
|
|
@ -640,7 +692,7 @@ function renderKbPathTable(snapshot) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-local-path.ts
|
// src/netx/kb-local-path.ts
|
||||||
import { existsSync as existsSync2, realpathSync, statSync as statSync2 } from "node:fs";
|
import { existsSync as existsSync3, realpathSync, statSync as statSync3 } from "node:fs";
|
||||||
import { join as join4, relative, resolve as resolve2, sep } from "node:path";
|
import { join as join4, relative, resolve as resolve2, sep } from "node:path";
|
||||||
var KB_LOCAL_WRITABLE_ROOTS = Object.freeze([
|
var KB_LOCAL_WRITABLE_ROOTS = Object.freeze([
|
||||||
"memories",
|
"memories",
|
||||||
|
|
@ -749,7 +801,7 @@ function resolveExistingWritableFile(localRoot, pathArg) {
|
||||||
}
|
}
|
||||||
const checked = resolveWritableLocalPath(localRoot, ...relative(localRoot, absolutePath).split(/[/\\]/).filter(Boolean));
|
const checked = resolveWritableLocalPath(localRoot, ...relative(localRoot, absolutePath).split(/[/\\]/).filter(Boolean));
|
||||||
try {
|
try {
|
||||||
if (existsSync2(checked.absolutePath)) {
|
if (existsSync3(checked.absolutePath)) {
|
||||||
const realFile = realpathSync(checked.absolutePath);
|
const realFile = realpathSync(checked.absolutePath);
|
||||||
const realLocal = realpathSync(localRoot);
|
const realLocal = realpathSync(localRoot);
|
||||||
const realRel = relative(realLocal, realFile);
|
const realRel = relative(realLocal, realFile);
|
||||||
|
|
@ -760,7 +812,7 @@ function resolveExistingWritableFile(localRoot, pathArg) {
|
||||||
if (!KB_LOCAL_WRITABLE_ROOTS.includes(top)) {
|
if (!KB_LOCAL_WRITABLE_ROOTS.includes(top)) {
|
||||||
throw new Error(`path not under writable roots: ${realRel}`);
|
throw new Error(`path not under writable roots: ${realRel}`);
|
||||||
}
|
}
|
||||||
if (!statSync2(realFile).isFile()) {
|
if (!statSync3(realFile).isFile()) {
|
||||||
throw new Error(`not a file: ${realFile}`);
|
throw new Error(`not a file: ${realFile}`);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
@ -813,7 +865,7 @@ function readKbIdentity(snapshot) {
|
||||||
const dir = join5(localRoot, "identity");
|
const dir = join5(localRoot, "identity");
|
||||||
let names;
|
let names;
|
||||||
try {
|
try {
|
||||||
names = readdirSync2(dir).filter((name) => /\.md$/i.test(name) && !/^readme\.md$/i.test(name)).sort();
|
names = readdirSync3(dir).filter((name) => /\.md$/i.test(name) && !/^readme\.md$/i.test(name)).sort();
|
||||||
} catch {
|
} catch {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
@ -825,9 +877,9 @@ function readKbIdentity(snapshot) {
|
||||||
const full = join5(dir, name);
|
const full = join5(dir, name);
|
||||||
let text;
|
let text;
|
||||||
try {
|
try {
|
||||||
if (!statSync3(full).isFile())
|
if (!statSync4(full).isFile())
|
||||||
continue;
|
continue;
|
||||||
text = readFileSync2(full, "utf8").replace(/^\uFEFF/, "").trim();
|
text = readFileSync3(full, "utf8").replace(/^\uFEFF/, "").trim();
|
||||||
} catch {
|
} catch {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -950,13 +1002,13 @@ import { defineTool } from "@deepseek-ai/dsh-tools";
|
||||||
|
|
||||||
// src/netx/kb-local-ops.ts
|
// src/netx/kb-local-ops.ts
|
||||||
import {
|
import {
|
||||||
existsSync as existsSync3,
|
existsSync as existsSync4,
|
||||||
mkdirSync,
|
mkdirSync as mkdirSync2,
|
||||||
readdirSync as readdirSync3,
|
readdirSync as readdirSync4,
|
||||||
readFileSync as readFileSync3,
|
readFileSync as readFileSync4,
|
||||||
rmSync,
|
rmSync as rmSync2,
|
||||||
statSync as statSync4,
|
statSync as statSync5,
|
||||||
writeFileSync
|
writeFileSync as writeFileSync2
|
||||||
} from "node:fs";
|
} from "node:fs";
|
||||||
import { dirname as dirname3, join as join6 } from "node:path";
|
import { dirname as dirname3, join as join6 } from "node:path";
|
||||||
function requireLocalRoot(snapshot) {
|
function requireLocalRoot(snapshot) {
|
||||||
|
|
@ -1004,12 +1056,12 @@ function createMemory(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = memoryFileName({ date: args.date, slug: args.slug });
|
const name = memoryFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, memoryDir(args.bucket), name);
|
const target = resolveWritableLocalPath(localRoot, memoryDir(args.bucket), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1023,12 +1075,12 @@ function createDraft(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = draftFileName({ date: args.date, slug: args.slug });
|
const name = draftFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, draftDir(args.domain), name);
|
const target = resolveWritableLocalPath(localRoot, draftDir(args.domain), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, ensureDraftBody(args.body), "utf8");
|
writeFileSync2(target.absolutePath, ensureDraftBody(args.body), "utf8");
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: existed ? "updated" : "created",
|
action: existed ? "updated" : "created",
|
||||||
|
|
@ -1040,12 +1092,12 @@ function createSuggestion(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = suggestionFileName({ date: args.date, slug: args.slug });
|
const name = suggestionFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, suggestionDir(args.kind), name);
|
const target = resolveWritableLocalPath(localRoot, suggestionDir(args.kind), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1059,12 +1111,12 @@ function createRef(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = refFileName(args.slug);
|
const name = refFileName(args.slug);
|
||||||
const target = resolveWritableLocalPath(localRoot, refDir(args.area, args.device), name);
|
const target = resolveWritableLocalPath(localRoot, refDir(args.area, args.device), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1077,13 +1129,13 @@ function createRef(snapshot, args) {
|
||||||
function updateLocalFile(snapshot, args) {
|
function updateLocalFile(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const target = resolveExistingWritableFile(localRoot, args.path);
|
const target = resolveExistingWritableFile(localRoot, args.path);
|
||||||
if (!existsSync3(target.absolutePath)) {
|
if (!existsSync4(target.absolutePath)) {
|
||||||
throw new Error(`file not found: ${target.relativePath}`);
|
throw new Error(`file not found: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
const body = target.writableRoot === "drafts" ? ensureDraftBody(args.body) : args.body.endsWith(`
|
const body = target.writableRoot === "drafts" ? ensureDraftBody(args.body) : args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`;
|
`;
|
||||||
writeFileSync(target.absolutePath, body, "utf8");
|
writeFileSync2(target.absolutePath, body, "utf8");
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: "updated",
|
action: "updated",
|
||||||
|
|
@ -1094,10 +1146,10 @@ function updateLocalFile(snapshot, args) {
|
||||||
function deleteLocalFile(snapshot, args) {
|
function deleteLocalFile(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const target = resolveExistingWritableFile(localRoot, args.path);
|
const target = resolveExistingWritableFile(localRoot, args.path);
|
||||||
if (!existsSync3(target.absolutePath)) {
|
if (!existsSync4(target.absolutePath)) {
|
||||||
throw new Error(`file not found: ${target.relativePath}`);
|
throw new Error(`file not found: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
rmSync(target.absolutePath, { force: false });
|
rmSync2(target.absolutePath, { force: false });
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: "deleted",
|
action: "deleted",
|
||||||
|
|
@ -1108,7 +1160,7 @@ function deleteLocalFile(snapshot, args) {
|
||||||
function walkFiles(dir, baseLocal, out) {
|
function walkFiles(dir, baseLocal, out) {
|
||||||
let entries;
|
let entries;
|
||||||
try {
|
try {
|
||||||
entries = readdirSync3(dir);
|
entries = readdirSync4(dir);
|
||||||
} catch {
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1118,7 +1170,7 @@ function walkFiles(dir, baseLocal, out) {
|
||||||
const full = join6(dir, name);
|
const full = join6(dir, name);
|
||||||
let st;
|
let st;
|
||||||
try {
|
try {
|
||||||
st = statSync4(full);
|
st = statSync5(full);
|
||||||
} catch {
|
} catch {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -1152,7 +1204,7 @@ function listLocalFiles(snapshot, args = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-read-ops.ts
|
// src/netx/kb-read-ops.ts
|
||||||
import { readdirSync as readdirSync4, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync as statSync5 } from "node:fs";
|
import { readdirSync as readdirSync5, readFileSync as readFileSync5, realpathSync as realpathSync2, statSync as statSync6 } from "node:fs";
|
||||||
import { extname, join as join7, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
import { extname, join as join7, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
||||||
var TEXT_EXTENSIONS = new Set([".md", ".markdown", ".txt", ".json", ".yml", ".yaml", ".csv", ".ps1", ".sh", ".cfg", ".conf", ".log"]);
|
var TEXT_EXTENSIONS = new Set([".md", ".markdown", ".txt", ".json", ".yml", ".yaml", ".csv", ".ps1", ".sh", ".cfg", ".conf", ".log"]);
|
||||||
var SKIP_DIRS = new Set([".git", "node_modules"]);
|
var SKIP_DIRS = new Set([".git", "node_modules"]);
|
||||||
|
|
@ -1197,14 +1249,14 @@ function resolveKbReadPath(snapshot, pathArg) {
|
||||||
}
|
}
|
||||||
function readKbFile(snapshot, args) {
|
function readKbFile(snapshot, args) {
|
||||||
const target = resolveKbReadPath(snapshot, args.path);
|
const target = resolveKbReadPath(snapshot, args.path);
|
||||||
const st = statSync5(target.absolutePath);
|
const st = statSync6(target.absolutePath);
|
||||||
if (st.isDirectory()) {
|
if (st.isDirectory()) {
|
||||||
const entries = readdirSync4(target.absolutePath, { withFileTypes: true }).filter((entry) => !SKIP_DIRS.has(entry.name)).map((entry) => {
|
const entries = readdirSync5(target.absolutePath, { withFileTypes: true }).filter((entry) => !SKIP_DIRS.has(entry.name)).map((entry) => {
|
||||||
if (entry.isDirectory())
|
if (entry.isDirectory())
|
||||||
return { name: entry.name, type: "dir" };
|
return { name: entry.name, type: "dir" };
|
||||||
let size;
|
let size;
|
||||||
try {
|
try {
|
||||||
size = statSync5(join7(target.absolutePath, entry.name)).size;
|
size = statSync6(join7(target.absolutePath, entry.name)).size;
|
||||||
} catch {}
|
} catch {}
|
||||||
return { name: entry.name, type: "file", size };
|
return { name: entry.name, type: "file", size };
|
||||||
}).sort((a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1);
|
}).sort((a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1);
|
||||||
|
|
@ -1213,7 +1265,7 @@ function readKbFile(snapshot, args) {
|
||||||
if (!TEXT_EXTENSIONS.has(extname(target.absolutePath).toLowerCase())) {
|
if (!TEXT_EXTENSIONS.has(extname(target.absolutePath).toLowerCase())) {
|
||||||
throw new Error(`not a text KB file: ${target.relativePath}`);
|
throw new Error(`not a text KB file: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
const lines = readFileSync4(target.absolutePath, "utf8").replace(/^\uFEFF/, "").replace(/\r\n?/g, `
|
const lines = readFileSync5(target.absolutePath, "utf8").replace(/^\uFEFF/, "").replace(/\r\n?/g, `
|
||||||
`).split(`
|
`).split(`
|
||||||
`);
|
`);
|
||||||
const start = Math.max(1, Math.floor(args.offset ?? 1));
|
const start = Math.max(1, Math.floor(args.offset ?? 1));
|
||||||
|
|
@ -1258,7 +1310,7 @@ function cachedText(path, mtimeMs, size) {
|
||||||
const hit = cache.files.get(path);
|
const hit = cache.files.get(path);
|
||||||
if (hit && hit.mtimeMs === mtimeMs && hit.size === size)
|
if (hit && hit.mtimeMs === mtimeMs && hit.size === size)
|
||||||
return hit;
|
return hit;
|
||||||
const text = readFileSync4(path, "utf8").replace(/^\uFEFF/, "");
|
const text = readFileSync5(path, "utf8").replace(/^\uFEFF/, "");
|
||||||
if (hit)
|
if (hit)
|
||||||
cache.chars -= hit.text.length;
|
cache.chars -= hit.text.length;
|
||||||
if (cache.chars + text.length > CACHE_MAX_CHARS) {
|
if (cache.chars + text.length > CACHE_MAX_CHARS) {
|
||||||
|
|
@ -1273,7 +1325,7 @@ function cachedText(path, mtimeMs, size) {
|
||||||
function walkTextFiles(dir, out) {
|
function walkTextFiles(dir, out) {
|
||||||
let entries;
|
let entries;
|
||||||
try {
|
try {
|
||||||
entries = readdirSync4(dir, { withFileTypes: true });
|
entries = readdirSync5(dir, { withFileTypes: true });
|
||||||
} catch {
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1288,7 +1340,7 @@ function walkTextFiles(dir, out) {
|
||||||
if (!entry.isFile() || !TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase()))
|
if (!entry.isFile() || !TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase()))
|
||||||
continue;
|
continue;
|
||||||
try {
|
try {
|
||||||
const st = statSync5(full);
|
const st = statSync6(full);
|
||||||
if (st.size <= SEARCH_MAX_FILE_BYTES)
|
if (st.size <= SEARCH_MAX_FILE_BYTES)
|
||||||
out.push({ path: full, mtimeMs: st.mtimeMs, size: st.size });
|
out.push({ path: full, mtimeMs: st.mtimeMs, size: st.size });
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
@ -1318,7 +1370,7 @@ function searchKb(snapshot, args) {
|
||||||
const files = [];
|
const files = [];
|
||||||
const seen = new Set;
|
const seen = new Set;
|
||||||
for (const dir of dirs) {
|
for (const dir of dirs) {
|
||||||
const st = statSync5(dir);
|
const st = statSync6(dir);
|
||||||
const batch = [];
|
const batch = [];
|
||||||
if (st.isFile())
|
if (st.isFile())
|
||||||
batch.push({ path: dir, mtimeMs: st.mtimeMs, size: st.size });
|
batch.push({ path: dir, mtimeMs: st.mtimeMs, size: st.size });
|
||||||
|
|
@ -1432,7 +1484,7 @@ function toLosslessJson(value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/tool-mount-status.ts
|
// src/netx/tool-mount-status.ts
|
||||||
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
||||||
import { homedir as homedir2 } from "node:os";
|
import { homedir as homedir2 } from "node:os";
|
||||||
import { dirname as dirname4, join as join8 } from "node:path";
|
import { dirname as dirname4, join as join8 } from "node:path";
|
||||||
var KEY = Symbol.for("dsh-netxops.mount-inventory");
|
var KEY = Symbol.for("dsh-netxops.mount-inventory");
|
||||||
|
|
@ -1459,8 +1511,8 @@ function getToolMountStatus() {
|
||||||
function persist() {
|
function persist() {
|
||||||
try {
|
try {
|
||||||
const path = join8(process.env.DSH_HOME?.trim() || join8(homedir2(), ".dsh"), "netxops-mount-status.json");
|
const path = join8(process.env.DSH_HOME?.trim() || join8(homedir2(), ".dsh"), "netxops-mount-status.json");
|
||||||
mkdirSync2(dirname4(path), { recursive: true });
|
mkdirSync3(dirname4(path), { recursive: true });
|
||||||
writeFileSync2(path, `${JSON.stringify({ mount: getToolMountStatus() ?? null, mounts: getToolMountStatuses() }, null, 2)}
|
writeFileSync3(path, `${JSON.stringify({ mount: getToolMountStatus() ?? null, mounts: getToolMountStatuses() }, null, 2)}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
@ -1811,7 +1863,7 @@ async function registerKbPackSkills(ctx, snapshot, options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/tools.ts
|
// src/netx/tools.ts
|
||||||
import { readFileSync as readFileSync5 } from "node:fs";
|
import { readFileSync as readFileSync6 } from "node:fs";
|
||||||
import { dirname as dirname5, join as join10 } from "node:path";
|
import { dirname as dirname5, join as join10 } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
|
import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
|
||||||
|
|
@ -1916,7 +1968,7 @@ function parametersFromInputSchema(schema) {
|
||||||
function packageVersion() {
|
function packageVersion() {
|
||||||
try {
|
try {
|
||||||
const here = dirname5(fileURLToPath(import.meta.url));
|
const here = dirname5(fileURLToPath(import.meta.url));
|
||||||
const pkg = JSON.parse(readFileSync5(join10(here, "..", "package.json"), "utf8"));
|
const pkg = JSON.parse(readFileSync6(join10(here, "..", "package.json"), "utf8"));
|
||||||
return typeof pkg.version === "string" ? pkg.version : "0";
|
return typeof pkg.version === "string" ? pkg.version : "0";
|
||||||
} catch {
|
} catch {
|
||||||
return "0";
|
return "0";
|
||||||
|
|
@ -2048,7 +2100,7 @@ function registerNetxTools(ctx, connection, options) {
|
||||||
function packageVersion2() {
|
function packageVersion2() {
|
||||||
try {
|
try {
|
||||||
const here = dirname6(fileURLToPath2(import.meta.url));
|
const here = dirname6(fileURLToPath2(import.meta.url));
|
||||||
const pkg = JSON.parse(readFileSync6(join11(here, "..", "package.json"), "utf8"));
|
const pkg = JSON.parse(readFileSync7(join11(here, "..", "package.json"), "utf8"));
|
||||||
return typeof pkg.version === "string" ? pkg.version : "0";
|
return typeof pkg.version === "string" ? pkg.version : "0";
|
||||||
} catch {
|
} catch {
|
||||||
return "0";
|
return "0";
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// src/netx/group-tools-plugin.ts
|
// src/netx/group-tools-plugin.ts
|
||||||
import { readFileSync as readFileSync6 } from "node:fs";
|
import { readFileSync as readFileSync7 } from "node:fs";
|
||||||
import { dirname as dirname6, join as join11 } from "node:path";
|
import { dirname as dirname6, join as join11 } from "node:path";
|
||||||
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
||||||
|
|
||||||
|
|
@ -267,6 +267,15 @@ function mcpContentText(content, fallback = "") {
|
||||||
|
|
||||||
// src/netx/skills-cache.ts
|
// src/netx/skills-cache.ts
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
|
import {
|
||||||
|
existsSync,
|
||||||
|
mkdirSync,
|
||||||
|
readdirSync,
|
||||||
|
readFileSync,
|
||||||
|
rmSync,
|
||||||
|
statSync,
|
||||||
|
writeFileSync
|
||||||
|
} from "node:fs";
|
||||||
import { homedir } from "node:os";
|
import { homedir } from "node:os";
|
||||||
import { dirname, join } from "node:path";
|
import { dirname, join } from "node:path";
|
||||||
|
|
||||||
|
|
@ -293,6 +302,7 @@ function normalizeApiUrl(raw) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/skills-cache.ts
|
// src/netx/skills-cache.ts
|
||||||
|
var STALE_CACHE_MS = 30 * 24 * 60 * 60 * 1000;
|
||||||
function dshHome() {
|
function dshHome() {
|
||||||
const fromEnv = process.env.DSH_HOME?.trim();
|
const fromEnv = process.env.DSH_HOME?.trim();
|
||||||
return fromEnv || join(homedir(), ".dsh");
|
return fromEnv || join(homedir(), ".dsh");
|
||||||
|
|
@ -305,9 +315,46 @@ function skillsCacheKey(apiUrl) {
|
||||||
const base = normalizeSkillsApiUrl(apiUrl);
|
const base = normalizeSkillsApiUrl(apiUrl);
|
||||||
return createHash("sha256").update(base).digest("hex").slice(0, 16);
|
return createHash("sha256").update(base).digest("hex").slice(0, 16);
|
||||||
}
|
}
|
||||||
function skillsCacheDir(apiUrl) {
|
function cacheParent() {
|
||||||
return join(dshHome(), "netxops-mcp-skills", skillsCacheKey(apiUrl));
|
return join(dshHome(), "netxops-mcp-skills");
|
||||||
}
|
}
|
||||||
|
function skillsCacheDir(apiUrl) {
|
||||||
|
return join(cacheParent(), skillsCacheKey(apiUrl));
|
||||||
|
}
|
||||||
|
function tokenFingerprint(token) {
|
||||||
|
const raw = token.trim();
|
||||||
|
return raw ? createHash("sha256").update(raw).digest("hex").slice(0, 16) : "";
|
||||||
|
}
|
||||||
|
function readMeta(root) {
|
||||||
|
try {
|
||||||
|
const meta = JSON.parse(readFileSync(join(root, ".meta.json"), "utf8"));
|
||||||
|
return meta && typeof meta === "object" && !Array.isArray(meta) ? meta : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function usableSkillsCacheDir(apiUrl, token) {
|
||||||
|
if (!apiUrl.trim())
|
||||||
|
return "";
|
||||||
|
const root = skillsCacheDir(apiUrl);
|
||||||
|
const pulledWith = readMeta(root)?.tokenFp;
|
||||||
|
if (pulledWith && pulledWith !== tokenFingerprint(token))
|
||||||
|
return "";
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
function cachedUnavailableTools(root) {
|
||||||
|
const raw = readMeta(root)?.unavailableTools;
|
||||||
|
const out = {};
|
||||||
|
if (!raw || typeof raw !== "object")
|
||||||
|
return out;
|
||||||
|
for (const group of CAPABILITY_GROUP_IDS) {
|
||||||
|
const names = raw[group];
|
||||||
|
if (Array.isArray(names))
|
||||||
|
out[group] = names.filter((n) => typeof n === "string");
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
var summaryMemo = new Map;
|
||||||
|
|
||||||
// src/netx/mcp-store.ts
|
// src/netx/mcp-store.ts
|
||||||
var STORE_KEY = Symbol.for("dsh-netxops.mcp-catalog-store");
|
var STORE_KEY = Symbol.for("dsh-netxops.mcp-catalog-store");
|
||||||
|
|
@ -512,12 +559,12 @@ function disabledFootnote(names, lang) {
|
||||||
const en = (lang ?? "").trim().toLowerCase().startsWith("en");
|
const en = (lang ?? "").trim().toLowerCase().startsWith("en");
|
||||||
return en ? `
|
return en ? `
|
||||||
|
|
||||||
## Disabled tools
|
## Unavailable tools
|
||||||
The following tools are disabled: ${list}. Do not call them.
|
The following tools are disabled or not granted to this token: ${list}. Do not call them; skip steps that need them.
|
||||||
` : `
|
` : `
|
||||||
|
|
||||||
## 已禁用工具
|
## 不可用工具
|
||||||
以下工具已禁用:${list},不要调用。
|
以下工具已禁用或当前 token 无权使用:${list},不要调用,涉及它们的步骤请跳过。
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
function resolveSkillsRoot(options) {
|
function resolveSkillsRoot(options) {
|
||||||
|
|
@ -527,7 +574,7 @@ function resolveSkillsRoot(options) {
|
||||||
const connection = getNetxConnection();
|
const connection = getNetxConnection();
|
||||||
if (!connection?.apiUrl?.trim())
|
if (!connection?.apiUrl?.trim())
|
||||||
return "";
|
return "";
|
||||||
return skillsCacheDir(connection.apiUrl);
|
return usableSkillsCacheDir(connection.apiUrl, connection.token ?? "");
|
||||||
}
|
}
|
||||||
async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions) {
|
async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions) {
|
||||||
const options = signalOrOptions instanceof AbortSignal || signalOrOptions === undefined ? { signal: signalOrOptions } : signalOrOptions;
|
const options = signalOrOptions instanceof AbortSignal || signalOrOptions === undefined ? { signal: signalOrOptions } : signalOrOptions;
|
||||||
|
|
@ -546,6 +593,7 @@ async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions
|
||||||
}
|
}
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
|
const unavailable = cachedUnavailableTools(root);
|
||||||
try {
|
try {
|
||||||
for (const groupId of CAPABILITY_GROUP_IDS) {
|
for (const groupId of CAPABILITY_GROUP_IDS) {
|
||||||
if (!enabled.has(groupId))
|
if (!enabled.has(groupId))
|
||||||
|
|
@ -559,7 +607,11 @@ async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions
|
||||||
if (skills.length === 0) {
|
if (skills.length === 0) {
|
||||||
log?.warn?.("%s: no skills loaded for group=%s from %s", providerLabel, groupId, join2(root, SKILL_DIR_BY_GROUP[groupId]));
|
log?.warn?.("%s: no skills loaded for group=%s from %s", providerLabel, groupId, join2(root, SKILL_DIR_BY_GROUP[groupId]));
|
||||||
}
|
}
|
||||||
const footnote = disabledFootnote(options.disabledByGroup?.[groupId] ?? [], options.lang);
|
const offNames = new Set([
|
||||||
|
...options.disabledByGroup?.[groupId] ?? [],
|
||||||
|
...(unavailable[groupId] ?? []).map(publicToolName)
|
||||||
|
]);
|
||||||
|
const footnote = disabledFootnote([...offNames].sort(), options.lang);
|
||||||
for (const skill of skills) {
|
for (const skill of skills) {
|
||||||
const base = groupId === "topology" ? `${skill.content}
|
const base = groupId === "topology" ? `${skill.content}
|
||||||
|
|
||||||
|
|
@ -593,11 +645,11 @@ Use only tools present in this session. Dual-unit/orbit/polish recipes require s
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-context-skill.ts
|
// src/netx/kb-context-skill.ts
|
||||||
import { readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync3 } from "node:fs";
|
import { readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync4 } from "node:fs";
|
||||||
import { join as join5 } from "node:path";
|
import { join as join5 } from "node:path";
|
||||||
|
|
||||||
// src/netx/kb-manifest.ts
|
// src/netx/kb-manifest.ts
|
||||||
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
||||||
import { basename, dirname as dirname2, join as join3, resolve } from "node:path";
|
import { basename, dirname as dirname2, join as join3, resolve } from "node:path";
|
||||||
var EMPTY_CONTENT = {
|
var EMPTY_CONTENT = {
|
||||||
hasRegions: false,
|
hasRegions: false,
|
||||||
|
|
@ -624,7 +676,7 @@ function resolveKbPaths(snapshot) {
|
||||||
const paths = { skills: "_skills", ...snapshot.paths };
|
const paths = { skills: "_skills", ...snapshot.paths };
|
||||||
return Object.entries(paths).filter((entry) => typeof entry[1] === "string" && entry[1].trim() !== "").map(([key, rel]) => {
|
return Object.entries(paths).filter((entry) => typeof entry[1] === "string" && entry[1].trim() !== "").map(([key, rel]) => {
|
||||||
const absolute = resolve(snapshot.realRoot, rel);
|
const absolute = resolve(snapshot.realRoot, rel);
|
||||||
return { key, relative: rel, absolute, exists: existsSync(absolute) };
|
return { key, relative: rel, absolute, exists: existsSync2(absolute) };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function renderKbPathTable(snapshot) {
|
function renderKbPathTable(snapshot) {
|
||||||
|
|
@ -640,7 +692,7 @@ function renderKbPathTable(snapshot) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-local-path.ts
|
// src/netx/kb-local-path.ts
|
||||||
import { existsSync as existsSync2, realpathSync, statSync as statSync2 } from "node:fs";
|
import { existsSync as existsSync3, realpathSync, statSync as statSync3 } from "node:fs";
|
||||||
import { join as join4, relative, resolve as resolve2, sep } from "node:path";
|
import { join as join4, relative, resolve as resolve2, sep } from "node:path";
|
||||||
var KB_LOCAL_WRITABLE_ROOTS = Object.freeze([
|
var KB_LOCAL_WRITABLE_ROOTS = Object.freeze([
|
||||||
"memories",
|
"memories",
|
||||||
|
|
@ -749,7 +801,7 @@ function resolveExistingWritableFile(localRoot, pathArg) {
|
||||||
}
|
}
|
||||||
const checked = resolveWritableLocalPath(localRoot, ...relative(localRoot, absolutePath).split(/[/\\]/).filter(Boolean));
|
const checked = resolveWritableLocalPath(localRoot, ...relative(localRoot, absolutePath).split(/[/\\]/).filter(Boolean));
|
||||||
try {
|
try {
|
||||||
if (existsSync2(checked.absolutePath)) {
|
if (existsSync3(checked.absolutePath)) {
|
||||||
const realFile = realpathSync(checked.absolutePath);
|
const realFile = realpathSync(checked.absolutePath);
|
||||||
const realLocal = realpathSync(localRoot);
|
const realLocal = realpathSync(localRoot);
|
||||||
const realRel = relative(realLocal, realFile);
|
const realRel = relative(realLocal, realFile);
|
||||||
|
|
@ -760,7 +812,7 @@ function resolveExistingWritableFile(localRoot, pathArg) {
|
||||||
if (!KB_LOCAL_WRITABLE_ROOTS.includes(top)) {
|
if (!KB_LOCAL_WRITABLE_ROOTS.includes(top)) {
|
||||||
throw new Error(`path not under writable roots: ${realRel}`);
|
throw new Error(`path not under writable roots: ${realRel}`);
|
||||||
}
|
}
|
||||||
if (!statSync2(realFile).isFile()) {
|
if (!statSync3(realFile).isFile()) {
|
||||||
throw new Error(`not a file: ${realFile}`);
|
throw new Error(`not a file: ${realFile}`);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
@ -813,7 +865,7 @@ function readKbIdentity(snapshot) {
|
||||||
const dir = join5(localRoot, "identity");
|
const dir = join5(localRoot, "identity");
|
||||||
let names;
|
let names;
|
||||||
try {
|
try {
|
||||||
names = readdirSync2(dir).filter((name) => /\.md$/i.test(name) && !/^readme\.md$/i.test(name)).sort();
|
names = readdirSync3(dir).filter((name) => /\.md$/i.test(name) && !/^readme\.md$/i.test(name)).sort();
|
||||||
} catch {
|
} catch {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
@ -825,9 +877,9 @@ function readKbIdentity(snapshot) {
|
||||||
const full = join5(dir, name);
|
const full = join5(dir, name);
|
||||||
let text;
|
let text;
|
||||||
try {
|
try {
|
||||||
if (!statSync3(full).isFile())
|
if (!statSync4(full).isFile())
|
||||||
continue;
|
continue;
|
||||||
text = readFileSync2(full, "utf8").replace(/^\uFEFF/, "").trim();
|
text = readFileSync3(full, "utf8").replace(/^\uFEFF/, "").trim();
|
||||||
} catch {
|
} catch {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -950,13 +1002,13 @@ import { defineTool } from "@deepseek-ai/dsh-tools";
|
||||||
|
|
||||||
// src/netx/kb-local-ops.ts
|
// src/netx/kb-local-ops.ts
|
||||||
import {
|
import {
|
||||||
existsSync as existsSync3,
|
existsSync as existsSync4,
|
||||||
mkdirSync,
|
mkdirSync as mkdirSync2,
|
||||||
readdirSync as readdirSync3,
|
readdirSync as readdirSync4,
|
||||||
readFileSync as readFileSync3,
|
readFileSync as readFileSync4,
|
||||||
rmSync,
|
rmSync as rmSync2,
|
||||||
statSync as statSync4,
|
statSync as statSync5,
|
||||||
writeFileSync
|
writeFileSync as writeFileSync2
|
||||||
} from "node:fs";
|
} from "node:fs";
|
||||||
import { dirname as dirname3, join as join6 } from "node:path";
|
import { dirname as dirname3, join as join6 } from "node:path";
|
||||||
function requireLocalRoot(snapshot) {
|
function requireLocalRoot(snapshot) {
|
||||||
|
|
@ -1004,12 +1056,12 @@ function createMemory(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = memoryFileName({ date: args.date, slug: args.slug });
|
const name = memoryFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, memoryDir(args.bucket), name);
|
const target = resolveWritableLocalPath(localRoot, memoryDir(args.bucket), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1023,12 +1075,12 @@ function createDraft(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = draftFileName({ date: args.date, slug: args.slug });
|
const name = draftFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, draftDir(args.domain), name);
|
const target = resolveWritableLocalPath(localRoot, draftDir(args.domain), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, ensureDraftBody(args.body), "utf8");
|
writeFileSync2(target.absolutePath, ensureDraftBody(args.body), "utf8");
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: existed ? "updated" : "created",
|
action: existed ? "updated" : "created",
|
||||||
|
|
@ -1040,12 +1092,12 @@ function createSuggestion(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = suggestionFileName({ date: args.date, slug: args.slug });
|
const name = suggestionFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, suggestionDir(args.kind), name);
|
const target = resolveWritableLocalPath(localRoot, suggestionDir(args.kind), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1059,12 +1111,12 @@ function createRef(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = refFileName(args.slug);
|
const name = refFileName(args.slug);
|
||||||
const target = resolveWritableLocalPath(localRoot, refDir(args.area, args.device), name);
|
const target = resolveWritableLocalPath(localRoot, refDir(args.area, args.device), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1077,13 +1129,13 @@ function createRef(snapshot, args) {
|
||||||
function updateLocalFile(snapshot, args) {
|
function updateLocalFile(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const target = resolveExistingWritableFile(localRoot, args.path);
|
const target = resolveExistingWritableFile(localRoot, args.path);
|
||||||
if (!existsSync3(target.absolutePath)) {
|
if (!existsSync4(target.absolutePath)) {
|
||||||
throw new Error(`file not found: ${target.relativePath}`);
|
throw new Error(`file not found: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
const body = target.writableRoot === "drafts" ? ensureDraftBody(args.body) : args.body.endsWith(`
|
const body = target.writableRoot === "drafts" ? ensureDraftBody(args.body) : args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`;
|
`;
|
||||||
writeFileSync(target.absolutePath, body, "utf8");
|
writeFileSync2(target.absolutePath, body, "utf8");
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: "updated",
|
action: "updated",
|
||||||
|
|
@ -1094,10 +1146,10 @@ function updateLocalFile(snapshot, args) {
|
||||||
function deleteLocalFile(snapshot, args) {
|
function deleteLocalFile(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const target = resolveExistingWritableFile(localRoot, args.path);
|
const target = resolveExistingWritableFile(localRoot, args.path);
|
||||||
if (!existsSync3(target.absolutePath)) {
|
if (!existsSync4(target.absolutePath)) {
|
||||||
throw new Error(`file not found: ${target.relativePath}`);
|
throw new Error(`file not found: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
rmSync(target.absolutePath, { force: false });
|
rmSync2(target.absolutePath, { force: false });
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: "deleted",
|
action: "deleted",
|
||||||
|
|
@ -1108,7 +1160,7 @@ function deleteLocalFile(snapshot, args) {
|
||||||
function walkFiles(dir, baseLocal, out) {
|
function walkFiles(dir, baseLocal, out) {
|
||||||
let entries;
|
let entries;
|
||||||
try {
|
try {
|
||||||
entries = readdirSync3(dir);
|
entries = readdirSync4(dir);
|
||||||
} catch {
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1118,7 +1170,7 @@ function walkFiles(dir, baseLocal, out) {
|
||||||
const full = join6(dir, name);
|
const full = join6(dir, name);
|
||||||
let st;
|
let st;
|
||||||
try {
|
try {
|
||||||
st = statSync4(full);
|
st = statSync5(full);
|
||||||
} catch {
|
} catch {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -1152,7 +1204,7 @@ function listLocalFiles(snapshot, args = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-read-ops.ts
|
// src/netx/kb-read-ops.ts
|
||||||
import { readdirSync as readdirSync4, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync as statSync5 } from "node:fs";
|
import { readdirSync as readdirSync5, readFileSync as readFileSync5, realpathSync as realpathSync2, statSync as statSync6 } from "node:fs";
|
||||||
import { extname, join as join7, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
import { extname, join as join7, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
||||||
var TEXT_EXTENSIONS = new Set([".md", ".markdown", ".txt", ".json", ".yml", ".yaml", ".csv", ".ps1", ".sh", ".cfg", ".conf", ".log"]);
|
var TEXT_EXTENSIONS = new Set([".md", ".markdown", ".txt", ".json", ".yml", ".yaml", ".csv", ".ps1", ".sh", ".cfg", ".conf", ".log"]);
|
||||||
var SKIP_DIRS = new Set([".git", "node_modules"]);
|
var SKIP_DIRS = new Set([".git", "node_modules"]);
|
||||||
|
|
@ -1197,14 +1249,14 @@ function resolveKbReadPath(snapshot, pathArg) {
|
||||||
}
|
}
|
||||||
function readKbFile(snapshot, args) {
|
function readKbFile(snapshot, args) {
|
||||||
const target = resolveKbReadPath(snapshot, args.path);
|
const target = resolveKbReadPath(snapshot, args.path);
|
||||||
const st = statSync5(target.absolutePath);
|
const st = statSync6(target.absolutePath);
|
||||||
if (st.isDirectory()) {
|
if (st.isDirectory()) {
|
||||||
const entries = readdirSync4(target.absolutePath, { withFileTypes: true }).filter((entry) => !SKIP_DIRS.has(entry.name)).map((entry) => {
|
const entries = readdirSync5(target.absolutePath, { withFileTypes: true }).filter((entry) => !SKIP_DIRS.has(entry.name)).map((entry) => {
|
||||||
if (entry.isDirectory())
|
if (entry.isDirectory())
|
||||||
return { name: entry.name, type: "dir" };
|
return { name: entry.name, type: "dir" };
|
||||||
let size;
|
let size;
|
||||||
try {
|
try {
|
||||||
size = statSync5(join7(target.absolutePath, entry.name)).size;
|
size = statSync6(join7(target.absolutePath, entry.name)).size;
|
||||||
} catch {}
|
} catch {}
|
||||||
return { name: entry.name, type: "file", size };
|
return { name: entry.name, type: "file", size };
|
||||||
}).sort((a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1);
|
}).sort((a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1);
|
||||||
|
|
@ -1213,7 +1265,7 @@ function readKbFile(snapshot, args) {
|
||||||
if (!TEXT_EXTENSIONS.has(extname(target.absolutePath).toLowerCase())) {
|
if (!TEXT_EXTENSIONS.has(extname(target.absolutePath).toLowerCase())) {
|
||||||
throw new Error(`not a text KB file: ${target.relativePath}`);
|
throw new Error(`not a text KB file: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
const lines = readFileSync4(target.absolutePath, "utf8").replace(/^\uFEFF/, "").replace(/\r\n?/g, `
|
const lines = readFileSync5(target.absolutePath, "utf8").replace(/^\uFEFF/, "").replace(/\r\n?/g, `
|
||||||
`).split(`
|
`).split(`
|
||||||
`);
|
`);
|
||||||
const start = Math.max(1, Math.floor(args.offset ?? 1));
|
const start = Math.max(1, Math.floor(args.offset ?? 1));
|
||||||
|
|
@ -1258,7 +1310,7 @@ function cachedText(path, mtimeMs, size) {
|
||||||
const hit = cache.files.get(path);
|
const hit = cache.files.get(path);
|
||||||
if (hit && hit.mtimeMs === mtimeMs && hit.size === size)
|
if (hit && hit.mtimeMs === mtimeMs && hit.size === size)
|
||||||
return hit;
|
return hit;
|
||||||
const text = readFileSync4(path, "utf8").replace(/^\uFEFF/, "");
|
const text = readFileSync5(path, "utf8").replace(/^\uFEFF/, "");
|
||||||
if (hit)
|
if (hit)
|
||||||
cache.chars -= hit.text.length;
|
cache.chars -= hit.text.length;
|
||||||
if (cache.chars + text.length > CACHE_MAX_CHARS) {
|
if (cache.chars + text.length > CACHE_MAX_CHARS) {
|
||||||
|
|
@ -1273,7 +1325,7 @@ function cachedText(path, mtimeMs, size) {
|
||||||
function walkTextFiles(dir, out) {
|
function walkTextFiles(dir, out) {
|
||||||
let entries;
|
let entries;
|
||||||
try {
|
try {
|
||||||
entries = readdirSync4(dir, { withFileTypes: true });
|
entries = readdirSync5(dir, { withFileTypes: true });
|
||||||
} catch {
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1288,7 +1340,7 @@ function walkTextFiles(dir, out) {
|
||||||
if (!entry.isFile() || !TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase()))
|
if (!entry.isFile() || !TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase()))
|
||||||
continue;
|
continue;
|
||||||
try {
|
try {
|
||||||
const st = statSync5(full);
|
const st = statSync6(full);
|
||||||
if (st.size <= SEARCH_MAX_FILE_BYTES)
|
if (st.size <= SEARCH_MAX_FILE_BYTES)
|
||||||
out.push({ path: full, mtimeMs: st.mtimeMs, size: st.size });
|
out.push({ path: full, mtimeMs: st.mtimeMs, size: st.size });
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
@ -1318,7 +1370,7 @@ function searchKb(snapshot, args) {
|
||||||
const files = [];
|
const files = [];
|
||||||
const seen = new Set;
|
const seen = new Set;
|
||||||
for (const dir of dirs) {
|
for (const dir of dirs) {
|
||||||
const st = statSync5(dir);
|
const st = statSync6(dir);
|
||||||
const batch = [];
|
const batch = [];
|
||||||
if (st.isFile())
|
if (st.isFile())
|
||||||
batch.push({ path: dir, mtimeMs: st.mtimeMs, size: st.size });
|
batch.push({ path: dir, mtimeMs: st.mtimeMs, size: st.size });
|
||||||
|
|
@ -1432,7 +1484,7 @@ function toLosslessJson(value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/tool-mount-status.ts
|
// src/netx/tool-mount-status.ts
|
||||||
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
||||||
import { homedir as homedir2 } from "node:os";
|
import { homedir as homedir2 } from "node:os";
|
||||||
import { dirname as dirname4, join as join8 } from "node:path";
|
import { dirname as dirname4, join as join8 } from "node:path";
|
||||||
var KEY = Symbol.for("dsh-netxops.mount-inventory");
|
var KEY = Symbol.for("dsh-netxops.mount-inventory");
|
||||||
|
|
@ -1459,8 +1511,8 @@ function getToolMountStatus() {
|
||||||
function persist() {
|
function persist() {
|
||||||
try {
|
try {
|
||||||
const path = join8(process.env.DSH_HOME?.trim() || join8(homedir2(), ".dsh"), "netxops-mount-status.json");
|
const path = join8(process.env.DSH_HOME?.trim() || join8(homedir2(), ".dsh"), "netxops-mount-status.json");
|
||||||
mkdirSync2(dirname4(path), { recursive: true });
|
mkdirSync3(dirname4(path), { recursive: true });
|
||||||
writeFileSync2(path, `${JSON.stringify({ mount: getToolMountStatus() ?? null, mounts: getToolMountStatuses() }, null, 2)}
|
writeFileSync3(path, `${JSON.stringify({ mount: getToolMountStatus() ?? null, mounts: getToolMountStatuses() }, null, 2)}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
@ -1811,7 +1863,7 @@ async function registerKbPackSkills(ctx, snapshot, options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/tools.ts
|
// src/netx/tools.ts
|
||||||
import { readFileSync as readFileSync5 } from "node:fs";
|
import { readFileSync as readFileSync6 } from "node:fs";
|
||||||
import { dirname as dirname5, join as join10 } from "node:path";
|
import { dirname as dirname5, join as join10 } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
|
import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
|
||||||
|
|
@ -1916,7 +1968,7 @@ function parametersFromInputSchema(schema) {
|
||||||
function packageVersion() {
|
function packageVersion() {
|
||||||
try {
|
try {
|
||||||
const here = dirname5(fileURLToPath(import.meta.url));
|
const here = dirname5(fileURLToPath(import.meta.url));
|
||||||
const pkg = JSON.parse(readFileSync5(join10(here, "..", "package.json"), "utf8"));
|
const pkg = JSON.parse(readFileSync6(join10(here, "..", "package.json"), "utf8"));
|
||||||
return typeof pkg.version === "string" ? pkg.version : "0";
|
return typeof pkg.version === "string" ? pkg.version : "0";
|
||||||
} catch {
|
} catch {
|
||||||
return "0";
|
return "0";
|
||||||
|
|
@ -2048,7 +2100,7 @@ function registerNetxTools(ctx, connection, options) {
|
||||||
function packageVersion2() {
|
function packageVersion2() {
|
||||||
try {
|
try {
|
||||||
const here = dirname6(fileURLToPath2(import.meta.url));
|
const here = dirname6(fileURLToPath2(import.meta.url));
|
||||||
const pkg = JSON.parse(readFileSync6(join11(here, "..", "package.json"), "utf8"));
|
const pkg = JSON.parse(readFileSync7(join11(here, "..", "package.json"), "utf8"));
|
||||||
return typeof pkg.version === "string" ? pkg.version : "0";
|
return typeof pkg.version === "string" ? pkg.version : "0";
|
||||||
} catch {
|
} catch {
|
||||||
return "0";
|
return "0";
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// src/netx/group-tools-plugin.ts
|
// src/netx/group-tools-plugin.ts
|
||||||
import { readFileSync as readFileSync6 } from "node:fs";
|
import { readFileSync as readFileSync7 } from "node:fs";
|
||||||
import { dirname as dirname6, join as join11 } from "node:path";
|
import { dirname as dirname6, join as join11 } from "node:path";
|
||||||
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
||||||
|
|
||||||
|
|
@ -267,6 +267,15 @@ function mcpContentText(content, fallback = "") {
|
||||||
|
|
||||||
// src/netx/skills-cache.ts
|
// src/netx/skills-cache.ts
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
|
import {
|
||||||
|
existsSync,
|
||||||
|
mkdirSync,
|
||||||
|
readdirSync,
|
||||||
|
readFileSync,
|
||||||
|
rmSync,
|
||||||
|
statSync,
|
||||||
|
writeFileSync
|
||||||
|
} from "node:fs";
|
||||||
import { homedir } from "node:os";
|
import { homedir } from "node:os";
|
||||||
import { dirname, join } from "node:path";
|
import { dirname, join } from "node:path";
|
||||||
|
|
||||||
|
|
@ -293,6 +302,7 @@ function normalizeApiUrl(raw) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/skills-cache.ts
|
// src/netx/skills-cache.ts
|
||||||
|
var STALE_CACHE_MS = 30 * 24 * 60 * 60 * 1000;
|
||||||
function dshHome() {
|
function dshHome() {
|
||||||
const fromEnv = process.env.DSH_HOME?.trim();
|
const fromEnv = process.env.DSH_HOME?.trim();
|
||||||
return fromEnv || join(homedir(), ".dsh");
|
return fromEnv || join(homedir(), ".dsh");
|
||||||
|
|
@ -305,9 +315,46 @@ function skillsCacheKey(apiUrl) {
|
||||||
const base = normalizeSkillsApiUrl(apiUrl);
|
const base = normalizeSkillsApiUrl(apiUrl);
|
||||||
return createHash("sha256").update(base).digest("hex").slice(0, 16);
|
return createHash("sha256").update(base).digest("hex").slice(0, 16);
|
||||||
}
|
}
|
||||||
function skillsCacheDir(apiUrl) {
|
function cacheParent() {
|
||||||
return join(dshHome(), "netxops-mcp-skills", skillsCacheKey(apiUrl));
|
return join(dshHome(), "netxops-mcp-skills");
|
||||||
}
|
}
|
||||||
|
function skillsCacheDir(apiUrl) {
|
||||||
|
return join(cacheParent(), skillsCacheKey(apiUrl));
|
||||||
|
}
|
||||||
|
function tokenFingerprint(token) {
|
||||||
|
const raw = token.trim();
|
||||||
|
return raw ? createHash("sha256").update(raw).digest("hex").slice(0, 16) : "";
|
||||||
|
}
|
||||||
|
function readMeta(root) {
|
||||||
|
try {
|
||||||
|
const meta = JSON.parse(readFileSync(join(root, ".meta.json"), "utf8"));
|
||||||
|
return meta && typeof meta === "object" && !Array.isArray(meta) ? meta : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function usableSkillsCacheDir(apiUrl, token) {
|
||||||
|
if (!apiUrl.trim())
|
||||||
|
return "";
|
||||||
|
const root = skillsCacheDir(apiUrl);
|
||||||
|
const pulledWith = readMeta(root)?.tokenFp;
|
||||||
|
if (pulledWith && pulledWith !== tokenFingerprint(token))
|
||||||
|
return "";
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
function cachedUnavailableTools(root) {
|
||||||
|
const raw = readMeta(root)?.unavailableTools;
|
||||||
|
const out = {};
|
||||||
|
if (!raw || typeof raw !== "object")
|
||||||
|
return out;
|
||||||
|
for (const group of CAPABILITY_GROUP_IDS) {
|
||||||
|
const names = raw[group];
|
||||||
|
if (Array.isArray(names))
|
||||||
|
out[group] = names.filter((n) => typeof n === "string");
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
var summaryMemo = new Map;
|
||||||
|
|
||||||
// src/netx/mcp-store.ts
|
// src/netx/mcp-store.ts
|
||||||
var STORE_KEY = Symbol.for("dsh-netxops.mcp-catalog-store");
|
var STORE_KEY = Symbol.for("dsh-netxops.mcp-catalog-store");
|
||||||
|
|
@ -512,12 +559,12 @@ function disabledFootnote(names, lang) {
|
||||||
const en = (lang ?? "").trim().toLowerCase().startsWith("en");
|
const en = (lang ?? "").trim().toLowerCase().startsWith("en");
|
||||||
return en ? `
|
return en ? `
|
||||||
|
|
||||||
## Disabled tools
|
## Unavailable tools
|
||||||
The following tools are disabled: ${list}. Do not call them.
|
The following tools are disabled or not granted to this token: ${list}. Do not call them; skip steps that need them.
|
||||||
` : `
|
` : `
|
||||||
|
|
||||||
## 已禁用工具
|
## 不可用工具
|
||||||
以下工具已禁用:${list},不要调用。
|
以下工具已禁用或当前 token 无权使用:${list},不要调用,涉及它们的步骤请跳过。
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
function resolveSkillsRoot(options) {
|
function resolveSkillsRoot(options) {
|
||||||
|
|
@ -527,7 +574,7 @@ function resolveSkillsRoot(options) {
|
||||||
const connection = getNetxConnection();
|
const connection = getNetxConnection();
|
||||||
if (!connection?.apiUrl?.trim())
|
if (!connection?.apiUrl?.trim())
|
||||||
return "";
|
return "";
|
||||||
return skillsCacheDir(connection.apiUrl);
|
return usableSkillsCacheDir(connection.apiUrl, connection.token ?? "");
|
||||||
}
|
}
|
||||||
async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions) {
|
async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions) {
|
||||||
const options = signalOrOptions instanceof AbortSignal || signalOrOptions === undefined ? { signal: signalOrOptions } : signalOrOptions;
|
const options = signalOrOptions instanceof AbortSignal || signalOrOptions === undefined ? { signal: signalOrOptions } : signalOrOptions;
|
||||||
|
|
@ -546,6 +593,7 @@ async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions
|
||||||
}
|
}
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
|
const unavailable = cachedUnavailableTools(root);
|
||||||
try {
|
try {
|
||||||
for (const groupId of CAPABILITY_GROUP_IDS) {
|
for (const groupId of CAPABILITY_GROUP_IDS) {
|
||||||
if (!enabled.has(groupId))
|
if (!enabled.has(groupId))
|
||||||
|
|
@ -559,7 +607,11 @@ async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions
|
||||||
if (skills.length === 0) {
|
if (skills.length === 0) {
|
||||||
log?.warn?.("%s: no skills loaded for group=%s from %s", providerLabel, groupId, join2(root, SKILL_DIR_BY_GROUP[groupId]));
|
log?.warn?.("%s: no skills loaded for group=%s from %s", providerLabel, groupId, join2(root, SKILL_DIR_BY_GROUP[groupId]));
|
||||||
}
|
}
|
||||||
const footnote = disabledFootnote(options.disabledByGroup?.[groupId] ?? [], options.lang);
|
const offNames = new Set([
|
||||||
|
...options.disabledByGroup?.[groupId] ?? [],
|
||||||
|
...(unavailable[groupId] ?? []).map(publicToolName)
|
||||||
|
]);
|
||||||
|
const footnote = disabledFootnote([...offNames].sort(), options.lang);
|
||||||
for (const skill of skills) {
|
for (const skill of skills) {
|
||||||
const base = groupId === "topology" ? `${skill.content}
|
const base = groupId === "topology" ? `${skill.content}
|
||||||
|
|
||||||
|
|
@ -593,11 +645,11 @@ Use only tools present in this session. Dual-unit/orbit/polish recipes require s
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-context-skill.ts
|
// src/netx/kb-context-skill.ts
|
||||||
import { readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync3 } from "node:fs";
|
import { readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync4 } from "node:fs";
|
||||||
import { join as join5 } from "node:path";
|
import { join as join5 } from "node:path";
|
||||||
|
|
||||||
// src/netx/kb-manifest.ts
|
// src/netx/kb-manifest.ts
|
||||||
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
||||||
import { basename, dirname as dirname2, join as join3, resolve } from "node:path";
|
import { basename, dirname as dirname2, join as join3, resolve } from "node:path";
|
||||||
var EMPTY_CONTENT = {
|
var EMPTY_CONTENT = {
|
||||||
hasRegions: false,
|
hasRegions: false,
|
||||||
|
|
@ -624,7 +676,7 @@ function resolveKbPaths(snapshot) {
|
||||||
const paths = { skills: "_skills", ...snapshot.paths };
|
const paths = { skills: "_skills", ...snapshot.paths };
|
||||||
return Object.entries(paths).filter((entry) => typeof entry[1] === "string" && entry[1].trim() !== "").map(([key, rel]) => {
|
return Object.entries(paths).filter((entry) => typeof entry[1] === "string" && entry[1].trim() !== "").map(([key, rel]) => {
|
||||||
const absolute = resolve(snapshot.realRoot, rel);
|
const absolute = resolve(snapshot.realRoot, rel);
|
||||||
return { key, relative: rel, absolute, exists: existsSync(absolute) };
|
return { key, relative: rel, absolute, exists: existsSync2(absolute) };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function renderKbPathTable(snapshot) {
|
function renderKbPathTable(snapshot) {
|
||||||
|
|
@ -640,7 +692,7 @@ function renderKbPathTable(snapshot) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-local-path.ts
|
// src/netx/kb-local-path.ts
|
||||||
import { existsSync as existsSync2, realpathSync, statSync as statSync2 } from "node:fs";
|
import { existsSync as existsSync3, realpathSync, statSync as statSync3 } from "node:fs";
|
||||||
import { join as join4, relative, resolve as resolve2, sep } from "node:path";
|
import { join as join4, relative, resolve as resolve2, sep } from "node:path";
|
||||||
var KB_LOCAL_WRITABLE_ROOTS = Object.freeze([
|
var KB_LOCAL_WRITABLE_ROOTS = Object.freeze([
|
||||||
"memories",
|
"memories",
|
||||||
|
|
@ -749,7 +801,7 @@ function resolveExistingWritableFile(localRoot, pathArg) {
|
||||||
}
|
}
|
||||||
const checked = resolveWritableLocalPath(localRoot, ...relative(localRoot, absolutePath).split(/[/\\]/).filter(Boolean));
|
const checked = resolveWritableLocalPath(localRoot, ...relative(localRoot, absolutePath).split(/[/\\]/).filter(Boolean));
|
||||||
try {
|
try {
|
||||||
if (existsSync2(checked.absolutePath)) {
|
if (existsSync3(checked.absolutePath)) {
|
||||||
const realFile = realpathSync(checked.absolutePath);
|
const realFile = realpathSync(checked.absolutePath);
|
||||||
const realLocal = realpathSync(localRoot);
|
const realLocal = realpathSync(localRoot);
|
||||||
const realRel = relative(realLocal, realFile);
|
const realRel = relative(realLocal, realFile);
|
||||||
|
|
@ -760,7 +812,7 @@ function resolveExistingWritableFile(localRoot, pathArg) {
|
||||||
if (!KB_LOCAL_WRITABLE_ROOTS.includes(top)) {
|
if (!KB_LOCAL_WRITABLE_ROOTS.includes(top)) {
|
||||||
throw new Error(`path not under writable roots: ${realRel}`);
|
throw new Error(`path not under writable roots: ${realRel}`);
|
||||||
}
|
}
|
||||||
if (!statSync2(realFile).isFile()) {
|
if (!statSync3(realFile).isFile()) {
|
||||||
throw new Error(`not a file: ${realFile}`);
|
throw new Error(`not a file: ${realFile}`);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
@ -813,7 +865,7 @@ function readKbIdentity(snapshot) {
|
||||||
const dir = join5(localRoot, "identity");
|
const dir = join5(localRoot, "identity");
|
||||||
let names;
|
let names;
|
||||||
try {
|
try {
|
||||||
names = readdirSync2(dir).filter((name) => /\.md$/i.test(name) && !/^readme\.md$/i.test(name)).sort();
|
names = readdirSync3(dir).filter((name) => /\.md$/i.test(name) && !/^readme\.md$/i.test(name)).sort();
|
||||||
} catch {
|
} catch {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
@ -825,9 +877,9 @@ function readKbIdentity(snapshot) {
|
||||||
const full = join5(dir, name);
|
const full = join5(dir, name);
|
||||||
let text;
|
let text;
|
||||||
try {
|
try {
|
||||||
if (!statSync3(full).isFile())
|
if (!statSync4(full).isFile())
|
||||||
continue;
|
continue;
|
||||||
text = readFileSync2(full, "utf8").replace(/^\uFEFF/, "").trim();
|
text = readFileSync3(full, "utf8").replace(/^\uFEFF/, "").trim();
|
||||||
} catch {
|
} catch {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -950,13 +1002,13 @@ import { defineTool } from "@deepseek-ai/dsh-tools";
|
||||||
|
|
||||||
// src/netx/kb-local-ops.ts
|
// src/netx/kb-local-ops.ts
|
||||||
import {
|
import {
|
||||||
existsSync as existsSync3,
|
existsSync as existsSync4,
|
||||||
mkdirSync,
|
mkdirSync as mkdirSync2,
|
||||||
readdirSync as readdirSync3,
|
readdirSync as readdirSync4,
|
||||||
readFileSync as readFileSync3,
|
readFileSync as readFileSync4,
|
||||||
rmSync,
|
rmSync as rmSync2,
|
||||||
statSync as statSync4,
|
statSync as statSync5,
|
||||||
writeFileSync
|
writeFileSync as writeFileSync2
|
||||||
} from "node:fs";
|
} from "node:fs";
|
||||||
import { dirname as dirname3, join as join6 } from "node:path";
|
import { dirname as dirname3, join as join6 } from "node:path";
|
||||||
function requireLocalRoot(snapshot) {
|
function requireLocalRoot(snapshot) {
|
||||||
|
|
@ -1004,12 +1056,12 @@ function createMemory(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = memoryFileName({ date: args.date, slug: args.slug });
|
const name = memoryFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, memoryDir(args.bucket), name);
|
const target = resolveWritableLocalPath(localRoot, memoryDir(args.bucket), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1023,12 +1075,12 @@ function createDraft(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = draftFileName({ date: args.date, slug: args.slug });
|
const name = draftFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, draftDir(args.domain), name);
|
const target = resolveWritableLocalPath(localRoot, draftDir(args.domain), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, ensureDraftBody(args.body), "utf8");
|
writeFileSync2(target.absolutePath, ensureDraftBody(args.body), "utf8");
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: existed ? "updated" : "created",
|
action: existed ? "updated" : "created",
|
||||||
|
|
@ -1040,12 +1092,12 @@ function createSuggestion(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = suggestionFileName({ date: args.date, slug: args.slug });
|
const name = suggestionFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, suggestionDir(args.kind), name);
|
const target = resolveWritableLocalPath(localRoot, suggestionDir(args.kind), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1059,12 +1111,12 @@ function createRef(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = refFileName(args.slug);
|
const name = refFileName(args.slug);
|
||||||
const target = resolveWritableLocalPath(localRoot, refDir(args.area, args.device), name);
|
const target = resolveWritableLocalPath(localRoot, refDir(args.area, args.device), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1077,13 +1129,13 @@ function createRef(snapshot, args) {
|
||||||
function updateLocalFile(snapshot, args) {
|
function updateLocalFile(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const target = resolveExistingWritableFile(localRoot, args.path);
|
const target = resolveExistingWritableFile(localRoot, args.path);
|
||||||
if (!existsSync3(target.absolutePath)) {
|
if (!existsSync4(target.absolutePath)) {
|
||||||
throw new Error(`file not found: ${target.relativePath}`);
|
throw new Error(`file not found: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
const body = target.writableRoot === "drafts" ? ensureDraftBody(args.body) : args.body.endsWith(`
|
const body = target.writableRoot === "drafts" ? ensureDraftBody(args.body) : args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`;
|
`;
|
||||||
writeFileSync(target.absolutePath, body, "utf8");
|
writeFileSync2(target.absolutePath, body, "utf8");
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: "updated",
|
action: "updated",
|
||||||
|
|
@ -1094,10 +1146,10 @@ function updateLocalFile(snapshot, args) {
|
||||||
function deleteLocalFile(snapshot, args) {
|
function deleteLocalFile(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const target = resolveExistingWritableFile(localRoot, args.path);
|
const target = resolveExistingWritableFile(localRoot, args.path);
|
||||||
if (!existsSync3(target.absolutePath)) {
|
if (!existsSync4(target.absolutePath)) {
|
||||||
throw new Error(`file not found: ${target.relativePath}`);
|
throw new Error(`file not found: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
rmSync(target.absolutePath, { force: false });
|
rmSync2(target.absolutePath, { force: false });
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: "deleted",
|
action: "deleted",
|
||||||
|
|
@ -1108,7 +1160,7 @@ function deleteLocalFile(snapshot, args) {
|
||||||
function walkFiles(dir, baseLocal, out) {
|
function walkFiles(dir, baseLocal, out) {
|
||||||
let entries;
|
let entries;
|
||||||
try {
|
try {
|
||||||
entries = readdirSync3(dir);
|
entries = readdirSync4(dir);
|
||||||
} catch {
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1118,7 +1170,7 @@ function walkFiles(dir, baseLocal, out) {
|
||||||
const full = join6(dir, name);
|
const full = join6(dir, name);
|
||||||
let st;
|
let st;
|
||||||
try {
|
try {
|
||||||
st = statSync4(full);
|
st = statSync5(full);
|
||||||
} catch {
|
} catch {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -1152,7 +1204,7 @@ function listLocalFiles(snapshot, args = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-read-ops.ts
|
// src/netx/kb-read-ops.ts
|
||||||
import { readdirSync as readdirSync4, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync as statSync5 } from "node:fs";
|
import { readdirSync as readdirSync5, readFileSync as readFileSync5, realpathSync as realpathSync2, statSync as statSync6 } from "node:fs";
|
||||||
import { extname, join as join7, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
import { extname, join as join7, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
||||||
var TEXT_EXTENSIONS = new Set([".md", ".markdown", ".txt", ".json", ".yml", ".yaml", ".csv", ".ps1", ".sh", ".cfg", ".conf", ".log"]);
|
var TEXT_EXTENSIONS = new Set([".md", ".markdown", ".txt", ".json", ".yml", ".yaml", ".csv", ".ps1", ".sh", ".cfg", ".conf", ".log"]);
|
||||||
var SKIP_DIRS = new Set([".git", "node_modules"]);
|
var SKIP_DIRS = new Set([".git", "node_modules"]);
|
||||||
|
|
@ -1197,14 +1249,14 @@ function resolveKbReadPath(snapshot, pathArg) {
|
||||||
}
|
}
|
||||||
function readKbFile(snapshot, args) {
|
function readKbFile(snapshot, args) {
|
||||||
const target = resolveKbReadPath(snapshot, args.path);
|
const target = resolveKbReadPath(snapshot, args.path);
|
||||||
const st = statSync5(target.absolutePath);
|
const st = statSync6(target.absolutePath);
|
||||||
if (st.isDirectory()) {
|
if (st.isDirectory()) {
|
||||||
const entries = readdirSync4(target.absolutePath, { withFileTypes: true }).filter((entry) => !SKIP_DIRS.has(entry.name)).map((entry) => {
|
const entries = readdirSync5(target.absolutePath, { withFileTypes: true }).filter((entry) => !SKIP_DIRS.has(entry.name)).map((entry) => {
|
||||||
if (entry.isDirectory())
|
if (entry.isDirectory())
|
||||||
return { name: entry.name, type: "dir" };
|
return { name: entry.name, type: "dir" };
|
||||||
let size;
|
let size;
|
||||||
try {
|
try {
|
||||||
size = statSync5(join7(target.absolutePath, entry.name)).size;
|
size = statSync6(join7(target.absolutePath, entry.name)).size;
|
||||||
} catch {}
|
} catch {}
|
||||||
return { name: entry.name, type: "file", size };
|
return { name: entry.name, type: "file", size };
|
||||||
}).sort((a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1);
|
}).sort((a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1);
|
||||||
|
|
@ -1213,7 +1265,7 @@ function readKbFile(snapshot, args) {
|
||||||
if (!TEXT_EXTENSIONS.has(extname(target.absolutePath).toLowerCase())) {
|
if (!TEXT_EXTENSIONS.has(extname(target.absolutePath).toLowerCase())) {
|
||||||
throw new Error(`not a text KB file: ${target.relativePath}`);
|
throw new Error(`not a text KB file: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
const lines = readFileSync4(target.absolutePath, "utf8").replace(/^\uFEFF/, "").replace(/\r\n?/g, `
|
const lines = readFileSync5(target.absolutePath, "utf8").replace(/^\uFEFF/, "").replace(/\r\n?/g, `
|
||||||
`).split(`
|
`).split(`
|
||||||
`);
|
`);
|
||||||
const start = Math.max(1, Math.floor(args.offset ?? 1));
|
const start = Math.max(1, Math.floor(args.offset ?? 1));
|
||||||
|
|
@ -1258,7 +1310,7 @@ function cachedText(path, mtimeMs, size) {
|
||||||
const hit = cache.files.get(path);
|
const hit = cache.files.get(path);
|
||||||
if (hit && hit.mtimeMs === mtimeMs && hit.size === size)
|
if (hit && hit.mtimeMs === mtimeMs && hit.size === size)
|
||||||
return hit;
|
return hit;
|
||||||
const text = readFileSync4(path, "utf8").replace(/^\uFEFF/, "");
|
const text = readFileSync5(path, "utf8").replace(/^\uFEFF/, "");
|
||||||
if (hit)
|
if (hit)
|
||||||
cache.chars -= hit.text.length;
|
cache.chars -= hit.text.length;
|
||||||
if (cache.chars + text.length > CACHE_MAX_CHARS) {
|
if (cache.chars + text.length > CACHE_MAX_CHARS) {
|
||||||
|
|
@ -1273,7 +1325,7 @@ function cachedText(path, mtimeMs, size) {
|
||||||
function walkTextFiles(dir, out) {
|
function walkTextFiles(dir, out) {
|
||||||
let entries;
|
let entries;
|
||||||
try {
|
try {
|
||||||
entries = readdirSync4(dir, { withFileTypes: true });
|
entries = readdirSync5(dir, { withFileTypes: true });
|
||||||
} catch {
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1288,7 +1340,7 @@ function walkTextFiles(dir, out) {
|
||||||
if (!entry.isFile() || !TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase()))
|
if (!entry.isFile() || !TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase()))
|
||||||
continue;
|
continue;
|
||||||
try {
|
try {
|
||||||
const st = statSync5(full);
|
const st = statSync6(full);
|
||||||
if (st.size <= SEARCH_MAX_FILE_BYTES)
|
if (st.size <= SEARCH_MAX_FILE_BYTES)
|
||||||
out.push({ path: full, mtimeMs: st.mtimeMs, size: st.size });
|
out.push({ path: full, mtimeMs: st.mtimeMs, size: st.size });
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
@ -1318,7 +1370,7 @@ function searchKb(snapshot, args) {
|
||||||
const files = [];
|
const files = [];
|
||||||
const seen = new Set;
|
const seen = new Set;
|
||||||
for (const dir of dirs) {
|
for (const dir of dirs) {
|
||||||
const st = statSync5(dir);
|
const st = statSync6(dir);
|
||||||
const batch = [];
|
const batch = [];
|
||||||
if (st.isFile())
|
if (st.isFile())
|
||||||
batch.push({ path: dir, mtimeMs: st.mtimeMs, size: st.size });
|
batch.push({ path: dir, mtimeMs: st.mtimeMs, size: st.size });
|
||||||
|
|
@ -1432,7 +1484,7 @@ function toLosslessJson(value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/tool-mount-status.ts
|
// src/netx/tool-mount-status.ts
|
||||||
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
||||||
import { homedir as homedir2 } from "node:os";
|
import { homedir as homedir2 } from "node:os";
|
||||||
import { dirname as dirname4, join as join8 } from "node:path";
|
import { dirname as dirname4, join as join8 } from "node:path";
|
||||||
var KEY = Symbol.for("dsh-netxops.mount-inventory");
|
var KEY = Symbol.for("dsh-netxops.mount-inventory");
|
||||||
|
|
@ -1459,8 +1511,8 @@ function getToolMountStatus() {
|
||||||
function persist() {
|
function persist() {
|
||||||
try {
|
try {
|
||||||
const path = join8(process.env.DSH_HOME?.trim() || join8(homedir2(), ".dsh"), "netxops-mount-status.json");
|
const path = join8(process.env.DSH_HOME?.trim() || join8(homedir2(), ".dsh"), "netxops-mount-status.json");
|
||||||
mkdirSync2(dirname4(path), { recursive: true });
|
mkdirSync3(dirname4(path), { recursive: true });
|
||||||
writeFileSync2(path, `${JSON.stringify({ mount: getToolMountStatus() ?? null, mounts: getToolMountStatuses() }, null, 2)}
|
writeFileSync3(path, `${JSON.stringify({ mount: getToolMountStatus() ?? null, mounts: getToolMountStatuses() }, null, 2)}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
@ -1811,7 +1863,7 @@ async function registerKbPackSkills(ctx, snapshot, options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/tools.ts
|
// src/netx/tools.ts
|
||||||
import { readFileSync as readFileSync5 } from "node:fs";
|
import { readFileSync as readFileSync6 } from "node:fs";
|
||||||
import { dirname as dirname5, join as join10 } from "node:path";
|
import { dirname as dirname5, join as join10 } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
|
import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
|
||||||
|
|
@ -1916,7 +1968,7 @@ function parametersFromInputSchema(schema) {
|
||||||
function packageVersion() {
|
function packageVersion() {
|
||||||
try {
|
try {
|
||||||
const here = dirname5(fileURLToPath(import.meta.url));
|
const here = dirname5(fileURLToPath(import.meta.url));
|
||||||
const pkg = JSON.parse(readFileSync5(join10(here, "..", "package.json"), "utf8"));
|
const pkg = JSON.parse(readFileSync6(join10(here, "..", "package.json"), "utf8"));
|
||||||
return typeof pkg.version === "string" ? pkg.version : "0";
|
return typeof pkg.version === "string" ? pkg.version : "0";
|
||||||
} catch {
|
} catch {
|
||||||
return "0";
|
return "0";
|
||||||
|
|
@ -2048,7 +2100,7 @@ function registerNetxTools(ctx, connection, options) {
|
||||||
function packageVersion2() {
|
function packageVersion2() {
|
||||||
try {
|
try {
|
||||||
const here = dirname6(fileURLToPath2(import.meta.url));
|
const here = dirname6(fileURLToPath2(import.meta.url));
|
||||||
const pkg = JSON.parse(readFileSync6(join11(here, "..", "package.json"), "utf8"));
|
const pkg = JSON.parse(readFileSync7(join11(here, "..", "package.json"), "utf8"));
|
||||||
return typeof pkg.version === "string" ? pkg.version : "0";
|
return typeof pkg.version === "string" ? pkg.version : "0";
|
||||||
} catch {
|
} catch {
|
||||||
return "0";
|
return "0";
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// src/netx/group-tools-plugin.ts
|
// src/netx/group-tools-plugin.ts
|
||||||
import { readFileSync as readFileSync6 } from "node:fs";
|
import { readFileSync as readFileSync7 } from "node:fs";
|
||||||
import { dirname as dirname6, join as join11 } from "node:path";
|
import { dirname as dirname6, join as join11 } from "node:path";
|
||||||
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
||||||
|
|
||||||
|
|
@ -267,6 +267,15 @@ function mcpContentText(content, fallback = "") {
|
||||||
|
|
||||||
// src/netx/skills-cache.ts
|
// src/netx/skills-cache.ts
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
|
import {
|
||||||
|
existsSync,
|
||||||
|
mkdirSync,
|
||||||
|
readdirSync,
|
||||||
|
readFileSync,
|
||||||
|
rmSync,
|
||||||
|
statSync,
|
||||||
|
writeFileSync
|
||||||
|
} from "node:fs";
|
||||||
import { homedir } from "node:os";
|
import { homedir } from "node:os";
|
||||||
import { dirname, join } from "node:path";
|
import { dirname, join } from "node:path";
|
||||||
|
|
||||||
|
|
@ -293,6 +302,7 @@ function normalizeApiUrl(raw) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/skills-cache.ts
|
// src/netx/skills-cache.ts
|
||||||
|
var STALE_CACHE_MS = 30 * 24 * 60 * 60 * 1000;
|
||||||
function dshHome() {
|
function dshHome() {
|
||||||
const fromEnv = process.env.DSH_HOME?.trim();
|
const fromEnv = process.env.DSH_HOME?.trim();
|
||||||
return fromEnv || join(homedir(), ".dsh");
|
return fromEnv || join(homedir(), ".dsh");
|
||||||
|
|
@ -305,9 +315,46 @@ function skillsCacheKey(apiUrl) {
|
||||||
const base = normalizeSkillsApiUrl(apiUrl);
|
const base = normalizeSkillsApiUrl(apiUrl);
|
||||||
return createHash("sha256").update(base).digest("hex").slice(0, 16);
|
return createHash("sha256").update(base).digest("hex").slice(0, 16);
|
||||||
}
|
}
|
||||||
function skillsCacheDir(apiUrl) {
|
function cacheParent() {
|
||||||
return join(dshHome(), "netxops-mcp-skills", skillsCacheKey(apiUrl));
|
return join(dshHome(), "netxops-mcp-skills");
|
||||||
}
|
}
|
||||||
|
function skillsCacheDir(apiUrl) {
|
||||||
|
return join(cacheParent(), skillsCacheKey(apiUrl));
|
||||||
|
}
|
||||||
|
function tokenFingerprint(token) {
|
||||||
|
const raw = token.trim();
|
||||||
|
return raw ? createHash("sha256").update(raw).digest("hex").slice(0, 16) : "";
|
||||||
|
}
|
||||||
|
function readMeta(root) {
|
||||||
|
try {
|
||||||
|
const meta = JSON.parse(readFileSync(join(root, ".meta.json"), "utf8"));
|
||||||
|
return meta && typeof meta === "object" && !Array.isArray(meta) ? meta : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function usableSkillsCacheDir(apiUrl, token) {
|
||||||
|
if (!apiUrl.trim())
|
||||||
|
return "";
|
||||||
|
const root = skillsCacheDir(apiUrl);
|
||||||
|
const pulledWith = readMeta(root)?.tokenFp;
|
||||||
|
if (pulledWith && pulledWith !== tokenFingerprint(token))
|
||||||
|
return "";
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
function cachedUnavailableTools(root) {
|
||||||
|
const raw = readMeta(root)?.unavailableTools;
|
||||||
|
const out = {};
|
||||||
|
if (!raw || typeof raw !== "object")
|
||||||
|
return out;
|
||||||
|
for (const group of CAPABILITY_GROUP_IDS) {
|
||||||
|
const names = raw[group];
|
||||||
|
if (Array.isArray(names))
|
||||||
|
out[group] = names.filter((n) => typeof n === "string");
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
var summaryMemo = new Map;
|
||||||
|
|
||||||
// src/netx/mcp-store.ts
|
// src/netx/mcp-store.ts
|
||||||
var STORE_KEY = Symbol.for("dsh-netxops.mcp-catalog-store");
|
var STORE_KEY = Symbol.for("dsh-netxops.mcp-catalog-store");
|
||||||
|
|
@ -512,12 +559,12 @@ function disabledFootnote(names, lang) {
|
||||||
const en = (lang ?? "").trim().toLowerCase().startsWith("en");
|
const en = (lang ?? "").trim().toLowerCase().startsWith("en");
|
||||||
return en ? `
|
return en ? `
|
||||||
|
|
||||||
## Disabled tools
|
## Unavailable tools
|
||||||
The following tools are disabled: ${list}. Do not call them.
|
The following tools are disabled or not granted to this token: ${list}. Do not call them; skip steps that need them.
|
||||||
` : `
|
` : `
|
||||||
|
|
||||||
## 已禁用工具
|
## 不可用工具
|
||||||
以下工具已禁用:${list},不要调用。
|
以下工具已禁用或当前 token 无权使用:${list},不要调用,涉及它们的步骤请跳过。
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
function resolveSkillsRoot(options) {
|
function resolveSkillsRoot(options) {
|
||||||
|
|
@ -527,7 +574,7 @@ function resolveSkillsRoot(options) {
|
||||||
const connection = getNetxConnection();
|
const connection = getNetxConnection();
|
||||||
if (!connection?.apiUrl?.trim())
|
if (!connection?.apiUrl?.trim())
|
||||||
return "";
|
return "";
|
||||||
return skillsCacheDir(connection.apiUrl);
|
return usableSkillsCacheDir(connection.apiUrl, connection.token ?? "");
|
||||||
}
|
}
|
||||||
async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions) {
|
async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions) {
|
||||||
const options = signalOrOptions instanceof AbortSignal || signalOrOptions === undefined ? { signal: signalOrOptions } : signalOrOptions;
|
const options = signalOrOptions instanceof AbortSignal || signalOrOptions === undefined ? { signal: signalOrOptions } : signalOrOptions;
|
||||||
|
|
@ -546,6 +593,7 @@ async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions
|
||||||
}
|
}
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
|
const unavailable = cachedUnavailableTools(root);
|
||||||
try {
|
try {
|
||||||
for (const groupId of CAPABILITY_GROUP_IDS) {
|
for (const groupId of CAPABILITY_GROUP_IDS) {
|
||||||
if (!enabled.has(groupId))
|
if (!enabled.has(groupId))
|
||||||
|
|
@ -559,7 +607,11 @@ async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions
|
||||||
if (skills.length === 0) {
|
if (skills.length === 0) {
|
||||||
log?.warn?.("%s: no skills loaded for group=%s from %s", providerLabel, groupId, join2(root, SKILL_DIR_BY_GROUP[groupId]));
|
log?.warn?.("%s: no skills loaded for group=%s from %s", providerLabel, groupId, join2(root, SKILL_DIR_BY_GROUP[groupId]));
|
||||||
}
|
}
|
||||||
const footnote = disabledFootnote(options.disabledByGroup?.[groupId] ?? [], options.lang);
|
const offNames = new Set([
|
||||||
|
...options.disabledByGroup?.[groupId] ?? [],
|
||||||
|
...(unavailable[groupId] ?? []).map(publicToolName)
|
||||||
|
]);
|
||||||
|
const footnote = disabledFootnote([...offNames].sort(), options.lang);
|
||||||
for (const skill of skills) {
|
for (const skill of skills) {
|
||||||
const base = groupId === "topology" ? `${skill.content}
|
const base = groupId === "topology" ? `${skill.content}
|
||||||
|
|
||||||
|
|
@ -593,11 +645,11 @@ Use only tools present in this session. Dual-unit/orbit/polish recipes require s
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-context-skill.ts
|
// src/netx/kb-context-skill.ts
|
||||||
import { readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync3 } from "node:fs";
|
import { readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync4 } from "node:fs";
|
||||||
import { join as join5 } from "node:path";
|
import { join as join5 } from "node:path";
|
||||||
|
|
||||||
// src/netx/kb-manifest.ts
|
// src/netx/kb-manifest.ts
|
||||||
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
||||||
import { basename, dirname as dirname2, join as join3, resolve } from "node:path";
|
import { basename, dirname as dirname2, join as join3, resolve } from "node:path";
|
||||||
var EMPTY_CONTENT = {
|
var EMPTY_CONTENT = {
|
||||||
hasRegions: false,
|
hasRegions: false,
|
||||||
|
|
@ -624,7 +676,7 @@ function resolveKbPaths(snapshot) {
|
||||||
const paths = { skills: "_skills", ...snapshot.paths };
|
const paths = { skills: "_skills", ...snapshot.paths };
|
||||||
return Object.entries(paths).filter((entry) => typeof entry[1] === "string" && entry[1].trim() !== "").map(([key, rel]) => {
|
return Object.entries(paths).filter((entry) => typeof entry[1] === "string" && entry[1].trim() !== "").map(([key, rel]) => {
|
||||||
const absolute = resolve(snapshot.realRoot, rel);
|
const absolute = resolve(snapshot.realRoot, rel);
|
||||||
return { key, relative: rel, absolute, exists: existsSync(absolute) };
|
return { key, relative: rel, absolute, exists: existsSync2(absolute) };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function renderKbPathTable(snapshot) {
|
function renderKbPathTable(snapshot) {
|
||||||
|
|
@ -640,7 +692,7 @@ function renderKbPathTable(snapshot) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-local-path.ts
|
// src/netx/kb-local-path.ts
|
||||||
import { existsSync as existsSync2, realpathSync, statSync as statSync2 } from "node:fs";
|
import { existsSync as existsSync3, realpathSync, statSync as statSync3 } from "node:fs";
|
||||||
import { join as join4, relative, resolve as resolve2, sep } from "node:path";
|
import { join as join4, relative, resolve as resolve2, sep } from "node:path";
|
||||||
var KB_LOCAL_WRITABLE_ROOTS = Object.freeze([
|
var KB_LOCAL_WRITABLE_ROOTS = Object.freeze([
|
||||||
"memories",
|
"memories",
|
||||||
|
|
@ -749,7 +801,7 @@ function resolveExistingWritableFile(localRoot, pathArg) {
|
||||||
}
|
}
|
||||||
const checked = resolveWritableLocalPath(localRoot, ...relative(localRoot, absolutePath).split(/[/\\]/).filter(Boolean));
|
const checked = resolveWritableLocalPath(localRoot, ...relative(localRoot, absolutePath).split(/[/\\]/).filter(Boolean));
|
||||||
try {
|
try {
|
||||||
if (existsSync2(checked.absolutePath)) {
|
if (existsSync3(checked.absolutePath)) {
|
||||||
const realFile = realpathSync(checked.absolutePath);
|
const realFile = realpathSync(checked.absolutePath);
|
||||||
const realLocal = realpathSync(localRoot);
|
const realLocal = realpathSync(localRoot);
|
||||||
const realRel = relative(realLocal, realFile);
|
const realRel = relative(realLocal, realFile);
|
||||||
|
|
@ -760,7 +812,7 @@ function resolveExistingWritableFile(localRoot, pathArg) {
|
||||||
if (!KB_LOCAL_WRITABLE_ROOTS.includes(top)) {
|
if (!KB_LOCAL_WRITABLE_ROOTS.includes(top)) {
|
||||||
throw new Error(`path not under writable roots: ${realRel}`);
|
throw new Error(`path not under writable roots: ${realRel}`);
|
||||||
}
|
}
|
||||||
if (!statSync2(realFile).isFile()) {
|
if (!statSync3(realFile).isFile()) {
|
||||||
throw new Error(`not a file: ${realFile}`);
|
throw new Error(`not a file: ${realFile}`);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
@ -813,7 +865,7 @@ function readKbIdentity(snapshot) {
|
||||||
const dir = join5(localRoot, "identity");
|
const dir = join5(localRoot, "identity");
|
||||||
let names;
|
let names;
|
||||||
try {
|
try {
|
||||||
names = readdirSync2(dir).filter((name) => /\.md$/i.test(name) && !/^readme\.md$/i.test(name)).sort();
|
names = readdirSync3(dir).filter((name) => /\.md$/i.test(name) && !/^readme\.md$/i.test(name)).sort();
|
||||||
} catch {
|
} catch {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
@ -825,9 +877,9 @@ function readKbIdentity(snapshot) {
|
||||||
const full = join5(dir, name);
|
const full = join5(dir, name);
|
||||||
let text;
|
let text;
|
||||||
try {
|
try {
|
||||||
if (!statSync3(full).isFile())
|
if (!statSync4(full).isFile())
|
||||||
continue;
|
continue;
|
||||||
text = readFileSync2(full, "utf8").replace(/^\uFEFF/, "").trim();
|
text = readFileSync3(full, "utf8").replace(/^\uFEFF/, "").trim();
|
||||||
} catch {
|
} catch {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -950,13 +1002,13 @@ import { defineTool } from "@deepseek-ai/dsh-tools";
|
||||||
|
|
||||||
// src/netx/kb-local-ops.ts
|
// src/netx/kb-local-ops.ts
|
||||||
import {
|
import {
|
||||||
existsSync as existsSync3,
|
existsSync as existsSync4,
|
||||||
mkdirSync,
|
mkdirSync as mkdirSync2,
|
||||||
readdirSync as readdirSync3,
|
readdirSync as readdirSync4,
|
||||||
readFileSync as readFileSync3,
|
readFileSync as readFileSync4,
|
||||||
rmSync,
|
rmSync as rmSync2,
|
||||||
statSync as statSync4,
|
statSync as statSync5,
|
||||||
writeFileSync
|
writeFileSync as writeFileSync2
|
||||||
} from "node:fs";
|
} from "node:fs";
|
||||||
import { dirname as dirname3, join as join6 } from "node:path";
|
import { dirname as dirname3, join as join6 } from "node:path";
|
||||||
function requireLocalRoot(snapshot) {
|
function requireLocalRoot(snapshot) {
|
||||||
|
|
@ -1004,12 +1056,12 @@ function createMemory(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = memoryFileName({ date: args.date, slug: args.slug });
|
const name = memoryFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, memoryDir(args.bucket), name);
|
const target = resolveWritableLocalPath(localRoot, memoryDir(args.bucket), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1023,12 +1075,12 @@ function createDraft(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = draftFileName({ date: args.date, slug: args.slug });
|
const name = draftFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, draftDir(args.domain), name);
|
const target = resolveWritableLocalPath(localRoot, draftDir(args.domain), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, ensureDraftBody(args.body), "utf8");
|
writeFileSync2(target.absolutePath, ensureDraftBody(args.body), "utf8");
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: existed ? "updated" : "created",
|
action: existed ? "updated" : "created",
|
||||||
|
|
@ -1040,12 +1092,12 @@ function createSuggestion(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = suggestionFileName({ date: args.date, slug: args.slug });
|
const name = suggestionFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, suggestionDir(args.kind), name);
|
const target = resolveWritableLocalPath(localRoot, suggestionDir(args.kind), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1059,12 +1111,12 @@ function createRef(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = refFileName(args.slug);
|
const name = refFileName(args.slug);
|
||||||
const target = resolveWritableLocalPath(localRoot, refDir(args.area, args.device), name);
|
const target = resolveWritableLocalPath(localRoot, refDir(args.area, args.device), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1077,13 +1129,13 @@ function createRef(snapshot, args) {
|
||||||
function updateLocalFile(snapshot, args) {
|
function updateLocalFile(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const target = resolveExistingWritableFile(localRoot, args.path);
|
const target = resolveExistingWritableFile(localRoot, args.path);
|
||||||
if (!existsSync3(target.absolutePath)) {
|
if (!existsSync4(target.absolutePath)) {
|
||||||
throw new Error(`file not found: ${target.relativePath}`);
|
throw new Error(`file not found: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
const body = target.writableRoot === "drafts" ? ensureDraftBody(args.body) : args.body.endsWith(`
|
const body = target.writableRoot === "drafts" ? ensureDraftBody(args.body) : args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`;
|
`;
|
||||||
writeFileSync(target.absolutePath, body, "utf8");
|
writeFileSync2(target.absolutePath, body, "utf8");
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: "updated",
|
action: "updated",
|
||||||
|
|
@ -1094,10 +1146,10 @@ function updateLocalFile(snapshot, args) {
|
||||||
function deleteLocalFile(snapshot, args) {
|
function deleteLocalFile(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const target = resolveExistingWritableFile(localRoot, args.path);
|
const target = resolveExistingWritableFile(localRoot, args.path);
|
||||||
if (!existsSync3(target.absolutePath)) {
|
if (!existsSync4(target.absolutePath)) {
|
||||||
throw new Error(`file not found: ${target.relativePath}`);
|
throw new Error(`file not found: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
rmSync(target.absolutePath, { force: false });
|
rmSync2(target.absolutePath, { force: false });
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: "deleted",
|
action: "deleted",
|
||||||
|
|
@ -1108,7 +1160,7 @@ function deleteLocalFile(snapshot, args) {
|
||||||
function walkFiles(dir, baseLocal, out) {
|
function walkFiles(dir, baseLocal, out) {
|
||||||
let entries;
|
let entries;
|
||||||
try {
|
try {
|
||||||
entries = readdirSync3(dir);
|
entries = readdirSync4(dir);
|
||||||
} catch {
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1118,7 +1170,7 @@ function walkFiles(dir, baseLocal, out) {
|
||||||
const full = join6(dir, name);
|
const full = join6(dir, name);
|
||||||
let st;
|
let st;
|
||||||
try {
|
try {
|
||||||
st = statSync4(full);
|
st = statSync5(full);
|
||||||
} catch {
|
} catch {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -1152,7 +1204,7 @@ function listLocalFiles(snapshot, args = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-read-ops.ts
|
// src/netx/kb-read-ops.ts
|
||||||
import { readdirSync as readdirSync4, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync as statSync5 } from "node:fs";
|
import { readdirSync as readdirSync5, readFileSync as readFileSync5, realpathSync as realpathSync2, statSync as statSync6 } from "node:fs";
|
||||||
import { extname, join as join7, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
import { extname, join as join7, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
||||||
var TEXT_EXTENSIONS = new Set([".md", ".markdown", ".txt", ".json", ".yml", ".yaml", ".csv", ".ps1", ".sh", ".cfg", ".conf", ".log"]);
|
var TEXT_EXTENSIONS = new Set([".md", ".markdown", ".txt", ".json", ".yml", ".yaml", ".csv", ".ps1", ".sh", ".cfg", ".conf", ".log"]);
|
||||||
var SKIP_DIRS = new Set([".git", "node_modules"]);
|
var SKIP_DIRS = new Set([".git", "node_modules"]);
|
||||||
|
|
@ -1197,14 +1249,14 @@ function resolveKbReadPath(snapshot, pathArg) {
|
||||||
}
|
}
|
||||||
function readKbFile(snapshot, args) {
|
function readKbFile(snapshot, args) {
|
||||||
const target = resolveKbReadPath(snapshot, args.path);
|
const target = resolveKbReadPath(snapshot, args.path);
|
||||||
const st = statSync5(target.absolutePath);
|
const st = statSync6(target.absolutePath);
|
||||||
if (st.isDirectory()) {
|
if (st.isDirectory()) {
|
||||||
const entries = readdirSync4(target.absolutePath, { withFileTypes: true }).filter((entry) => !SKIP_DIRS.has(entry.name)).map((entry) => {
|
const entries = readdirSync5(target.absolutePath, { withFileTypes: true }).filter((entry) => !SKIP_DIRS.has(entry.name)).map((entry) => {
|
||||||
if (entry.isDirectory())
|
if (entry.isDirectory())
|
||||||
return { name: entry.name, type: "dir" };
|
return { name: entry.name, type: "dir" };
|
||||||
let size;
|
let size;
|
||||||
try {
|
try {
|
||||||
size = statSync5(join7(target.absolutePath, entry.name)).size;
|
size = statSync6(join7(target.absolutePath, entry.name)).size;
|
||||||
} catch {}
|
} catch {}
|
||||||
return { name: entry.name, type: "file", size };
|
return { name: entry.name, type: "file", size };
|
||||||
}).sort((a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1);
|
}).sort((a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1);
|
||||||
|
|
@ -1213,7 +1265,7 @@ function readKbFile(snapshot, args) {
|
||||||
if (!TEXT_EXTENSIONS.has(extname(target.absolutePath).toLowerCase())) {
|
if (!TEXT_EXTENSIONS.has(extname(target.absolutePath).toLowerCase())) {
|
||||||
throw new Error(`not a text KB file: ${target.relativePath}`);
|
throw new Error(`not a text KB file: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
const lines = readFileSync4(target.absolutePath, "utf8").replace(/^\uFEFF/, "").replace(/\r\n?/g, `
|
const lines = readFileSync5(target.absolutePath, "utf8").replace(/^\uFEFF/, "").replace(/\r\n?/g, `
|
||||||
`).split(`
|
`).split(`
|
||||||
`);
|
`);
|
||||||
const start = Math.max(1, Math.floor(args.offset ?? 1));
|
const start = Math.max(1, Math.floor(args.offset ?? 1));
|
||||||
|
|
@ -1258,7 +1310,7 @@ function cachedText(path, mtimeMs, size) {
|
||||||
const hit = cache.files.get(path);
|
const hit = cache.files.get(path);
|
||||||
if (hit && hit.mtimeMs === mtimeMs && hit.size === size)
|
if (hit && hit.mtimeMs === mtimeMs && hit.size === size)
|
||||||
return hit;
|
return hit;
|
||||||
const text = readFileSync4(path, "utf8").replace(/^\uFEFF/, "");
|
const text = readFileSync5(path, "utf8").replace(/^\uFEFF/, "");
|
||||||
if (hit)
|
if (hit)
|
||||||
cache.chars -= hit.text.length;
|
cache.chars -= hit.text.length;
|
||||||
if (cache.chars + text.length > CACHE_MAX_CHARS) {
|
if (cache.chars + text.length > CACHE_MAX_CHARS) {
|
||||||
|
|
@ -1273,7 +1325,7 @@ function cachedText(path, mtimeMs, size) {
|
||||||
function walkTextFiles(dir, out) {
|
function walkTextFiles(dir, out) {
|
||||||
let entries;
|
let entries;
|
||||||
try {
|
try {
|
||||||
entries = readdirSync4(dir, { withFileTypes: true });
|
entries = readdirSync5(dir, { withFileTypes: true });
|
||||||
} catch {
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1288,7 +1340,7 @@ function walkTextFiles(dir, out) {
|
||||||
if (!entry.isFile() || !TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase()))
|
if (!entry.isFile() || !TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase()))
|
||||||
continue;
|
continue;
|
||||||
try {
|
try {
|
||||||
const st = statSync5(full);
|
const st = statSync6(full);
|
||||||
if (st.size <= SEARCH_MAX_FILE_BYTES)
|
if (st.size <= SEARCH_MAX_FILE_BYTES)
|
||||||
out.push({ path: full, mtimeMs: st.mtimeMs, size: st.size });
|
out.push({ path: full, mtimeMs: st.mtimeMs, size: st.size });
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
@ -1318,7 +1370,7 @@ function searchKb(snapshot, args) {
|
||||||
const files = [];
|
const files = [];
|
||||||
const seen = new Set;
|
const seen = new Set;
|
||||||
for (const dir of dirs) {
|
for (const dir of dirs) {
|
||||||
const st = statSync5(dir);
|
const st = statSync6(dir);
|
||||||
const batch = [];
|
const batch = [];
|
||||||
if (st.isFile())
|
if (st.isFile())
|
||||||
batch.push({ path: dir, mtimeMs: st.mtimeMs, size: st.size });
|
batch.push({ path: dir, mtimeMs: st.mtimeMs, size: st.size });
|
||||||
|
|
@ -1432,7 +1484,7 @@ function toLosslessJson(value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/tool-mount-status.ts
|
// src/netx/tool-mount-status.ts
|
||||||
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
||||||
import { homedir as homedir2 } from "node:os";
|
import { homedir as homedir2 } from "node:os";
|
||||||
import { dirname as dirname4, join as join8 } from "node:path";
|
import { dirname as dirname4, join as join8 } from "node:path";
|
||||||
var KEY = Symbol.for("dsh-netxops.mount-inventory");
|
var KEY = Symbol.for("dsh-netxops.mount-inventory");
|
||||||
|
|
@ -1459,8 +1511,8 @@ function getToolMountStatus() {
|
||||||
function persist() {
|
function persist() {
|
||||||
try {
|
try {
|
||||||
const path = join8(process.env.DSH_HOME?.trim() || join8(homedir2(), ".dsh"), "netxops-mount-status.json");
|
const path = join8(process.env.DSH_HOME?.trim() || join8(homedir2(), ".dsh"), "netxops-mount-status.json");
|
||||||
mkdirSync2(dirname4(path), { recursive: true });
|
mkdirSync3(dirname4(path), { recursive: true });
|
||||||
writeFileSync2(path, `${JSON.stringify({ mount: getToolMountStatus() ?? null, mounts: getToolMountStatuses() }, null, 2)}
|
writeFileSync3(path, `${JSON.stringify({ mount: getToolMountStatus() ?? null, mounts: getToolMountStatuses() }, null, 2)}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
@ -1811,7 +1863,7 @@ async function registerKbPackSkills(ctx, snapshot, options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/tools.ts
|
// src/netx/tools.ts
|
||||||
import { readFileSync as readFileSync5 } from "node:fs";
|
import { readFileSync as readFileSync6 } from "node:fs";
|
||||||
import { dirname as dirname5, join as join10 } from "node:path";
|
import { dirname as dirname5, join as join10 } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
|
import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
|
||||||
|
|
@ -1916,7 +1968,7 @@ function parametersFromInputSchema(schema) {
|
||||||
function packageVersion() {
|
function packageVersion() {
|
||||||
try {
|
try {
|
||||||
const here = dirname5(fileURLToPath(import.meta.url));
|
const here = dirname5(fileURLToPath(import.meta.url));
|
||||||
const pkg = JSON.parse(readFileSync5(join10(here, "..", "package.json"), "utf8"));
|
const pkg = JSON.parse(readFileSync6(join10(here, "..", "package.json"), "utf8"));
|
||||||
return typeof pkg.version === "string" ? pkg.version : "0";
|
return typeof pkg.version === "string" ? pkg.version : "0";
|
||||||
} catch {
|
} catch {
|
||||||
return "0";
|
return "0";
|
||||||
|
|
@ -2048,7 +2100,7 @@ function registerNetxTools(ctx, connection, options) {
|
||||||
function packageVersion2() {
|
function packageVersion2() {
|
||||||
try {
|
try {
|
||||||
const here = dirname6(fileURLToPath2(import.meta.url));
|
const here = dirname6(fileURLToPath2(import.meta.url));
|
||||||
const pkg = JSON.parse(readFileSync6(join11(here, "..", "package.json"), "utf8"));
|
const pkg = JSON.parse(readFileSync7(join11(here, "..", "package.json"), "utf8"));
|
||||||
return typeof pkg.version === "string" ? pkg.version : "0";
|
return typeof pkg.version === "string" ? pkg.version : "0";
|
||||||
} catch {
|
} catch {
|
||||||
return "0";
|
return "0";
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// src/netx/group-tools-plugin.ts
|
// src/netx/group-tools-plugin.ts
|
||||||
import { readFileSync as readFileSync6 } from "node:fs";
|
import { readFileSync as readFileSync7 } from "node:fs";
|
||||||
import { dirname as dirname6, join as join11 } from "node:path";
|
import { dirname as dirname6, join as join11 } from "node:path";
|
||||||
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
||||||
|
|
||||||
|
|
@ -267,6 +267,15 @@ function mcpContentText(content, fallback = "") {
|
||||||
|
|
||||||
// src/netx/skills-cache.ts
|
// src/netx/skills-cache.ts
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
|
import {
|
||||||
|
existsSync,
|
||||||
|
mkdirSync,
|
||||||
|
readdirSync,
|
||||||
|
readFileSync,
|
||||||
|
rmSync,
|
||||||
|
statSync,
|
||||||
|
writeFileSync
|
||||||
|
} from "node:fs";
|
||||||
import { homedir } from "node:os";
|
import { homedir } from "node:os";
|
||||||
import { dirname, join } from "node:path";
|
import { dirname, join } from "node:path";
|
||||||
|
|
||||||
|
|
@ -293,6 +302,7 @@ function normalizeApiUrl(raw) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/skills-cache.ts
|
// src/netx/skills-cache.ts
|
||||||
|
var STALE_CACHE_MS = 30 * 24 * 60 * 60 * 1000;
|
||||||
function dshHome() {
|
function dshHome() {
|
||||||
const fromEnv = process.env.DSH_HOME?.trim();
|
const fromEnv = process.env.DSH_HOME?.trim();
|
||||||
return fromEnv || join(homedir(), ".dsh");
|
return fromEnv || join(homedir(), ".dsh");
|
||||||
|
|
@ -305,9 +315,46 @@ function skillsCacheKey(apiUrl) {
|
||||||
const base = normalizeSkillsApiUrl(apiUrl);
|
const base = normalizeSkillsApiUrl(apiUrl);
|
||||||
return createHash("sha256").update(base).digest("hex").slice(0, 16);
|
return createHash("sha256").update(base).digest("hex").slice(0, 16);
|
||||||
}
|
}
|
||||||
function skillsCacheDir(apiUrl) {
|
function cacheParent() {
|
||||||
return join(dshHome(), "netxops-mcp-skills", skillsCacheKey(apiUrl));
|
return join(dshHome(), "netxops-mcp-skills");
|
||||||
}
|
}
|
||||||
|
function skillsCacheDir(apiUrl) {
|
||||||
|
return join(cacheParent(), skillsCacheKey(apiUrl));
|
||||||
|
}
|
||||||
|
function tokenFingerprint(token) {
|
||||||
|
const raw = token.trim();
|
||||||
|
return raw ? createHash("sha256").update(raw).digest("hex").slice(0, 16) : "";
|
||||||
|
}
|
||||||
|
function readMeta(root) {
|
||||||
|
try {
|
||||||
|
const meta = JSON.parse(readFileSync(join(root, ".meta.json"), "utf8"));
|
||||||
|
return meta && typeof meta === "object" && !Array.isArray(meta) ? meta : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function usableSkillsCacheDir(apiUrl, token) {
|
||||||
|
if (!apiUrl.trim())
|
||||||
|
return "";
|
||||||
|
const root = skillsCacheDir(apiUrl);
|
||||||
|
const pulledWith = readMeta(root)?.tokenFp;
|
||||||
|
if (pulledWith && pulledWith !== tokenFingerprint(token))
|
||||||
|
return "";
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
function cachedUnavailableTools(root) {
|
||||||
|
const raw = readMeta(root)?.unavailableTools;
|
||||||
|
const out = {};
|
||||||
|
if (!raw || typeof raw !== "object")
|
||||||
|
return out;
|
||||||
|
for (const group of CAPABILITY_GROUP_IDS) {
|
||||||
|
const names = raw[group];
|
||||||
|
if (Array.isArray(names))
|
||||||
|
out[group] = names.filter((n) => typeof n === "string");
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
var summaryMemo = new Map;
|
||||||
|
|
||||||
// src/netx/mcp-store.ts
|
// src/netx/mcp-store.ts
|
||||||
var STORE_KEY = Symbol.for("dsh-netxops.mcp-catalog-store");
|
var STORE_KEY = Symbol.for("dsh-netxops.mcp-catalog-store");
|
||||||
|
|
@ -512,12 +559,12 @@ function disabledFootnote(names, lang) {
|
||||||
const en = (lang ?? "").trim().toLowerCase().startsWith("en");
|
const en = (lang ?? "").trim().toLowerCase().startsWith("en");
|
||||||
return en ? `
|
return en ? `
|
||||||
|
|
||||||
## Disabled tools
|
## Unavailable tools
|
||||||
The following tools are disabled: ${list}. Do not call them.
|
The following tools are disabled or not granted to this token: ${list}. Do not call them; skip steps that need them.
|
||||||
` : `
|
` : `
|
||||||
|
|
||||||
## 已禁用工具
|
## 不可用工具
|
||||||
以下工具已禁用:${list},不要调用。
|
以下工具已禁用或当前 token 无权使用:${list},不要调用,涉及它们的步骤请跳过。
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
function resolveSkillsRoot(options) {
|
function resolveSkillsRoot(options) {
|
||||||
|
|
@ -527,7 +574,7 @@ function resolveSkillsRoot(options) {
|
||||||
const connection = getNetxConnection();
|
const connection = getNetxConnection();
|
||||||
if (!connection?.apiUrl?.trim())
|
if (!connection?.apiUrl?.trim())
|
||||||
return "";
|
return "";
|
||||||
return skillsCacheDir(connection.apiUrl);
|
return usableSkillsCacheDir(connection.apiUrl, connection.token ?? "");
|
||||||
}
|
}
|
||||||
async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions) {
|
async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions) {
|
||||||
const options = signalOrOptions instanceof AbortSignal || signalOrOptions === undefined ? { signal: signalOrOptions } : signalOrOptions;
|
const options = signalOrOptions instanceof AbortSignal || signalOrOptions === undefined ? { signal: signalOrOptions } : signalOrOptions;
|
||||||
|
|
@ -546,6 +593,7 @@ async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions
|
||||||
}
|
}
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
|
const unavailable = cachedUnavailableTools(root);
|
||||||
try {
|
try {
|
||||||
for (const groupId of CAPABILITY_GROUP_IDS) {
|
for (const groupId of CAPABILITY_GROUP_IDS) {
|
||||||
if (!enabled.has(groupId))
|
if (!enabled.has(groupId))
|
||||||
|
|
@ -559,7 +607,11 @@ async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions
|
||||||
if (skills.length === 0) {
|
if (skills.length === 0) {
|
||||||
log?.warn?.("%s: no skills loaded for group=%s from %s", providerLabel, groupId, join2(root, SKILL_DIR_BY_GROUP[groupId]));
|
log?.warn?.("%s: no skills loaded for group=%s from %s", providerLabel, groupId, join2(root, SKILL_DIR_BY_GROUP[groupId]));
|
||||||
}
|
}
|
||||||
const footnote = disabledFootnote(options.disabledByGroup?.[groupId] ?? [], options.lang);
|
const offNames = new Set([
|
||||||
|
...options.disabledByGroup?.[groupId] ?? [],
|
||||||
|
...(unavailable[groupId] ?? []).map(publicToolName)
|
||||||
|
]);
|
||||||
|
const footnote = disabledFootnote([...offNames].sort(), options.lang);
|
||||||
for (const skill of skills) {
|
for (const skill of skills) {
|
||||||
const base = groupId === "topology" ? `${skill.content}
|
const base = groupId === "topology" ? `${skill.content}
|
||||||
|
|
||||||
|
|
@ -593,11 +645,11 @@ Use only tools present in this session. Dual-unit/orbit/polish recipes require s
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-context-skill.ts
|
// src/netx/kb-context-skill.ts
|
||||||
import { readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync3 } from "node:fs";
|
import { readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync4 } from "node:fs";
|
||||||
import { join as join5 } from "node:path";
|
import { join as join5 } from "node:path";
|
||||||
|
|
||||||
// src/netx/kb-manifest.ts
|
// src/netx/kb-manifest.ts
|
||||||
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
||||||
import { basename, dirname as dirname2, join as join3, resolve } from "node:path";
|
import { basename, dirname as dirname2, join as join3, resolve } from "node:path";
|
||||||
var EMPTY_CONTENT = {
|
var EMPTY_CONTENT = {
|
||||||
hasRegions: false,
|
hasRegions: false,
|
||||||
|
|
@ -624,7 +676,7 @@ function resolveKbPaths(snapshot) {
|
||||||
const paths = { skills: "_skills", ...snapshot.paths };
|
const paths = { skills: "_skills", ...snapshot.paths };
|
||||||
return Object.entries(paths).filter((entry) => typeof entry[1] === "string" && entry[1].trim() !== "").map(([key, rel]) => {
|
return Object.entries(paths).filter((entry) => typeof entry[1] === "string" && entry[1].trim() !== "").map(([key, rel]) => {
|
||||||
const absolute = resolve(snapshot.realRoot, rel);
|
const absolute = resolve(snapshot.realRoot, rel);
|
||||||
return { key, relative: rel, absolute, exists: existsSync(absolute) };
|
return { key, relative: rel, absolute, exists: existsSync2(absolute) };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function renderKbPathTable(snapshot) {
|
function renderKbPathTable(snapshot) {
|
||||||
|
|
@ -640,7 +692,7 @@ function renderKbPathTable(snapshot) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-local-path.ts
|
// src/netx/kb-local-path.ts
|
||||||
import { existsSync as existsSync2, realpathSync, statSync as statSync2 } from "node:fs";
|
import { existsSync as existsSync3, realpathSync, statSync as statSync3 } from "node:fs";
|
||||||
import { join as join4, relative, resolve as resolve2, sep } from "node:path";
|
import { join as join4, relative, resolve as resolve2, sep } from "node:path";
|
||||||
var KB_LOCAL_WRITABLE_ROOTS = Object.freeze([
|
var KB_LOCAL_WRITABLE_ROOTS = Object.freeze([
|
||||||
"memories",
|
"memories",
|
||||||
|
|
@ -749,7 +801,7 @@ function resolveExistingWritableFile(localRoot, pathArg) {
|
||||||
}
|
}
|
||||||
const checked = resolveWritableLocalPath(localRoot, ...relative(localRoot, absolutePath).split(/[/\\]/).filter(Boolean));
|
const checked = resolveWritableLocalPath(localRoot, ...relative(localRoot, absolutePath).split(/[/\\]/).filter(Boolean));
|
||||||
try {
|
try {
|
||||||
if (existsSync2(checked.absolutePath)) {
|
if (existsSync3(checked.absolutePath)) {
|
||||||
const realFile = realpathSync(checked.absolutePath);
|
const realFile = realpathSync(checked.absolutePath);
|
||||||
const realLocal = realpathSync(localRoot);
|
const realLocal = realpathSync(localRoot);
|
||||||
const realRel = relative(realLocal, realFile);
|
const realRel = relative(realLocal, realFile);
|
||||||
|
|
@ -760,7 +812,7 @@ function resolveExistingWritableFile(localRoot, pathArg) {
|
||||||
if (!KB_LOCAL_WRITABLE_ROOTS.includes(top)) {
|
if (!KB_LOCAL_WRITABLE_ROOTS.includes(top)) {
|
||||||
throw new Error(`path not under writable roots: ${realRel}`);
|
throw new Error(`path not under writable roots: ${realRel}`);
|
||||||
}
|
}
|
||||||
if (!statSync2(realFile).isFile()) {
|
if (!statSync3(realFile).isFile()) {
|
||||||
throw new Error(`not a file: ${realFile}`);
|
throw new Error(`not a file: ${realFile}`);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
@ -813,7 +865,7 @@ function readKbIdentity(snapshot) {
|
||||||
const dir = join5(localRoot, "identity");
|
const dir = join5(localRoot, "identity");
|
||||||
let names;
|
let names;
|
||||||
try {
|
try {
|
||||||
names = readdirSync2(dir).filter((name) => /\.md$/i.test(name) && !/^readme\.md$/i.test(name)).sort();
|
names = readdirSync3(dir).filter((name) => /\.md$/i.test(name) && !/^readme\.md$/i.test(name)).sort();
|
||||||
} catch {
|
} catch {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
@ -825,9 +877,9 @@ function readKbIdentity(snapshot) {
|
||||||
const full = join5(dir, name);
|
const full = join5(dir, name);
|
||||||
let text;
|
let text;
|
||||||
try {
|
try {
|
||||||
if (!statSync3(full).isFile())
|
if (!statSync4(full).isFile())
|
||||||
continue;
|
continue;
|
||||||
text = readFileSync2(full, "utf8").replace(/^\uFEFF/, "").trim();
|
text = readFileSync3(full, "utf8").replace(/^\uFEFF/, "").trim();
|
||||||
} catch {
|
} catch {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -950,13 +1002,13 @@ import { defineTool } from "@deepseek-ai/dsh-tools";
|
||||||
|
|
||||||
// src/netx/kb-local-ops.ts
|
// src/netx/kb-local-ops.ts
|
||||||
import {
|
import {
|
||||||
existsSync as existsSync3,
|
existsSync as existsSync4,
|
||||||
mkdirSync,
|
mkdirSync as mkdirSync2,
|
||||||
readdirSync as readdirSync3,
|
readdirSync as readdirSync4,
|
||||||
readFileSync as readFileSync3,
|
readFileSync as readFileSync4,
|
||||||
rmSync,
|
rmSync as rmSync2,
|
||||||
statSync as statSync4,
|
statSync as statSync5,
|
||||||
writeFileSync
|
writeFileSync as writeFileSync2
|
||||||
} from "node:fs";
|
} from "node:fs";
|
||||||
import { dirname as dirname3, join as join6 } from "node:path";
|
import { dirname as dirname3, join as join6 } from "node:path";
|
||||||
function requireLocalRoot(snapshot) {
|
function requireLocalRoot(snapshot) {
|
||||||
|
|
@ -1004,12 +1056,12 @@ function createMemory(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = memoryFileName({ date: args.date, slug: args.slug });
|
const name = memoryFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, memoryDir(args.bucket), name);
|
const target = resolveWritableLocalPath(localRoot, memoryDir(args.bucket), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1023,12 +1075,12 @@ function createDraft(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = draftFileName({ date: args.date, slug: args.slug });
|
const name = draftFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, draftDir(args.domain), name);
|
const target = resolveWritableLocalPath(localRoot, draftDir(args.domain), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, ensureDraftBody(args.body), "utf8");
|
writeFileSync2(target.absolutePath, ensureDraftBody(args.body), "utf8");
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: existed ? "updated" : "created",
|
action: existed ? "updated" : "created",
|
||||||
|
|
@ -1040,12 +1092,12 @@ function createSuggestion(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = suggestionFileName({ date: args.date, slug: args.slug });
|
const name = suggestionFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, suggestionDir(args.kind), name);
|
const target = resolveWritableLocalPath(localRoot, suggestionDir(args.kind), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1059,12 +1111,12 @@ function createRef(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = refFileName(args.slug);
|
const name = refFileName(args.slug);
|
||||||
const target = resolveWritableLocalPath(localRoot, refDir(args.area, args.device), name);
|
const target = resolveWritableLocalPath(localRoot, refDir(args.area, args.device), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1077,13 +1129,13 @@ function createRef(snapshot, args) {
|
||||||
function updateLocalFile(snapshot, args) {
|
function updateLocalFile(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const target = resolveExistingWritableFile(localRoot, args.path);
|
const target = resolveExistingWritableFile(localRoot, args.path);
|
||||||
if (!existsSync3(target.absolutePath)) {
|
if (!existsSync4(target.absolutePath)) {
|
||||||
throw new Error(`file not found: ${target.relativePath}`);
|
throw new Error(`file not found: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
const body = target.writableRoot === "drafts" ? ensureDraftBody(args.body) : args.body.endsWith(`
|
const body = target.writableRoot === "drafts" ? ensureDraftBody(args.body) : args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`;
|
`;
|
||||||
writeFileSync(target.absolutePath, body, "utf8");
|
writeFileSync2(target.absolutePath, body, "utf8");
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: "updated",
|
action: "updated",
|
||||||
|
|
@ -1094,10 +1146,10 @@ function updateLocalFile(snapshot, args) {
|
||||||
function deleteLocalFile(snapshot, args) {
|
function deleteLocalFile(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const target = resolveExistingWritableFile(localRoot, args.path);
|
const target = resolveExistingWritableFile(localRoot, args.path);
|
||||||
if (!existsSync3(target.absolutePath)) {
|
if (!existsSync4(target.absolutePath)) {
|
||||||
throw new Error(`file not found: ${target.relativePath}`);
|
throw new Error(`file not found: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
rmSync(target.absolutePath, { force: false });
|
rmSync2(target.absolutePath, { force: false });
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: "deleted",
|
action: "deleted",
|
||||||
|
|
@ -1108,7 +1160,7 @@ function deleteLocalFile(snapshot, args) {
|
||||||
function walkFiles(dir, baseLocal, out) {
|
function walkFiles(dir, baseLocal, out) {
|
||||||
let entries;
|
let entries;
|
||||||
try {
|
try {
|
||||||
entries = readdirSync3(dir);
|
entries = readdirSync4(dir);
|
||||||
} catch {
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1118,7 +1170,7 @@ function walkFiles(dir, baseLocal, out) {
|
||||||
const full = join6(dir, name);
|
const full = join6(dir, name);
|
||||||
let st;
|
let st;
|
||||||
try {
|
try {
|
||||||
st = statSync4(full);
|
st = statSync5(full);
|
||||||
} catch {
|
} catch {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -1152,7 +1204,7 @@ function listLocalFiles(snapshot, args = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-read-ops.ts
|
// src/netx/kb-read-ops.ts
|
||||||
import { readdirSync as readdirSync4, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync as statSync5 } from "node:fs";
|
import { readdirSync as readdirSync5, readFileSync as readFileSync5, realpathSync as realpathSync2, statSync as statSync6 } from "node:fs";
|
||||||
import { extname, join as join7, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
import { extname, join as join7, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
||||||
var TEXT_EXTENSIONS = new Set([".md", ".markdown", ".txt", ".json", ".yml", ".yaml", ".csv", ".ps1", ".sh", ".cfg", ".conf", ".log"]);
|
var TEXT_EXTENSIONS = new Set([".md", ".markdown", ".txt", ".json", ".yml", ".yaml", ".csv", ".ps1", ".sh", ".cfg", ".conf", ".log"]);
|
||||||
var SKIP_DIRS = new Set([".git", "node_modules"]);
|
var SKIP_DIRS = new Set([".git", "node_modules"]);
|
||||||
|
|
@ -1197,14 +1249,14 @@ function resolveKbReadPath(snapshot, pathArg) {
|
||||||
}
|
}
|
||||||
function readKbFile(snapshot, args) {
|
function readKbFile(snapshot, args) {
|
||||||
const target = resolveKbReadPath(snapshot, args.path);
|
const target = resolveKbReadPath(snapshot, args.path);
|
||||||
const st = statSync5(target.absolutePath);
|
const st = statSync6(target.absolutePath);
|
||||||
if (st.isDirectory()) {
|
if (st.isDirectory()) {
|
||||||
const entries = readdirSync4(target.absolutePath, { withFileTypes: true }).filter((entry) => !SKIP_DIRS.has(entry.name)).map((entry) => {
|
const entries = readdirSync5(target.absolutePath, { withFileTypes: true }).filter((entry) => !SKIP_DIRS.has(entry.name)).map((entry) => {
|
||||||
if (entry.isDirectory())
|
if (entry.isDirectory())
|
||||||
return { name: entry.name, type: "dir" };
|
return { name: entry.name, type: "dir" };
|
||||||
let size;
|
let size;
|
||||||
try {
|
try {
|
||||||
size = statSync5(join7(target.absolutePath, entry.name)).size;
|
size = statSync6(join7(target.absolutePath, entry.name)).size;
|
||||||
} catch {}
|
} catch {}
|
||||||
return { name: entry.name, type: "file", size };
|
return { name: entry.name, type: "file", size };
|
||||||
}).sort((a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1);
|
}).sort((a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1);
|
||||||
|
|
@ -1213,7 +1265,7 @@ function readKbFile(snapshot, args) {
|
||||||
if (!TEXT_EXTENSIONS.has(extname(target.absolutePath).toLowerCase())) {
|
if (!TEXT_EXTENSIONS.has(extname(target.absolutePath).toLowerCase())) {
|
||||||
throw new Error(`not a text KB file: ${target.relativePath}`);
|
throw new Error(`not a text KB file: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
const lines = readFileSync4(target.absolutePath, "utf8").replace(/^\uFEFF/, "").replace(/\r\n?/g, `
|
const lines = readFileSync5(target.absolutePath, "utf8").replace(/^\uFEFF/, "").replace(/\r\n?/g, `
|
||||||
`).split(`
|
`).split(`
|
||||||
`);
|
`);
|
||||||
const start = Math.max(1, Math.floor(args.offset ?? 1));
|
const start = Math.max(1, Math.floor(args.offset ?? 1));
|
||||||
|
|
@ -1258,7 +1310,7 @@ function cachedText(path, mtimeMs, size) {
|
||||||
const hit = cache.files.get(path);
|
const hit = cache.files.get(path);
|
||||||
if (hit && hit.mtimeMs === mtimeMs && hit.size === size)
|
if (hit && hit.mtimeMs === mtimeMs && hit.size === size)
|
||||||
return hit;
|
return hit;
|
||||||
const text = readFileSync4(path, "utf8").replace(/^\uFEFF/, "");
|
const text = readFileSync5(path, "utf8").replace(/^\uFEFF/, "");
|
||||||
if (hit)
|
if (hit)
|
||||||
cache.chars -= hit.text.length;
|
cache.chars -= hit.text.length;
|
||||||
if (cache.chars + text.length > CACHE_MAX_CHARS) {
|
if (cache.chars + text.length > CACHE_MAX_CHARS) {
|
||||||
|
|
@ -1273,7 +1325,7 @@ function cachedText(path, mtimeMs, size) {
|
||||||
function walkTextFiles(dir, out) {
|
function walkTextFiles(dir, out) {
|
||||||
let entries;
|
let entries;
|
||||||
try {
|
try {
|
||||||
entries = readdirSync4(dir, { withFileTypes: true });
|
entries = readdirSync5(dir, { withFileTypes: true });
|
||||||
} catch {
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1288,7 +1340,7 @@ function walkTextFiles(dir, out) {
|
||||||
if (!entry.isFile() || !TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase()))
|
if (!entry.isFile() || !TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase()))
|
||||||
continue;
|
continue;
|
||||||
try {
|
try {
|
||||||
const st = statSync5(full);
|
const st = statSync6(full);
|
||||||
if (st.size <= SEARCH_MAX_FILE_BYTES)
|
if (st.size <= SEARCH_MAX_FILE_BYTES)
|
||||||
out.push({ path: full, mtimeMs: st.mtimeMs, size: st.size });
|
out.push({ path: full, mtimeMs: st.mtimeMs, size: st.size });
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
@ -1318,7 +1370,7 @@ function searchKb(snapshot, args) {
|
||||||
const files = [];
|
const files = [];
|
||||||
const seen = new Set;
|
const seen = new Set;
|
||||||
for (const dir of dirs) {
|
for (const dir of dirs) {
|
||||||
const st = statSync5(dir);
|
const st = statSync6(dir);
|
||||||
const batch = [];
|
const batch = [];
|
||||||
if (st.isFile())
|
if (st.isFile())
|
||||||
batch.push({ path: dir, mtimeMs: st.mtimeMs, size: st.size });
|
batch.push({ path: dir, mtimeMs: st.mtimeMs, size: st.size });
|
||||||
|
|
@ -1432,7 +1484,7 @@ function toLosslessJson(value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/tool-mount-status.ts
|
// src/netx/tool-mount-status.ts
|
||||||
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
||||||
import { homedir as homedir2 } from "node:os";
|
import { homedir as homedir2 } from "node:os";
|
||||||
import { dirname as dirname4, join as join8 } from "node:path";
|
import { dirname as dirname4, join as join8 } from "node:path";
|
||||||
var KEY = Symbol.for("dsh-netxops.mount-inventory");
|
var KEY = Symbol.for("dsh-netxops.mount-inventory");
|
||||||
|
|
@ -1459,8 +1511,8 @@ function getToolMountStatus() {
|
||||||
function persist() {
|
function persist() {
|
||||||
try {
|
try {
|
||||||
const path = join8(process.env.DSH_HOME?.trim() || join8(homedir2(), ".dsh"), "netxops-mount-status.json");
|
const path = join8(process.env.DSH_HOME?.trim() || join8(homedir2(), ".dsh"), "netxops-mount-status.json");
|
||||||
mkdirSync2(dirname4(path), { recursive: true });
|
mkdirSync3(dirname4(path), { recursive: true });
|
||||||
writeFileSync2(path, `${JSON.stringify({ mount: getToolMountStatus() ?? null, mounts: getToolMountStatuses() }, null, 2)}
|
writeFileSync3(path, `${JSON.stringify({ mount: getToolMountStatus() ?? null, mounts: getToolMountStatuses() }, null, 2)}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
@ -1811,7 +1863,7 @@ async function registerKbPackSkills(ctx, snapshot, options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/tools.ts
|
// src/netx/tools.ts
|
||||||
import { readFileSync as readFileSync5 } from "node:fs";
|
import { readFileSync as readFileSync6 } from "node:fs";
|
||||||
import { dirname as dirname5, join as join10 } from "node:path";
|
import { dirname as dirname5, join as join10 } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
|
import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
|
||||||
|
|
@ -1916,7 +1968,7 @@ function parametersFromInputSchema(schema) {
|
||||||
function packageVersion() {
|
function packageVersion() {
|
||||||
try {
|
try {
|
||||||
const here = dirname5(fileURLToPath(import.meta.url));
|
const here = dirname5(fileURLToPath(import.meta.url));
|
||||||
const pkg = JSON.parse(readFileSync5(join10(here, "..", "package.json"), "utf8"));
|
const pkg = JSON.parse(readFileSync6(join10(here, "..", "package.json"), "utf8"));
|
||||||
return typeof pkg.version === "string" ? pkg.version : "0";
|
return typeof pkg.version === "string" ? pkg.version : "0";
|
||||||
} catch {
|
} catch {
|
||||||
return "0";
|
return "0";
|
||||||
|
|
@ -2048,7 +2100,7 @@ function registerNetxTools(ctx, connection, options) {
|
||||||
function packageVersion2() {
|
function packageVersion2() {
|
||||||
try {
|
try {
|
||||||
const here = dirname6(fileURLToPath2(import.meta.url));
|
const here = dirname6(fileURLToPath2(import.meta.url));
|
||||||
const pkg = JSON.parse(readFileSync6(join11(here, "..", "package.json"), "utf8"));
|
const pkg = JSON.parse(readFileSync7(join11(here, "..", "package.json"), "utf8"));
|
||||||
return typeof pkg.version === "string" ? pkg.version : "0";
|
return typeof pkg.version === "string" ? pkg.version : "0";
|
||||||
} catch {
|
} catch {
|
||||||
return "0";
|
return "0";
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// src/netx/group-tools-plugin.ts
|
// src/netx/group-tools-plugin.ts
|
||||||
import { readFileSync as readFileSync6 } from "node:fs";
|
import { readFileSync as readFileSync7 } from "node:fs";
|
||||||
import { dirname as dirname6, join as join11 } from "node:path";
|
import { dirname as dirname6, join as join11 } from "node:path";
|
||||||
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
||||||
|
|
||||||
|
|
@ -267,6 +267,15 @@ function mcpContentText(content, fallback = "") {
|
||||||
|
|
||||||
// src/netx/skills-cache.ts
|
// src/netx/skills-cache.ts
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
|
import {
|
||||||
|
existsSync,
|
||||||
|
mkdirSync,
|
||||||
|
readdirSync,
|
||||||
|
readFileSync,
|
||||||
|
rmSync,
|
||||||
|
statSync,
|
||||||
|
writeFileSync
|
||||||
|
} from "node:fs";
|
||||||
import { homedir } from "node:os";
|
import { homedir } from "node:os";
|
||||||
import { dirname, join } from "node:path";
|
import { dirname, join } from "node:path";
|
||||||
|
|
||||||
|
|
@ -293,6 +302,7 @@ function normalizeApiUrl(raw) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/skills-cache.ts
|
// src/netx/skills-cache.ts
|
||||||
|
var STALE_CACHE_MS = 30 * 24 * 60 * 60 * 1000;
|
||||||
function dshHome() {
|
function dshHome() {
|
||||||
const fromEnv = process.env.DSH_HOME?.trim();
|
const fromEnv = process.env.DSH_HOME?.trim();
|
||||||
return fromEnv || join(homedir(), ".dsh");
|
return fromEnv || join(homedir(), ".dsh");
|
||||||
|
|
@ -305,9 +315,46 @@ function skillsCacheKey(apiUrl) {
|
||||||
const base = normalizeSkillsApiUrl(apiUrl);
|
const base = normalizeSkillsApiUrl(apiUrl);
|
||||||
return createHash("sha256").update(base).digest("hex").slice(0, 16);
|
return createHash("sha256").update(base).digest("hex").slice(0, 16);
|
||||||
}
|
}
|
||||||
function skillsCacheDir(apiUrl) {
|
function cacheParent() {
|
||||||
return join(dshHome(), "netxops-mcp-skills", skillsCacheKey(apiUrl));
|
return join(dshHome(), "netxops-mcp-skills");
|
||||||
}
|
}
|
||||||
|
function skillsCacheDir(apiUrl) {
|
||||||
|
return join(cacheParent(), skillsCacheKey(apiUrl));
|
||||||
|
}
|
||||||
|
function tokenFingerprint(token) {
|
||||||
|
const raw = token.trim();
|
||||||
|
return raw ? createHash("sha256").update(raw).digest("hex").slice(0, 16) : "";
|
||||||
|
}
|
||||||
|
function readMeta(root) {
|
||||||
|
try {
|
||||||
|
const meta = JSON.parse(readFileSync(join(root, ".meta.json"), "utf8"));
|
||||||
|
return meta && typeof meta === "object" && !Array.isArray(meta) ? meta : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function usableSkillsCacheDir(apiUrl, token) {
|
||||||
|
if (!apiUrl.trim())
|
||||||
|
return "";
|
||||||
|
const root = skillsCacheDir(apiUrl);
|
||||||
|
const pulledWith = readMeta(root)?.tokenFp;
|
||||||
|
if (pulledWith && pulledWith !== tokenFingerprint(token))
|
||||||
|
return "";
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
function cachedUnavailableTools(root) {
|
||||||
|
const raw = readMeta(root)?.unavailableTools;
|
||||||
|
const out = {};
|
||||||
|
if (!raw || typeof raw !== "object")
|
||||||
|
return out;
|
||||||
|
for (const group of CAPABILITY_GROUP_IDS) {
|
||||||
|
const names = raw[group];
|
||||||
|
if (Array.isArray(names))
|
||||||
|
out[group] = names.filter((n) => typeof n === "string");
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
var summaryMemo = new Map;
|
||||||
|
|
||||||
// src/netx/mcp-store.ts
|
// src/netx/mcp-store.ts
|
||||||
var STORE_KEY = Symbol.for("dsh-netxops.mcp-catalog-store");
|
var STORE_KEY = Symbol.for("dsh-netxops.mcp-catalog-store");
|
||||||
|
|
@ -512,12 +559,12 @@ function disabledFootnote(names, lang) {
|
||||||
const en = (lang ?? "").trim().toLowerCase().startsWith("en");
|
const en = (lang ?? "").trim().toLowerCase().startsWith("en");
|
||||||
return en ? `
|
return en ? `
|
||||||
|
|
||||||
## Disabled tools
|
## Unavailable tools
|
||||||
The following tools are disabled: ${list}. Do not call them.
|
The following tools are disabled or not granted to this token: ${list}. Do not call them; skip steps that need them.
|
||||||
` : `
|
` : `
|
||||||
|
|
||||||
## 已禁用工具
|
## 不可用工具
|
||||||
以下工具已禁用:${list},不要调用。
|
以下工具已禁用或当前 token 无权使用:${list},不要调用,涉及它们的步骤请跳过。
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
function resolveSkillsRoot(options) {
|
function resolveSkillsRoot(options) {
|
||||||
|
|
@ -527,7 +574,7 @@ function resolveSkillsRoot(options) {
|
||||||
const connection = getNetxConnection();
|
const connection = getNetxConnection();
|
||||||
if (!connection?.apiUrl?.trim())
|
if (!connection?.apiUrl?.trim())
|
||||||
return "";
|
return "";
|
||||||
return skillsCacheDir(connection.apiUrl);
|
return usableSkillsCacheDir(connection.apiUrl, connection.token ?? "");
|
||||||
}
|
}
|
||||||
async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions) {
|
async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions) {
|
||||||
const options = signalOrOptions instanceof AbortSignal || signalOrOptions === undefined ? { signal: signalOrOptions } : signalOrOptions;
|
const options = signalOrOptions instanceof AbortSignal || signalOrOptions === undefined ? { signal: signalOrOptions } : signalOrOptions;
|
||||||
|
|
@ -546,6 +593,7 @@ async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions
|
||||||
}
|
}
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
|
const unavailable = cachedUnavailableTools(root);
|
||||||
try {
|
try {
|
||||||
for (const groupId of CAPABILITY_GROUP_IDS) {
|
for (const groupId of CAPABILITY_GROUP_IDS) {
|
||||||
if (!enabled.has(groupId))
|
if (!enabled.has(groupId))
|
||||||
|
|
@ -559,7 +607,11 @@ async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions
|
||||||
if (skills.length === 0) {
|
if (skills.length === 0) {
|
||||||
log?.warn?.("%s: no skills loaded for group=%s from %s", providerLabel, groupId, join2(root, SKILL_DIR_BY_GROUP[groupId]));
|
log?.warn?.("%s: no skills loaded for group=%s from %s", providerLabel, groupId, join2(root, SKILL_DIR_BY_GROUP[groupId]));
|
||||||
}
|
}
|
||||||
const footnote = disabledFootnote(options.disabledByGroup?.[groupId] ?? [], options.lang);
|
const offNames = new Set([
|
||||||
|
...options.disabledByGroup?.[groupId] ?? [],
|
||||||
|
...(unavailable[groupId] ?? []).map(publicToolName)
|
||||||
|
]);
|
||||||
|
const footnote = disabledFootnote([...offNames].sort(), options.lang);
|
||||||
for (const skill of skills) {
|
for (const skill of skills) {
|
||||||
const base = groupId === "topology" ? `${skill.content}
|
const base = groupId === "topology" ? `${skill.content}
|
||||||
|
|
||||||
|
|
@ -593,11 +645,11 @@ Use only tools present in this session. Dual-unit/orbit/polish recipes require s
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-context-skill.ts
|
// src/netx/kb-context-skill.ts
|
||||||
import { readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync3 } from "node:fs";
|
import { readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync4 } from "node:fs";
|
||||||
import { join as join5 } from "node:path";
|
import { join as join5 } from "node:path";
|
||||||
|
|
||||||
// src/netx/kb-manifest.ts
|
// src/netx/kb-manifest.ts
|
||||||
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
||||||
import { basename, dirname as dirname2, join as join3, resolve } from "node:path";
|
import { basename, dirname as dirname2, join as join3, resolve } from "node:path";
|
||||||
var EMPTY_CONTENT = {
|
var EMPTY_CONTENT = {
|
||||||
hasRegions: false,
|
hasRegions: false,
|
||||||
|
|
@ -624,7 +676,7 @@ function resolveKbPaths(snapshot) {
|
||||||
const paths = { skills: "_skills", ...snapshot.paths };
|
const paths = { skills: "_skills", ...snapshot.paths };
|
||||||
return Object.entries(paths).filter((entry) => typeof entry[1] === "string" && entry[1].trim() !== "").map(([key, rel]) => {
|
return Object.entries(paths).filter((entry) => typeof entry[1] === "string" && entry[1].trim() !== "").map(([key, rel]) => {
|
||||||
const absolute = resolve(snapshot.realRoot, rel);
|
const absolute = resolve(snapshot.realRoot, rel);
|
||||||
return { key, relative: rel, absolute, exists: existsSync(absolute) };
|
return { key, relative: rel, absolute, exists: existsSync2(absolute) };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function renderKbPathTable(snapshot) {
|
function renderKbPathTable(snapshot) {
|
||||||
|
|
@ -640,7 +692,7 @@ function renderKbPathTable(snapshot) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-local-path.ts
|
// src/netx/kb-local-path.ts
|
||||||
import { existsSync as existsSync2, realpathSync, statSync as statSync2 } from "node:fs";
|
import { existsSync as existsSync3, realpathSync, statSync as statSync3 } from "node:fs";
|
||||||
import { join as join4, relative, resolve as resolve2, sep } from "node:path";
|
import { join as join4, relative, resolve as resolve2, sep } from "node:path";
|
||||||
var KB_LOCAL_WRITABLE_ROOTS = Object.freeze([
|
var KB_LOCAL_WRITABLE_ROOTS = Object.freeze([
|
||||||
"memories",
|
"memories",
|
||||||
|
|
@ -749,7 +801,7 @@ function resolveExistingWritableFile(localRoot, pathArg) {
|
||||||
}
|
}
|
||||||
const checked = resolveWritableLocalPath(localRoot, ...relative(localRoot, absolutePath).split(/[/\\]/).filter(Boolean));
|
const checked = resolveWritableLocalPath(localRoot, ...relative(localRoot, absolutePath).split(/[/\\]/).filter(Boolean));
|
||||||
try {
|
try {
|
||||||
if (existsSync2(checked.absolutePath)) {
|
if (existsSync3(checked.absolutePath)) {
|
||||||
const realFile = realpathSync(checked.absolutePath);
|
const realFile = realpathSync(checked.absolutePath);
|
||||||
const realLocal = realpathSync(localRoot);
|
const realLocal = realpathSync(localRoot);
|
||||||
const realRel = relative(realLocal, realFile);
|
const realRel = relative(realLocal, realFile);
|
||||||
|
|
@ -760,7 +812,7 @@ function resolveExistingWritableFile(localRoot, pathArg) {
|
||||||
if (!KB_LOCAL_WRITABLE_ROOTS.includes(top)) {
|
if (!KB_LOCAL_WRITABLE_ROOTS.includes(top)) {
|
||||||
throw new Error(`path not under writable roots: ${realRel}`);
|
throw new Error(`path not under writable roots: ${realRel}`);
|
||||||
}
|
}
|
||||||
if (!statSync2(realFile).isFile()) {
|
if (!statSync3(realFile).isFile()) {
|
||||||
throw new Error(`not a file: ${realFile}`);
|
throw new Error(`not a file: ${realFile}`);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
|
@ -813,7 +865,7 @@ function readKbIdentity(snapshot) {
|
||||||
const dir = join5(localRoot, "identity");
|
const dir = join5(localRoot, "identity");
|
||||||
let names;
|
let names;
|
||||||
try {
|
try {
|
||||||
names = readdirSync2(dir).filter((name) => /\.md$/i.test(name) && !/^readme\.md$/i.test(name)).sort();
|
names = readdirSync3(dir).filter((name) => /\.md$/i.test(name) && !/^readme\.md$/i.test(name)).sort();
|
||||||
} catch {
|
} catch {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
@ -825,9 +877,9 @@ function readKbIdentity(snapshot) {
|
||||||
const full = join5(dir, name);
|
const full = join5(dir, name);
|
||||||
let text;
|
let text;
|
||||||
try {
|
try {
|
||||||
if (!statSync3(full).isFile())
|
if (!statSync4(full).isFile())
|
||||||
continue;
|
continue;
|
||||||
text = readFileSync2(full, "utf8").replace(/^\uFEFF/, "").trim();
|
text = readFileSync3(full, "utf8").replace(/^\uFEFF/, "").trim();
|
||||||
} catch {
|
} catch {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -950,13 +1002,13 @@ import { defineTool } from "@deepseek-ai/dsh-tools";
|
||||||
|
|
||||||
// src/netx/kb-local-ops.ts
|
// src/netx/kb-local-ops.ts
|
||||||
import {
|
import {
|
||||||
existsSync as existsSync3,
|
existsSync as existsSync4,
|
||||||
mkdirSync,
|
mkdirSync as mkdirSync2,
|
||||||
readdirSync as readdirSync3,
|
readdirSync as readdirSync4,
|
||||||
readFileSync as readFileSync3,
|
readFileSync as readFileSync4,
|
||||||
rmSync,
|
rmSync as rmSync2,
|
||||||
statSync as statSync4,
|
statSync as statSync5,
|
||||||
writeFileSync
|
writeFileSync as writeFileSync2
|
||||||
} from "node:fs";
|
} from "node:fs";
|
||||||
import { dirname as dirname3, join as join6 } from "node:path";
|
import { dirname as dirname3, join as join6 } from "node:path";
|
||||||
function requireLocalRoot(snapshot) {
|
function requireLocalRoot(snapshot) {
|
||||||
|
|
@ -1004,12 +1056,12 @@ function createMemory(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = memoryFileName({ date: args.date, slug: args.slug });
|
const name = memoryFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, memoryDir(args.bucket), name);
|
const target = resolveWritableLocalPath(localRoot, memoryDir(args.bucket), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1023,12 +1075,12 @@ function createDraft(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = draftFileName({ date: args.date, slug: args.slug });
|
const name = draftFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, draftDir(args.domain), name);
|
const target = resolveWritableLocalPath(localRoot, draftDir(args.domain), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, ensureDraftBody(args.body), "utf8");
|
writeFileSync2(target.absolutePath, ensureDraftBody(args.body), "utf8");
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: existed ? "updated" : "created",
|
action: existed ? "updated" : "created",
|
||||||
|
|
@ -1040,12 +1092,12 @@ function createSuggestion(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = suggestionFileName({ date: args.date, slug: args.slug });
|
const name = suggestionFileName({ date: args.date, slug: args.slug });
|
||||||
const target = resolveWritableLocalPath(localRoot, suggestionDir(args.kind), name);
|
const target = resolveWritableLocalPath(localRoot, suggestionDir(args.kind), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1059,12 +1111,12 @@ function createRef(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const name = refFileName(args.slug);
|
const name = refFileName(args.slug);
|
||||||
const target = resolveWritableLocalPath(localRoot, refDir(args.area, args.device), name);
|
const target = resolveWritableLocalPath(localRoot, refDir(args.area, args.device), name);
|
||||||
if (existsSync3(target.absolutePath) && !args.overwrite) {
|
if (existsSync4(target.absolutePath) && !args.overwrite) {
|
||||||
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
throw new Error(`file already exists (pass overwrite=true to replace): ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
mkdirSync(dirname3(target.absolutePath), { recursive: true });
|
mkdirSync2(dirname3(target.absolutePath), { recursive: true });
|
||||||
const existed = existsSync3(target.absolutePath);
|
const existed = existsSync4(target.absolutePath);
|
||||||
writeFileSync(target.absolutePath, args.body.endsWith(`
|
writeFileSync2(target.absolutePath, args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
return {
|
return {
|
||||||
|
|
@ -1077,13 +1129,13 @@ function createRef(snapshot, args) {
|
||||||
function updateLocalFile(snapshot, args) {
|
function updateLocalFile(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const target = resolveExistingWritableFile(localRoot, args.path);
|
const target = resolveExistingWritableFile(localRoot, args.path);
|
||||||
if (!existsSync3(target.absolutePath)) {
|
if (!existsSync4(target.absolutePath)) {
|
||||||
throw new Error(`file not found: ${target.relativePath}`);
|
throw new Error(`file not found: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
const body = target.writableRoot === "drafts" ? ensureDraftBody(args.body) : args.body.endsWith(`
|
const body = target.writableRoot === "drafts" ? ensureDraftBody(args.body) : args.body.endsWith(`
|
||||||
`) ? args.body : `${args.body}
|
`) ? args.body : `${args.body}
|
||||||
`;
|
`;
|
||||||
writeFileSync(target.absolutePath, body, "utf8");
|
writeFileSync2(target.absolutePath, body, "utf8");
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: "updated",
|
action: "updated",
|
||||||
|
|
@ -1094,10 +1146,10 @@ function updateLocalFile(snapshot, args) {
|
||||||
function deleteLocalFile(snapshot, args) {
|
function deleteLocalFile(snapshot, args) {
|
||||||
const localRoot = requireLocalRoot(snapshot);
|
const localRoot = requireLocalRoot(snapshot);
|
||||||
const target = resolveExistingWritableFile(localRoot, args.path);
|
const target = resolveExistingWritableFile(localRoot, args.path);
|
||||||
if (!existsSync3(target.absolutePath)) {
|
if (!existsSync4(target.absolutePath)) {
|
||||||
throw new Error(`file not found: ${target.relativePath}`);
|
throw new Error(`file not found: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
rmSync(target.absolutePath, { force: false });
|
rmSync2(target.absolutePath, { force: false });
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
action: "deleted",
|
action: "deleted",
|
||||||
|
|
@ -1108,7 +1160,7 @@ function deleteLocalFile(snapshot, args) {
|
||||||
function walkFiles(dir, baseLocal, out) {
|
function walkFiles(dir, baseLocal, out) {
|
||||||
let entries;
|
let entries;
|
||||||
try {
|
try {
|
||||||
entries = readdirSync3(dir);
|
entries = readdirSync4(dir);
|
||||||
} catch {
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1118,7 +1170,7 @@ function walkFiles(dir, baseLocal, out) {
|
||||||
const full = join6(dir, name);
|
const full = join6(dir, name);
|
||||||
let st;
|
let st;
|
||||||
try {
|
try {
|
||||||
st = statSync4(full);
|
st = statSync5(full);
|
||||||
} catch {
|
} catch {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -1152,7 +1204,7 @@ function listLocalFiles(snapshot, args = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/kb-read-ops.ts
|
// src/netx/kb-read-ops.ts
|
||||||
import { readdirSync as readdirSync4, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync as statSync5 } from "node:fs";
|
import { readdirSync as readdirSync5, readFileSync as readFileSync5, realpathSync as realpathSync2, statSync as statSync6 } from "node:fs";
|
||||||
import { extname, join as join7, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
import { extname, join as join7, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
||||||
var TEXT_EXTENSIONS = new Set([".md", ".markdown", ".txt", ".json", ".yml", ".yaml", ".csv", ".ps1", ".sh", ".cfg", ".conf", ".log"]);
|
var TEXT_EXTENSIONS = new Set([".md", ".markdown", ".txt", ".json", ".yml", ".yaml", ".csv", ".ps1", ".sh", ".cfg", ".conf", ".log"]);
|
||||||
var SKIP_DIRS = new Set([".git", "node_modules"]);
|
var SKIP_DIRS = new Set([".git", "node_modules"]);
|
||||||
|
|
@ -1197,14 +1249,14 @@ function resolveKbReadPath(snapshot, pathArg) {
|
||||||
}
|
}
|
||||||
function readKbFile(snapshot, args) {
|
function readKbFile(snapshot, args) {
|
||||||
const target = resolveKbReadPath(snapshot, args.path);
|
const target = resolveKbReadPath(snapshot, args.path);
|
||||||
const st = statSync5(target.absolutePath);
|
const st = statSync6(target.absolutePath);
|
||||||
if (st.isDirectory()) {
|
if (st.isDirectory()) {
|
||||||
const entries = readdirSync4(target.absolutePath, { withFileTypes: true }).filter((entry) => !SKIP_DIRS.has(entry.name)).map((entry) => {
|
const entries = readdirSync5(target.absolutePath, { withFileTypes: true }).filter((entry) => !SKIP_DIRS.has(entry.name)).map((entry) => {
|
||||||
if (entry.isDirectory())
|
if (entry.isDirectory())
|
||||||
return { name: entry.name, type: "dir" };
|
return { name: entry.name, type: "dir" };
|
||||||
let size;
|
let size;
|
||||||
try {
|
try {
|
||||||
size = statSync5(join7(target.absolutePath, entry.name)).size;
|
size = statSync6(join7(target.absolutePath, entry.name)).size;
|
||||||
} catch {}
|
} catch {}
|
||||||
return { name: entry.name, type: "file", size };
|
return { name: entry.name, type: "file", size };
|
||||||
}).sort((a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1);
|
}).sort((a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "dir" ? -1 : 1);
|
||||||
|
|
@ -1213,7 +1265,7 @@ function readKbFile(snapshot, args) {
|
||||||
if (!TEXT_EXTENSIONS.has(extname(target.absolutePath).toLowerCase())) {
|
if (!TEXT_EXTENSIONS.has(extname(target.absolutePath).toLowerCase())) {
|
||||||
throw new Error(`not a text KB file: ${target.relativePath}`);
|
throw new Error(`not a text KB file: ${target.relativePath}`);
|
||||||
}
|
}
|
||||||
const lines = readFileSync4(target.absolutePath, "utf8").replace(/^\uFEFF/, "").replace(/\r\n?/g, `
|
const lines = readFileSync5(target.absolutePath, "utf8").replace(/^\uFEFF/, "").replace(/\r\n?/g, `
|
||||||
`).split(`
|
`).split(`
|
||||||
`);
|
`);
|
||||||
const start = Math.max(1, Math.floor(args.offset ?? 1));
|
const start = Math.max(1, Math.floor(args.offset ?? 1));
|
||||||
|
|
@ -1258,7 +1310,7 @@ function cachedText(path, mtimeMs, size) {
|
||||||
const hit = cache.files.get(path);
|
const hit = cache.files.get(path);
|
||||||
if (hit && hit.mtimeMs === mtimeMs && hit.size === size)
|
if (hit && hit.mtimeMs === mtimeMs && hit.size === size)
|
||||||
return hit;
|
return hit;
|
||||||
const text = readFileSync4(path, "utf8").replace(/^\uFEFF/, "");
|
const text = readFileSync5(path, "utf8").replace(/^\uFEFF/, "");
|
||||||
if (hit)
|
if (hit)
|
||||||
cache.chars -= hit.text.length;
|
cache.chars -= hit.text.length;
|
||||||
if (cache.chars + text.length > CACHE_MAX_CHARS) {
|
if (cache.chars + text.length > CACHE_MAX_CHARS) {
|
||||||
|
|
@ -1273,7 +1325,7 @@ function cachedText(path, mtimeMs, size) {
|
||||||
function walkTextFiles(dir, out) {
|
function walkTextFiles(dir, out) {
|
||||||
let entries;
|
let entries;
|
||||||
try {
|
try {
|
||||||
entries = readdirSync4(dir, { withFileTypes: true });
|
entries = readdirSync5(dir, { withFileTypes: true });
|
||||||
} catch {
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1288,7 +1340,7 @@ function walkTextFiles(dir, out) {
|
||||||
if (!entry.isFile() || !TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase()))
|
if (!entry.isFile() || !TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase()))
|
||||||
continue;
|
continue;
|
||||||
try {
|
try {
|
||||||
const st = statSync5(full);
|
const st = statSync6(full);
|
||||||
if (st.size <= SEARCH_MAX_FILE_BYTES)
|
if (st.size <= SEARCH_MAX_FILE_BYTES)
|
||||||
out.push({ path: full, mtimeMs: st.mtimeMs, size: st.size });
|
out.push({ path: full, mtimeMs: st.mtimeMs, size: st.size });
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
@ -1318,7 +1370,7 @@ function searchKb(snapshot, args) {
|
||||||
const files = [];
|
const files = [];
|
||||||
const seen = new Set;
|
const seen = new Set;
|
||||||
for (const dir of dirs) {
|
for (const dir of dirs) {
|
||||||
const st = statSync5(dir);
|
const st = statSync6(dir);
|
||||||
const batch = [];
|
const batch = [];
|
||||||
if (st.isFile())
|
if (st.isFile())
|
||||||
batch.push({ path: dir, mtimeMs: st.mtimeMs, size: st.size });
|
batch.push({ path: dir, mtimeMs: st.mtimeMs, size: st.size });
|
||||||
|
|
@ -1432,7 +1484,7 @@ function toLosslessJson(value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/tool-mount-status.ts
|
// src/netx/tool-mount-status.ts
|
||||||
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
||||||
import { homedir as homedir2 } from "node:os";
|
import { homedir as homedir2 } from "node:os";
|
||||||
import { dirname as dirname4, join as join8 } from "node:path";
|
import { dirname as dirname4, join as join8 } from "node:path";
|
||||||
var KEY = Symbol.for("dsh-netxops.mount-inventory");
|
var KEY = Symbol.for("dsh-netxops.mount-inventory");
|
||||||
|
|
@ -1459,8 +1511,8 @@ function getToolMountStatus() {
|
||||||
function persist() {
|
function persist() {
|
||||||
try {
|
try {
|
||||||
const path = join8(process.env.DSH_HOME?.trim() || join8(homedir2(), ".dsh"), "netxops-mount-status.json");
|
const path = join8(process.env.DSH_HOME?.trim() || join8(homedir2(), ".dsh"), "netxops-mount-status.json");
|
||||||
mkdirSync2(dirname4(path), { recursive: true });
|
mkdirSync3(dirname4(path), { recursive: true });
|
||||||
writeFileSync2(path, `${JSON.stringify({ mount: getToolMountStatus() ?? null, mounts: getToolMountStatuses() }, null, 2)}
|
writeFileSync3(path, `${JSON.stringify({ mount: getToolMountStatus() ?? null, mounts: getToolMountStatuses() }, null, 2)}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
@ -1811,7 +1863,7 @@ async function registerKbPackSkills(ctx, snapshot, options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/tools.ts
|
// src/netx/tools.ts
|
||||||
import { readFileSync as readFileSync5 } from "node:fs";
|
import { readFileSync as readFileSync6 } from "node:fs";
|
||||||
import { dirname as dirname5, join as join10 } from "node:path";
|
import { dirname as dirname5, join as join10 } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
|
import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
|
||||||
|
|
@ -1916,7 +1968,7 @@ function parametersFromInputSchema(schema) {
|
||||||
function packageVersion() {
|
function packageVersion() {
|
||||||
try {
|
try {
|
||||||
const here = dirname5(fileURLToPath(import.meta.url));
|
const here = dirname5(fileURLToPath(import.meta.url));
|
||||||
const pkg = JSON.parse(readFileSync5(join10(here, "..", "package.json"), "utf8"));
|
const pkg = JSON.parse(readFileSync6(join10(here, "..", "package.json"), "utf8"));
|
||||||
return typeof pkg.version === "string" ? pkg.version : "0";
|
return typeof pkg.version === "string" ? pkg.version : "0";
|
||||||
} catch {
|
} catch {
|
||||||
return "0";
|
return "0";
|
||||||
|
|
@ -2048,7 +2100,7 @@ function registerNetxTools(ctx, connection, options) {
|
||||||
function packageVersion2() {
|
function packageVersion2() {
|
||||||
try {
|
try {
|
||||||
const here = dirname6(fileURLToPath2(import.meta.url));
|
const here = dirname6(fileURLToPath2(import.meta.url));
|
||||||
const pkg = JSON.parse(readFileSync6(join11(here, "..", "package.json"), "utf8"));
|
const pkg = JSON.parse(readFileSync7(join11(here, "..", "package.json"), "utf8"));
|
||||||
return typeof pkg.version === "string" ? pkg.version : "0";
|
return typeof pkg.version === "string" ? pkg.version : "0";
|
||||||
} catch {
|
} catch {
|
||||||
return "0";
|
return "0";
|
||||||
|
|
|
||||||
|
|
@ -1070,6 +1070,40 @@ function serializeDisabledTools(names) {
|
||||||
return JSON.stringify([...new Set(names.map((n) => n.trim()).filter(Boolean))].sort());
|
return JSON.stringify([...new Set(names.map((n) => n.trim()).filter(Boolean))].sort());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// src/netx/http.ts
|
||||||
|
function normalizeApiUrl(raw) {
|
||||||
|
const trimmed = typeof raw === "string" ? raw.trim() : "";
|
||||||
|
if (!trimmed)
|
||||||
|
return { ok: false, error: "empty" };
|
||||||
|
let parsed;
|
||||||
|
try {
|
||||||
|
parsed = new URL(trimmed);
|
||||||
|
} catch {
|
||||||
|
return { ok: false, error: "invalid_url" };
|
||||||
|
}
|
||||||
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
||||||
|
return { ok: false, error: "unsupported_scheme" };
|
||||||
|
}
|
||||||
|
if (parsed.username || parsed.password) {
|
||||||
|
return { ok: false, error: "credentials_in_url" };
|
||||||
|
}
|
||||||
|
const path = parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/$/, "");
|
||||||
|
const search = parsed.search && parsed.search !== "?" ? parsed.search : "";
|
||||||
|
return { ok: true, url: `${parsed.origin}${path}${search}` };
|
||||||
|
}
|
||||||
|
function isInsecureRemoteApiUrl(raw) {
|
||||||
|
let url;
|
||||||
|
try {
|
||||||
|
url = new URL(raw.trim());
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (url.protocol !== "http:")
|
||||||
|
return false;
|
||||||
|
const host = url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
||||||
|
return !(host === "localhost" || host.endsWith(".localhost") || host === "::1" || /^127\./.test(host));
|
||||||
|
}
|
||||||
|
|
||||||
// src/client/NetxopsCard.tsx
|
// src/client/NetxopsCard.tsx
|
||||||
var jsx_runtime = require("react/jsx-runtime");
|
var jsx_runtime = require("react/jsx-runtime");
|
||||||
function phaseLocaleKey(phase) {
|
function phaseLocaleKey(phase) {
|
||||||
|
|
@ -1641,6 +1675,10 @@ function NetxopsCard(props) {
|
||||||
props.resetField("apiUrl");
|
props.resetField("apiUrl");
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
isInsecureRemoteApiUrl(state.apiUrl.text) ? /* @__PURE__ */ jsx_runtime.jsx("p", {
|
||||||
|
className: "dsh-nx-invalid",
|
||||||
|
children: t("apiUrlInsecure")
|
||||||
|
}) : null,
|
||||||
/* @__PURE__ */ jsx_runtime.jsxs("div", {
|
/* @__PURE__ */ jsx_runtime.jsxs("div", {
|
||||||
className: "dsh-nx-fieldGrid",
|
className: "dsh-nx-fieldGrid",
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -2161,28 +2199,6 @@ function NetxopsCard(props) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/http.ts
|
|
||||||
function normalizeApiUrl(raw) {
|
|
||||||
const trimmed = typeof raw === "string" ? raw.trim() : "";
|
|
||||||
if (!trimmed)
|
|
||||||
return { ok: false, error: "empty" };
|
|
||||||
let parsed;
|
|
||||||
try {
|
|
||||||
parsed = new URL(trimmed);
|
|
||||||
} catch {
|
|
||||||
return { ok: false, error: "invalid_url" };
|
|
||||||
}
|
|
||||||
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
||||||
return { ok: false, error: "unsupported_scheme" };
|
|
||||||
}
|
|
||||||
if (parsed.username || parsed.password) {
|
|
||||||
return { ok: false, error: "credentials_in_url" };
|
|
||||||
}
|
|
||||||
const path = parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/$/, "");
|
|
||||||
const search = parsed.search && parsed.search !== "?" ? parsed.search : "";
|
|
||||||
return { ok: true, url: `${parsed.origin}${path}${search}` };
|
|
||||||
}
|
|
||||||
|
|
||||||
// src/client/snapshot-store.ts
|
// src/client/snapshot-store.ts
|
||||||
function createSnapshotStore(init) {
|
function createSnapshotStore(init) {
|
||||||
let state = init;
|
let state = init;
|
||||||
|
|
@ -3228,6 +3244,7 @@ var en = {
|
||||||
sectionExport: "Session export",
|
sectionExport: "Session export",
|
||||||
apiUrl: "API URL",
|
apiUrl: "API URL",
|
||||||
apiUrlHint: "http://127.0.0.1:8890",
|
apiUrlHint: "http://127.0.0.1:8890",
|
||||||
|
apiUrlInsecure: "Plain HTTP to a remote host: the token and the skills sent to the model are not encrypted. Use https://.",
|
||||||
apiUrlInvalid: "Enter an HTTP or HTTPS URL without credentials.",
|
apiUrlInvalid: "Enter an HTTP or HTTPS URL without credentials.",
|
||||||
lang: "API / alarm language",
|
lang: "API / alarm language",
|
||||||
thinkingLanguage: "Thinking language",
|
thinkingLanguage: "Thinking language",
|
||||||
|
|
@ -3324,6 +3341,7 @@ var zh = {
|
||||||
sectionExport: "会话导出",
|
sectionExport: "会话导出",
|
||||||
apiUrl: "API 地址",
|
apiUrl: "API 地址",
|
||||||
apiUrlHint: "http://127.0.0.1:8890",
|
apiUrlHint: "http://127.0.0.1:8890",
|
||||||
|
apiUrlInsecure: "连接的是远程主机且用的是明文 HTTP:token 和发给模型的 skill 都不加密,请改用 https://。",
|
||||||
apiUrlInvalid: "请输入不含账号密码的 HTTP 或 HTTPS 地址。",
|
apiUrlInvalid: "请输入不含账号密码的 HTTP 或 HTTPS 地址。",
|
||||||
lang: "API / 告警语言",
|
lang: "API / 告警语言",
|
||||||
thinkingLanguage: "思考语言",
|
thinkingLanguage: "思考语言",
|
||||||
|
|
|
||||||
140
lib/index.js
140
lib/index.js
|
|
@ -1398,6 +1398,7 @@ import {
|
||||||
} from "node:fs";
|
} from "node:fs";
|
||||||
import { homedir as homedir3 } from "node:os";
|
import { homedir as homedir3 } from "node:os";
|
||||||
import { dirname as dirname2, join as join3 } from "node:path";
|
import { dirname as dirname2, join as join3 } from "node:path";
|
||||||
|
var STALE_CACHE_MS = 30 * 24 * 60 * 60 * 1000;
|
||||||
function dshHome() {
|
function dshHome() {
|
||||||
const fromEnv = process.env.DSH_HOME?.trim();
|
const fromEnv = process.env.DSH_HOME?.trim();
|
||||||
return fromEnv || join3(homedir3(), ".dsh");
|
return fromEnv || join3(homedir3(), ".dsh");
|
||||||
|
|
@ -1410,8 +1411,65 @@ function skillsCacheKey(apiUrl) {
|
||||||
const base = normalizeSkillsApiUrl(apiUrl);
|
const base = normalizeSkillsApiUrl(apiUrl);
|
||||||
return createHash2("sha256").update(base).digest("hex").slice(0, 16);
|
return createHash2("sha256").update(base).digest("hex").slice(0, 16);
|
||||||
}
|
}
|
||||||
|
function cacheParent() {
|
||||||
|
return join3(dshHome(), "netxops-mcp-skills");
|
||||||
|
}
|
||||||
function skillsCacheDir(apiUrl) {
|
function skillsCacheDir(apiUrl) {
|
||||||
return join3(dshHome(), "netxops-mcp-skills", skillsCacheKey(apiUrl));
|
return join3(cacheParent(), skillsCacheKey(apiUrl));
|
||||||
|
}
|
||||||
|
function tokenFingerprint(token) {
|
||||||
|
const raw = token.trim();
|
||||||
|
return raw ? createHash2("sha256").update(raw).digest("hex").slice(0, 16) : "";
|
||||||
|
}
|
||||||
|
function readMeta(root) {
|
||||||
|
try {
|
||||||
|
const meta = JSON.parse(readFileSync3(join3(root, ".meta.json"), "utf8"));
|
||||||
|
return meta && typeof meta === "object" && !Array.isArray(meta) ? meta : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function usableSkillsCacheDir(apiUrl, token) {
|
||||||
|
if (!apiUrl.trim())
|
||||||
|
return "";
|
||||||
|
const root = skillsCacheDir(apiUrl);
|
||||||
|
const pulledWith = readMeta(root)?.tokenFp;
|
||||||
|
if (pulledWith && pulledWith !== tokenFingerprint(token))
|
||||||
|
return "";
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
function cachedUnavailableTools(root) {
|
||||||
|
const raw = readMeta(root)?.unavailableTools;
|
||||||
|
const out = {};
|
||||||
|
if (!raw || typeof raw !== "object")
|
||||||
|
return out;
|
||||||
|
for (const group of CAPABILITY_GROUP_IDS) {
|
||||||
|
const names = raw[group];
|
||||||
|
if (Array.isArray(names))
|
||||||
|
out[group] = names.filter((n) => typeof n === "string");
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
function pruneStaleCaches(keep) {
|
||||||
|
const parent = cacheParent();
|
||||||
|
let entries;
|
||||||
|
try {
|
||||||
|
entries = readdirSync(parent);
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const now = Date.now();
|
||||||
|
for (const name of entries) {
|
||||||
|
const dir = join3(parent, name);
|
||||||
|
if (dir === keep)
|
||||||
|
continue;
|
||||||
|
try {
|
||||||
|
const listedAt = Date.parse(readMeta(dir)?.listedAt ?? "");
|
||||||
|
const age = now - (Number.isFinite(listedAt) ? listedAt : statSync2(dir).mtimeMs);
|
||||||
|
if (age > STALE_CACHE_MS)
|
||||||
|
rmSync(dir, { recursive: true, force: true });
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function isSafeSegment(value) {
|
function isSafeSegment(value) {
|
||||||
return Boolean(value) && !value.includes("..") && !value.includes("/") && !value.includes("\\") && !value.includes("\x00");
|
return Boolean(value) && !value.includes("..") && !value.includes("/") && !value.includes("\\") && !value.includes("\x00");
|
||||||
|
|
@ -1422,12 +1480,17 @@ function isSafeRelPath(relPath) {
|
||||||
const parts = relPath.replace(/\\/g, "/").split("/");
|
const parts = relPath.replace(/\\/g, "/").split("/");
|
||||||
return parts.every((part) => part.length > 0 && part !== "." && part !== "..");
|
return parts.every((part) => part.length > 0 && part !== "." && part !== "..");
|
||||||
}
|
}
|
||||||
function replaceSkillsCache(apiUrl, files) {
|
function replaceSkillsCache(apiUrl, files, options = {}) {
|
||||||
const root = skillsCacheDir(apiUrl);
|
const root = skillsCacheDir(apiUrl);
|
||||||
rmSync(root, { recursive: true, force: true });
|
rmSync(root, { recursive: true, force: true });
|
||||||
mkdirSync2(root, { recursive: true });
|
mkdirSync2(root, { recursive: true });
|
||||||
let written = 0;
|
let written = 0;
|
||||||
|
const unavailableTools = {};
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
if (file.unavailableTools?.length && SKILL_DIR_BY_GROUP[file.group]) {
|
||||||
|
const merged = new Set([...unavailableTools[file.group] ?? [], ...file.unavailableTools]);
|
||||||
|
unavailableTools[file.group] = [...merged].sort();
|
||||||
|
}
|
||||||
const groupDir = SKILL_DIR_BY_GROUP[file.group];
|
const groupDir = SKILL_DIR_BY_GROUP[file.group];
|
||||||
if (!groupDir || !isSafeSegment(file.skill) || !isSafeRelPath(file.relPath))
|
if (!groupDir || !isSafeSegment(file.skill) || !isSafeRelPath(file.relPath))
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -1445,10 +1508,14 @@ function replaceSkillsCache(apiUrl, files) {
|
||||||
apiUrl: normalized,
|
apiUrl: normalized,
|
||||||
mcpUrl: normalized ? `${normalized}/mcp` : "",
|
mcpUrl: normalized ? `${normalized}/mcp` : "",
|
||||||
listedAt: new Date().toISOString(),
|
listedAt: new Date().toISOString(),
|
||||||
fileCount: written
|
fileCount: written,
|
||||||
|
...options.token !== undefined ? { tokenFp: tokenFingerprint(options.token) } : {},
|
||||||
|
unavailableTools
|
||||||
};
|
};
|
||||||
writeFileSync3(join3(root, ".meta.json"), `${JSON.stringify(meta, null, 2)}
|
writeFileSync3(join3(root, ".meta.json"), `${JSON.stringify(meta, null, 2)}
|
||||||
`, "utf8");
|
`, "utf8");
|
||||||
|
summaryMemo.delete(root);
|
||||||
|
pruneStaleCaches(root);
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
function countFiles(dir) {
|
function countFiles(dir) {
|
||||||
|
|
@ -1459,17 +1526,17 @@ function countFiles(dir) {
|
||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
function listCachedSkills(apiUrl) {
|
var summaryMemo = new Map;
|
||||||
const root = skillsCacheDir(apiUrl);
|
function listCachedSkills(apiUrl, token = "") {
|
||||||
|
const root = usableSkillsCacheDir(apiUrl, token);
|
||||||
|
if (!root || !existsSync3(root))
|
||||||
|
return { listedAt: null, skills: [] };
|
||||||
|
const metaListedAt = readMeta(root)?.listedAt;
|
||||||
|
const listedAt = typeof metaListedAt === "string" ? metaListedAt : null;
|
||||||
|
const memo = summaryMemo.get(root);
|
||||||
|
if (memo && memo.listedAt === listedAt)
|
||||||
|
return memo;
|
||||||
const skills = [];
|
const skills = [];
|
||||||
let listedAt = null;
|
|
||||||
if (!apiUrl.trim() || !existsSync3(root))
|
|
||||||
return { listedAt, skills };
|
|
||||||
try {
|
|
||||||
const meta = JSON.parse(readFileSync3(join3(root, ".meta.json"), "utf8"));
|
|
||||||
if (typeof meta.listedAt === "string")
|
|
||||||
listedAt = meta.listedAt;
|
|
||||||
} catch {}
|
|
||||||
for (const group of CAPABILITY_GROUP_IDS) {
|
for (const group of CAPABILITY_GROUP_IDS) {
|
||||||
const groupDir = join3(root, SKILL_DIR_BY_GROUP[group]);
|
const groupDir = join3(root, SKILL_DIR_BY_GROUP[group]);
|
||||||
if (!existsSync3(groupDir))
|
if (!existsSync3(groupDir))
|
||||||
|
|
@ -1483,7 +1550,15 @@ function listCachedSkills(apiUrl) {
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
return { listedAt, skills };
|
const listing = { listedAt, skills };
|
||||||
|
summaryMemo.set(root, listing);
|
||||||
|
return listing;
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/netx/mcp-types.ts
|
||||||
|
var NETX_TOOL_PREFIX = "netx__";
|
||||||
|
function publicToolName(serverName) {
|
||||||
|
return `${NETX_TOOL_PREFIX}${serverName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/netx/group-skills.ts
|
// src/netx/group-skills.ts
|
||||||
|
|
@ -1614,12 +1689,12 @@ function disabledFootnote(names, lang) {
|
||||||
const en = (lang ?? "").trim().toLowerCase().startsWith("en");
|
const en = (lang ?? "").trim().toLowerCase().startsWith("en");
|
||||||
return en ? `
|
return en ? `
|
||||||
|
|
||||||
## Disabled tools
|
## Unavailable tools
|
||||||
The following tools are disabled: ${list}. Do not call them.
|
The following tools are disabled or not granted to this token: ${list}. Do not call them; skip steps that need them.
|
||||||
` : `
|
` : `
|
||||||
|
|
||||||
## 已禁用工具
|
## 不可用工具
|
||||||
以下工具已禁用:${list},不要调用。
|
以下工具已禁用或当前 token 无权使用:${list},不要调用,涉及它们的步骤请跳过。
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
function resolveSkillsRoot(options) {
|
function resolveSkillsRoot(options) {
|
||||||
|
|
@ -1629,7 +1704,7 @@ function resolveSkillsRoot(options) {
|
||||||
const connection = getNetxConnection();
|
const connection = getNetxConnection();
|
||||||
if (!connection?.apiUrl?.trim())
|
if (!connection?.apiUrl?.trim())
|
||||||
return "";
|
return "";
|
||||||
return skillsCacheDir(connection.apiUrl);
|
return usableSkillsCacheDir(connection.apiUrl, connection.token ?? "");
|
||||||
}
|
}
|
||||||
async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions) {
|
async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions) {
|
||||||
const options = signalOrOptions instanceof AbortSignal || signalOrOptions === undefined ? { signal: signalOrOptions } : signalOrOptions;
|
const options = signalOrOptions instanceof AbortSignal || signalOrOptions === undefined ? { signal: signalOrOptions } : signalOrOptions;
|
||||||
|
|
@ -1648,6 +1723,7 @@ async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions
|
||||||
}
|
}
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
|
const unavailable = cachedUnavailableTools(root);
|
||||||
try {
|
try {
|
||||||
for (const groupId of CAPABILITY_GROUP_IDS) {
|
for (const groupId of CAPABILITY_GROUP_IDS) {
|
||||||
if (!enabled.has(groupId))
|
if (!enabled.has(groupId))
|
||||||
|
|
@ -1661,7 +1737,11 @@ async function registerGroupSkills(ctx, groupIds, providerLabel, signalOrOptions
|
||||||
if (skills.length === 0) {
|
if (skills.length === 0) {
|
||||||
log?.warn?.("%s: no skills loaded for group=%s from %s", providerLabel, groupId, join4(root, SKILL_DIR_BY_GROUP[groupId]));
|
log?.warn?.("%s: no skills loaded for group=%s from %s", providerLabel, groupId, join4(root, SKILL_DIR_BY_GROUP[groupId]));
|
||||||
}
|
}
|
||||||
const footnote = disabledFootnote(options.disabledByGroup?.[groupId] ?? [], options.lang);
|
const offNames = new Set([
|
||||||
|
...options.disabledByGroup?.[groupId] ?? [],
|
||||||
|
...(unavailable[groupId] ?? []).map(publicToolName)
|
||||||
|
]);
|
||||||
|
const footnote = disabledFootnote([...offNames].sort(), options.lang);
|
||||||
for (const skill of skills) {
|
for (const skill of skills) {
|
||||||
const base = groupId === "topology" ? `${skill.content}
|
const base = groupId === "topology" ? `${skill.content}
|
||||||
|
|
||||||
|
|
@ -4581,12 +4661,6 @@ import { dirname as dirname6, join as join12 } from "node:path";
|
||||||
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
||||||
import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
|
import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
|
||||||
|
|
||||||
// src/netx/mcp-types.ts
|
|
||||||
var NETX_TOOL_PREFIX = "netx__";
|
|
||||||
function publicToolName(serverName) {
|
|
||||||
return `${NETX_TOOL_PREFIX}${serverName}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// src/netx/mcp-catalog.ts
|
// src/netx/mcp-catalog.ts
|
||||||
function disabledSet(disabledTools) {
|
function disabledSet(disabledTools) {
|
||||||
const out = new Set;
|
const out = new Set;
|
||||||
|
|
@ -4720,7 +4794,14 @@ function parseSkillResource(resource, text) {
|
||||||
}
|
}
|
||||||
if (!relPath || relPath.includes(".."))
|
if (!relPath || relPath.includes(".."))
|
||||||
return null;
|
return null;
|
||||||
return { group, skill, relPath, text };
|
const unavailableTools = Array.isArray(netx.unavailableTools) ? netx.unavailableTools.filter((n) => typeof n === "string" && n.length > 0) : [];
|
||||||
|
return {
|
||||||
|
group,
|
||||||
|
skill,
|
||||||
|
relPath,
|
||||||
|
text,
|
||||||
|
...netx.entry === true && unavailableTools.length > 0 ? { unavailableTools } : {}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
function resourcesCapability(capabilities) {
|
function resourcesCapability(capabilities) {
|
||||||
if (!capabilities || typeof capabilities !== "object" || Array.isArray(capabilities))
|
if (!capabilities || typeof capabilities !== "object" || Array.isArray(capabilities))
|
||||||
|
|
@ -5086,7 +5167,7 @@ async function refreshMcpCatalog(input) {
|
||||||
if (gen !== store5().generation)
|
if (gen !== store5().generation)
|
||||||
return store5().state;
|
return store5().state;
|
||||||
if (fetched.skills !== undefined) {
|
if (fetched.skills !== undefined) {
|
||||||
replaceSkillsCache(input.apiUrl, fetched.skills);
|
replaceSkillsCache(input.apiUrl, fetched.skills, { token: input.token });
|
||||||
} else {
|
} else {
|
||||||
console.warn("netxops: mcp skills pull failed, keeping cache — %s", fetched.skillsError);
|
console.warn("netxops: mcp skills pull failed, keeping cache — %s", fetched.skillsError);
|
||||||
}
|
}
|
||||||
|
|
@ -5513,7 +5594,8 @@ function installPresetPublication(ctx) {
|
||||||
|
|
||||||
// src/index.ts
|
// src/index.ts
|
||||||
function mcpCatalogPayload(catalog) {
|
function mcpCatalogPayload(catalog) {
|
||||||
const cached = listCachedSkills(getNetxConnection()?.apiUrl ?? "");
|
const connection = getNetxConnection();
|
||||||
|
const cached = listCachedSkills(connection?.apiUrl ?? "", connection?.token ?? "");
|
||||||
return {
|
return {
|
||||||
phase: catalog.phase,
|
phase: catalog.phase,
|
||||||
mcpUrl: catalog.mcpUrl,
|
mcpUrl: catalog.mcpUrl,
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import { ensureStyles } from './styles.ts'
|
||||||
import { sessionsExportReasonLocaleKey } from './sessions-export-view.ts'
|
import { sessionsExportReasonLocaleKey } from './sessions-export-view.ts'
|
||||||
import { parsePresetIds } from '../netx/capability-groups.ts'
|
import { parsePresetIds } from '../netx/capability-groups.ts'
|
||||||
import { parseDisabledTools } from '../netx/mcp-catalog.ts'
|
import { parseDisabledTools } from '../netx/mcp-catalog.ts'
|
||||||
|
import { isInsecureRemoteApiUrl } from '../netx/http.ts'
|
||||||
import type { McpSkillRow, McpToolRow } from './mcp-catalog-view.ts'
|
import type { McpSkillRow, McpToolRow } from './mcp-catalog-view.ts'
|
||||||
import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
|
import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
|
||||||
import type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'
|
import type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'
|
||||||
|
|
@ -559,6 +560,9 @@ export function NetxopsCard(props: NetxopsCardProps) {
|
||||||
onEdit={(text) => { props.edit('apiUrl', text) }}
|
onEdit={(text) => { props.edit('apiUrl', text) }}
|
||||||
onReset={() => { props.resetField('apiUrl') }}
|
onReset={() => { props.resetField('apiUrl') }}
|
||||||
/>
|
/>
|
||||||
|
{isInsecureRemoteApiUrl(state.apiUrl.text)
|
||||||
|
? <p className="dsh-nx-invalid">{t('apiUrlInsecure')}</p>
|
||||||
|
: null}
|
||||||
<div className="dsh-nx-fieldGrid">
|
<div className="dsh-nx-fieldGrid">
|
||||||
<SelectField
|
<SelectField
|
||||||
id="netxops-lang"
|
id="netxops-lang"
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ export type NetxopsLocaleKey =
|
||||||
| 'sectionExport'
|
| 'sectionExport'
|
||||||
| 'apiUrl'
|
| 'apiUrl'
|
||||||
| 'apiUrlHint'
|
| 'apiUrlHint'
|
||||||
|
| 'apiUrlInsecure'
|
||||||
| 'apiUrlInvalid'
|
| 'apiUrlInvalid'
|
||||||
| 'lang'
|
| 'lang'
|
||||||
| 'thinkingLanguage'
|
| 'thinkingLanguage'
|
||||||
|
|
@ -120,6 +121,7 @@ export const en: Record<NetxopsLocaleKey, string> = {
|
||||||
sectionExport: 'Session export',
|
sectionExport: 'Session export',
|
||||||
apiUrl: 'API URL',
|
apiUrl: 'API URL',
|
||||||
apiUrlHint: 'http://127.0.0.1:8890',
|
apiUrlHint: 'http://127.0.0.1:8890',
|
||||||
|
apiUrlInsecure: 'Plain HTTP to a remote host: the token and the skills sent to the model are not encrypted. Use https://.',
|
||||||
apiUrlInvalid: 'Enter an HTTP or HTTPS URL without credentials.',
|
apiUrlInvalid: 'Enter an HTTP or HTTPS URL without credentials.',
|
||||||
lang: 'API / alarm language',
|
lang: 'API / alarm language',
|
||||||
thinkingLanguage: 'Thinking language',
|
thinkingLanguage: 'Thinking language',
|
||||||
|
|
@ -217,6 +219,7 @@ export const zh: Record<NetxopsLocaleKey, string> = {
|
||||||
sectionExport: '会话导出',
|
sectionExport: '会话导出',
|
||||||
apiUrl: 'API 地址',
|
apiUrl: 'API 地址',
|
||||||
apiUrlHint: 'http://127.0.0.1:8890',
|
apiUrlHint: 'http://127.0.0.1:8890',
|
||||||
|
apiUrlInsecure: '连接的是远程主机且用的是明文 HTTP:token 和发给模型的 skill 都不加密,请改用 https://。',
|
||||||
apiUrlInvalid: '请输入不含账号密码的 HTTP 或 HTTPS 地址。',
|
apiUrlInvalid: '请输入不含账号密码的 HTTP 或 HTTPS 地址。',
|
||||||
lang: 'API / 告警语言',
|
lang: 'API / 告警语言',
|
||||||
thinkingLanguage: '思考语言',
|
thinkingLanguage: '思考语言',
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,8 @@ import {
|
||||||
|
|
||||||
/** Plugins-card catalog payload: tools from the MCP store + skills from the disk cache. */
|
/** Plugins-card catalog payload: tools from the MCP store + skills from the disk cache. */
|
||||||
function mcpCatalogPayload(catalog: ReturnType<typeof getMcpCatalogState>) {
|
function mcpCatalogPayload(catalog: ReturnType<typeof getMcpCatalogState>) {
|
||||||
const cached = listCachedSkills(getNetxConnection()?.apiUrl ?? '')
|
const connection = getNetxConnection()
|
||||||
|
const cached = listCachedSkills(connection?.apiUrl ?? '', connection?.token ?? '')
|
||||||
return {
|
return {
|
||||||
phase: catalog.phase,
|
phase: catalog.phase,
|
||||||
mcpUrl: catalog.mcpUrl,
|
mcpUrl: catalog.mcpUrl,
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ import {
|
||||||
type NetxCapabilityGroupId,
|
type NetxCapabilityGroupId,
|
||||||
} from './capability-groups.ts'
|
} from './capability-groups.ts'
|
||||||
import { getNetxConnection } from './runtime.ts'
|
import { getNetxConnection } from './runtime.ts'
|
||||||
import { skillsCacheDir } from './skills-cache.ts'
|
import { cachedUnavailableTools, usableSkillsCacheDir } from './skills-cache.ts'
|
||||||
|
import { publicToolName } from './mcp-types.ts'
|
||||||
|
|
||||||
interface ParsedSkill {
|
interface ParsedSkill {
|
||||||
name: string
|
name: string
|
||||||
|
|
@ -181,8 +182,8 @@ function disabledFootnote(names: readonly string[], lang?: string): string {
|
||||||
const list = names.join(', ')
|
const list = names.join(', ')
|
||||||
const en = (lang ?? '').trim().toLowerCase().startsWith('en')
|
const en = (lang ?? '').trim().toLowerCase().startsWith('en')
|
||||||
return en
|
return en
|
||||||
? `\n\n## Disabled tools\nThe following tools are disabled: ${list}. Do not call them.\n`
|
? `\n\n## Unavailable tools\nThe following tools are disabled or not granted to this token: ${list}. Do not call them; skip steps that need them.\n`
|
||||||
: `\n\n## 已禁用工具\n以下工具已禁用:${list},不要调用。\n`
|
: `\n\n## 不可用工具\n以下工具已禁用或当前 token 无权使用:${list},不要调用,涉及它们的步骤请跳过。\n`
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveSkillsRoot(options: RegisterGroupSkillsOptions): string {
|
function resolveSkillsRoot(options: RegisterGroupSkillsOptions): string {
|
||||||
|
|
@ -191,7 +192,7 @@ function resolveSkillsRoot(options: RegisterGroupSkillsOptions): string {
|
||||||
}
|
}
|
||||||
const connection = getNetxConnection()
|
const connection = getNetxConnection()
|
||||||
if (!connection?.apiUrl?.trim()) return ''
|
if (!connection?.apiUrl?.trim()) return ''
|
||||||
return skillsCacheDir(connection.apiUrl)
|
return usableSkillsCacheDir(connection.apiUrl, connection.token ?? '')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -236,6 +237,7 @@ export async function registerGroupSkills(
|
||||||
}
|
}
|
||||||
return () => {}
|
return () => {}
|
||||||
}
|
}
|
||||||
|
const unavailable = cachedUnavailableTools(root)
|
||||||
try {
|
try {
|
||||||
for (const groupId of CAPABILITY_GROUP_IDS) {
|
for (const groupId of CAPABILITY_GROUP_IDS) {
|
||||||
if (!enabled.has(groupId)) continue
|
if (!enabled.has(groupId)) continue
|
||||||
|
|
@ -252,7 +254,11 @@ export async function registerGroupSkills(
|
||||||
join(root, SKILL_DIR_BY_GROUP[groupId]),
|
join(root, SKILL_DIR_BY_GROUP[groupId]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const footnote = disabledFootnote(options.disabledByGroup?.[groupId] ?? [], options.lang)
|
const offNames = new Set([
|
||||||
|
...(options.disabledByGroup?.[groupId] ?? []),
|
||||||
|
...(unavailable[groupId] ?? []).map(publicToolName),
|
||||||
|
])
|
||||||
|
const footnote = disabledFootnote([...offNames].sort(), options.lang)
|
||||||
for (const skill of skills) {
|
for (const skill of skills) {
|
||||||
const base = groupId === 'topology'
|
const base = groupId === 'topology'
|
||||||
? `${skill.content}\n\n## DeepSeek Harness REST capabilities\nUse only tools present in this session. Dual-unit/orbit/polish recipes require server-side topology tools. Use only tools present in this session.\n`
|
? `${skill.content}\n\n## DeepSeek Harness REST capabilities\nUse only tools present in this session. Dual-unit/orbit/polish recipes require server-side topology tools. Use only tools present in this session.\n`
|
||||||
|
|
|
||||||
|
|
@ -32,3 +32,19 @@ export function normalizeApiUrl(raw: string): NormalizeApiUrlResult {
|
||||||
const search = parsed.search && parsed.search !== '?' ? parsed.search : ''
|
const search = parsed.search && parsed.search !== '?' ? parsed.search : ''
|
||||||
return { ok: true, url: `${parsed.origin}${path}${search}` }
|
return { ok: true, url: `${parsed.origin}${path}${search}` }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True for plain `http://` to a non-loopback host: the token and the skills
|
||||||
|
* injected into the model's prompt would travel unencrypted.
|
||||||
|
*/
|
||||||
|
export function isInsecureRemoteApiUrl(raw: string): boolean {
|
||||||
|
let url: URL
|
||||||
|
try {
|
||||||
|
url = new URL(raw.trim())
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (url.protocol !== 'http:') return false
|
||||||
|
const host = url.hostname.replace(/^\[|\]$/g, '').toLowerCase()
|
||||||
|
return !(host === 'localhost' || host.endsWith('.localhost') || host === '::1' || /^127\./.test(host))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,16 @@ function parseSkillResource(
|
||||||
if (parts.length >= 3) relPath = parts.slice(2).join('/')
|
if (parts.length >= 3) relPath = parts.slice(2).join('/')
|
||||||
}
|
}
|
||||||
if (!relPath || relPath.includes('..')) return null
|
if (!relPath || relPath.includes('..')) return null
|
||||||
return { group, skill, relPath, text }
|
const unavailableTools = Array.isArray(netx.unavailableTools)
|
||||||
|
? netx.unavailableTools.filter((n): n is string => typeof n === 'string' && n.length > 0)
|
||||||
|
: []
|
||||||
|
return {
|
||||||
|
group,
|
||||||
|
skill,
|
||||||
|
relPath,
|
||||||
|
text,
|
||||||
|
...(netx.entry === true && unavailableTools.length > 0 ? { unavailableTools } : {}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resourcesCapability(capabilities: unknown): boolean {
|
function resourcesCapability(capabilities: unknown): boolean {
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ export async function refreshMcpCatalog(input: RefreshMcpCatalogInput): Promise<
|
||||||
|
|
||||||
if (fetched.skills !== undefined) {
|
if (fetched.skills !== undefined) {
|
||||||
// Includes "no resources capability" (empty list) → wipe stale files.
|
// Includes "no resources capability" (empty list) → wipe stale files.
|
||||||
replaceSkillsCache(input.apiUrl, fetched.skills)
|
replaceSkillsCache(input.apiUrl, fetched.skills, { token: input.token })
|
||||||
} else {
|
} else {
|
||||||
// Transient resources failure: keep the last good skill cache.
|
// Transient resources failure: keep the last good skill cache.
|
||||||
console.warn('netxops: mcp skills pull failed, keeping cache — %s', fetched.skillsError)
|
console.warn('netxops: mcp skills pull failed, keeping cache — %s', fetched.skillsError)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
* On-disk cache of netx MCP skill resources, keyed by API URL.
|
* On-disk cache of netx MCP skill resources, keyed by API URL.
|
||||||
*
|
*
|
||||||
* Layout: `$DSH_HOME/netxops-mcp-skills/<key>/<groupDir>/<skill>/<relPath>`
|
* Layout: `$DSH_HOME/netxops-mcp-skills/<key>/<groupDir>/<skill>/<relPath>`
|
||||||
|
* plus `.meta.json` (listedAt, token fingerprint, per-group unavailable tools).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createHash } from 'node:crypto'
|
import { createHash } from 'node:crypto'
|
||||||
|
|
@ -29,8 +30,23 @@ export interface CachedSkillFile {
|
||||||
/** Posix-relative path inside the skill directory (e.g. `SKILL.md`). */
|
/** Posix-relative path inside the skill directory (e.g. `SKILL.md`). */
|
||||||
relPath: string
|
relPath: string
|
||||||
text: string
|
text: string
|
||||||
|
/** SKILL.md only: server tools of this group the token cannot call. */
|
||||||
|
unavailableTools?: readonly string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SkillsCacheMeta {
|
||||||
|
apiUrl?: string
|
||||||
|
mcpUrl?: string
|
||||||
|
listedAt?: string
|
||||||
|
fileCount?: number
|
||||||
|
/** sha256 prefix of the token that pulled this cache. */
|
||||||
|
tokenFp?: string
|
||||||
|
unavailableTools?: Partial<Record<NetxCapabilityGroupId, string[]>>
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Caches untouched for this long (other netx addresses) are pruned on refresh. */
|
||||||
|
const STALE_CACHE_MS = 30 * 24 * 60 * 60 * 1000
|
||||||
|
|
||||||
function dshHome(): string {
|
function dshHome(): string {
|
||||||
const fromEnv = process.env.DSH_HOME?.trim()
|
const fromEnv = process.env.DSH_HOME?.trim()
|
||||||
return fromEnv || join(homedir(), '.dsh')
|
return fromEnv || join(homedir(), '.dsh')
|
||||||
|
|
@ -48,9 +64,75 @@ export function skillsCacheKey(apiUrl: string): string {
|
||||||
return createHash('sha256').update(base).digest('hex').slice(0, 16)
|
return createHash('sha256').update(base).digest('hex').slice(0, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cacheParent(): string {
|
||||||
|
return join(dshHome(), 'netxops-mcp-skills')
|
||||||
|
}
|
||||||
|
|
||||||
/** Absolute cache root for one netx API URL (may not exist yet). */
|
/** Absolute cache root for one netx API URL (may not exist yet). */
|
||||||
export function skillsCacheDir(apiUrl: string): string {
|
export function skillsCacheDir(apiUrl: string): string {
|
||||||
return join(dshHome(), 'netxops-mcp-skills', skillsCacheKey(apiUrl))
|
return join(cacheParent(), skillsCacheKey(apiUrl))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Short fingerprint of a token (never the token itself). */
|
||||||
|
export function tokenFingerprint(token: string): string {
|
||||||
|
const raw = token.trim()
|
||||||
|
return raw ? createHash('sha256').update(raw).digest('hex').slice(0, 16) : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function readMeta(root: string): SkillsCacheMeta | null {
|
||||||
|
try {
|
||||||
|
const meta = JSON.parse(readFileSync(join(root, '.meta.json'), 'utf8')) as unknown
|
||||||
|
return meta && typeof meta === 'object' && !Array.isArray(meta) ? meta as SkillsCacheMeta : null
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache root usable for this connection, or '' when the cache was pulled with
|
||||||
|
* a different token (switching to a narrower token must not keep mounting the
|
||||||
|
* broader token's skills while netx is offline).
|
||||||
|
*/
|
||||||
|
export function usableSkillsCacheDir(apiUrl: string, token: string): string {
|
||||||
|
if (!apiUrl.trim()) return ''
|
||||||
|
const root = skillsCacheDir(apiUrl)
|
||||||
|
const pulledWith = readMeta(root)?.tokenFp
|
||||||
|
if (pulledWith && pulledWith !== tokenFingerprint(token)) return ''
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Per-group tools the pulling token could not call (from `.meta.json`). */
|
||||||
|
export function cachedUnavailableTools(root: string): Partial<Record<NetxCapabilityGroupId, string[]>> {
|
||||||
|
const raw = readMeta(root)?.unavailableTools
|
||||||
|
const out: Partial<Record<NetxCapabilityGroupId, string[]>> = {}
|
||||||
|
if (!raw || typeof raw !== 'object') return out
|
||||||
|
for (const group of CAPABILITY_GROUP_IDS) {
|
||||||
|
const names = raw[group]
|
||||||
|
if (Array.isArray(names)) out[group] = names.filter((n): n is string => typeof n === 'string')
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function pruneStaleCaches(keep: string): void {
|
||||||
|
const parent = cacheParent()
|
||||||
|
let entries: string[]
|
||||||
|
try {
|
||||||
|
entries = readdirSync(parent)
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const now = Date.now()
|
||||||
|
for (const name of entries) {
|
||||||
|
const dir = join(parent, name)
|
||||||
|
if (dir === keep) continue
|
||||||
|
try {
|
||||||
|
const listedAt = Date.parse(readMeta(dir)?.listedAt ?? '')
|
||||||
|
const age = now - (Number.isFinite(listedAt) ? listedAt : statSync(dir).mtimeMs)
|
||||||
|
if (age > STALE_CACHE_MS) rmSync(dir, { recursive: true, force: true })
|
||||||
|
} catch {
|
||||||
|
// best effort
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSafeSegment(value: string): boolean {
|
function isSafeSegment(value: string): boolean {
|
||||||
|
|
@ -72,12 +154,21 @@ function isSafeRelPath(relPath: string): boolean {
|
||||||
* Call only after a successful tools/list when skills were resolved
|
* Call only after a successful tools/list when skills were resolved
|
||||||
* (including “no resources / empty list”).
|
* (including “no resources / empty list”).
|
||||||
*/
|
*/
|
||||||
export function replaceSkillsCache(apiUrl: string, files: readonly CachedSkillFile[]): string {
|
export function replaceSkillsCache(
|
||||||
|
apiUrl: string,
|
||||||
|
files: readonly CachedSkillFile[],
|
||||||
|
options: { token?: string } = {},
|
||||||
|
): string {
|
||||||
const root = skillsCacheDir(apiUrl)
|
const root = skillsCacheDir(apiUrl)
|
||||||
rmSync(root, { recursive: true, force: true })
|
rmSync(root, { recursive: true, force: true })
|
||||||
mkdirSync(root, { recursive: true })
|
mkdirSync(root, { recursive: true })
|
||||||
let written = 0
|
let written = 0
|
||||||
|
const unavailableTools: Partial<Record<NetxCapabilityGroupId, string[]>> = {}
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
if (file.unavailableTools?.length && SKILL_DIR_BY_GROUP[file.group]) {
|
||||||
|
const merged = new Set([...(unavailableTools[file.group] ?? []), ...file.unavailableTools])
|
||||||
|
unavailableTools[file.group] = [...merged].sort()
|
||||||
|
}
|
||||||
const groupDir = SKILL_DIR_BY_GROUP[file.group]
|
const groupDir = SKILL_DIR_BY_GROUP[file.group]
|
||||||
if (!groupDir || !isSafeSegment(file.skill) || !isSafeRelPath(file.relPath)) continue
|
if (!groupDir || !isSafeSegment(file.skill) || !isSafeRelPath(file.relPath)) continue
|
||||||
const rel = file.relPath.replace(/\\/g, '/')
|
const rel = file.relPath.replace(/\\/g, '/')
|
||||||
|
|
@ -88,13 +179,17 @@ export function replaceSkillsCache(apiUrl: string, files: readonly CachedSkillFi
|
||||||
written += 1
|
written += 1
|
||||||
}
|
}
|
||||||
const normalized = normalizeSkillsApiUrl(apiUrl)
|
const normalized = normalizeSkillsApiUrl(apiUrl)
|
||||||
const meta = {
|
const meta: SkillsCacheMeta = {
|
||||||
apiUrl: normalized,
|
apiUrl: normalized,
|
||||||
mcpUrl: normalized ? `${normalized}/mcp` : '',
|
mcpUrl: normalized ? `${normalized}/mcp` : '',
|
||||||
listedAt: new Date().toISOString(),
|
listedAt: new Date().toISOString(),
|
||||||
fileCount: written,
|
fileCount: written,
|
||||||
|
...(options.token !== undefined ? { tokenFp: tokenFingerprint(options.token) } : {}),
|
||||||
|
unavailableTools,
|
||||||
}
|
}
|
||||||
writeFileSync(join(root, '.meta.json'), `${JSON.stringify(meta, null, 2)}\n`, 'utf8')
|
writeFileSync(join(root, '.meta.json'), `${JSON.stringify(meta, null, 2)}\n`, 'utf8')
|
||||||
|
summaryMemo.delete(root)
|
||||||
|
pruneStaleCaches(root)
|
||||||
return root
|
return root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,18 +220,20 @@ function countFiles(dir: string): number {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Skills currently in the cache for `apiUrl` (what the plugin will mount). */
|
type CachedSkillsListing = { listedAt: string | null; skills: CachedSkillSummary[] }
|
||||||
export function listCachedSkills(apiUrl: string): { listedAt: string | null; skills: CachedSkillSummary[] } {
|
|
||||||
const root = skillsCacheDir(apiUrl)
|
/** root → listing; invalidated by replaceSkillsCache, keyed by .meta.json listedAt. */
|
||||||
|
const summaryMemo = new Map<string, CachedSkillsListing>()
|
||||||
|
|
||||||
|
/** Skills the plugin will mount for this connection (Plugins card polls this). */
|
||||||
|
export function listCachedSkills(apiUrl: string, token = ''): CachedSkillsListing {
|
||||||
|
const root = usableSkillsCacheDir(apiUrl, token)
|
||||||
|
if (!root || !existsSync(root)) return { listedAt: null, skills: [] }
|
||||||
|
const metaListedAt = readMeta(root)?.listedAt
|
||||||
|
const listedAt = typeof metaListedAt === 'string' ? metaListedAt : null
|
||||||
|
const memo = summaryMemo.get(root)
|
||||||
|
if (memo && memo.listedAt === listedAt) return memo
|
||||||
const skills: CachedSkillSummary[] = []
|
const skills: CachedSkillSummary[] = []
|
||||||
let listedAt: string | null = null
|
|
||||||
if (!apiUrl.trim() || !existsSync(root)) return { listedAt, skills }
|
|
||||||
try {
|
|
||||||
const meta = JSON.parse(readFileSync(join(root, '.meta.json'), 'utf8')) as { listedAt?: unknown }
|
|
||||||
if (typeof meta.listedAt === 'string') listedAt = meta.listedAt
|
|
||||||
} catch {
|
|
||||||
// no meta yet
|
|
||||||
}
|
|
||||||
for (const group of CAPABILITY_GROUP_IDS) {
|
for (const group of CAPABILITY_GROUP_IDS) {
|
||||||
const groupDir = join(root, SKILL_DIR_BY_GROUP[group])
|
const groupDir = join(root, SKILL_DIR_BY_GROUP[group])
|
||||||
if (!existsSync(groupDir)) continue
|
if (!existsSync(groupDir)) continue
|
||||||
|
|
@ -150,5 +247,7 @@ export function listCachedSkills(apiUrl: string): { listedAt: string | null; ski
|
||||||
// unreadable group dir → skip
|
// unreadable group dir → skip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { listedAt, skills }
|
const listing = { listedAt, skills }
|
||||||
|
summaryMemo.set(root, listing)
|
||||||
|
return listing
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ test('registerGroupSkills appends disabled footnote; skips disabled groups', asy
|
||||||
lang: 'zh',
|
lang: 'zh',
|
||||||
})
|
})
|
||||||
assert.ok(registered.length >= 1)
|
assert.ok(registered.length >= 1)
|
||||||
assert.match(registered[0].content, /以下工具已禁用:netx__execManagedNe,不要调用/)
|
assert.match(registered[0].content, /以下工具已禁用或当前 token 无权使用:netx__execManagedNe,不要调用/)
|
||||||
dispose()
|
dispose()
|
||||||
|
|
||||||
registered.length = 0
|
registered.length = 0
|
||||||
|
|
@ -71,6 +71,6 @@ test('registerGroupSkills appends disabled footnote; skips disabled groups', asy
|
||||||
disabledByGroup: { ops: ['netx__execManagedNe'] },
|
disabledByGroup: { ops: ['netx__execManagedNe'] },
|
||||||
lang: 'en',
|
lang: 'en',
|
||||||
})
|
})
|
||||||
assert.match(registered[0].content, /The following tools are disabled: netx__execManagedNe/)
|
assert.match(registered[0].content, /disabled or not granted to this token: netx__execManagedNe/)
|
||||||
dispose3()
|
dispose3()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
import assert from 'node:assert/strict'
|
import assert from 'node:assert/strict'
|
||||||
import test from 'node:test'
|
import test from 'node:test'
|
||||||
import http from 'node:http'
|
import http from 'node:http'
|
||||||
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, utimesSync, writeFileSync } from 'node:fs'
|
||||||
import { tmpdir } from 'node:os'
|
import { tmpdir } from 'node:os'
|
||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { refreshMcpCatalog, resetMcpCatalog } from '../src/netx/mcp-store.ts'
|
||||||
import { publishNetxConnection } from '../src/netx/runtime.ts'
|
import { publishNetxConnection } from '../src/netx/runtime.ts'
|
||||||
import {
|
import {
|
||||||
hasSkillsCache,
|
hasSkillsCache,
|
||||||
|
usableSkillsCacheDir,
|
||||||
listCachedSkills,
|
listCachedSkills,
|
||||||
replaceSkillsCache,
|
replaceSkillsCache,
|
||||||
skillsCacheDir,
|
skillsCacheDir,
|
||||||
|
|
@ -48,6 +49,7 @@ function skillResources(files) {
|
||||||
group: f.group,
|
group: f.group,
|
||||||
skill: f.skill,
|
skill: f.skill,
|
||||||
entry: f.relPath === 'SKILL.md',
|
entry: f.relPath === 'SKILL.md',
|
||||||
|
...(f.unavailableTools ? { unavailableTools: f.unavailableTools } : {}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
text: f.text,
|
text: f.text,
|
||||||
|
|
@ -365,3 +367,55 @@ test('listCachedSkills summarizes cached skills per group for the Plugins card',
|
||||||
])
|
])
|
||||||
assert.deepEqual(listCachedSkills('').skills, [])
|
assert.deepEqual(listCachedSkills('').skills, [])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('tools the token cannot call are merged into the skill footnote', async (t) => {
|
||||||
|
withTempHome(t)
|
||||||
|
const resources = skillResources([{
|
||||||
|
group: 'ops', groupDir: 'ops', skill: 'netx-ops', relPath: 'SKILL.md',
|
||||||
|
text: OPS_SKILL, unavailableTools: ['execManagedNe'],
|
||||||
|
}])
|
||||||
|
const mock = await startMockMcp({ resources })
|
||||||
|
t.after(() => mock.server.close())
|
||||||
|
await refreshMcpCatalog({ apiUrl: mock.apiUrl, token: 'secret' })
|
||||||
|
publishNetxConnection({ apiUrl: mock.apiUrl, token: 'secret', lang: 'zh', toolCallTimeoutMs: 5_000 })
|
||||||
|
const { ctx, registered } = fakeSkillsCtx()
|
||||||
|
await registerGroupSkills(ctx, ['ops'], 'test', { disabledByGroup: { ops: ['netx__queryNmsAlarms'] } })
|
||||||
|
assert.match(registered[0].content, /netx__execManagedNe/)
|
||||||
|
assert.match(registered[0].content, /netx__queryNmsAlarms/)
|
||||||
|
assert.match(registered[0].content, /当前 token 无权使用/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('cache pulled with another token is not mounted', async (t) => {
|
||||||
|
withTempHome(t)
|
||||||
|
replaceSkillsCache(API, [{ group: 'ops', skill: 'netx-ops', relPath: 'SKILL.md', text: OPS_SKILL }], { token: 'wide' })
|
||||||
|
assert.ok(usableSkillsCacheDir(API, 'wide'))
|
||||||
|
assert.equal(usableSkillsCacheDir(API, 'narrow'), '')
|
||||||
|
assert.deepEqual(listCachedSkills(API, 'narrow').skills, [])
|
||||||
|
publishNetxConnection({ apiUrl: API, token: 'narrow', lang: 'zh', toolCallTimeoutMs: 5_000 })
|
||||||
|
const { ctx, registered } = fakeSkillsCtx()
|
||||||
|
await registerGroupSkills(ctx, ['ops'], 'test')
|
||||||
|
assert.equal(registered.length, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('refresh prunes caches of other netx addresses unused for 30 days', (t) => {
|
||||||
|
const home = withTempHome(t)
|
||||||
|
const stale = join(home, 'netxops-mcp-skills', 'deadbeefdeadbeef')
|
||||||
|
mkdirSync(stale, { recursive: true })
|
||||||
|
writeFileSync(join(stale, '.meta.json'), JSON.stringify({ listedAt: '2020-01-01T00:00:00.000Z' }))
|
||||||
|
const fresh = join(home, 'netxops-mcp-skills', 'feedfacefeedface')
|
||||||
|
mkdirSync(fresh, { recursive: true })
|
||||||
|
writeFileSync(join(fresh, '.meta.json'), JSON.stringify({ listedAt: new Date().toISOString() }))
|
||||||
|
replaceSkillsCache(API, [], { token: 'secret' })
|
||||||
|
assert.equal(existsSync(stale), false)
|
||||||
|
assert.equal(existsSync(fresh), true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('isInsecureRemoteApiUrl flags plain http only for non-loopback hosts', async () => {
|
||||||
|
const { isInsecureRemoteApiUrl } = await import('../src/netx/http.ts')
|
||||||
|
assert.equal(isInsecureRemoteApiUrl('http://127.0.0.1:8890'), false)
|
||||||
|
assert.equal(isInsecureRemoteApiUrl('http://localhost:8890'), false)
|
||||||
|
assert.equal(isInsecureRemoteApiUrl('http://[::1]:8890'), false)
|
||||||
|
assert.equal(isInsecureRemoteApiUrl('https://netx.example.com'), false)
|
||||||
|
assert.equal(isInsecureRemoteApiUrl('http://192.168.1.10:8890'), true)
|
||||||
|
assert.equal(isInsecureRemoteApiUrl('not a url'), false)
|
||||||
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue