@xyagent/cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +860 -0
- package/bin/agentlink +8585 -0
- package/bin/agentlink-agent +7 -0
- package/bin/agentlink-mcp-stdio +173 -0
- package/docs/LOGGING.md +159 -0
- package/package.json +38 -0
- package/src/core.mjs +320 -0
- package/src/tunnel.mjs +291 -0
- package/src/tunnel_proxy.mjs +913 -0
- package/src/tunnel_service.mjs +699 -0
- package/src/tunnel_state.mjs +301 -0
- package/src/unified_producer.mjs +259 -0
- package/src-ext/README.md +75 -0
- package/src-ext/bin.mjs +128 -0
- package/src-ext/commands/agent.mjs +66 -0
- package/src-ext/commands/help.mjs +31 -0
- package/src-ext/commands/pair.mjs +455 -0
- package/src-ext/commands/reload.mjs +78 -0
- package/src-ext/commands/service.mjs +64 -0
- package/src-ext/core/accountResolver.mjs +137 -0
- package/src-ext/core/activeRuns.mjs +76 -0
- package/src-ext/core/agentlinkToolsBootstrap.mjs +75 -0
- package/src-ext/core/agentlinkToolsRegistry.mjs +323 -0
- package/src-ext/core/agentlinkToolsServer.mjs +109 -0
- package/src-ext/core/approvalGateway.mjs +132 -0
- package/src-ext/core/attachAuditLog.mjs +79 -0
- package/src-ext/core/attachDenylist.mjs +128 -0
- package/src-ext/core/autoTunnelDetector.mjs +98 -0
- package/src-ext/core/autoTunnelGateway.mjs +393 -0
- package/src-ext/core/autoTunnelWiring.mjs +184 -0
- package/src-ext/core/boundedMap.mjs +98 -0
- package/src-ext/core/bridgeAttachAutoScanner.mjs +237 -0
- package/src-ext/core/bridgeHooks.mjs +168 -0
- package/src-ext/core/bridgeSelfUninstall.mjs +133 -0
- package/src-ext/core/claudeTrust.mjs +74 -0
- package/src-ext/core/codexTrust.mjs +77 -0
- package/src-ext/core/cosUploadClient.mjs +326 -0
- package/src-ext/core/createWorkspace.mjs +170 -0
- package/src-ext/core/daemonRestart.mjs +49 -0
- package/src-ext/core/defaultWorkspace.mjs +65 -0
- package/src-ext/core/ensureGitRepo.mjs +57 -0
- package/src-ext/core/hermesProfile.mjs +75 -0
- package/src-ext/core/interruptGateway.mjs +119 -0
- package/src-ext/core/interruptRunListener.mjs +106 -0
- package/src-ext/core/mcpRuntimeFanout.mjs +236 -0
- package/src-ext/core/mcpStdioClient.mjs +316 -0
- package/src-ext/core/openclawGatewayClient.mjs +230 -0
- package/src-ext/core/pairCodeClient.mjs +331 -0
- package/src-ext/core/pairInventory.mjs +59 -0
- package/src-ext/core/pathExpand.mjs +34 -0
- package/src-ext/core/preprocessAttachments.mjs +113 -0
- package/src-ext/core/relayWorker.mjs +284 -0
- package/src-ext/core/relayWorkerHttp.mjs +58 -0
- package/src-ext/core/scanWorkspaces.mjs +842 -0
- package/src-ext/core/sessionSlot.mjs +70 -0
- package/src-ext/core/stateMachine.mjs +85 -0
- package/src-ext/core/toolsServerLifecycle.mjs +169 -0
- package/src-ext/core/unifiedDispatchHandler.mjs +439 -0
- package/src-ext/core/usageReporter.mjs +207 -0
- package/src-ext/openclaw-plugin/envelope-builder.cjs +619 -0
- package/src-ext/openclaw-plugin/index.cjs +1919 -0
- package/src-ext/openclaw-plugin/openclaw.plugin.json +20 -0
- package/src-ext/openclaw-plugin/package.json +12 -0
- package/src-ext/openclaw-plugin/relay-defaults.json +3 -0
- package/src-ext/openclaw-plugin/todoTranslatorUtils.cjs +101 -0
- package/src-ext/runtime/_shared/cliExec.mjs +138 -0
- package/src-ext/runtime/_shared/cosTextRefs.mjs +120 -0
- package/src-ext/runtime/_shared/fetchAndBase64Encode.mjs +52 -0
- package/src-ext/runtime/_shared/relayObjectToBlock.mjs +307 -0
- package/src-ext/runtime/_shared/sessionPicker.mjs +418 -0
- package/src-ext/runtime/_shared/slashCommandRouter.mjs +1346 -0
- package/src-ext/runtime/_shared/todoTranslatorUtils.mjs +94 -0
- package/src-ext/runtime/claude/askUserQuestionTranslator.mjs +241 -0
- package/src-ext/runtime/claude/customCommandsCollector.mjs +132 -0
- package/src-ext/runtime/claude/handleRequest.mjs +1418 -0
- package/src-ext/runtime/claude/index.mjs +160 -0
- package/src-ext/runtime/claude/internalCommandNames.mjs +58 -0
- package/src-ext/runtime/claude/launcher.mjs +313 -0
- package/src-ext/runtime/claude/mcpConfigAdapter.mjs +174 -0
- package/src-ext/runtime/claude/mcpConfigWriter.mjs +57 -0
- package/src-ext/runtime/claude/modelContextWindow.mjs +30 -0
- package/src-ext/runtime/claude/modelScanner.mjs +257 -0
- package/src-ext/runtime/claude/permissionInterruptBlock.mjs +86 -0
- package/src-ext/runtime/claude/permissionPromptServer.mjs +170 -0
- package/src-ext/runtime/claude/permissionPromptServerWorker.mjs +214 -0
- package/src-ext/runtime/claude/preflight.mjs +62 -0
- package/src-ext/runtime/claude/stdoutParser.mjs +365 -0
- package/src-ext/runtime/claude/todoTranslator.mjs +66 -0
- package/src-ext/runtime/claude/usageProbe.mjs +576 -0
- package/src-ext/runtime/claude/usageProbeStrip.mjs +14 -0
- package/src-ext/runtime/codex/codexCustomCommandsCollector.mjs +87 -0
- package/src-ext/runtime/codex/elicitationHandler.mjs +91 -0
- package/src-ext/runtime/codex/eventMapper.mjs +154 -0
- package/src-ext/runtime/codex/handleRequest.mjs +1152 -0
- package/src-ext/runtime/codex/index.mjs +142 -0
- package/src-ext/runtime/codex/mcpConfigAdapter.mjs +155 -0
- package/src-ext/runtime/codex/modelScanner.mjs +305 -0
- package/src-ext/runtime/codex/planNotificationHandler.mjs +61 -0
- package/src-ext/runtime/codex/preflight.mjs +67 -0
- package/src-ext/runtime/hermes/commandBackend.mjs +117 -0
- package/src-ext/runtime/hermes/envSetup.mjs +229 -0
- package/src-ext/runtime/hermes/gatewayManager.mjs +625 -0
- package/src-ext/runtime/hermes/handleRequest.mjs +1257 -0
- package/src-ext/runtime/hermes/httpBackend.mjs +219 -0
- package/src-ext/runtime/hermes/index.mjs +196 -0
- package/src-ext/runtime/hermes/mcpConfigAdapter.mjs +189 -0
- package/src-ext/runtime/hermes/preflight.mjs +359 -0
- package/src-ext/runtime/hermes/responsesBackend.mjs +276 -0
- package/src-ext/runtime/hermes/sessionStore.mjs +78 -0
- package/src-ext/runtime/hermes/todoTranslator.mjs +90 -0
- package/src-ext/runtime/hermes/workspaceRpc.mjs +124 -0
- package/src-ext/runtime/openclaw/buildOpenclawDaemonInput.mjs +143 -0
- package/src-ext/runtime/openclaw/mcpConfigAdapter.mjs +91 -0
- package/src-ext/runtime/openclaw/openclawConfig.mjs +145 -0
- package/src-ext/runtime/picoclaw/constants.mjs +39 -0
- package/src-ext/runtime/picoclaw/handleRequest.mjs +289 -0
- package/src-ext/runtime/picoclaw/index.mjs +314 -0
- package/src-ext/runtime/picoclaw/pairFlow.mjs +224 -0
- package/src-ext/runtime/picoclaw/state.mjs +78 -0
- package/src-ext/runtime/picoclaw/todoTranslator.mjs +67 -0
- package/src-ext/runtime/picoclaw/translator.mjs +272 -0
- package/src-ext/runtime/picoclaw/wsClient.mjs +129 -0
- package/src-ext/service/serviceManager.mjs +521 -0
- package/src-shared/README.md +44 -0
- package/src-shared/chat_push_normalizer.mjs +113 -0
- package/src-shared/correlation.mjs +25 -0
- package/src-shared/envelope_builder.mjs +612 -0
- package/src-shared/logger.mjs +167 -0
- package/src-shared/relay-defaults.json +3 -0
- package/src-shared/relay_revoke.mjs +46 -0
- package/src-shared/vendor/README.md +60 -0
- package/src-shared/vendor/smol-toml/LICENSE +24 -0
- package/src-shared/vendor/smol-toml/README.md +236 -0
- package/src-shared/vendor/smol-toml/VENDORED.md +37 -0
- package/src-shared/vendor/smol-toml/dist/date.d.ts +41 -0
- package/src-shared/vendor/smol-toml/dist/date.js +127 -0
- package/src-shared/vendor/smol-toml/dist/error.d.ts +38 -0
- package/src-shared/vendor/smol-toml/dist/error.js +63 -0
- package/src-shared/vendor/smol-toml/dist/extract.d.ts +30 -0
- package/src-shared/vendor/smol-toml/dist/extract.js +109 -0
- package/src-shared/vendor/smol-toml/dist/index.cjs +900 -0
- package/src-shared/vendor/smol-toml/dist/index.d.ts +43 -0
- package/src-shared/vendor/smol-toml/dist/index.js +33 -0
- package/src-shared/vendor/smol-toml/dist/parse.d.ts +37 -0
- package/src-shared/vendor/smol-toml/dist/parse.js +148 -0
- package/src-shared/vendor/smol-toml/dist/primitive.d.ts +31 -0
- package/src-shared/vendor/smol-toml/dist/primitive.js +178 -0
- package/src-shared/vendor/smol-toml/dist/stringify.d.ts +31 -0
- package/src-shared/vendor/smol-toml/dist/stringify.js +163 -0
- package/src-shared/vendor/smol-toml/dist/struct.d.ts +32 -0
- package/src-shared/vendor/smol-toml/dist/struct.js +194 -0
- package/src-shared/vendor/smol-toml/dist/util.d.ts +42 -0
- package/src-shared/vendor/smol-toml/dist/util.js +100 -0
- package/src-shared/vendor/smol-toml/package.json +54 -0
- package/src-shared/vendor/yaml/LICENSE +13 -0
- package/src-shared/vendor/yaml/README.md +155 -0
- package/src-shared/vendor/yaml/VENDORED.md +29 -0
- package/src-shared/vendor/yaml/bin.mjs +11 -0
- package/src-shared/vendor/yaml/browser/dist/compose/compose-collection.js +88 -0
- package/src-shared/vendor/yaml/browser/dist/compose/compose-doc.js +42 -0
- package/src-shared/vendor/yaml/browser/dist/compose/compose-node.js +92 -0
- package/src-shared/vendor/yaml/browser/dist/compose/compose-scalar.js +80 -0
- package/src-shared/vendor/yaml/browser/dist/compose/composer.js +217 -0
- package/src-shared/vendor/yaml/browser/dist/compose/resolve-block-map.js +113 -0
- package/src-shared/vendor/yaml/browser/dist/compose/resolve-block-scalar.js +198 -0
- package/src-shared/vendor/yaml/browser/dist/compose/resolve-block-seq.js +47 -0
- package/src-shared/vendor/yaml/browser/dist/compose/resolve-end.js +37 -0
- package/src-shared/vendor/yaml/browser/dist/compose/resolve-flow-collection.js +203 -0
- package/src-shared/vendor/yaml/browser/dist/compose/resolve-flow-scalar.js +223 -0
- package/src-shared/vendor/yaml/browser/dist/compose/resolve-props.js +148 -0
- package/src-shared/vendor/yaml/browser/dist/compose/util-contains-newline.js +34 -0
- package/src-shared/vendor/yaml/browser/dist/compose/util-empty-scalar-position.js +27 -0
- package/src-shared/vendor/yaml/browser/dist/compose/util-flow-indent-check.js +15 -0
- package/src-shared/vendor/yaml/browser/dist/compose/util-map-includes.js +17 -0
- package/src-shared/vendor/yaml/browser/dist/doc/Document.js +334 -0
- package/src-shared/vendor/yaml/browser/dist/doc/anchors.js +72 -0
- package/src-shared/vendor/yaml/browser/dist/doc/applyReviver.js +55 -0
- package/src-shared/vendor/yaml/browser/dist/doc/createNode.js +89 -0
- package/src-shared/vendor/yaml/browser/dist/doc/directives.js +176 -0
- package/src-shared/vendor/yaml/browser/dist/errors.js +57 -0
- package/src-shared/vendor/yaml/browser/dist/index.js +17 -0
- package/src-shared/vendor/yaml/browser/dist/log.js +14 -0
- package/src-shared/vendor/yaml/browser/dist/nodes/Alias.js +101 -0
- package/src-shared/vendor/yaml/browser/dist/nodes/Collection.js +147 -0
- package/src-shared/vendor/yaml/browser/dist/nodes/Node.js +38 -0
- package/src-shared/vendor/yaml/browser/dist/nodes/Pair.js +36 -0
- package/src-shared/vendor/yaml/browser/dist/nodes/Scalar.js +24 -0
- package/src-shared/vendor/yaml/browser/dist/nodes/YAMLMap.js +144 -0
- package/src-shared/vendor/yaml/browser/dist/nodes/YAMLSeq.js +113 -0
- package/src-shared/vendor/yaml/browser/dist/nodes/addPairToJSMap.js +104 -0
- package/src-shared/vendor/yaml/browser/dist/nodes/identity.js +36 -0
- package/src-shared/vendor/yaml/browser/dist/nodes/toJS.js +37 -0
- package/src-shared/vendor/yaml/browser/dist/parse/cst-scalar.js +214 -0
- package/src-shared/vendor/yaml/browser/dist/parse/cst-stringify.js +61 -0
- package/src-shared/vendor/yaml/browser/dist/parse/cst-visit.js +97 -0
- package/src-shared/vendor/yaml/browser/dist/parse/cst.js +98 -0
- package/src-shared/vendor/yaml/browser/dist/parse/lexer.js +717 -0
- package/src-shared/vendor/yaml/browser/dist/parse/line-counter.js +39 -0
- package/src-shared/vendor/yaml/browser/dist/parse/parser.js +954 -0
- package/src-shared/vendor/yaml/browser/dist/public-api.js +99 -0
- package/src-shared/vendor/yaml/browser/dist/schema/Schema.js +38 -0
- package/src-shared/vendor/yaml/browser/dist/schema/common/map.js +17 -0
- package/src-shared/vendor/yaml/browser/dist/schema/common/null.js +15 -0
- package/src-shared/vendor/yaml/browser/dist/schema/common/seq.js +17 -0
- package/src-shared/vendor/yaml/browser/dist/schema/common/string.js +14 -0
- package/src-shared/vendor/yaml/browser/dist/schema/core/bool.js +19 -0
- package/src-shared/vendor/yaml/browser/dist/schema/core/float.js +43 -0
- package/src-shared/vendor/yaml/browser/dist/schema/core/int.js +38 -0
- package/src-shared/vendor/yaml/browser/dist/schema/core/schema.js +23 -0
- package/src-shared/vendor/yaml/browser/dist/schema/json/schema.js +62 -0
- package/src-shared/vendor/yaml/browser/dist/schema/tags.js +83 -0
- package/src-shared/vendor/yaml/browser/dist/schema/yaml-1.1/binary.js +66 -0
- package/src-shared/vendor/yaml/browser/dist/schema/yaml-1.1/bool.js +26 -0
- package/src-shared/vendor/yaml/browser/dist/schema/yaml-1.1/float.js +46 -0
- package/src-shared/vendor/yaml/browser/dist/schema/yaml-1.1/int.js +71 -0
- package/src-shared/vendor/yaml/browser/dist/schema/yaml-1.1/omap.js +74 -0
- package/src-shared/vendor/yaml/browser/dist/schema/yaml-1.1/pairs.js +78 -0
- package/src-shared/vendor/yaml/browser/dist/schema/yaml-1.1/schema.js +37 -0
- package/src-shared/vendor/yaml/browser/dist/schema/yaml-1.1/set.js +93 -0
- package/src-shared/vendor/yaml/browser/dist/schema/yaml-1.1/timestamp.js +101 -0
- package/src-shared/vendor/yaml/browser/dist/stringify/foldFlowLines.js +146 -0
- package/src-shared/vendor/yaml/browser/dist/stringify/stringify.js +124 -0
- package/src-shared/vendor/yaml/browser/dist/stringify/stringifyCollection.js +143 -0
- package/src-shared/vendor/yaml/browser/dist/stringify/stringifyComment.js +20 -0
- package/src-shared/vendor/yaml/browser/dist/stringify/stringifyDocument.js +85 -0
- package/src-shared/vendor/yaml/browser/dist/stringify/stringifyNumber.js +24 -0
- package/src-shared/vendor/yaml/browser/dist/stringify/stringifyPair.js +150 -0
- package/src-shared/vendor/yaml/browser/dist/stringify/stringifyString.js +328 -0
- package/src-shared/vendor/yaml/browser/dist/util.js +11 -0
- package/src-shared/vendor/yaml/browser/dist/visit.js +233 -0
- package/src-shared/vendor/yaml/browser/index.js +5 -0
- package/src-shared/vendor/yaml/browser/package.json +3 -0
- package/src-shared/vendor/yaml/dist/cli.d.ts +8 -0
- package/src-shared/vendor/yaml/dist/cli.mjs +199 -0
- package/src-shared/vendor/yaml/dist/compose/compose-collection.d.ts +11 -0
- package/src-shared/vendor/yaml/dist/compose/compose-collection.js +90 -0
- package/src-shared/vendor/yaml/dist/compose/compose-doc.d.ts +7 -0
- package/src-shared/vendor/yaml/dist/compose/compose-doc.js +44 -0
- package/src-shared/vendor/yaml/dist/compose/compose-node.d.ts +28 -0
- package/src-shared/vendor/yaml/dist/compose/compose-node.js +95 -0
- package/src-shared/vendor/yaml/dist/compose/compose-scalar.d.ts +5 -0
- package/src-shared/vendor/yaml/dist/compose/compose-scalar.js +82 -0
- package/src-shared/vendor/yaml/dist/compose/composer.d.ts +62 -0
- package/src-shared/vendor/yaml/dist/compose/composer.js +221 -0
- package/src-shared/vendor/yaml/dist/compose/resolve-block-map.d.ts +6 -0
- package/src-shared/vendor/yaml/dist/compose/resolve-block-map.js +115 -0
- package/src-shared/vendor/yaml/dist/compose/resolve-block-scalar.d.ts +11 -0
- package/src-shared/vendor/yaml/dist/compose/resolve-block-scalar.js +200 -0
- package/src-shared/vendor/yaml/dist/compose/resolve-block-seq.d.ts +6 -0
- package/src-shared/vendor/yaml/dist/compose/resolve-block-seq.js +49 -0
- package/src-shared/vendor/yaml/dist/compose/resolve-end.d.ts +6 -0
- package/src-shared/vendor/yaml/dist/compose/resolve-end.js +39 -0
- package/src-shared/vendor/yaml/dist/compose/resolve-flow-collection.d.ts +7 -0
- package/src-shared/vendor/yaml/dist/compose/resolve-flow-collection.js +205 -0
- package/src-shared/vendor/yaml/dist/compose/resolve-flow-scalar.d.ts +10 -0
- package/src-shared/vendor/yaml/dist/compose/resolve-flow-scalar.js +225 -0
- package/src-shared/vendor/yaml/dist/compose/resolve-props.d.ts +23 -0
- package/src-shared/vendor/yaml/dist/compose/resolve-props.js +150 -0
- package/src-shared/vendor/yaml/dist/compose/util-contains-newline.d.ts +2 -0
- package/src-shared/vendor/yaml/dist/compose/util-contains-newline.js +36 -0
- package/src-shared/vendor/yaml/dist/compose/util-empty-scalar-position.d.ts +2 -0
- package/src-shared/vendor/yaml/dist/compose/util-empty-scalar-position.js +29 -0
- package/src-shared/vendor/yaml/dist/compose/util-flow-indent-check.d.ts +3 -0
- package/src-shared/vendor/yaml/dist/compose/util-flow-indent-check.js +17 -0
- package/src-shared/vendor/yaml/dist/compose/util-map-includes.d.ts +4 -0
- package/src-shared/vendor/yaml/dist/compose/util-map-includes.js +19 -0
- package/src-shared/vendor/yaml/dist/doc/Document.d.ts +141 -0
- package/src-shared/vendor/yaml/dist/doc/Document.js +336 -0
- package/src-shared/vendor/yaml/dist/doc/anchors.d.ts +24 -0
- package/src-shared/vendor/yaml/dist/doc/anchors.js +77 -0
- package/src-shared/vendor/yaml/dist/doc/applyReviver.d.ts +9 -0
- package/src-shared/vendor/yaml/dist/doc/applyReviver.js +57 -0
- package/src-shared/vendor/yaml/dist/doc/createNode.d.ts +17 -0
- package/src-shared/vendor/yaml/dist/doc/createNode.js +91 -0
- package/src-shared/vendor/yaml/dist/doc/directives.d.ts +49 -0
- package/src-shared/vendor/yaml/dist/doc/directives.js +178 -0
- package/src-shared/vendor/yaml/dist/errors.d.ts +21 -0
- package/src-shared/vendor/yaml/dist/errors.js +62 -0
- package/src-shared/vendor/yaml/dist/index.d.ts +22 -0
- package/src-shared/vendor/yaml/dist/index.js +50 -0
- package/src-shared/vendor/yaml/dist/log.d.ts +3 -0
- package/src-shared/vendor/yaml/dist/log.js +17 -0
- package/src-shared/vendor/yaml/dist/nodes/Alias.d.ts +28 -0
- package/src-shared/vendor/yaml/dist/nodes/Alias.js +103 -0
- package/src-shared/vendor/yaml/dist/nodes/Collection.d.ts +73 -0
- package/src-shared/vendor/yaml/dist/nodes/Collection.js +151 -0
- package/src-shared/vendor/yaml/dist/nodes/Node.d.ts +47 -0
- package/src-shared/vendor/yaml/dist/nodes/Node.js +40 -0
- package/src-shared/vendor/yaml/dist/nodes/Pair.d.ts +21 -0
- package/src-shared/vendor/yaml/dist/nodes/Pair.js +39 -0
- package/src-shared/vendor/yaml/dist/nodes/Scalar.d.ts +42 -0
- package/src-shared/vendor/yaml/dist/nodes/Scalar.js +27 -0
- package/src-shared/vendor/yaml/dist/nodes/YAMLMap.d.ts +53 -0
- package/src-shared/vendor/yaml/dist/nodes/YAMLMap.js +147 -0
- package/src-shared/vendor/yaml/dist/nodes/YAMLSeq.d.ts +60 -0
- package/src-shared/vendor/yaml/dist/nodes/YAMLSeq.js +115 -0
- package/src-shared/vendor/yaml/dist/nodes/addPairToJSMap.d.ts +4 -0
- package/src-shared/vendor/yaml/dist/nodes/addPairToJSMap.js +106 -0
- package/src-shared/vendor/yaml/dist/nodes/identity.d.ts +23 -0
- package/src-shared/vendor/yaml/dist/nodes/identity.js +53 -0
- package/src-shared/vendor/yaml/dist/nodes/toJS.d.ts +27 -0
- package/src-shared/vendor/yaml/dist/nodes/toJS.js +39 -0
- package/src-shared/vendor/yaml/dist/options.d.ts +338 -0
- package/src-shared/vendor/yaml/dist/parse/cst-scalar.d.ts +64 -0
- package/src-shared/vendor/yaml/dist/parse/cst-scalar.js +218 -0
- package/src-shared/vendor/yaml/dist/parse/cst-stringify.d.ts +8 -0
- package/src-shared/vendor/yaml/dist/parse/cst-stringify.js +63 -0
- package/src-shared/vendor/yaml/dist/parse/cst-visit.d.ts +39 -0
- package/src-shared/vendor/yaml/dist/parse/cst-visit.js +99 -0
- package/src-shared/vendor/yaml/dist/parse/cst.d.ts +108 -0
- package/src-shared/vendor/yaml/dist/parse/cst.js +112 -0
- package/src-shared/vendor/yaml/dist/parse/lexer.d.ts +87 -0
- package/src-shared/vendor/yaml/dist/parse/lexer.js +719 -0
- package/src-shared/vendor/yaml/dist/parse/line-counter.d.ts +22 -0
- package/src-shared/vendor/yaml/dist/parse/line-counter.js +41 -0
- package/src-shared/vendor/yaml/dist/parse/parser.d.ts +84 -0
- package/src-shared/vendor/yaml/dist/parse/parser.js +958 -0
- package/src-shared/vendor/yaml/dist/public-api.d.ts +43 -0
- package/src-shared/vendor/yaml/dist/public-api.js +104 -0
- package/src-shared/vendor/yaml/dist/schema/Schema.d.ts +18 -0
- package/src-shared/vendor/yaml/dist/schema/Schema.js +40 -0
- package/src-shared/vendor/yaml/dist/schema/common/map.d.ts +2 -0
- package/src-shared/vendor/yaml/dist/schema/common/map.js +19 -0
- package/src-shared/vendor/yaml/dist/schema/common/null.d.ts +4 -0
- package/src-shared/vendor/yaml/dist/schema/common/null.js +17 -0
- package/src-shared/vendor/yaml/dist/schema/common/seq.d.ts +2 -0
- package/src-shared/vendor/yaml/dist/schema/common/seq.js +19 -0
- package/src-shared/vendor/yaml/dist/schema/common/string.d.ts +2 -0
- package/src-shared/vendor/yaml/dist/schema/common/string.js +16 -0
- package/src-shared/vendor/yaml/dist/schema/core/bool.d.ts +4 -0
- package/src-shared/vendor/yaml/dist/schema/core/bool.js +21 -0
- package/src-shared/vendor/yaml/dist/schema/core/float.d.ts +4 -0
- package/src-shared/vendor/yaml/dist/schema/core/float.js +47 -0
- package/src-shared/vendor/yaml/dist/schema/core/int.d.ts +4 -0
- package/src-shared/vendor/yaml/dist/schema/core/int.js +42 -0
- package/src-shared/vendor/yaml/dist/schema/core/schema.d.ts +1 -0
- package/src-shared/vendor/yaml/dist/schema/core/schema.js +25 -0
- package/src-shared/vendor/yaml/dist/schema/json/schema.d.ts +2 -0
- package/src-shared/vendor/yaml/dist/schema/json/schema.js +64 -0
- package/src-shared/vendor/yaml/dist/schema/json-schema.d.ts +69 -0
- package/src-shared/vendor/yaml/dist/schema/tags.d.ts +40 -0
- package/src-shared/vendor/yaml/dist/schema/tags.js +86 -0
- package/src-shared/vendor/yaml/dist/schema/types.d.ts +90 -0
- package/src-shared/vendor/yaml/dist/schema/yaml-1.1/binary.d.ts +2 -0
- package/src-shared/vendor/yaml/dist/schema/yaml-1.1/binary.js +68 -0
- package/src-shared/vendor/yaml/dist/schema/yaml-1.1/bool.d.ts +7 -0
- package/src-shared/vendor/yaml/dist/schema/yaml-1.1/bool.js +29 -0
- package/src-shared/vendor/yaml/dist/schema/yaml-1.1/float.d.ts +4 -0
- package/src-shared/vendor/yaml/dist/schema/yaml-1.1/float.js +50 -0
- package/src-shared/vendor/yaml/dist/schema/yaml-1.1/int.d.ts +5 -0
- package/src-shared/vendor/yaml/dist/schema/yaml-1.1/int.js +76 -0
- package/src-shared/vendor/yaml/dist/schema/yaml-1.1/omap.d.ts +28 -0
- package/src-shared/vendor/yaml/dist/schema/yaml-1.1/omap.js +77 -0
- package/src-shared/vendor/yaml/dist/schema/yaml-1.1/pairs.d.ts +10 -0
- package/src-shared/vendor/yaml/dist/schema/yaml-1.1/pairs.js +82 -0
- package/src-shared/vendor/yaml/dist/schema/yaml-1.1/schema.d.ts +1 -0
- package/src-shared/vendor/yaml/dist/schema/yaml-1.1/schema.js +39 -0
- package/src-shared/vendor/yaml/dist/schema/yaml-1.1/set.d.ts +28 -0
- package/src-shared/vendor/yaml/dist/schema/yaml-1.1/set.js +96 -0
- package/src-shared/vendor/yaml/dist/schema/yaml-1.1/timestamp.d.ts +6 -0
- package/src-shared/vendor/yaml/dist/schema/yaml-1.1/timestamp.js +105 -0
- package/src-shared/vendor/yaml/dist/stringify/foldFlowLines.d.ts +34 -0
- package/src-shared/vendor/yaml/dist/stringify/foldFlowLines.js +151 -0
- package/src-shared/vendor/yaml/dist/stringify/stringify.d.ts +21 -0
- package/src-shared/vendor/yaml/dist/stringify/stringify.js +127 -0
- package/src-shared/vendor/yaml/dist/stringify/stringifyCollection.d.ts +17 -0
- package/src-shared/vendor/yaml/dist/stringify/stringifyCollection.js +145 -0
- package/src-shared/vendor/yaml/dist/stringify/stringifyComment.d.ts +10 -0
- package/src-shared/vendor/yaml/dist/stringify/stringifyComment.js +24 -0
- package/src-shared/vendor/yaml/dist/stringify/stringifyDocument.d.ts +4 -0
- package/src-shared/vendor/yaml/dist/stringify/stringifyDocument.js +87 -0
- package/src-shared/vendor/yaml/dist/stringify/stringifyNumber.d.ts +2 -0
- package/src-shared/vendor/yaml/dist/stringify/stringifyNumber.js +26 -0
- package/src-shared/vendor/yaml/dist/stringify/stringifyPair.d.ts +3 -0
- package/src-shared/vendor/yaml/dist/stringify/stringifyPair.js +152 -0
- package/src-shared/vendor/yaml/dist/stringify/stringifyString.d.ts +9 -0
- package/src-shared/vendor/yaml/dist/stringify/stringifyString.js +330 -0
- package/src-shared/vendor/yaml/dist/test-events.d.ts +4 -0
- package/src-shared/vendor/yaml/dist/test-events.js +134 -0
- package/src-shared/vendor/yaml/dist/util.d.ts +12 -0
- package/src-shared/vendor/yaml/dist/util.js +28 -0
- package/src-shared/vendor/yaml/dist/visit.d.ts +102 -0
- package/src-shared/vendor/yaml/dist/visit.js +236 -0
- package/src-shared/vendor/yaml/package.json +96 -0
- package/src-shared/vendor/yaml/util.js +2 -0
|
@@ -0,0 +1,842 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scanWorkspaces.mjs — relay RPC `workspaces.scan` implementation
|
|
3
|
+
*
|
|
4
|
+
* Exports:
|
|
5
|
+
* readOpenClawWorkspaces({ gatewayId, limit, includeBranch, workspaceRoot? })
|
|
6
|
+
* readClaudeWorkspaces({ gatewayId, limit, includeBranch, claudeJsonPath? })
|
|
7
|
+
* readCodexWorkspaces({ gatewayId, limit, includeBranch, codexHome? })
|
|
8
|
+
* readHermesWorkspaces()
|
|
9
|
+
* scanWorkspacesByRuntime({ runtimeKind, gatewayId, params })
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import crypto from "node:crypto";
|
|
13
|
+
import { execFile } from "node:child_process";
|
|
14
|
+
import fs from "node:fs";
|
|
15
|
+
import os from "node:os";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { defaultWorkspacePath } from "./defaultWorkspace.mjs";
|
|
18
|
+
|
|
19
|
+
// ─── constants ───────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
const DEFAULT_LIMIT = 50;
|
|
22
|
+
const MAX_LIMIT = 500;
|
|
23
|
+
const HARD_TIMEOUT_MS = 5000;
|
|
24
|
+
const GIT_BRANCH_TIMEOUT_MS = 200;
|
|
25
|
+
|
|
26
|
+
// Runtime metadata — agentKind values must match dart stub ScannedAgentGroup.kind exactly.
|
|
27
|
+
const RUNTIME_META = {
|
|
28
|
+
OpenClaw: {
|
|
29
|
+
agentKind: "OpenClaw",
|
|
30
|
+
emoji: "🐼",
|
|
31
|
+
tagline: "开源多模态智能体运行时",
|
|
32
|
+
idPrefix: "oc",
|
|
33
|
+
},
|
|
34
|
+
"Claude Code": {
|
|
35
|
+
agentKind: "Claude Code",
|
|
36
|
+
emoji: "🧠",
|
|
37
|
+
tagline: "Anthropic 官方 AI 编程助手",
|
|
38
|
+
idPrefix: "cc",
|
|
39
|
+
},
|
|
40
|
+
Codex: {
|
|
41
|
+
agentKind: "Codex",
|
|
42
|
+
emoji: "💡",
|
|
43
|
+
tagline: "OpenAI Codex CLI 智能体",
|
|
44
|
+
idPrefix: "cx",
|
|
45
|
+
},
|
|
46
|
+
Hermes: {
|
|
47
|
+
agentKind: "Hermes",
|
|
48
|
+
emoji: "🪽",
|
|
49
|
+
tagline: "Agentlink 内置对话智能体",
|
|
50
|
+
idPrefix: "hm",
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// runtimeKind string → RUNTIME_META key
|
|
55
|
+
const RUNTIME_KIND_MAP = {
|
|
56
|
+
openclaw: "OpenClaw",
|
|
57
|
+
"claude code": "Claude Code",
|
|
58
|
+
claude: "Claude Code",
|
|
59
|
+
codex: "Codex",
|
|
60
|
+
hermes: "Hermes",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// ─── utility functions ───────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Stable, deterministic workspace ID: <prefix>-<sha256(absolutePath).hex.slice(0,12)>
|
|
67
|
+
*/
|
|
68
|
+
export function pathToId(prefix, absolutePath) {
|
|
69
|
+
const hash = crypto.createHash("sha256").update(absolutePath, "utf8").digest("hex");
|
|
70
|
+
return `${prefix}-${hash.slice(0, 12)}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Shorten absolutePath to tilde form for display.
|
|
75
|
+
*/
|
|
76
|
+
export function tildify(absolutePath, home = os.homedir()) {
|
|
77
|
+
if (absolutePath === home) return "~";
|
|
78
|
+
if (absolutePath.startsWith(home + path.sep)) {
|
|
79
|
+
return "~" + absolutePath.slice(home.length);
|
|
80
|
+
}
|
|
81
|
+
return absolutePath;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Host metadata object.
|
|
86
|
+
*/
|
|
87
|
+
export function hostMeta(gatewayId) {
|
|
88
|
+
return {
|
|
89
|
+
id: gatewayId,
|
|
90
|
+
label: os.hostname(),
|
|
91
|
+
platform: process.platform,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get the current git branch for a directory. Returns null on timeout or error.
|
|
97
|
+
* @param {string} absolutePath
|
|
98
|
+
* @param {number} timeoutMs
|
|
99
|
+
* @returns {Promise<string|null>}
|
|
100
|
+
*/
|
|
101
|
+
export function gitBranch(absolutePath, timeoutMs = GIT_BRANCH_TIMEOUT_MS) {
|
|
102
|
+
return new Promise((resolve) => {
|
|
103
|
+
const timer = setTimeout(() => {
|
|
104
|
+
child.kill();
|
|
105
|
+
resolve(null);
|
|
106
|
+
}, timeoutMs);
|
|
107
|
+
|
|
108
|
+
const child = execFile(
|
|
109
|
+
"git",
|
|
110
|
+
["symbolic-ref", "--short", "HEAD"],
|
|
111
|
+
{ cwd: absolutePath, encoding: "utf8" },
|
|
112
|
+
(err, stdout) => {
|
|
113
|
+
clearTimeout(timer);
|
|
114
|
+
if (err) {
|
|
115
|
+
resolve(null);
|
|
116
|
+
} else {
|
|
117
|
+
resolve(stdout.trim() || null);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Run gitBranch for multiple paths concurrently, bounded by CPU count.
|
|
126
|
+
*/
|
|
127
|
+
async function resolveBranches(paths, includeBranch) {
|
|
128
|
+
if (!includeBranch) return paths.map(() => null);
|
|
129
|
+
const concurrency = Math.max(1, os.cpus().length);
|
|
130
|
+
const results = new Array(paths.length).fill(null);
|
|
131
|
+
for (let i = 0; i < paths.length; i += concurrency) {
|
|
132
|
+
const batch = paths.slice(i, i + concurrency);
|
|
133
|
+
const settled = await Promise.allSettled(
|
|
134
|
+
batch.map((p) => gitBranch(p)),
|
|
135
|
+
);
|
|
136
|
+
for (let j = 0; j < batch.length; j++) {
|
|
137
|
+
const r = settled[j];
|
|
138
|
+
results[i + j] = r.status === "fulfilled" ? r.value : null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return results;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ─── OpenClaw reader ─────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Read OpenClaw workspaces.
|
|
148
|
+
*
|
|
149
|
+
* OpenClaw 模型:workspace = agent 的家目录本身(cwd / 记忆目录),**1 个 agent
|
|
150
|
+
* 对应 1 个 workspace**(见官方 concepts/agent-workspace)。所以这里返回每个 agent
|
|
151
|
+
* 配置的 workspace 目录「本身」,而不是去列某个根目录下的项目子目录(那是
|
|
152
|
+
* Claude/Codex 的「多项目」口径,不适用于 OpenClaw)。
|
|
153
|
+
*
|
|
154
|
+
* 解析来源([resolveOpenClawAgentWorkspaceDirs],对齐 bin/agentlink):
|
|
155
|
+
* ~/.openclaw/openclaw.json 的 agents.list[].workspace(缺省继承
|
|
156
|
+
* agents.defaults.workspace);list 为空时即默认那一个。
|
|
157
|
+
*
|
|
158
|
+
* @param {object} opts
|
|
159
|
+
* @param {string} opts.gatewayId
|
|
160
|
+
* @param {number} opts.limit
|
|
161
|
+
* @param {boolean} opts.includeBranch
|
|
162
|
+
* @param {string} [opts.workspaceRoot] 测试注入:视作唯一 agent 的 workspace 目录
|
|
163
|
+
*/
|
|
164
|
+
export async function readOpenClawWorkspaces({
|
|
165
|
+
gatewayId,
|
|
166
|
+
limit,
|
|
167
|
+
includeBranch,
|
|
168
|
+
workspaceRoot,
|
|
169
|
+
}) {
|
|
170
|
+
const warnings = [];
|
|
171
|
+
|
|
172
|
+
let dirs;
|
|
173
|
+
if (workspaceRoot) {
|
|
174
|
+
dirs = [path.resolve(workspaceRoot)];
|
|
175
|
+
} else {
|
|
176
|
+
try {
|
|
177
|
+
dirs = resolveOpenClawAgentWorkspaceDirs();
|
|
178
|
+
} catch (err) {
|
|
179
|
+
warnings.push(`OpenClaw: failed to resolve workspace dirs: ${err.message}`);
|
|
180
|
+
return { workspaces: [], warnings, truncated: false };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 去重 + 只保留真实存在的目录(不存在不算错误:agent 可能还没初始化 workspace)。
|
|
185
|
+
const seen = new Set();
|
|
186
|
+
const existing = [];
|
|
187
|
+
for (const d of dirs) {
|
|
188
|
+
const abs = path.resolve(d);
|
|
189
|
+
if (seen.has(abs)) continue;
|
|
190
|
+
seen.add(abs);
|
|
191
|
+
try {
|
|
192
|
+
if (fs.statSync(abs).isDirectory()) existing.push(abs);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
if (err.code === "EACCES" || err.code === "EPERM") {
|
|
195
|
+
warnings.push(`OpenClaw: permission denied reading ${abs}: ${err.message}`);
|
|
196
|
+
}
|
|
197
|
+
// ENOENT / 其它:静默跳过(未初始化的 workspace 不报错)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const effectiveLimit = clampLimit(limit);
|
|
202
|
+
const truncated = existing.length > effectiveLimit;
|
|
203
|
+
const selected = existing.slice(0, effectiveLimit);
|
|
204
|
+
|
|
205
|
+
const branches = await resolveBranches(selected, includeBranch);
|
|
206
|
+
const meta = RUNTIME_META["OpenClaw"];
|
|
207
|
+
const workspaces = selected.map((abs, i) => ({
|
|
208
|
+
id: pathToId(meta.idPrefix, abs),
|
|
209
|
+
name: path.basename(abs),
|
|
210
|
+
path: abs,
|
|
211
|
+
absolutePath: abs,
|
|
212
|
+
branch: branches[i],
|
|
213
|
+
lastUsedAtMs: null,
|
|
214
|
+
}));
|
|
215
|
+
|
|
216
|
+
return { workspaces, warnings, truncated };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ─── Claude Code reader ───────────────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 从 ~/.claude.json 的 projects 中挑出应展示的项目路径。
|
|
223
|
+
*
|
|
224
|
+
* 入选条件(任一满足):
|
|
225
|
+
* 1. 显式信任:hasTrustDialogAccepted === true
|
|
226
|
+
* 2. 实际用过:存在 lastSessionId / lastCost / lastDuration(跑过 session 的痕迹)。
|
|
227
|
+
* 新版 Claude Code 在祖先目录已信任时子目录项目自身标记为 false,仅靠条件 1 会漏掉
|
|
228
|
+
* 用户真正在用的项目(P0:「我电脑上项目都没扫出来」)。
|
|
229
|
+
* 3. 信任继承:某个显式信任目录是该路径的祖先(与 Claude Code 的目录信任语义一致)。
|
|
230
|
+
*
|
|
231
|
+
* @param {Record<string, object>} projects ~/.claude.json 的 projects 对象
|
|
232
|
+
* @returns {string[]} 原始路径列表(未 normalize)
|
|
233
|
+
*/
|
|
234
|
+
export function selectClaudeProjectPaths(projects) {
|
|
235
|
+
const entries = Object.entries(projects).filter(([, meta]) => meta && typeof meta === "object");
|
|
236
|
+
const explicitlyTrusted = entries
|
|
237
|
+
.filter(([, meta]) => meta.hasTrustDialogAccepted === true)
|
|
238
|
+
.map(([p]) => p);
|
|
239
|
+
const trustedPrefixes = explicitlyTrusted.map((p) => (p.endsWith(path.sep) ? p : p + path.sep));
|
|
240
|
+
|
|
241
|
+
const hasUsageEvidence = (meta) =>
|
|
242
|
+
(typeof meta.lastSessionId === "string" && meta.lastSessionId !== "") ||
|
|
243
|
+
typeof meta.lastCost === "number" ||
|
|
244
|
+
typeof meta.lastDuration === "number";
|
|
245
|
+
const hasTrustedAncestor = (p) => trustedPrefixes.some((prefix) => p.startsWith(prefix));
|
|
246
|
+
|
|
247
|
+
return entries
|
|
248
|
+
.filter(
|
|
249
|
+
([p, meta]) =>
|
|
250
|
+
meta.hasTrustDialogAccepted === true || hasUsageEvidence(meta) || hasTrustedAncestor(p),
|
|
251
|
+
)
|
|
252
|
+
.map(([p]) => p);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Read workspaces from Claude Code's ~/.claude.json trusted projects list.
|
|
257
|
+
*
|
|
258
|
+
* @param {object} opts
|
|
259
|
+
* @param {string} opts.gatewayId
|
|
260
|
+
* @param {number} opts.limit
|
|
261
|
+
* @param {boolean} opts.includeBranch
|
|
262
|
+
* @param {string} [opts.claudeJsonPath] override for testing
|
|
263
|
+
*/
|
|
264
|
+
export async function readClaudeWorkspaces({
|
|
265
|
+
gatewayId,
|
|
266
|
+
limit,
|
|
267
|
+
includeBranch,
|
|
268
|
+
claudeJsonPath,
|
|
269
|
+
}) {
|
|
270
|
+
const warnings = [];
|
|
271
|
+
const effectiveLimit = clampLimit(limit);
|
|
272
|
+
const home = os.homedir();
|
|
273
|
+
|
|
274
|
+
// align-runtime-default-workspace: 合成 default entry(无条件,不建目录,按 path 去重)
|
|
275
|
+
const meta = RUNTIME_META["Claude Code"];
|
|
276
|
+
const defaultPath = defaultWorkspacePath("claude");
|
|
277
|
+
const defaultEntry = {
|
|
278
|
+
id: pathToId(meta.idPrefix, defaultPath),
|
|
279
|
+
name: "default",
|
|
280
|
+
path: defaultPath,
|
|
281
|
+
absolutePath: defaultPath,
|
|
282
|
+
branch: null,
|
|
283
|
+
lastUsedAtMs: null,
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const filePath = claudeJsonPath ?? path.join(home, ".claude.json");
|
|
287
|
+
|
|
288
|
+
let trustedPaths;
|
|
289
|
+
try {
|
|
290
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
291
|
+
const obj = JSON.parse(raw);
|
|
292
|
+
const projects = obj?.projects;
|
|
293
|
+
if (!projects || typeof projects !== "object") {
|
|
294
|
+
trustedPaths = [];
|
|
295
|
+
} else {
|
|
296
|
+
trustedPaths = selectClaudeProjectPaths(projects);
|
|
297
|
+
}
|
|
298
|
+
} catch (err) {
|
|
299
|
+
if (err.code !== "ENOENT") {
|
|
300
|
+
if (err.code === "EACCES" || err.code === "EPERM") {
|
|
301
|
+
warnings.push(`Claude Code: permission denied reading ${filePath}: ${err.message}`);
|
|
302
|
+
}
|
|
303
|
+
// For ENOENT: file doesn't exist — not an error per spec
|
|
304
|
+
}
|
|
305
|
+
// 即使文件不存在,仍返回 default entry
|
|
306
|
+
return { workspaces: [defaultEntry], warnings, truncated: false };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Normalize paths: expand ~ and resolve to absolute
|
|
310
|
+
const normalizedPaths = trustedPaths.map((p) => path.resolve(p.replace(/^~/, home)));
|
|
311
|
+
|
|
312
|
+
// Filter to existing paths only
|
|
313
|
+
const existing = normalizedPaths.filter((p) => {
|
|
314
|
+
try { return fs.existsSync(p); } catch { return false; }
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// 去重:排除与 defaultPath 相同的路径(default 优先,排首位)
|
|
318
|
+
const dedupedExisting = existing.filter((p) => p !== defaultPath);
|
|
319
|
+
|
|
320
|
+
// 将 default 计入 limit 总数(default 排首位,先占 1 个槽位)
|
|
321
|
+
const totalCount = 1 + dedupedExisting.length;
|
|
322
|
+
const truncated = totalCount > effectiveLimit;
|
|
323
|
+
// effectiveLimit - 1 给剩余项目(default 已占 1 位)
|
|
324
|
+
const remainingLimit = Math.max(0, effectiveLimit - 1);
|
|
325
|
+
const selected = dedupedExisting.slice(0, remainingLimit);
|
|
326
|
+
|
|
327
|
+
const branches = await resolveBranches(selected, includeBranch);
|
|
328
|
+
|
|
329
|
+
const workspaces = [
|
|
330
|
+
defaultEntry,
|
|
331
|
+
...selected.map((absPath, i) => ({
|
|
332
|
+
id: pathToId(meta.idPrefix, absPath),
|
|
333
|
+
name: path.basename(absPath),
|
|
334
|
+
path: absPath,
|
|
335
|
+
absolutePath: absPath,
|
|
336
|
+
branch: branches[i],
|
|
337
|
+
lastUsedAtMs: null,
|
|
338
|
+
})),
|
|
339
|
+
];
|
|
340
|
+
|
|
341
|
+
return { workspaces, warnings, truncated };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ─── Codex reader ─────────────────────────────────────────────────────────────
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Read workspaces from Codex session jsonl files.
|
|
348
|
+
* Reads the first line of each .jsonl file to extract `cwd`.
|
|
349
|
+
* Dedupes by absolutePath.
|
|
350
|
+
*
|
|
351
|
+
* @param {object} opts
|
|
352
|
+
* @param {string} opts.gatewayId
|
|
353
|
+
* @param {number} opts.limit
|
|
354
|
+
* @param {boolean} opts.includeBranch
|
|
355
|
+
* @param {string} [opts.codexHome] override for testing (~/.codex)
|
|
356
|
+
*/
|
|
357
|
+
export async function readCodexWorkspaces({
|
|
358
|
+
gatewayId,
|
|
359
|
+
limit,
|
|
360
|
+
includeBranch,
|
|
361
|
+
codexHome,
|
|
362
|
+
}) {
|
|
363
|
+
const warnings = [];
|
|
364
|
+
const effectiveLimit = clampLimit(limit);
|
|
365
|
+
const home = os.homedir();
|
|
366
|
+
|
|
367
|
+
// align-runtime-default-workspace: 合成 default entry(无条件,不建目录,按 path 去重)
|
|
368
|
+
const meta = RUNTIME_META["Codex"];
|
|
369
|
+
const defaultPath = defaultWorkspacePath("codex");
|
|
370
|
+
const defaultEntry = {
|
|
371
|
+
id: pathToId(meta.idPrefix, defaultPath),
|
|
372
|
+
name: "default",
|
|
373
|
+
path: defaultPath,
|
|
374
|
+
absolutePath: defaultPath,
|
|
375
|
+
branch: null,
|
|
376
|
+
lastUsedAtMs: null,
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const baseDir = codexHome ?? path.join(home, ".codex");
|
|
380
|
+
const sessionDirs = [
|
|
381
|
+
path.join(baseDir, "sessions"),
|
|
382
|
+
path.join(baseDir, "archived_sessions"),
|
|
383
|
+
];
|
|
384
|
+
|
|
385
|
+
const cwdByPath = new Map(); // absolutePath → true (for dedupe)
|
|
386
|
+
|
|
387
|
+
for (const dir of sessionDirs) {
|
|
388
|
+
let files;
|
|
389
|
+
try {
|
|
390
|
+
files = fs.readdirSync(dir);
|
|
391
|
+
} catch (err) {
|
|
392
|
+
if (err.code === "EACCES" || err.code === "EPERM") {
|
|
393
|
+
warnings.push(`Codex: permission denied reading ${dir}: ${err.message}`);
|
|
394
|
+
}
|
|
395
|
+
// ENOENT: dir doesn't exist — skip silently
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
for (const file of files) {
|
|
400
|
+
if (!file.endsWith(".jsonl")) continue;
|
|
401
|
+
const filePath = path.join(dir, file);
|
|
402
|
+
try {
|
|
403
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
404
|
+
const firstLine = content.split("\n")[0].trim();
|
|
405
|
+
if (!firstLine) continue;
|
|
406
|
+
const parsed = JSON.parse(firstLine);
|
|
407
|
+
const cwd = parsed?.cwd;
|
|
408
|
+
if (typeof cwd === "string" && cwd) {
|
|
409
|
+
cwdByPath.set(path.resolve(cwd.replace(/^~/, home)), true);
|
|
410
|
+
}
|
|
411
|
+
} catch (err) {
|
|
412
|
+
warnings.push(`Codex: failed to parse first line of ${file}: ${err.message}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// 去重:排除与 defaultPath 相同的路径(default 优先,排首位)
|
|
418
|
+
const uniquePaths = Array.from(cwdByPath.keys()).filter((p) => p !== defaultPath);
|
|
419
|
+
|
|
420
|
+
// 将 default 计入 limit 总数(default 排首位,先占 1 个槽位)
|
|
421
|
+
const totalCount = 1 + uniquePaths.length;
|
|
422
|
+
const truncated = totalCount > effectiveLimit;
|
|
423
|
+
const remainingLimit = Math.max(0, effectiveLimit - 1);
|
|
424
|
+
const selected = uniquePaths.slice(0, remainingLimit);
|
|
425
|
+
|
|
426
|
+
const branches = await resolveBranches(selected, includeBranch);
|
|
427
|
+
|
|
428
|
+
const workspaces = [
|
|
429
|
+
defaultEntry,
|
|
430
|
+
...selected.map((absPath, i) => ({
|
|
431
|
+
id: pathToId(meta.idPrefix, absPath),
|
|
432
|
+
name: path.basename(absPath),
|
|
433
|
+
path: absPath,
|
|
434
|
+
absolutePath: absPath,
|
|
435
|
+
branch: branches[i],
|
|
436
|
+
lastUsedAtMs: null,
|
|
437
|
+
})),
|
|
438
|
+
];
|
|
439
|
+
|
|
440
|
+
return { workspaces, warnings, truncated };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// ─── Hermes reader ─────────────────────────────────────────────────────────────
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* hermes profile id 正则(对齐 hermes 官方 _PROFILE_ID_RE)。
|
|
447
|
+
* 目录名必须符合此正则才会被扫描。
|
|
448
|
+
*/
|
|
449
|
+
const HERMES_PROFILE_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* 极简 YAML 解析器 — 仅支持 hermes config.yaml 中用到的两层键值结构。
|
|
453
|
+
*
|
|
454
|
+
* 支持的格式:
|
|
455
|
+
* model: string-value (model 是字符串)
|
|
456
|
+
* model: (model 是 dict)
|
|
457
|
+
* default: string (可选键 default 或 model)
|
|
458
|
+
* model: string (可选键)
|
|
459
|
+
* provider: string
|
|
460
|
+
*
|
|
461
|
+
* @param {string} content
|
|
462
|
+
* @returns {{ model?: any, [key: string]: any }}
|
|
463
|
+
*/
|
|
464
|
+
function parseSimpleYaml(content) {
|
|
465
|
+
const result = {};
|
|
466
|
+
let currentKey = null;
|
|
467
|
+
let currentObj = null;
|
|
468
|
+
|
|
469
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
470
|
+
// Skip blank lines and comment lines
|
|
471
|
+
if (!rawLine.trim() || rawLine.trim().startsWith("#")) continue;
|
|
472
|
+
|
|
473
|
+
const indented = rawLine.startsWith(" ") || rawLine.startsWith("\t");
|
|
474
|
+
if (!indented) {
|
|
475
|
+
// Top-level key
|
|
476
|
+
const colonIdx = rawLine.indexOf(":");
|
|
477
|
+
if (colonIdx < 0) continue;
|
|
478
|
+
currentKey = rawLine.slice(0, colonIdx).trim();
|
|
479
|
+
const rest = rawLine.slice(colonIdx + 1).trim();
|
|
480
|
+
if (rest) {
|
|
481
|
+
// Inline value — strip optional quotes
|
|
482
|
+
result[currentKey] = stripYamlQuotes(rest);
|
|
483
|
+
currentObj = null;
|
|
484
|
+
} else {
|
|
485
|
+
// Dict value follows
|
|
486
|
+
currentObj = {};
|
|
487
|
+
result[currentKey] = currentObj;
|
|
488
|
+
}
|
|
489
|
+
} else if (currentObj !== null) {
|
|
490
|
+
// Nested key under currentKey
|
|
491
|
+
const trimmed = rawLine.trim();
|
|
492
|
+
const colonIdx = trimmed.indexOf(":");
|
|
493
|
+
if (colonIdx < 0) continue;
|
|
494
|
+
const k = trimmed.slice(0, colonIdx).trim();
|
|
495
|
+
const v = trimmed.slice(colonIdx + 1).trim();
|
|
496
|
+
currentObj[k] = v ? stripYamlQuotes(v) : "";
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return result;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function stripYamlQuotes(s) {
|
|
503
|
+
if (s.length >= 2 && ((s[0] === '"' && s[s.length - 1] === '"') ||
|
|
504
|
+
(s[0] === "'" && s[s.length - 1] === "'"))) {
|
|
505
|
+
return s.slice(1, -1);
|
|
506
|
+
}
|
|
507
|
+
return s;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* 从 config.yaml 解析的 model dict/string 提取 { model, provider }。
|
|
512
|
+
* 对齐 design.md §config.yaml 读法。
|
|
513
|
+
*
|
|
514
|
+
* @param {any} raw parseSimpleYaml(content)["model"]
|
|
515
|
+
* @returns {{ model: string|null, provider: string|null }}
|
|
516
|
+
*/
|
|
517
|
+
function extractModelProvider(raw) {
|
|
518
|
+
if (!raw) return { model: null, provider: null };
|
|
519
|
+
if (typeof raw === "string") {
|
|
520
|
+
return { model: raw || null, provider: null };
|
|
521
|
+
}
|
|
522
|
+
if (typeof raw === "object") {
|
|
523
|
+
const model = raw["default"] || raw["model"] || null;
|
|
524
|
+
const provider = raw["provider"] || null;
|
|
525
|
+
return {
|
|
526
|
+
model: model || null,
|
|
527
|
+
provider: provider || null,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
return { model: null, provider: null };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* 从 SOUL.md 内容提取第一个非注释非空文本行作为 identityName。
|
|
535
|
+
* 失败或内容全空 → null(不报 warning)。
|
|
536
|
+
*
|
|
537
|
+
* @param {string} content
|
|
538
|
+
* @returns {string|null}
|
|
539
|
+
*/
|
|
540
|
+
function extractIdentityName(content) {
|
|
541
|
+
for (const line of content.split(/\r?\n/)) {
|
|
542
|
+
const trimmed = line.trim();
|
|
543
|
+
if (!trimmed) continue;
|
|
544
|
+
// Skip markdown comment lines (<!-- ... -->) but NOT heading lines (# ...)
|
|
545
|
+
// per spec: 「第一个非注释非空文本行」
|
|
546
|
+
if (trimmed.startsWith("<!--")) continue;
|
|
547
|
+
return trimmed;
|
|
548
|
+
}
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* 扫描 ~/.hermes/profiles/* 已有 profile,返回可供 App 展示的 workspace 列表。
|
|
554
|
+
*
|
|
555
|
+
* @param {object} opts
|
|
556
|
+
* @param {string} opts.gatewayId
|
|
557
|
+
* @param {number} opts.limit
|
|
558
|
+
* @param {boolean} opts.includeBranch
|
|
559
|
+
* @param {string} [opts.hermesProfilesRoot] 测试注入:override ~/.hermes/profiles
|
|
560
|
+
*/
|
|
561
|
+
export async function readHermesWorkspaces({
|
|
562
|
+
gatewayId,
|
|
563
|
+
limit,
|
|
564
|
+
includeBranch,
|
|
565
|
+
hermesProfilesRoot,
|
|
566
|
+
} = {}) {
|
|
567
|
+
const warnings = [];
|
|
568
|
+
const effectiveLimit = clampLimit(limit);
|
|
569
|
+
const profilesRoot = hermesProfilesRoot ?? path.join(os.homedir(), ".hermes", "profiles");
|
|
570
|
+
|
|
571
|
+
// align-runtime-default-workspace: default profile root = dirname(profilesRoot) = ~/.hermes
|
|
572
|
+
// hermesProfilesRoot 注入时,default root 取 path.dirname(hermesProfilesRoot)
|
|
573
|
+
const hermesRoot = path.dirname(profilesRoot);
|
|
574
|
+
const meta = RUNTIME_META["Hermes"];
|
|
575
|
+
|
|
576
|
+
// 1. List profiles directory
|
|
577
|
+
let entries;
|
|
578
|
+
try {
|
|
579
|
+
entries = fs.readdirSync(profilesRoot, { withFileTypes: true });
|
|
580
|
+
} catch (err) {
|
|
581
|
+
if (err.code === "ENOENT") {
|
|
582
|
+
// profiles 目录不存在 — 检查 hermesRoot 目录是否存在来决定是否加 default
|
|
583
|
+
let hermesRootExists = false;
|
|
584
|
+
try {
|
|
585
|
+
const stat = fs.statSync(hermesRoot);
|
|
586
|
+
hermesRootExists = stat.isDirectory();
|
|
587
|
+
} catch {
|
|
588
|
+
hermesRootExists = false;
|
|
589
|
+
}
|
|
590
|
+
if (!hermesRootExists) {
|
|
591
|
+
// hermesRoot 也不存在 → 完全返回空
|
|
592
|
+
return { workspaces: [], warnings: [], truncated: false };
|
|
593
|
+
}
|
|
594
|
+
// hermesRoot 存在但 profiles/ 不存在 — 仍 emit default profile
|
|
595
|
+
} else if (err.code === "EACCES" || err.code === "EPERM") {
|
|
596
|
+
warnings.push(`Hermes: permission denied reading profiles directory ${profilesRoot}: ${err.message}`);
|
|
597
|
+
return { workspaces: [], warnings, truncated: false };
|
|
598
|
+
} else {
|
|
599
|
+
throw err;
|
|
600
|
+
}
|
|
601
|
+
entries = []; // ENOENT 时 entries 为空,继续处理 default profile
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// 2. Filter: only directories matching HERMES_PROFILE_ID_RE
|
|
605
|
+
const validProfileNames = (entries || [])
|
|
606
|
+
.filter((e) => {
|
|
607
|
+
if (!e.isDirectory()) return false;
|
|
608
|
+
return HERMES_PROFILE_ID_RE.test(e.name);
|
|
609
|
+
})
|
|
610
|
+
.map((e) => e.name)
|
|
611
|
+
.sort(); // Sort alphabetically
|
|
612
|
+
|
|
613
|
+
// 3. 构建 default profile entry(读 hermesRoot/config.yaml + SOUL.md,缺失降级不报 warning)
|
|
614
|
+
let defaultModel = null;
|
|
615
|
+
let defaultProvider = null;
|
|
616
|
+
let defaultIdentityName = null;
|
|
617
|
+
try {
|
|
618
|
+
const rootConfigPath = path.join(hermesRoot, "config.yaml");
|
|
619
|
+
const configContent = fs.readFileSync(rootConfigPath, "utf8");
|
|
620
|
+
const parsed = parseSimpleYaml(configContent);
|
|
621
|
+
const extracted = extractModelProvider(parsed["model"]);
|
|
622
|
+
defaultModel = extracted.model;
|
|
623
|
+
defaultProvider = extracted.provider;
|
|
624
|
+
} catch {
|
|
625
|
+
// ENOENT 或其他错误 → 降级为 null,不报 warning
|
|
626
|
+
}
|
|
627
|
+
try {
|
|
628
|
+
const rootSoulPath = path.join(hermesRoot, "SOUL.md");
|
|
629
|
+
const soulContent = fs.readFileSync(rootSoulPath, "utf8");
|
|
630
|
+
defaultIdentityName = extractIdentityName(soulContent);
|
|
631
|
+
} catch {
|
|
632
|
+
// 不存在或读失败 → null,不报 warning
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const defaultEntry = {
|
|
636
|
+
id: pathToId(meta.idPrefix, hermesRoot),
|
|
637
|
+
name: "default",
|
|
638
|
+
path: hermesRoot,
|
|
639
|
+
absolutePath: hermesRoot,
|
|
640
|
+
model: defaultModel,
|
|
641
|
+
provider: defaultProvider,
|
|
642
|
+
identityName: defaultIdentityName,
|
|
643
|
+
branch: null,
|
|
644
|
+
lastUsedAtMs: null,
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
// 4. Apply limit(default 排首位,计入 limit)
|
|
648
|
+
const totalCount = 1 + validProfileNames.length;
|
|
649
|
+
const truncated = totalCount > effectiveLimit;
|
|
650
|
+
const remainingLimit = Math.max(0, effectiveLimit - 1);
|
|
651
|
+
const selected = validProfileNames.slice(0, remainingLimit);
|
|
652
|
+
|
|
653
|
+
// 5. Build profile workspace entries
|
|
654
|
+
const workspaces = [defaultEntry];
|
|
655
|
+
|
|
656
|
+
for (const profileName of selected) {
|
|
657
|
+
const absolutePath = path.join(profilesRoot, profileName);
|
|
658
|
+
|
|
659
|
+
// Read config.yaml
|
|
660
|
+
let model = null;
|
|
661
|
+
let provider = null;
|
|
662
|
+
try {
|
|
663
|
+
const configPath = path.join(absolutePath, "config.yaml");
|
|
664
|
+
const content = fs.readFileSync(configPath, "utf8");
|
|
665
|
+
const parsed = parseSimpleYaml(content);
|
|
666
|
+
const extracted = extractModelProvider(parsed["model"]);
|
|
667
|
+
model = extracted.model;
|
|
668
|
+
provider = extracted.provider;
|
|
669
|
+
} catch (err) {
|
|
670
|
+
if (err.code === "EACCES" || err.code === "EPERM") {
|
|
671
|
+
warnings.push(`Hermes: permission denied reading ${profileName}/config.yaml: ${err.message}`);
|
|
672
|
+
} else if (err.code !== "ENOENT") {
|
|
673
|
+
warnings.push(`Hermes: failed to parse ${profileName}/config.yaml: ${err.message}`);
|
|
674
|
+
}
|
|
675
|
+
// model/provider remain null — profile still appears in list
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Read SOUL.md for identityName (failure → null, no warning)
|
|
679
|
+
let identityName = null;
|
|
680
|
+
try {
|
|
681
|
+
const soulPath = path.join(absolutePath, "SOUL.md");
|
|
682
|
+
const content = fs.readFileSync(soulPath, "utf8");
|
|
683
|
+
identityName = extractIdentityName(content);
|
|
684
|
+
} catch {
|
|
685
|
+
// ENOENT or any error → identityName stays null, no warning per spec
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
workspaces.push({
|
|
689
|
+
id: pathToId(meta.idPrefix, absolutePath),
|
|
690
|
+
name: profileName,
|
|
691
|
+
path: absolutePath,
|
|
692
|
+
absolutePath,
|
|
693
|
+
model,
|
|
694
|
+
provider,
|
|
695
|
+
identityName,
|
|
696
|
+
branch: null, // hermes profile 无 git branch 概念
|
|
697
|
+
lastUsedAtMs: null, // 暂不实现
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
return { workspaces, warnings, truncated };
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// ─── main export ─────────────────────────────────────────────────────────────
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Dispatch to the correct runtime reader and return a full response shape.
|
|
708
|
+
*
|
|
709
|
+
* @param {object} opts
|
|
710
|
+
* @param {string} opts.runtimeKind e.g. "openclaw" | "hermes" | "claude" | "codex"
|
|
711
|
+
* @param {string} opts.gatewayId
|
|
712
|
+
* @param {object} [opts.params] { limit?, includeBranch? }
|
|
713
|
+
* @returns {Promise<object>} Full response shape
|
|
714
|
+
*/
|
|
715
|
+
export async function scanWorkspacesByRuntime({ runtimeKind, gatewayId, params }) {
|
|
716
|
+
const kind = normalizeRuntimeKind(runtimeKind);
|
|
717
|
+
const meta = RUNTIME_META[kind] ?? RUNTIME_META["OpenClaw"];
|
|
718
|
+
const limit = clampLimit(params?.limit ?? DEFAULT_LIMIT);
|
|
719
|
+
const includeBranch = params?.includeBranch === true;
|
|
720
|
+
|
|
721
|
+
const readerArgs = { gatewayId, limit, includeBranch };
|
|
722
|
+
// Pass through hermes-specific test-injection param
|
|
723
|
+
if (params?.hermesProfilesRoot != null) {
|
|
724
|
+
readerArgs.hermesProfilesRoot = params.hermesProfilesRoot;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Wrap reader in 5s hard timeout
|
|
728
|
+
const readerPromise = runReader(kind, readerArgs);
|
|
729
|
+
const timeoutPromise = new Promise((resolve) =>
|
|
730
|
+
setTimeout(
|
|
731
|
+
() => resolve({ workspaces: [], warnings: ["scan timed out after 5s"], truncated: false }),
|
|
732
|
+
HARD_TIMEOUT_MS,
|
|
733
|
+
),
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
let readerResult;
|
|
737
|
+
try {
|
|
738
|
+
readerResult = await Promise.race([readerPromise, timeoutPromise]);
|
|
739
|
+
} catch (err) {
|
|
740
|
+
// Catch top-level reader errors and degrade gracefully
|
|
741
|
+
readerResult = {
|
|
742
|
+
workspaces: [],
|
|
743
|
+
warnings: [`reader error: ${err.message}`],
|
|
744
|
+
truncated: false,
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const { workspaces, warnings, truncated } = readerResult;
|
|
749
|
+
|
|
750
|
+
return {
|
|
751
|
+
ok: true,
|
|
752
|
+
agentKind: meta.agentKind,
|
|
753
|
+
emoji: meta.emoji,
|
|
754
|
+
tagline: meta.tagline,
|
|
755
|
+
host: hostMeta(gatewayId),
|
|
756
|
+
workspaces: workspaces ?? [],
|
|
757
|
+
truncated: truncated ?? false,
|
|
758
|
+
scannedAt: new Date().toISOString(),
|
|
759
|
+
warnings: warnings ?? [],
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// ─── private helpers ──────────────────────────────────────────────────────────
|
|
764
|
+
|
|
765
|
+
function clampLimit(raw) {
|
|
766
|
+
const n = typeof raw === "number" ? raw : DEFAULT_LIMIT;
|
|
767
|
+
return Math.min(Math.max(1, Math.floor(n)), MAX_LIMIT);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function normalizeRuntimeKind(kind) {
|
|
771
|
+
if (!kind) return "OpenClaw";
|
|
772
|
+
const lower = String(kind).toLowerCase().trim();
|
|
773
|
+
return RUNTIME_KIND_MAP[lower] ?? "OpenClaw";
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
async function runReader(kind, args) {
|
|
777
|
+
try {
|
|
778
|
+
switch (kind) {
|
|
779
|
+
case "OpenClaw": return await readOpenClawWorkspaces(args);
|
|
780
|
+
case "Claude Code": return await readClaudeWorkspaces(args);
|
|
781
|
+
case "Codex": return await readCodexWorkspaces(args);
|
|
782
|
+
case "Hermes": return await readHermesWorkspaces(args);
|
|
783
|
+
default: return await readOpenClawWorkspaces(args);
|
|
784
|
+
}
|
|
785
|
+
} catch (err) {
|
|
786
|
+
// Permission errors degrade to warnings
|
|
787
|
+
if (err.code === "EACCES" || err.code === "EPERM") {
|
|
788
|
+
return { workspaces: [], warnings: [`permission denied: ${err.message}`], truncated: false };
|
|
789
|
+
}
|
|
790
|
+
throw err;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Resolve OpenClaw home dir. Mirrors bin/agentlink resolveOpenClawHome():
|
|
796
|
+
* OPENCLAW_HOME 环境变量优先,否则 <home>/.openclaw。
|
|
797
|
+
*/
|
|
798
|
+
function resolveOpenClawHomeDir() {
|
|
799
|
+
const fromEnv = process.env.OPENCLAW_HOME?.trim();
|
|
800
|
+
if (fromEnv) return fromEnv;
|
|
801
|
+
const home =
|
|
802
|
+
(process.env.HOME ?? process.env.USERPROFILE ?? "").trim() || os.homedir();
|
|
803
|
+
return path.join(home, ".openclaw");
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* 解析每个 OpenClaw agent 的 workspace 目录(1 agent = 1 workspace),对齐
|
|
808
|
+
* bin/agentlink resolveConfiguredWorkspaceRoot():
|
|
809
|
+
* - 配置文件是 <OPENCLAW_HOME>/**openclaw.json**(不是 config.json!旧代码读错了
|
|
810
|
+
* 文件名 → 永远读不到,fallback 到不存在的 ~/workspace → 扫描恒空)。
|
|
811
|
+
* - agents.list[] 每个 agent 的 workspace(缺省继承 agents.defaults.workspace);
|
|
812
|
+
* list 为空 → 仅默认 workspace 一个。
|
|
813
|
+
* - 默认值:agents.defaults.workspace → 否则 <OPENCLAW_HOME>/workspace
|
|
814
|
+
* (即 ~/.openclaw/workspace,旧代码错写成 ~/workspace)。
|
|
815
|
+
* 相对路径相对配置文件目录解析。
|
|
816
|
+
*
|
|
817
|
+
* @returns {string[]} 绝对 workspace 目录列表(可能含不存在的,调用方负责过滤)
|
|
818
|
+
*/
|
|
819
|
+
function resolveOpenClawAgentWorkspaceDirs() {
|
|
820
|
+
const homeDir = resolveOpenClawHomeDir();
|
|
821
|
+
const configPath = path.join(homeDir, "openclaw.json");
|
|
822
|
+
let config = null;
|
|
823
|
+
try {
|
|
824
|
+
config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
825
|
+
} catch {
|
|
826
|
+
// 配置缺失 / 解析失败:退到默认 workspace(下方 defaultWs)。
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
const resolveWs = (raw) => {
|
|
830
|
+
const v = String(raw ?? "").trim();
|
|
831
|
+
if (!v) return null;
|
|
832
|
+
return path.isAbsolute(v) ? v : path.resolve(path.dirname(configPath), v);
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
const defaultWs =
|
|
836
|
+
resolveWs(config?.agents?.defaults?.workspace) ??
|
|
837
|
+
path.join(homeDir, "workspace");
|
|
838
|
+
|
|
839
|
+
const list = Array.isArray(config?.agents?.list) ? config.agents.list : [];
|
|
840
|
+
if (list.length === 0) return [defaultWs];
|
|
841
|
+
return list.map((a) => resolveWs(a?.workspace) ?? defaultWs);
|
|
842
|
+
}
|