@xopcai/xopc 0.0.23 → 0.0.24
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/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/agents-CiZMJZRp.js +216 -0
- package/dist/gateway/static/root/assets/agents-CiZMJZRp.js.map +1 -0
- package/dist/gateway/static/root/assets/apps-page-tZz69XM3.js +2 -0
- package/dist/gateway/static/root/assets/apps-page-tZz69XM3.js.map +1 -0
- package/dist/gateway/static/root/assets/{attachment-preview-renderer-CebH7fCz.js → attachment-preview-renderer-CxMJMbD2.js} +4 -4
- package/dist/gateway/static/root/assets/{attachment-preview-renderer-CebH7fCz.js.map → attachment-preview-renderer-CxMJMbD2.js.map} +1 -1
- package/dist/gateway/static/root/assets/{attachment-process-heavy-Dbf1--O6.js → attachment-process-heavy-EFXPGfWk.js} +6 -6
- package/dist/gateway/static/root/assets/{attachment-process-heavy-Dbf1--O6.js.map → attachment-process-heavy-EFXPGfWk.js.map} +1 -1
- package/dist/gateway/static/root/assets/{attachment-utils-core-Dt6UxMPV.js → attachment-utils-core-ECbeoa9H.js} +1 -1
- package/dist/gateway/static/root/assets/attachment-utils-core-ECbeoa9H.js.map +1 -0
- package/dist/gateway/static/root/assets/channels-settings-BAvk9-aK.js +9 -0
- package/dist/gateway/static/root/assets/{channels-settings-CGzrrBlT.js.map → channels-settings-BAvk9-aK.js.map} +1 -1
- package/dist/gateway/static/root/assets/cn-BMCV0OMB.js +2 -0
- package/dist/gateway/static/root/assets/cn-BMCV0OMB.js.map +1 -0
- package/dist/gateway/static/root/assets/cron-page-CANqvhK7.js +2 -0
- package/dist/gateway/static/root/assets/{cron-page-BGCdDf2w.js.map → cron-page-CANqvhK7.js.map} +1 -1
- package/dist/gateway/static/root/assets/cron-utils-DyOO6TdN.js +3 -0
- package/dist/gateway/static/root/assets/{cron-utils-Dnks4wWv.js.map → cron-utils-DyOO6TdN.js.map} +1 -1
- package/dist/gateway/static/root/assets/dist-Brod9LF3.js +2 -0
- package/dist/gateway/static/root/assets/{dist-BG1ChbY9.js.map → dist-Brod9LF3.js.map} +1 -1
- package/dist/gateway/static/root/assets/{docx-preview-DxcHM3sR.js → docx-preview-F-jKDMNv.js} +2 -2
- package/dist/gateway/static/root/assets/{docx-preview-DxcHM3sR.js.map → docx-preview-F-jKDMNv.js.map} +1 -1
- package/dist/gateway/static/root/assets/{excel-worksheet-utils-Dk66snKA.js → excel-worksheet-utils-DPfAinZn.js} +1 -1
- package/dist/gateway/static/root/assets/{excel-worksheet-utils-Dk66snKA.js.map → excel-worksheet-utils-DPfAinZn.js.map} +1 -1
- package/dist/gateway/static/root/assets/extension-debug-page-CDD7ozsC.js +2 -0
- package/dist/gateway/static/root/assets/{extension-debug-page-CRC16AbL.js.map → extension-debug-page-CDD7ozsC.js.map} +1 -1
- package/dist/gateway/static/root/assets/extension-page-UUFMjoWf.js +2 -0
- package/dist/gateway/static/root/assets/{extension-page-BagrJnbm.js.map → extension-page-UUFMjoWf.js.map} +1 -1
- package/dist/gateway/static/root/assets/extension-settings-page-CP9JNc4m.js +2 -0
- package/dist/gateway/static/root/assets/extension-settings-page-CP9JNc4m.js.map +1 -0
- package/dist/gateway/static/root/assets/index-BZvlG48D.js +150 -0
- package/dist/gateway/static/root/assets/index-BZvlG48D.js.map +1 -0
- package/dist/gateway/static/root/assets/index-DxkgyT8R.css +1 -0
- package/dist/gateway/static/root/assets/{jszip.min-DVUfmhpE.js → jszip.min-CL3dfyxs.js} +1 -1
- package/dist/gateway/static/root/assets/{jszip.min-DVUfmhpE.js.map → jszip.min-CL3dfyxs.js.map} +1 -1
- package/dist/gateway/static/root/assets/logs-page-Cr0eCGb4.js +2 -0
- package/dist/gateway/static/root/assets/{logs-page-Bo9EsE_D.js.map → logs-page-Cr0eCGb4.js.map} +1 -1
- package/dist/gateway/static/root/assets/{pdf--jE7rvON.js → pdf-CX6ji-QC.js} +1 -1
- package/dist/gateway/static/root/assets/{pdf--jE7rvON.js.map → pdf-CX6ji-QC.js.map} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-DwLHN5GJ.js +2 -0
- package/dist/gateway/static/root/assets/{sessions-page-CDgXq8qp.js.map → sessions-page-DwLHN5GJ.js.map} +1 -1
- package/dist/gateway/static/root/assets/settings-page-B3O3R0E4.js +2 -0
- package/dist/gateway/static/root/assets/settings-page-B3O3R0E4.js.map +1 -0
- package/dist/gateway/static/root/assets/skills-page-DgBYvH6B.js +3 -0
- package/dist/gateway/static/root/assets/{skills-page-BRS5qYTw.js.map → skills-page-DgBYvH6B.js.map} +1 -1
- package/dist/gateway/static/root/assets/vendor-swr-B5fPo7KK.js +2 -0
- package/dist/gateway/static/root/assets/{vendor-swr-Dp4nzp5h.js.map → vendor-swr-B5fPo7KK.js.map} +1 -1
- package/dist/gateway/static/root/assets/{xlsx-DVk38js7.js → xlsx-CPtvfmVF.js} +1 -1
- package/dist/gateway/static/root/assets/{xlsx-DVk38js7.js.map → xlsx-CPtvfmVF.js.map} +1 -1
- package/dist/gateway/static/root/index.html +5 -4
- package/dist/package.js +1 -1
- package/dist/src/agent/image/tool-model-config.js +2 -2
- package/dist/src/agent/image/tool-model-config.js.map +1 -1
- package/dist/src/agent/tools/execute-code-tool.js +1 -1
- package/dist/src/agent/tools/execute-code-tool.js.map +1 -1
- package/dist/src/cli/commands/extension-dev.d.ts +2 -0
- package/dist/src/cli/commands/extension-dev.js +196 -0
- package/dist/src/cli/commands/extension-dev.js.map +1 -0
- package/dist/src/cli/commands/extension-marketplace.d.ts +4 -0
- package/dist/src/cli/commands/extension-marketplace.js +145 -0
- package/dist/src/cli/commands/extension-marketplace.js.map +1 -0
- package/dist/src/cli/commands/extension-pack.d.ts +2 -0
- package/dist/src/cli/commands/extension-pack.js +242 -0
- package/dist/src/cli/commands/extension-pack.js.map +1 -0
- package/dist/src/cli/commands/extension.js +13 -0
- package/dist/src/cli/commands/extension.js.map +1 -1
- package/dist/src/cli/index.js +5 -1
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/config/schema.js +1 -1
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/extensions/api.d.ts +6 -1
- package/dist/src/extensions/api.js +30 -0
- package/dist/src/extensions/api.js.map +1 -1
- package/dist/src/extensions/engine-check.d.ts +14 -0
- package/dist/src/extensions/engine-check.js +148 -0
- package/dist/src/extensions/engine-check.js.map +1 -0
- package/dist/src/extensions/loader.js +23 -0
- package/dist/src/extensions/loader.js.map +1 -1
- package/dist/src/extensions/marketplace.d.ts +24 -0
- package/dist/src/extensions/marketplace.js +98 -0
- package/dist/src/extensions/marketplace.js.map +1 -0
- package/dist/src/extensions/normalize-manifest.js +16 -4
- package/dist/src/extensions/normalize-manifest.js.map +1 -1
- package/dist/src/extensions/sdk/index.d.ts +2 -0
- package/dist/src/extensions/sdk/index.js +2 -1
- package/dist/src/extensions/sdk/index.js.map +1 -1
- package/dist/src/extensions/sdk/testing.d.ts +49 -3
- package/dist/src/extensions/sdk/testing.js +174 -5
- package/dist/src/extensions/sdk/testing.js.map +1 -1
- package/dist/src/extensions/types/core.d.ts +5 -0
- package/dist/src/extensions/types/manifest.d.ts +17 -0
- package/dist/src/extensions/when-context.d.ts +6 -0
- package/dist/src/extensions/when-context.js +28 -0
- package/dist/src/extensions/when-context.js.map +1 -0
- package/dist/src/extensions/when-expression.d.ts +11 -0
- package/dist/src/extensions/when-expression.js +215 -0
- package/dist/src/extensions/when-expression.js.map +1 -0
- package/dist/src/gateway/hono/app.js +1 -1
- package/dist/src/gateway/hono/app.js.map +1 -1
- package/dist/src/gateway/hono/routes/auth-registry-extensions.js +28 -0
- package/dist/src/gateway/hono/routes/auth-registry-extensions.js.map +1 -1
- package/dist/src/providers/index.d.ts +6 -3
- package/dist/src/providers/index.js +12 -23
- package/dist/src/providers/index.js.map +1 -1
- package/package.json +2 -1
- package/dist/gateway/static/root/assets/agents-BY_DaknM.js +0 -216
- package/dist/gateway/static/root/assets/agents-BY_DaknM.js.map +0 -1
- package/dist/gateway/static/root/assets/apps-page-BO3nQbJY.js +0 -2
- package/dist/gateway/static/root/assets/apps-page-BO3nQbJY.js.map +0 -1
- package/dist/gateway/static/root/assets/attachment-utils-core-Dt6UxMPV.js.map +0 -1
- package/dist/gateway/static/root/assets/channels-settings-CGzrrBlT.js +0 -9
- package/dist/gateway/static/root/assets/cron-page-BGCdDf2w.js +0 -2
- package/dist/gateway/static/root/assets/cron-utils-Dnks4wWv.js +0 -3
- package/dist/gateway/static/root/assets/dist-BG1ChbY9.js +0 -2
- package/dist/gateway/static/root/assets/extension-debug-page-CRC16AbL.js +0 -2
- package/dist/gateway/static/root/assets/extension-page-BagrJnbm.js +0 -2
- package/dist/gateway/static/root/assets/extension-settings-page-DEpxRKKK.js +0 -2
- package/dist/gateway/static/root/assets/extension-settings-page-DEpxRKKK.js.map +0 -1
- package/dist/gateway/static/root/assets/index-CYVs9x8D.css +0 -1
- package/dist/gateway/static/root/assets/index-KNzRh6gu.js +0 -150
- package/dist/gateway/static/root/assets/index-KNzRh6gu.js.map +0 -1
- package/dist/gateway/static/root/assets/logs-page-Bo9EsE_D.js +0 -2
- package/dist/gateway/static/root/assets/sessions-page-CDgXq8qp.js +0 -2
- package/dist/gateway/static/root/assets/settings-page-BWMTFST6.js +0 -2
- package/dist/gateway/static/root/assets/settings-page-BWMTFST6.js.map +0 -1
- package/dist/gateway/static/root/assets/skills-page-BRS5qYTw.js +0 -3
- package/dist/gateway/static/root/assets/vendor-swr-Dp4nzp5h.js +0 -2
|
@@ -9,13 +9,14 @@
|
|
|
9
9
|
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
|
10
10
|
<link rel="apple-touch-icon" href="/logo.svg" />
|
|
11
11
|
<title>xopc</title>
|
|
12
|
-
<script type="module" crossorigin src="/assets/index-
|
|
12
|
+
<script type="module" crossorigin src="/assets/index-BZvlG48D.js"></script>
|
|
13
13
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-DWdDZTNf.js">
|
|
14
14
|
<link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-CXAvob9m.js">
|
|
15
|
+
<link rel="modulepreload" crossorigin href="/assets/cn-BMCV0OMB.js">
|
|
15
16
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-DbimaAId.js">
|
|
16
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-swr-
|
|
17
|
-
<link rel="modulepreload" crossorigin href="/assets/attachment-utils-core-
|
|
18
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
17
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-swr-B5fPo7KK.js">
|
|
18
|
+
<link rel="modulepreload" crossorigin href="/assets/attachment-utils-core-ECbeoa9H.js">
|
|
19
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DxkgyT8R.css">
|
|
19
20
|
</head>
|
|
20
21
|
<body>
|
|
21
22
|
<div id="root"></div>
|
package/dist/package.js
CHANGED
|
@@ -16,8 +16,8 @@ function resolveDefaultModelRef(cfg) {
|
|
|
16
16
|
const p2 = parseModelRef(getDefaultModelSync(cfg));
|
|
17
17
|
if (p2) return p2;
|
|
18
18
|
return {
|
|
19
|
-
provider: "
|
|
20
|
-
model: "
|
|
19
|
+
provider: "deepseek",
|
|
20
|
+
model: "deepseek-v4-flash"
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
function coerceToolModelConfig(model) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-model-config.js","names":[],"sources":["../../../../src/agent/image/tool-model-config.ts"],"sourcesContent":["import type { AgentModelConfig, Config } from '../../config/schema.js';\nimport { getAgentDefaultModelRef, parseModelRef } from '../../config/schema.js';\nimport {\n resolveAgentModelFallbackValues,\n resolveAgentModelPrimaryValue,\n} from '../../config/model-input.js';\nimport { getDefaultModelSync, getModelsByProvider, isProviderConfiguredSync } from '../../providers/index.js';\n\nexport type ToolModelConfig = { primary?: string; fallbacks?: string[] };\n\nexport function hasToolModelConfig(model: ToolModelConfig | undefined): boolean {\n return Boolean(\n model?.primary?.trim() || (model?.fallbacks ?? []).some((entry) => entry.trim().length > 0),\n );\n}\n\nexport function resolveDefaultModelRef(cfg?: Config): { provider: string; model: string } {\n const ref = cfg ? getAgentDefaultModelRef(cfg) : undefined;\n if (ref) {\n const p = parseModelRef(ref);\n if (p) {\n return p;\n }\n }\n const fallback = getDefaultModelSync(cfg);\n const p2 = parseModelRef(fallback);\n if (p2) {\n return p2;\n }\n return { provider: '
|
|
1
|
+
{"version":3,"file":"tool-model-config.js","names":[],"sources":["../../../../src/agent/image/tool-model-config.ts"],"sourcesContent":["import type { AgentModelConfig, Config } from '../../config/schema.js';\nimport { getAgentDefaultModelRef, parseModelRef } from '../../config/schema.js';\nimport {\n resolveAgentModelFallbackValues,\n resolveAgentModelPrimaryValue,\n} from '../../config/model-input.js';\nimport { getDefaultModelSync, getModelsByProvider, isProviderConfiguredSync } from '../../providers/index.js';\n\nexport type ToolModelConfig = { primary?: string; fallbacks?: string[] };\n\nexport function hasToolModelConfig(model: ToolModelConfig | undefined): boolean {\n return Boolean(\n model?.primary?.trim() || (model?.fallbacks ?? []).some((entry) => entry.trim().length > 0),\n );\n}\n\nexport function resolveDefaultModelRef(cfg?: Config): { provider: string; model: string } {\n const ref = cfg ? getAgentDefaultModelRef(cfg) : undefined;\n if (ref) {\n const p = parseModelRef(ref);\n if (p) {\n return p;\n }\n }\n const fallback = getDefaultModelSync(cfg);\n const p2 = parseModelRef(fallback);\n if (p2) {\n return p2;\n }\n return { provider: 'deepseek', model: 'deepseek-v4-flash' };\n}\n\nexport function coerceToolModelConfig(model?: AgentModelConfig): ToolModelConfig {\n const primary = resolveAgentModelPrimaryValue(model);\n const fallbacks = resolveAgentModelFallbackValues(model);\n return {\n ...(primary?.trim() ? { primary: primary.trim() } : {}),\n ...(fallbacks.length > 0 ? { fallbacks } : {}),\n };\n}\n\nexport function buildToolModelConfigFromCandidates(params: {\n explicit: ToolModelConfig;\n candidates: Array<string | null | undefined>;\n}): ToolModelConfig | null {\n if (hasToolModelConfig(params.explicit)) {\n return params.explicit;\n }\n\n const deduped: string[] = [];\n for (const candidate of params.candidates) {\n const trimmed = candidate?.trim();\n if (!trimmed || !trimmed.includes('/')) {\n continue;\n }\n const provider = trimmed.slice(0, trimmed.indexOf('/')).trim();\n if (!provider || !isProviderConfiguredSync(provider)) {\n continue;\n }\n if (!deduped.includes(trimmed)) {\n deduped.push(trimmed);\n }\n }\n\n if (deduped.length === 0) {\n return null;\n }\n\n return {\n primary: deduped[0],\n ...(deduped.length > 1 ? { fallbacks: deduped.slice(1) } : {}),\n };\n}\n\nfunction firstVisionModelRef(provider: string): string | undefined {\n const m = getModelsByProvider(provider).find((x) => x.input?.includes('image'));\n return m ? `${provider}/${m.id}` : undefined;\n}\n\n/**\n * Effective image understanding model: explicit `agents.defaults.imageModel`, else inferred from configured providers.\n */\nexport function resolveImageModelConfigForTool(params: { cfg?: Config }): ToolModelConfig | null {\n const explicit = coerceToolModelConfig(params.cfg?.agents?.defaults?.imageModel);\n if (hasToolModelConfig(explicit)) {\n return explicit;\n }\n\n const primary = resolveDefaultModelRef(params.cfg);\n const primaryCandidates: string[] = [];\n const vision = firstVisionModelRef(primary.provider);\n if (vision) {\n primaryCandidates.push(vision);\n }\n if (primary.provider === 'openai') {\n primaryCandidates.push('openai/gpt-4o-mini');\n }\n if (primary.provider === 'anthropic') {\n primaryCandidates.push('anthropic/claude-sonnet-4-5');\n }\n if (primary.provider === 'google') {\n primaryCandidates.push('google/gemini-2.0-flash');\n }\n\n return buildToolModelConfigFromCandidates({\n explicit,\n candidates: [\n ...primaryCandidates,\n firstVisionModelRef('openai') ?? 'openai/gpt-4o-mini',\n firstVisionModelRef('anthropic') ?? 'anthropic/claude-sonnet-4-5',\n ],\n });\n}\n"],"mappings":";;;;aACgF;gBAK8B;AAI9G,SAAgB,mBAAmB,OAA6C;AAC9E,QAAO,QACL,OAAO,SAAS,MAAM,KAAK,OAAO,aAAa,EAAE,EAAE,MAAM,UAAU,MAAM,MAAM,CAAC,SAAS,EAAE,CAC5F;;AAGH,SAAgB,uBAAuB,KAAmD;CACxF,MAAM,MAAM,MAAM,wBAAwB,IAAI,GAAG,KAAA;AACjD,KAAI,KAAK;EACP,MAAM,IAAI,cAAc,IAAI;AAC5B,MAAI,EACF,QAAO;;CAIX,MAAM,KAAK,cADM,oBAAoB,IACJ,CAAC;AAClC,KAAI,GACF,QAAO;AAET,QAAO;EAAE,UAAU;EAAY,OAAO;EAAqB;;AAG7D,SAAgB,sBAAsB,OAA2C;CAC/E,MAAM,UAAU,8BAA8B,MAAM;CACpD,MAAM,YAAY,gCAAgC,MAAM;AACxD,QAAO;EACL,GAAI,SAAS,MAAM,GAAG,EAAE,SAAS,QAAQ,MAAM,EAAE,GAAG,EAAE;EACtD,GAAI,UAAU,SAAS,IAAI,EAAE,WAAW,GAAG,EAAE;EAC9C;;AAGH,SAAgB,mCAAmC,QAGxB;AACzB,KAAI,mBAAmB,OAAO,SAAS,CACrC,QAAO,OAAO;CAGhB,MAAM,UAAoB,EAAE;AAC5B,MAAK,MAAM,aAAa,OAAO,YAAY;EACzC,MAAM,UAAU,WAAW,MAAM;AACjC,MAAI,CAAC,WAAW,CAAC,QAAQ,SAAS,IAAI,CACpC;EAEF,MAAM,WAAW,QAAQ,MAAM,GAAG,QAAQ,QAAQ,IAAI,CAAC,CAAC,MAAM;AAC9D,MAAI,CAAC,YAAY,CAAC,yBAAyB,SAAS,CAClD;AAEF,MAAI,CAAC,QAAQ,SAAS,QAAQ,CAC5B,SAAQ,KAAK,QAAQ;;AAIzB,KAAI,QAAQ,WAAW,EACrB,QAAO;AAGT,QAAO;EACL,SAAS,QAAQ;EACjB,GAAI,QAAQ,SAAS,IAAI,EAAE,WAAW,QAAQ,MAAM,EAAE,EAAE,GAAG,EAAE;EAC9D;;AAGH,SAAS,oBAAoB,UAAsC;CACjE,MAAM,IAAI,oBAAoB,SAAS,CAAC,MAAM,MAAM,EAAE,OAAO,SAAS,QAAQ,CAAC;AAC/E,QAAO,IAAI,GAAG,SAAS,GAAG,EAAE,OAAO,KAAA;;;;;AAMrC,SAAgB,+BAA+B,QAAkD;CAC/F,MAAM,WAAW,sBAAsB,OAAO,KAAK,QAAQ,UAAU,WAAW;AAChF,KAAI,mBAAmB,SAAS,CAC9B,QAAO;CAGT,MAAM,UAAU,uBAAuB,OAAO,IAAI;CAClD,MAAM,oBAA8B,EAAE;CACtC,MAAM,SAAS,oBAAoB,QAAQ,SAAS;AACpD,KAAI,OACF,mBAAkB,KAAK,OAAO;AAEhC,KAAI,QAAQ,aAAa,SACvB,mBAAkB,KAAK,qBAAqB;AAE9C,KAAI,QAAQ,aAAa,YACvB,mBAAkB,KAAK,8BAA8B;AAEvD,KAAI,QAAQ,aAAa,SACvB,mBAAkB,KAAK,0BAA0B;AAGnD,QAAO,mCAAmC;EACxC;EACA,YAAY;GACV,GAAG;GACH,oBAAoB,SAAS,IAAI;GACjC,oBAAoB,YAAY,IAAI;GACrC;EACF,CAAC"}
|
|
@@ -12,7 +12,7 @@ const MAX_TIMEOUT_SEC = 14400;
|
|
|
12
12
|
const ExecuteCodeSchema = Type.Object({
|
|
13
13
|
code: Type.String({ description: "JavaScript to run. Exposed as `tools` (async functions) and `console`:\n await tools.web_search(query, count?)\n await tools.web_fetch(url, maxChars?)\n await tools.read_file(path, limit?)\n await tools.write_file(path, content)\n await tools.grep(pattern, { path?, glob?, ignoreCase?, literal?, context?, limit? })\n await tools.find(pattern, { path?, limit? })\n await tools.shell(command)\n\nUse console.log() for output; combined stdout/stderr is returned." }),
|
|
14
14
|
timeout: Type.Optional(Type.Number({
|
|
15
|
-
description: "Execution timeout in seconds (default:
|
|
15
|
+
description: "Execution timeout in seconds (default: 3600 = 60m, max: 14400 = 4h)",
|
|
16
16
|
default: DEFAULT_TIMEOUT_SEC
|
|
17
17
|
}))
|
|
18
18
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execute-code-tool.js","names":[],"sources":["../../../../src/agent/tools/execute-code-tool.ts"],"sourcesContent":["/**\n * Programmatic tool calling (PTC): run sandboxed JS that invokes a subset of agent tools.\n * vm is not a strong security boundary; only trusted models should use this tool.\n */\n\nimport { Script, createContext } from 'node:vm';\n\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@mariozechner/pi-agent-core';\n\n/** Default script wall time when `timeout` omitted (30 minutes). */\nconst DEFAULT_TIMEOUT_SEC = 30 * 60;\n/** Hard cap for `timeout` parameter (4 hours). */\nconst MAX_TIMEOUT_SEC = 4 * 60 * 60;\n\nconst ExecuteCodeSchema = Type.Object({\n code: Type.String({\n description:\n 'JavaScript to run. Exposed as `tools` (async functions) and `console`:\\n' +\n ' await tools.web_search(query, count?)\\n' +\n ' await tools.web_fetch(url, maxChars?)\\n' +\n ' await tools.read_file(path, limit?)\\n' +\n ' await tools.write_file(path, content)\\n' +\n ' await tools.grep(pattern, { path?, glob?, ignoreCase?, literal?, context?, limit? })\\n' +\n ' await tools.find(pattern, { path?, limit? })\\n' +\n ' await tools.shell(command)\\n\\n' +\n 'Use console.log() for output; combined stdout/stderr is returned.',\n }),\n timeout: Type.Optional(\n Type.Number({\n description: 'Execution timeout in seconds (default: 1800 = 30m, max: 14400 = 4h)',\n default: DEFAULT_TIMEOUT_SEC,\n }),\n ),\n});\n\nexport const SANDBOX_ALLOWED_TOOLS = new Set([\n 'web_search',\n 'web_fetch',\n 'read_file',\n 'write_file',\n 'grep',\n 'find',\n 'shell',\n 'skills_list',\n 'skill_view',\n]);\n\nconst MAX_TIMEOUT_MS = MAX_TIMEOUT_SEC * 1000;\nconst MAX_TOOL_CALLS = 50;\nconst MAX_STDOUT_CHARS = 50_000;\nconst MAX_STDERR_CHARS = 10_000;\n\nfunction stringifyArg(a: unknown): string {\n if (typeof a === 'string') {\n return a;\n }\n try {\n return JSON.stringify(a);\n } catch {\n return String(a);\n }\n}\n\nfunction extractTextFromResult(result: AgentToolResult<unknown>): string {\n return result.content\n .filter((c): c is { type: 'text'; text: string } => c.type === 'text')\n .map((c) => c.text)\n .join('');\n}\n\nexport function buildSandboxToolMap(tools: AgentTool<any, any>[]): Map<string, AgentTool<any, any>> {\n const m = new Map<string, AgentTool<any, any>>();\n for (const t of tools) {\n if (SANDBOX_ALLOWED_TOOLS.has(t.name)) {\n m.set(t.name, t);\n }\n }\n return m;\n}\n\nfunction createToolsApi(\n getMap: () => Map<string, AgentTool<any, any>>,\n signal: AbortSignal | undefined,\n): Record<string, (...args: unknown[]) => Promise<string>> {\n let seq = 0;\n let calls = 0;\n\n const bump = (): void => {\n calls += 1;\n if (calls > MAX_TOOL_CALLS) {\n throw new Error(`Exceeded max sandbox tool calls (${MAX_TOOL_CALLS})`);\n }\n };\n\n const run = async (name: string, params: Record<string, unknown>): Promise<string> => {\n if (signal?.aborted) {\n throw new Error('aborted');\n }\n bump();\n const tool = getMap().get(name);\n if (!tool) {\n throw new Error(`Tool not available in sandbox: ${name}`);\n }\n const id = `ptc-${Date.now()}-${seq++}`;\n const result = await (tool as any).execute(id, params, signal);\n return extractTextFromResult(result);\n };\n\n return {\n web_search: async (query: unknown, count?: unknown) =>\n run('web_search', {\n query: String(query ?? ''),\n ...(typeof count === 'number' && Number.isFinite(count) ? { count } : {}),\n }),\n\n web_fetch: async (url: unknown, maxChars?: unknown) =>\n run('web_fetch', {\n url: String(url ?? ''),\n ...(typeof maxChars === 'number' && Number.isFinite(maxChars) ? { maxChars } : {}),\n }),\n\n read_file: async (path: unknown, limit?: unknown) =>\n run('read_file', {\n path: String(path ?? ''),\n ...(typeof limit === 'number' && Number.isFinite(limit) ? { limit } : {}),\n }),\n\n write_file: async (path: unknown, content: unknown) =>\n run('write_file', {\n path: String(path ?? ''),\n content: String(content ?? ''),\n }),\n\n grep: async (pattern: unknown, opts?: unknown) => {\n const o = opts && typeof opts === 'object' && opts !== null ? (opts as Record<string, unknown>) : {};\n return run('grep', {\n pattern: String(pattern ?? ''),\n ...(typeof o.path === 'string' ? { path: o.path } : {}),\n ...(typeof o.glob === 'string' ? { glob: o.glob } : {}),\n ...(typeof o.ignoreCase === 'boolean' ? { ignoreCase: o.ignoreCase } : {}),\n ...(typeof o.literal === 'boolean' ? { literal: o.literal } : {}),\n ...(typeof o.context === 'number' && Number.isFinite(o.context) ? { context: o.context } : {}),\n ...(typeof o.limit === 'number' && Number.isFinite(o.limit) ? { limit: o.limit } : {}),\n });\n },\n\n find: async (pattern: unknown, opts?: unknown) => {\n const o = opts && typeof opts === 'object' && opts !== null ? (opts as Record<string, unknown>) : {};\n return run('find', {\n pattern: String(pattern ?? ''),\n ...(typeof o.path === 'string' ? { path: o.path } : {}),\n ...(typeof o.limit === 'number' && Number.isFinite(o.limit) ? { limit: o.limit } : {}),\n });\n },\n\n shell: async (command: unknown) => run('shell', { command: String(command ?? '') }),\n };\n}\n\nasync function runSandboxedScript(\n code: string,\n sandbox: Record<string, unknown>,\n timeoutMs: number,\n signal?: AbortSignal,\n): Promise<void> {\n const context = createContext(sandbox, { name: 'execute_code' });\n const wrapped = `(async () => {\\n${code}\\n})()`;\n const script = new Script(wrapped, { filename: 'execute_code.vm' });\n const runPromise = script.runInContext(context) as Promise<unknown>;\n\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const timeoutPromise = new Promise<never>((_, rej) => {\n timeoutId = setTimeout(\n () => rej(new Error(`Script timed out after ${Math.round(timeoutMs / 1000)}s`)),\n timeoutMs,\n );\n });\n\n const abortPromise =\n signal &&\n new Promise<never>((_, rej) => {\n if (signal.aborted) {\n rej(new Error('aborted'));\n return;\n }\n signal.addEventListener('abort', () => rej(new Error('aborted')), { once: true });\n });\n\n const racers: Promise<unknown>[] = [runPromise, timeoutPromise];\n if (abortPromise) {\n racers.push(abortPromise);\n }\n\n try {\n await Promise.race(racers);\n } finally {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n }\n}\n\nexport interface ExecuteCodeToolDeps {\n getSandboxToolMap: () => Map<string, AgentTool<any, any>>;\n}\n\ntype ExecuteCodeParams = { code: string; timeout?: number };\n\nexport function createExecuteCodeTool(deps: ExecuteCodeToolDeps): AgentTool {\n return {\n name: 'execute_code',\n label: '⚡ Execute Code',\n description:\n 'Run sandboxed JavaScript that calls a subset of tools via `tools.*` (batch work in one step).\\n\\n' +\n 'Only stdout/stderr from `console` plus tool return text (as strings) are visible — not full tool JSON.\\n\\n' +\n 'WHEN TO USE: loops over files/URLs, simple branching between tool calls.\\n' +\n 'WHEN NOT TO USE: single tool calls; tasks needing full tool schemas or disallowed tools.\\n\\n' +\n 'API: `await tools.web_search(q, count?)`, `web_fetch(url, maxChars?)`, `read_file(path, limit?)`, ' +\n '`write_file(path, content)`, `grep(pattern, opts?)`, `find(glob, opts?)`, `shell(command)`. ' +\n 'Use `console.log` for output.',\n parameters: ExecuteCodeSchema,\n\n async execute(\n _toolCallId: string,\n params: any,\n signal?: AbortSignal,\n ): Promise<AgentToolResult<{ exitCode: number }>> {\n const p = params as ExecuteCodeParams;\n const sec = p.timeout ?? DEFAULT_TIMEOUT_SEC;\n const timeoutMs = Math.min(\n Math.max(1, Number.isFinite(sec) ? sec : DEFAULT_TIMEOUT_SEC) * 1000,\n MAX_TIMEOUT_MS,\n );\n\n const stdout: string[] = [];\n const stderr: string[] = [];\n\n const toolsApi = createToolsApi(deps.getSandboxToolMap, signal);\n\n const sandbox: Record<string, unknown> = {\n tools: toolsApi,\n Promise,\n JSON,\n Math,\n Date,\n Array,\n Object,\n String,\n Number,\n Boolean,\n RegExp,\n Map,\n Set,\n Error,\n TypeError,\n RangeError,\n parseInt,\n parseFloat,\n isNaN,\n isFinite,\n console: {\n log: (...args: unknown[]) => {\n stdout.push(args.map(stringifyArg).join(' '));\n },\n error: (...args: unknown[]) => {\n stderr.push(args.map(stringifyArg).join(' '));\n },\n warn: (...args: unknown[]) => {\n stderr.push(args.map(stringifyArg).join(' '));\n },\n },\n setTimeout,\n clearTimeout,\n };\n\n try {\n await runSandboxedScript(p.code, sandbox, timeoutMs, signal);\n\n let out = stdout.join('\\n');\n if (out.length > MAX_STDOUT_CHARS) {\n out = `${out.slice(0, MAX_STDOUT_CHARS)}\\n...(truncated)`;\n }\n\n const parts: string[] = [];\n if (out.length > 0) {\n parts.push(out);\n }\n if (stderr.length > 0) {\n let err = stderr.join('\\n');\n if (err.length > MAX_STDERR_CHARS) {\n err = `${err.slice(0, MAX_STDERR_CHARS)}\\n...(truncated)`;\n }\n parts.push(`\\nSTDERR:\\n${err}`);\n }\n\n return {\n content: [{ type: 'text', text: parts.join('') || '(no output)' }],\n details: { exitCode: 0 },\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text', text: `Execution failed: ${message}` }],\n details: { exitCode: 1 },\n };\n }\n },\n } as any;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"execute-code-tool.js","names":[],"sources":["../../../../src/agent/tools/execute-code-tool.ts"],"sourcesContent":["/**\n * Programmatic tool calling (PTC): run sandboxed JS that invokes a subset of agent tools.\n * vm is not a strong security boundary; only trusted models should use this tool.\n */\n\nimport { Script, createContext } from 'node:vm';\n\nimport { Type } from '@sinclair/typebox';\nimport type { AgentTool, AgentToolResult } from '@mariozechner/pi-agent-core';\n\n/** Default script wall time when `timeout` omitted (30 minutes). */\nconst DEFAULT_TIMEOUT_SEC = 30 * 60;\n/** Hard cap for `timeout` parameter (4 hours). */\nconst MAX_TIMEOUT_SEC = 4 * 60 * 60;\n\nconst ExecuteCodeSchema = Type.Object({\n code: Type.String({\n description:\n 'JavaScript to run. Exposed as `tools` (async functions) and `console`:\\n' +\n ' await tools.web_search(query, count?)\\n' +\n ' await tools.web_fetch(url, maxChars?)\\n' +\n ' await tools.read_file(path, limit?)\\n' +\n ' await tools.write_file(path, content)\\n' +\n ' await tools.grep(pattern, { path?, glob?, ignoreCase?, literal?, context?, limit? })\\n' +\n ' await tools.find(pattern, { path?, limit? })\\n' +\n ' await tools.shell(command)\\n\\n' +\n 'Use console.log() for output; combined stdout/stderr is returned.',\n }),\n timeout: Type.Optional(\n Type.Number({\n description: 'Execution timeout in seconds (default: 3600 = 60m, max: 14400 = 4h)',\n default: DEFAULT_TIMEOUT_SEC,\n }),\n ),\n});\n\nexport const SANDBOX_ALLOWED_TOOLS = new Set([\n 'web_search',\n 'web_fetch',\n 'read_file',\n 'write_file',\n 'grep',\n 'find',\n 'shell',\n 'skills_list',\n 'skill_view',\n]);\n\nconst MAX_TIMEOUT_MS = MAX_TIMEOUT_SEC * 1000;\nconst MAX_TOOL_CALLS = 50;\nconst MAX_STDOUT_CHARS = 50_000;\nconst MAX_STDERR_CHARS = 10_000;\n\nfunction stringifyArg(a: unknown): string {\n if (typeof a === 'string') {\n return a;\n }\n try {\n return JSON.stringify(a);\n } catch {\n return String(a);\n }\n}\n\nfunction extractTextFromResult(result: AgentToolResult<unknown>): string {\n return result.content\n .filter((c): c is { type: 'text'; text: string } => c.type === 'text')\n .map((c) => c.text)\n .join('');\n}\n\nexport function buildSandboxToolMap(tools: AgentTool<any, any>[]): Map<string, AgentTool<any, any>> {\n const m = new Map<string, AgentTool<any, any>>();\n for (const t of tools) {\n if (SANDBOX_ALLOWED_TOOLS.has(t.name)) {\n m.set(t.name, t);\n }\n }\n return m;\n}\n\nfunction createToolsApi(\n getMap: () => Map<string, AgentTool<any, any>>,\n signal: AbortSignal | undefined,\n): Record<string, (...args: unknown[]) => Promise<string>> {\n let seq = 0;\n let calls = 0;\n\n const bump = (): void => {\n calls += 1;\n if (calls > MAX_TOOL_CALLS) {\n throw new Error(`Exceeded max sandbox tool calls (${MAX_TOOL_CALLS})`);\n }\n };\n\n const run = async (name: string, params: Record<string, unknown>): Promise<string> => {\n if (signal?.aborted) {\n throw new Error('aborted');\n }\n bump();\n const tool = getMap().get(name);\n if (!tool) {\n throw new Error(`Tool not available in sandbox: ${name}`);\n }\n const id = `ptc-${Date.now()}-${seq++}`;\n const result = await (tool as any).execute(id, params, signal);\n return extractTextFromResult(result);\n };\n\n return {\n web_search: async (query: unknown, count?: unknown) =>\n run('web_search', {\n query: String(query ?? ''),\n ...(typeof count === 'number' && Number.isFinite(count) ? { count } : {}),\n }),\n\n web_fetch: async (url: unknown, maxChars?: unknown) =>\n run('web_fetch', {\n url: String(url ?? ''),\n ...(typeof maxChars === 'number' && Number.isFinite(maxChars) ? { maxChars } : {}),\n }),\n\n read_file: async (path: unknown, limit?: unknown) =>\n run('read_file', {\n path: String(path ?? ''),\n ...(typeof limit === 'number' && Number.isFinite(limit) ? { limit } : {}),\n }),\n\n write_file: async (path: unknown, content: unknown) =>\n run('write_file', {\n path: String(path ?? ''),\n content: String(content ?? ''),\n }),\n\n grep: async (pattern: unknown, opts?: unknown) => {\n const o = opts && typeof opts === 'object' && opts !== null ? (opts as Record<string, unknown>) : {};\n return run('grep', {\n pattern: String(pattern ?? ''),\n ...(typeof o.path === 'string' ? { path: o.path } : {}),\n ...(typeof o.glob === 'string' ? { glob: o.glob } : {}),\n ...(typeof o.ignoreCase === 'boolean' ? { ignoreCase: o.ignoreCase } : {}),\n ...(typeof o.literal === 'boolean' ? { literal: o.literal } : {}),\n ...(typeof o.context === 'number' && Number.isFinite(o.context) ? { context: o.context } : {}),\n ...(typeof o.limit === 'number' && Number.isFinite(o.limit) ? { limit: o.limit } : {}),\n });\n },\n\n find: async (pattern: unknown, opts?: unknown) => {\n const o = opts && typeof opts === 'object' && opts !== null ? (opts as Record<string, unknown>) : {};\n return run('find', {\n pattern: String(pattern ?? ''),\n ...(typeof o.path === 'string' ? { path: o.path } : {}),\n ...(typeof o.limit === 'number' && Number.isFinite(o.limit) ? { limit: o.limit } : {}),\n });\n },\n\n shell: async (command: unknown) => run('shell', { command: String(command ?? '') }),\n };\n}\n\nasync function runSandboxedScript(\n code: string,\n sandbox: Record<string, unknown>,\n timeoutMs: number,\n signal?: AbortSignal,\n): Promise<void> {\n const context = createContext(sandbox, { name: 'execute_code' });\n const wrapped = `(async () => {\\n${code}\\n})()`;\n const script = new Script(wrapped, { filename: 'execute_code.vm' });\n const runPromise = script.runInContext(context) as Promise<unknown>;\n\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const timeoutPromise = new Promise<never>((_, rej) => {\n timeoutId = setTimeout(\n () => rej(new Error(`Script timed out after ${Math.round(timeoutMs / 1000)}s`)),\n timeoutMs,\n );\n });\n\n const abortPromise =\n signal &&\n new Promise<never>((_, rej) => {\n if (signal.aborted) {\n rej(new Error('aborted'));\n return;\n }\n signal.addEventListener('abort', () => rej(new Error('aborted')), { once: true });\n });\n\n const racers: Promise<unknown>[] = [runPromise, timeoutPromise];\n if (abortPromise) {\n racers.push(abortPromise);\n }\n\n try {\n await Promise.race(racers);\n } finally {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n }\n}\n\nexport interface ExecuteCodeToolDeps {\n getSandboxToolMap: () => Map<string, AgentTool<any, any>>;\n}\n\ntype ExecuteCodeParams = { code: string; timeout?: number };\n\nexport function createExecuteCodeTool(deps: ExecuteCodeToolDeps): AgentTool {\n return {\n name: 'execute_code',\n label: '⚡ Execute Code',\n description:\n 'Run sandboxed JavaScript that calls a subset of tools via `tools.*` (batch work in one step).\\n\\n' +\n 'Only stdout/stderr from `console` plus tool return text (as strings) are visible — not full tool JSON.\\n\\n' +\n 'WHEN TO USE: loops over files/URLs, simple branching between tool calls.\\n' +\n 'WHEN NOT TO USE: single tool calls; tasks needing full tool schemas or disallowed tools.\\n\\n' +\n 'API: `await tools.web_search(q, count?)`, `web_fetch(url, maxChars?)`, `read_file(path, limit?)`, ' +\n '`write_file(path, content)`, `grep(pattern, opts?)`, `find(glob, opts?)`, `shell(command)`. ' +\n 'Use `console.log` for output.',\n parameters: ExecuteCodeSchema,\n\n async execute(\n _toolCallId: string,\n params: any,\n signal?: AbortSignal,\n ): Promise<AgentToolResult<{ exitCode: number }>> {\n const p = params as ExecuteCodeParams;\n const sec = p.timeout ?? DEFAULT_TIMEOUT_SEC;\n const timeoutMs = Math.min(\n Math.max(1, Number.isFinite(sec) ? sec : DEFAULT_TIMEOUT_SEC) * 1000,\n MAX_TIMEOUT_MS,\n );\n\n const stdout: string[] = [];\n const stderr: string[] = [];\n\n const toolsApi = createToolsApi(deps.getSandboxToolMap, signal);\n\n const sandbox: Record<string, unknown> = {\n tools: toolsApi,\n Promise,\n JSON,\n Math,\n Date,\n Array,\n Object,\n String,\n Number,\n Boolean,\n RegExp,\n Map,\n Set,\n Error,\n TypeError,\n RangeError,\n parseInt,\n parseFloat,\n isNaN,\n isFinite,\n console: {\n log: (...args: unknown[]) => {\n stdout.push(args.map(stringifyArg).join(' '));\n },\n error: (...args: unknown[]) => {\n stderr.push(args.map(stringifyArg).join(' '));\n },\n warn: (...args: unknown[]) => {\n stderr.push(args.map(stringifyArg).join(' '));\n },\n },\n setTimeout,\n clearTimeout,\n };\n\n try {\n await runSandboxedScript(p.code, sandbox, timeoutMs, signal);\n\n let out = stdout.join('\\n');\n if (out.length > MAX_STDOUT_CHARS) {\n out = `${out.slice(0, MAX_STDOUT_CHARS)}\\n...(truncated)`;\n }\n\n const parts: string[] = [];\n if (out.length > 0) {\n parts.push(out);\n }\n if (stderr.length > 0) {\n let err = stderr.join('\\n');\n if (err.length > MAX_STDERR_CHARS) {\n err = `${err.slice(0, MAX_STDERR_CHARS)}\\n...(truncated)`;\n }\n parts.push(`\\nSTDERR:\\n${err}`);\n }\n\n return {\n content: [{ type: 'text', text: parts.join('') || '(no output)' }],\n details: { exitCode: 0 },\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return {\n content: [{ type: 'text', text: `Execution failed: ${message}` }],\n details: { exitCode: 1 },\n };\n }\n },\n } as any;\n}\n"],"mappings":";;;;;;;;AAWA,MAAM,sBAAsB;;AAE5B,MAAM,kBAAkB;AAExB,MAAM,oBAAoB,KAAK,OAAO;CACpC,MAAM,KAAK,OAAO,EAChB,aACE,udASH,CAAC;CACF,SAAS,KAAK,SACZ,KAAK,OAAO;EACV,aAAa;EACb,SAAS;EACV,CAAC,CACH;CACF,CAAC;AAEF,MAAa,wBAAwB,IAAI,IAAI;CAC3C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAM,iBAAiB,kBAAkB;AACzC,MAAM,iBAAiB;AACvB,MAAM,mBAAmB;AACzB,MAAM,mBAAmB;AAEzB,SAAS,aAAa,GAAoB;AACxC,KAAI,OAAO,MAAM,SACf,QAAO;AAET,KAAI;AACF,SAAO,KAAK,UAAU,EAAE;SAClB;AACN,SAAO,OAAO,EAAE;;;AAIpB,SAAS,sBAAsB,QAA0C;AACvE,QAAO,OAAO,QACX,QAAQ,MAA2C,EAAE,SAAS,OAAO,CACrE,KAAK,MAAM,EAAE,KAAK,CAClB,KAAK,GAAG;;AAGb,SAAgB,oBAAoB,OAAgE;CAClG,MAAM,oBAAI,IAAI,KAAkC;AAChD,MAAK,MAAM,KAAK,MACd,KAAI,sBAAsB,IAAI,EAAE,KAAK,CACnC,GAAE,IAAI,EAAE,MAAM,EAAE;AAGpB,QAAO;;AAGT,SAAS,eACP,QACA,QACyD;CACzD,IAAI,MAAM;CACV,IAAI,QAAQ;CAEZ,MAAM,aAAmB;AACvB,WAAS;AACT,MAAI,QAAQ,eACV,OAAM,IAAI,MAAM,oCAAoC,eAAe,GAAG;;CAI1E,MAAM,MAAM,OAAO,MAAc,WAAqD;AACpF,MAAI,QAAQ,QACV,OAAM,IAAI,MAAM,UAAU;AAE5B,QAAM;EACN,MAAM,OAAO,QAAQ,CAAC,IAAI,KAAK;AAC/B,MAAI,CAAC,KACH,OAAM,IAAI,MAAM,kCAAkC,OAAO;EAE3D,MAAM,KAAK,OAAO,KAAK,KAAK,CAAC,GAAG;AAEhC,SAAO,sBAAsB,MADP,KAAa,QAAQ,IAAI,QAAQ,OAAO,CAC1B;;AAGtC,QAAO;EACL,YAAY,OAAO,OAAgB,UACjC,IAAI,cAAc;GAChB,OAAO,OAAO,SAAS,GAAG;GAC1B,GAAI,OAAO,UAAU,YAAY,OAAO,SAAS,MAAM,GAAG,EAAE,OAAO,GAAG,EAAE;GACzE,CAAC;EAEJ,WAAW,OAAO,KAAc,aAC9B,IAAI,aAAa;GACf,KAAK,OAAO,OAAO,GAAG;GACtB,GAAI,OAAO,aAAa,YAAY,OAAO,SAAS,SAAS,GAAG,EAAE,UAAU,GAAG,EAAE;GAClF,CAAC;EAEJ,WAAW,OAAO,MAAe,UAC/B,IAAI,aAAa;GACf,MAAM,OAAO,QAAQ,GAAG;GACxB,GAAI,OAAO,UAAU,YAAY,OAAO,SAAS,MAAM,GAAG,EAAE,OAAO,GAAG,EAAE;GACzE,CAAC;EAEJ,YAAY,OAAO,MAAe,YAChC,IAAI,cAAc;GAChB,MAAM,OAAO,QAAQ,GAAG;GACxB,SAAS,OAAO,WAAW,GAAG;GAC/B,CAAC;EAEJ,MAAM,OAAO,SAAkB,SAAmB;GAChD,MAAM,IAAI,QAAQ,OAAO,SAAS,YAAY,SAAS,OAAQ,OAAmC,EAAE;AACpG,UAAO,IAAI,QAAQ;IACjB,SAAS,OAAO,WAAW,GAAG;IAC9B,GAAI,OAAO,EAAE,SAAS,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,EAAE;IACtD,GAAI,OAAO,EAAE,SAAS,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,EAAE;IACtD,GAAI,OAAO,EAAE,eAAe,YAAY,EAAE,YAAY,EAAE,YAAY,GAAG,EAAE;IACzE,GAAI,OAAO,EAAE,YAAY,YAAY,EAAE,SAAS,EAAE,SAAS,GAAG,EAAE;IAChE,GAAI,OAAO,EAAE,YAAY,YAAY,OAAO,SAAS,EAAE,QAAQ,GAAG,EAAE,SAAS,EAAE,SAAS,GAAG,EAAE;IAC7F,GAAI,OAAO,EAAE,UAAU,YAAY,OAAO,SAAS,EAAE,MAAM,GAAG,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE;IACtF,CAAC;;EAGJ,MAAM,OAAO,SAAkB,SAAmB;GAChD,MAAM,IAAI,QAAQ,OAAO,SAAS,YAAY,SAAS,OAAQ,OAAmC,EAAE;AACpG,UAAO,IAAI,QAAQ;IACjB,SAAS,OAAO,WAAW,GAAG;IAC9B,GAAI,OAAO,EAAE,SAAS,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,EAAE;IACtD,GAAI,OAAO,EAAE,UAAU,YAAY,OAAO,SAAS,EAAE,MAAM,GAAG,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE;IACtF,CAAC;;EAGJ,OAAO,OAAO,YAAqB,IAAI,SAAS,EAAE,SAAS,OAAO,WAAW,GAAG,EAAE,CAAC;EACpF;;AAGH,eAAe,mBACb,MACA,SACA,WACA,QACe;CACf,MAAM,UAAU,cAAc,SAAS,EAAE,MAAM,gBAAgB,CAAC;CAGhE,MAAM,aAAa,IADA,OAAO,mBADS,KAAK,SACL,EAAE,UAAU,mBAAmB,CACzC,CAAC,aAAa,QAAQ;CAE/C,IAAI;CAEJ,MAAM,iBAAiB,IAAI,SAAgB,GAAG,QAAQ;AACpD,cAAY,iBACJ,oBAAI,IAAI,MAAM,0BAA0B,KAAK,MAAM,YAAY,IAAK,CAAC,GAAG,CAAC,EAC/E,UACD;GACD;CAEF,MAAM,eACJ,UACA,IAAI,SAAgB,GAAG,QAAQ;AAC7B,MAAI,OAAO,SAAS;AAClB,uBAAI,IAAI,MAAM,UAAU,CAAC;AACzB;;AAEF,SAAO,iBAAiB,eAAe,oBAAI,IAAI,MAAM,UAAU,CAAC,EAAE,EAAE,MAAM,MAAM,CAAC;GACjF;CAEJ,MAAM,SAA6B,CAAC,YAAY,eAAe;AAC/D,KAAI,aACF,QAAO,KAAK,aAAa;AAG3B,KAAI;AACF,QAAM,QAAQ,KAAK,OAAO;WAClB;AACR,MAAI,cAAc,KAAA,EAChB,cAAa,UAAU;;;AAW7B,SAAgB,sBAAsB,MAAsC;AAC1E,QAAO;EACL,MAAM;EACN,OAAO;EACP,aACE;EAOF,YAAY;EAEZ,MAAM,QACJ,aACA,QACA,QACgD;GAChD,MAAM,IAAI;GACV,MAAM,MAAM,EAAE,WAAW;GACzB,MAAM,YAAY,KAAK,IACrB,KAAK,IAAI,GAAG,OAAO,SAAS,IAAI,GAAG,MAAM,oBAAoB,GAAG,KAChE,eACD;GAED,MAAM,SAAmB,EAAE;GAC3B,MAAM,SAAmB,EAAE;GAI3B,MAAM,UAAmC;IACvC,OAHe,eAAe,KAAK,mBAAmB,OAGvC;IACf;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA,SAAS;KACP,MAAM,GAAG,SAAoB;AAC3B,aAAO,KAAK,KAAK,IAAI,aAAa,CAAC,KAAK,IAAI,CAAC;;KAE/C,QAAQ,GAAG,SAAoB;AAC7B,aAAO,KAAK,KAAK,IAAI,aAAa,CAAC,KAAK,IAAI,CAAC;;KAE/C,OAAO,GAAG,SAAoB;AAC5B,aAAO,KAAK,KAAK,IAAI,aAAa,CAAC,KAAK,IAAI,CAAC;;KAEhD;IACD;IACA;IACD;AAED,OAAI;AACF,UAAM,mBAAmB,EAAE,MAAM,SAAS,WAAW,OAAO;IAE5D,IAAI,MAAM,OAAO,KAAK,KAAK;AAC3B,QAAI,IAAI,SAAS,iBACf,OAAM,GAAG,IAAI,MAAM,GAAG,iBAAiB,CAAC;IAG1C,MAAM,QAAkB,EAAE;AAC1B,QAAI,IAAI,SAAS,EACf,OAAM,KAAK,IAAI;AAEjB,QAAI,OAAO,SAAS,GAAG;KACrB,IAAI,MAAM,OAAO,KAAK,KAAK;AAC3B,SAAI,IAAI,SAAS,iBACf,OAAM,GAAG,IAAI,MAAM,GAAG,iBAAiB,CAAC;AAE1C,WAAM,KAAK,cAAc,MAAM;;AAGjC,WAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,MAAM,KAAK,GAAG,IAAI;MAAe,CAAC;KAClE,SAAS,EAAE,UAAU,GAAG;KACzB;YACM,OAAO;AAEd,WAAO;KACL,SAAS,CAAC;MAAE,MAAM;MAAQ,MAAM,qBAFlB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;MAEJ,CAAC;KACjE,SAAS,EAAE,UAAU,GAAG;KACzB;;;EAGN"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { PACKAGE_VERSION, init_package_version } from "../../package-version.js";
|
|
2
|
+
import { init_agent_scope, resolveDefaultAgentId } from "../../agent/agent-scope.js";
|
|
3
|
+
import { createLogger } from "../../utils/logger/index.js";
|
|
4
|
+
import { init_logger } from "../../utils/logger.js";
|
|
5
|
+
import { init_paths, resolveConfigPath, resolveWorkspaceExtensionsDir } from "../../config/paths.js";
|
|
6
|
+
import { init_loader, loadConfig } from "../../config/loader.js";
|
|
7
|
+
import { normalizeExtensionManifest } from "../../extensions/normalize-manifest.js";
|
|
8
|
+
import { checkEngineCompatibility } from "../../extensions/engine-check.js";
|
|
9
|
+
import { seedMainAgentBootstrap } from "../../agent/context/workspace-seed.js";
|
|
10
|
+
import { initWorkspace } from "../utils/init-workspace.js";
|
|
11
|
+
import { colors } from "../utils/colors.js";
|
|
12
|
+
import { GatewayServer } from "../../gateway/server.js";
|
|
13
|
+
import { runGatewayLoop } from "../../gateway/run-loop.js";
|
|
14
|
+
import "../../gateway/index.js";
|
|
15
|
+
import { getContextWithOpts } from "../index.js";
|
|
16
|
+
import { join, resolve } from "node:path";
|
|
17
|
+
import { existsSync, mkdirSync, readFileSync, symlinkSync, unlinkSync, watch } from "node:fs";
|
|
18
|
+
import { Command } from "commander";
|
|
19
|
+
//#region src/cli/commands/extension-dev.ts
|
|
20
|
+
init_agent_scope();
|
|
21
|
+
init_loader();
|
|
22
|
+
init_paths();
|
|
23
|
+
init_package_version();
|
|
24
|
+
init_logger();
|
|
25
|
+
const log = createLogger("ExtensionDev");
|
|
26
|
+
const MANIFEST = "xopc.extension.json";
|
|
27
|
+
function isRecord(x) {
|
|
28
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
29
|
+
}
|
|
30
|
+
async function ensureGatewayReady(configPath, workspacePath, gatewayHost, gatewayPort) {
|
|
31
|
+
const result = await initWorkspace({
|
|
32
|
+
configPath,
|
|
33
|
+
workspacePath,
|
|
34
|
+
gatewayHost,
|
|
35
|
+
gatewayPort
|
|
36
|
+
});
|
|
37
|
+
if (result.configCreated || result.workspaceCreated) {
|
|
38
|
+
console.log("");
|
|
39
|
+
console.log("👋 First-time setup before starting the gateway...");
|
|
40
|
+
console.log("");
|
|
41
|
+
console.log("✅ Setup complete.");
|
|
42
|
+
console.log(` Config: ${configPath}`);
|
|
43
|
+
console.log(` Workspace: ${workspacePath}`);
|
|
44
|
+
console.log("");
|
|
45
|
+
seedMainAgentBootstrap(result.config);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function loadAndValidateManifest(extensionDir) {
|
|
49
|
+
const manifestPath = join(extensionDir, MANIFEST);
|
|
50
|
+
if (!existsSync(manifestPath)) {
|
|
51
|
+
console.error(colors.red("error:"), `Missing ${MANIFEST} in ${extensionDir}`);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const raw = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
56
|
+
if (!isRecord(raw)) {
|
|
57
|
+
console.error(colors.red("error:"), "Manifest must be a JSON object");
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const manifest = normalizeExtensionManifest(raw);
|
|
61
|
+
if (!manifest.id?.trim()) {
|
|
62
|
+
console.error(colors.red("error:"), "Manifest \"id\" is required");
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
if (manifest.engines?.xopc) {
|
|
66
|
+
const r = checkEngineCompatibility(PACKAGE_VERSION, manifest.engines.xopc);
|
|
67
|
+
if (r.parseWarning) console.log(colors.yellow("warning:"), r.reason ?? "engines.xopc could not be fully parsed — continuing");
|
|
68
|
+
else if (!r.compatible) console.log(colors.yellow("warning:"), r.reason ?? `engines.xopc may not match xopc ${PACKAGE_VERSION} — continuing`);
|
|
69
|
+
}
|
|
70
|
+
return manifest;
|
|
71
|
+
} catch (e) {
|
|
72
|
+
log.error({ err: e }, "Failed to read manifest");
|
|
73
|
+
console.error(colors.red("error:"), e instanceof Error ? e.message : String(e));
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function setupDevSymlink(extensionDir, extensionsDir, extensionId) {
|
|
78
|
+
mkdirSync(extensionsDir, { recursive: true });
|
|
79
|
+
const symlinkPath = join(extensionsDir, extensionId);
|
|
80
|
+
if (existsSync(symlinkPath)) unlinkSync(symlinkPath);
|
|
81
|
+
symlinkSync(extensionDir, symlinkPath, "dir");
|
|
82
|
+
return symlinkPath;
|
|
83
|
+
}
|
|
84
|
+
function cleanupSymlink(symlinkPath) {
|
|
85
|
+
if (!symlinkPath) return;
|
|
86
|
+
try {
|
|
87
|
+
if (existsSync(symlinkPath)) unlinkSync(symlinkPath);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
log.warn({
|
|
90
|
+
err: e,
|
|
91
|
+
symlinkPath
|
|
92
|
+
}, "Failed to remove dev symlink");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function shouldIgnorePath(relativePath) {
|
|
96
|
+
const parts = relativePath.split(/[/\\]/);
|
|
97
|
+
if (parts.some((p) => p === "node_modules")) return true;
|
|
98
|
+
if (parts.some((p) => p.startsWith("."))) return true;
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
function createExtensionDevCommand() {
|
|
102
|
+
return new Command("extension:dev").alias("ext:dev").description("Symlink an extension into the workspace for live development (optional file watch + gateway)").argument("[dir]", "Extension directory (default: current working directory)", ".").option("--port <number>", "Gateway port", "18790").option("--host <address>", "Gateway host", "127.0.0.1").option("--no-gateway", "Do not start the gateway (symlink only)").option("--no-watch", "Do not watch files for changes").action(async (dir, options) => {
|
|
103
|
+
const extensionDir = resolve(dir || ".");
|
|
104
|
+
const manifest = loadAndValidateManifest(extensionDir);
|
|
105
|
+
if (!manifest) process.exit(1);
|
|
106
|
+
const ctx = getContextWithOpts();
|
|
107
|
+
const config = loadConfig(ctx.configPath);
|
|
108
|
+
const symlinkPath = setupDevSymlink(extensionDir, resolveWorkspaceExtensionsDir(config, resolveDefaultAgentId(config)), manifest.id);
|
|
109
|
+
console.log(colors.green("✓"), `Dev symlink: ${symlinkPath} → ${extensionDir}`);
|
|
110
|
+
console.log(colors.cyan("Note:"), "restart the gateway or trigger config hot-reload so the extension reload picks up changes.");
|
|
111
|
+
let debounce = null;
|
|
112
|
+
let watcher = null;
|
|
113
|
+
if (options.watch) try {
|
|
114
|
+
watcher = watch(extensionDir, { recursive: true }, (_event, filename) => {
|
|
115
|
+
const rel = filename ? String(filename) : "";
|
|
116
|
+
if (rel && shouldIgnorePath(rel)) return;
|
|
117
|
+
if (debounce) clearTimeout(debounce);
|
|
118
|
+
debounce = setTimeout(() => {
|
|
119
|
+
const label = rel || "(unknown)";
|
|
120
|
+
if (/(^|[\\/])xopc\.extension\.json$/.test(rel) || rel === MANIFEST) console.log(colors.cyan("[watch]"), `manifest: ${label}`);
|
|
121
|
+
else if (/\.(html?|css|mjs|js|tsx?|jsx|json)$/i.test(label)) if (/^ui[\\/]/.test(rel) || /[\\/]ui[\\/]/.test(label)) console.log(colors.cyan("[watch]"), `ui: ${label}`);
|
|
122
|
+
else console.log(colors.cyan("[watch]"), `source: ${label}`);
|
|
123
|
+
else console.log(colors.cyan("[watch]"), `changed: ${label}`);
|
|
124
|
+
}, 300);
|
|
125
|
+
});
|
|
126
|
+
} catch (e) {
|
|
127
|
+
log.warn({ err: e }, "fs.watch failed; continuing without watch");
|
|
128
|
+
}
|
|
129
|
+
let cleaned = false;
|
|
130
|
+
const cleanup = () => {
|
|
131
|
+
if (cleaned) return;
|
|
132
|
+
cleaned = true;
|
|
133
|
+
if (debounce) clearTimeout(debounce);
|
|
134
|
+
if (watcher) try {
|
|
135
|
+
watcher.close();
|
|
136
|
+
} catch (e) {
|
|
137
|
+
log.warn({ err: e }, "watcher close failed");
|
|
138
|
+
}
|
|
139
|
+
cleanupSymlink(symlinkPath);
|
|
140
|
+
};
|
|
141
|
+
for (const sig of ["SIGINT", "SIGTERM"]) process.on(sig, () => {
|
|
142
|
+
cleanup();
|
|
143
|
+
process.exit(0);
|
|
144
|
+
});
|
|
145
|
+
if (!options.gateway) {
|
|
146
|
+
if (options.watch) console.log(colors.cyan("Watching…"), "Ctrl+C to stop and remove symlink");
|
|
147
|
+
else console.log(colors.cyan("Holding process…"), "Ctrl+C to stop and remove symlink (no file watch)");
|
|
148
|
+
await new Promise(() => {});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const port = parseInt(options.port, 10);
|
|
152
|
+
const host = options.host;
|
|
153
|
+
await ensureGatewayReady(ctx.configPath, ctx.workspacePath, host, port);
|
|
154
|
+
const cfg = loadConfig(ctx.configPath);
|
|
155
|
+
if (Number.isNaN(port)) {
|
|
156
|
+
console.error(colors.red("error:"), "Invalid --port");
|
|
157
|
+
cleanup();
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
console.log("");
|
|
161
|
+
console.log("🚀 Starting gateway (extension dev)…");
|
|
162
|
+
console.log(` Host: ${host}`);
|
|
163
|
+
console.log(` Port: ${port}`);
|
|
164
|
+
console.log("");
|
|
165
|
+
try {
|
|
166
|
+
await runGatewayLoop({
|
|
167
|
+
configPath: ctx.configPath || resolveConfigPath(),
|
|
168
|
+
port,
|
|
169
|
+
start: async () => {
|
|
170
|
+
const server = new GatewayServer({
|
|
171
|
+
host,
|
|
172
|
+
port,
|
|
173
|
+
token: cfg?.gateway?.auth?.token,
|
|
174
|
+
verbose: ctx.isVerbose,
|
|
175
|
+
configPath: ctx.configPath,
|
|
176
|
+
enableHotReload: true
|
|
177
|
+
});
|
|
178
|
+
await server.start();
|
|
179
|
+
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
180
|
+
const token = cfg?.gateway?.auth?.token;
|
|
181
|
+
console.log("✅ Gateway started");
|
|
182
|
+
console.log(` URL: http://${displayHost}:${port}`);
|
|
183
|
+
if (token) console.log(` Token: ${String(token).slice(0, 8)}...${String(token).slice(-8)}`);
|
|
184
|
+
console.log("");
|
|
185
|
+
return server;
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
} finally {
|
|
189
|
+
cleanup();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
//#endregion
|
|
194
|
+
export { createExtensionDevCommand };
|
|
195
|
+
|
|
196
|
+
//# sourceMappingURL=extension-dev.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension-dev.js","names":[],"sources":["../../../../src/cli/commands/extension-dev.ts"],"sourcesContent":["import {\n existsSync,\n mkdirSync,\n readFileSync,\n symlinkSync,\n unlinkSync,\n watch,\n type FSWatcher,\n} from 'node:fs';\nimport { join, resolve } from 'node:path';\n\nimport { Command } from 'commander';\n\nimport { resolveDefaultAgentId } from '../../agent/agent-scope.js';\nimport { loadConfig } from '../../config/loader.js';\nimport { resolveConfigPath } from '../../config/paths.js';\nimport { resolveWorkspaceExtensionsDir } from '../../config/paths.js';\nimport { checkEngineCompatibility } from '../../extensions/engine-check.js';\nimport type { ExtensionManifest } from '../../extensions/types/index.js';\nimport { normalizeExtensionManifest } from '../../extensions/normalize-manifest.js';\nimport { GatewayServer } from '../../gateway/index.js';\nimport { runGatewayLoop } from '../../gateway/run-loop.js';\nimport { PACKAGE_VERSION } from '../../package-version.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { colors } from '../utils/colors.js';\nimport { getContextWithOpts } from '../index.js';\nimport { initWorkspace } from '../utils/init-workspace.js';\nimport { seedMainAgentBootstrap } from '../../agent/context/workspace-seed.js';\n\nconst log = createLogger('ExtensionDev');\nconst MANIFEST = 'xopc.extension.json';\n\nfunction isRecord(x: unknown): x is Record<string, unknown> {\n return typeof x === 'object' && x !== null && !Array.isArray(x);\n}\n\nasync function ensureGatewayReady(\n configPath: string,\n workspacePath: string,\n gatewayHost: string,\n gatewayPort: number,\n): Promise<void> {\n const result = await initWorkspace({\n configPath,\n workspacePath,\n gatewayHost,\n gatewayPort,\n });\n\n if (result.configCreated || result.workspaceCreated) {\n console.log('');\n console.log('👋 First-time setup before starting the gateway...');\n console.log('');\n console.log('✅ Setup complete.');\n console.log(` Config: ${configPath}`);\n console.log(` Workspace: ${workspacePath}`);\n console.log('');\n seedMainAgentBootstrap(result.config);\n }\n}\n\nfunction loadAndValidateManifest(extensionDir: string): ExtensionManifest | null {\n const manifestPath = join(extensionDir, MANIFEST);\n if (!existsSync(manifestPath)) {\n console.error(colors.red('error:'), `Missing ${MANIFEST} in ${extensionDir}`);\n return null;\n }\n try {\n const raw = JSON.parse(readFileSync(manifestPath, 'utf-8') as string) as unknown;\n if (!isRecord(raw)) {\n console.error(colors.red('error:'), 'Manifest must be a JSON object');\n return null;\n }\n const manifest = normalizeExtensionManifest(raw);\n if (!manifest.id?.trim()) {\n console.error(colors.red('error:'), 'Manifest \"id\" is required');\n return null;\n }\n if (manifest.engines?.xopc) {\n const r = checkEngineCompatibility(PACKAGE_VERSION, manifest.engines.xopc);\n if (r.parseWarning) {\n console.log(\n colors.yellow('warning:'),\n r.reason ?? 'engines.xopc could not be fully parsed — continuing',\n );\n } else if (!r.compatible) {\n console.log(\n colors.yellow('warning:'),\n r.reason ?? `engines.xopc may not match xopc ${PACKAGE_VERSION} — continuing`,\n );\n }\n }\n return manifest;\n } catch (e) {\n log.error({ err: e }, 'Failed to read manifest');\n console.error(\n colors.red('error:'),\n e instanceof Error ? e.message : String(e),\n );\n return null;\n }\n}\n\nfunction setupDevSymlink(extensionDir: string, extensionsDir: string, extensionId: string): string {\n mkdirSync(extensionsDir, { recursive: true });\n const symlinkPath = join(extensionsDir, extensionId);\n if (existsSync(symlinkPath)) {\n unlinkSync(symlinkPath);\n }\n symlinkSync(extensionDir, symlinkPath, 'dir');\n return symlinkPath;\n}\n\nfunction cleanupSymlink(symlinkPath: string | null): void {\n if (!symlinkPath) return;\n try {\n if (existsSync(symlinkPath)) {\n unlinkSync(symlinkPath);\n }\n } catch (e) {\n log.warn({ err: e, symlinkPath }, 'Failed to remove dev symlink');\n }\n}\n\nfunction shouldIgnorePath(relativePath: string): boolean {\n const parts = relativePath.split(/[/\\\\]/);\n if (parts.some((p) => p === 'node_modules')) return true;\n if (parts.some((p) => p.startsWith('.'))) return true;\n return false;\n}\n\nexport function createExtensionDevCommand(): Command {\n return new Command('extension:dev')\n .alias('ext:dev')\n .description('Symlink an extension into the workspace for live development (optional file watch + gateway)')\n .argument('[dir]', 'Extension directory (default: current working directory)', '.')\n .option('--port <number>', 'Gateway port', '18790')\n .option('--host <address>', 'Gateway host', '127.0.0.1')\n .option('--no-gateway', 'Do not start the gateway (symlink only)')\n .option('--no-watch', 'Do not watch files for changes')\n .action(\n async (\n dir: string,\n options: { port: string; host: string; gateway: boolean; watch: boolean },\n ) => {\n const extensionDir = resolve(dir || '.');\n const manifest = loadAndValidateManifest(extensionDir);\n if (!manifest) {\n process.exit(1);\n }\n\n const ctx = getContextWithOpts();\n const config = loadConfig(ctx.configPath);\n const agentId = resolveDefaultAgentId(config);\n const extensionsDir = resolveWorkspaceExtensionsDir(config, agentId);\n const symlinkPath = setupDevSymlink(extensionDir, extensionsDir, manifest.id);\n\n console.log(\n colors.green('✓'),\n `Dev symlink: ${symlinkPath} → ${extensionDir}`,\n );\n console.log(\n colors.cyan('Note:'),\n 'restart the gateway or trigger config hot-reload so the extension reload picks up changes.',\n );\n\n let debounce: ReturnType<typeof setTimeout> | null = null;\n let watcher: FSWatcher | null = null;\n\n if (options.watch) {\n try {\n watcher = watch(\n extensionDir,\n { recursive: true },\n (_event, filename) => {\n const rel = filename ? String(filename) : '';\n if (rel && shouldIgnorePath(rel)) return;\n if (debounce) clearTimeout(debounce);\n debounce = setTimeout(() => {\n const label = rel || '(unknown)';\n if (/(^|[\\\\/])xopc\\.extension\\.json$/.test(rel) || rel === MANIFEST) {\n console.log(colors.cyan('[watch]'), `manifest: ${label}`);\n } else if (/\\.(html?|css|mjs|js|tsx?|jsx|json)$/i.test(label)) {\n if (/^ui[\\\\/]/.test(rel) || /[\\\\/]ui[\\\\/]/.test(label)) {\n console.log(colors.cyan('[watch]'), `ui: ${label}`);\n } else {\n console.log(colors.cyan('[watch]'), `source: ${label}`);\n }\n } else {\n console.log(colors.cyan('[watch]'), `changed: ${label}`);\n }\n }, 300);\n },\n );\n } catch (e) {\n log.warn({ err: e }, 'fs.watch failed; continuing without watch');\n }\n }\n\n let cleaned = false;\n const cleanup = () => {\n if (cleaned) return;\n cleaned = true;\n if (debounce) clearTimeout(debounce);\n if (watcher) {\n try {\n watcher.close();\n } catch (e) {\n log.warn({ err: e }, 'watcher close failed');\n }\n }\n cleanupSymlink(symlinkPath);\n };\n\n for (const sig of ['SIGINT', 'SIGTERM'] as const) {\n process.on(sig, () => {\n cleanup();\n process.exit(0);\n });\n }\n\n if (!options.gateway) {\n if (options.watch) {\n console.log(colors.cyan('Watching…'), 'Ctrl+C to stop and remove symlink');\n } else {\n console.log(\n colors.cyan('Holding process…'),\n 'Ctrl+C to stop and remove symlink (no file watch)',\n );\n }\n await new Promise(() => {\n /* until SIGINT / SIGTERM */\n });\n return;\n }\n\n const port = parseInt(options.port, 10);\n const host = options.host;\n await ensureGatewayReady(ctx.configPath, ctx.workspacePath, host, port);\n const cfg = loadConfig(ctx.configPath);\n\n if (Number.isNaN(port)) {\n console.error(colors.red('error:'), 'Invalid --port');\n cleanup();\n process.exit(1);\n }\n\n console.log('');\n console.log('🚀 Starting gateway (extension dev)…');\n console.log(` Host: ${host}`);\n console.log(` Port: ${port}`);\n console.log('');\n\n try {\n await runGatewayLoop({\n configPath: ctx.configPath || resolveConfigPath(),\n port,\n start: async () => {\n const server = new GatewayServer({\n host,\n port,\n token: cfg?.gateway?.auth?.token,\n verbose: ctx.isVerbose,\n configPath: ctx.configPath,\n enableHotReload: true,\n });\n await server.start();\n const displayHost = host === '0.0.0.0' ? 'localhost' : host;\n const token = cfg?.gateway?.auth?.token;\n console.log('✅ Gateway started');\n console.log(` URL: http://${displayHost}:${port}`);\n if (token) {\n console.log(\n ` Token: ${String(token).slice(0, 8)}...${String(token).slice(-8)}`,\n );\n }\n console.log('');\n return server;\n },\n });\n } finally {\n cleanup();\n }\n },\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { init_agent_scope, resolveDefaultAgentId } from "../../agent/agent-scope.js";
|
|
2
|
+
import { init_paths, resolveWorkspaceExtensionsDir } from "../../config/paths.js";
|
|
3
|
+
import { init_loader, loadConfig } from "../../config/loader.js";
|
|
4
|
+
import { normalizeExtensionManifest } from "../../extensions/normalize-manifest.js";
|
|
5
|
+
import { colors } from "../utils/colors.js";
|
|
6
|
+
import { fetchRegistry, findExtension, getRegistryUrl, listExtensions, searchExtensions } from "../../extensions/marketplace.js";
|
|
7
|
+
import { getExtensionLockfileManager } from "../../extensions/lockfile.js";
|
|
8
|
+
import { installFromNpm, resolveExtensionsDir } from "../../extensions/install.js";
|
|
9
|
+
import { getContextWithOpts } from "../index.js";
|
|
10
|
+
import { join, resolve } from "node:path";
|
|
11
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
12
|
+
import { Command } from "commander";
|
|
13
|
+
import { execSync } from "node:child_process";
|
|
14
|
+
//#region src/cli/commands/extension-marketplace.ts
|
|
15
|
+
init_agent_scope();
|
|
16
|
+
init_loader();
|
|
17
|
+
init_paths();
|
|
18
|
+
const MANIFEST = "xopc.extension.json";
|
|
19
|
+
function isRecord(x) {
|
|
20
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
21
|
+
}
|
|
22
|
+
function hasWorkspaceDeps(pkg) {
|
|
23
|
+
const deps = pkg.dependencies;
|
|
24
|
+
const dev = pkg.devDependencies;
|
|
25
|
+
const check = (d) => {
|
|
26
|
+
if (!isRecord(d)) return false;
|
|
27
|
+
return Object.values(d).some((v) => typeof v === "string" && v.startsWith("workspace:"));
|
|
28
|
+
};
|
|
29
|
+
return check(deps) || check(dev);
|
|
30
|
+
}
|
|
31
|
+
function createExtensionSearchCommand() {
|
|
32
|
+
return new Command("extension:search").alias("ext:search").description("Search the curated extension registry").argument("[keyword]", "Search text (omit to list all)", "").option("--category <cat>", "Filter by category").option("--json", "JSON output").action(async (keyword, opts) => {
|
|
33
|
+
try {
|
|
34
|
+
let rows;
|
|
35
|
+
if (opts.category?.trim()) {
|
|
36
|
+
rows = await listExtensions(opts.category.trim());
|
|
37
|
+
if (keyword.trim()) {
|
|
38
|
+
const k = keyword.trim().toLowerCase();
|
|
39
|
+
rows = rows.filter((e) => e.id.toLowerCase().includes(k) || e.name.toLowerCase().includes(k) || (e.description ?? "").toLowerCase().includes(k));
|
|
40
|
+
}
|
|
41
|
+
} else if (keyword.trim()) rows = await searchExtensions(keyword.trim());
|
|
42
|
+
else rows = (await fetchRegistry()).extensions;
|
|
43
|
+
if (opts.json) {
|
|
44
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (rows.length === 0) {
|
|
48
|
+
console.log("No extensions found.");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
console.log(`Registry: ${getRegistryUrl()}`);
|
|
52
|
+
console.log("");
|
|
53
|
+
for (const e of rows) {
|
|
54
|
+
const badge = e.verified ? ` ${colors.green("✓")}` : "";
|
|
55
|
+
console.log(`${colors.cyan(e.name)}${badge} ${colors.gray(e.version ?? "")}`);
|
|
56
|
+
console.log(` id: ${e.id} npm: ${e.npmPackage}`);
|
|
57
|
+
if (e.description) console.log(` ${e.description}`);
|
|
58
|
+
if (e.categories?.length) console.log(` categories: ${e.categories.join(", ")}`);
|
|
59
|
+
console.log("");
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error(colors.red("Error:"), err instanceof Error ? err.message : String(err));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function createExtensionPublishCommand() {
|
|
68
|
+
return new Command("extension:publish").alias("ext:publish").description("Publish extension to npm (public)").argument("[directory]", "Extension root", ".").option("--dry-run", "npm publish --dry-run", false).option("--access <level>", "npm access", "public").action((dir, opts) => {
|
|
69
|
+
const root = resolve(dir || ".");
|
|
70
|
+
const manifestPath = join(root, MANIFEST);
|
|
71
|
+
const pkgPath = join(root, "package.json");
|
|
72
|
+
if (!existsSync(manifestPath) || !existsSync(pkgPath)) {
|
|
73
|
+
console.error(colors.red("error:"), `Need ${MANIFEST} and package.json in ${root}`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
if (hasWorkspaceDeps(JSON.parse(readFileSync(pkgPath, "utf-8")))) {
|
|
78
|
+
console.error(colors.red("error:"), "Remove workspace:* dependencies before publishing.");
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
if (!normalizeExtensionManifest(JSON.parse(readFileSync(manifestPath, "utf-8"))).id) {
|
|
82
|
+
console.error(colors.red("error:"), "Invalid manifest id");
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.error(colors.red("error:"), e instanceof Error ? e.message : String(e));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
const args = ["publish", `--access=${opts.access}`];
|
|
90
|
+
if (opts.dryRun) args.push("--dry-run");
|
|
91
|
+
console.log(colors.cyan("Running:"), `npm ${args.join(" ")}`);
|
|
92
|
+
try {
|
|
93
|
+
execSync(`npm ${args.join(" ")}`, {
|
|
94
|
+
cwd: root,
|
|
95
|
+
stdio: "inherit"
|
|
96
|
+
});
|
|
97
|
+
} catch {
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function createExtensionUpdateCommand() {
|
|
103
|
+
return new Command("extension:update").alias("ext:update").description("Re-install extension(s) from npm using the lockfile / registry").argument("[extensionId]", "Specific extension id (default: all in lockfile)").option("--global", "Use global extensions directory", false).action(async (extensionId, opts) => {
|
|
104
|
+
const ctx = getContextWithOpts();
|
|
105
|
+
const cfg = loadConfig(ctx.configPath);
|
|
106
|
+
const agentId = resolveDefaultAgentId(cfg);
|
|
107
|
+
const targetDir = opts.global ? resolveExtensionsDir(ctx.workspacePath, true) : resolveWorkspaceExtensionsDir(cfg, agentId);
|
|
108
|
+
const data = await getExtensionLockfileManager().load();
|
|
109
|
+
const ids = extensionId?.trim() ? [extensionId.trim()] : Object.keys(data.extensions);
|
|
110
|
+
if (ids.length === 0) {
|
|
111
|
+
console.log("No extensions in lockfile.");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
for (const id of ids) {
|
|
115
|
+
const entry = data.extensions[id];
|
|
116
|
+
if (!entry) {
|
|
117
|
+
console.log(colors.yellow("skip"), id, "(not in lockfile)");
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (entry.source !== "npm") {
|
|
121
|
+
console.log(colors.yellow("skip"), id, `(source ${entry.source})`);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const spec = entry.resolved?.trim() || await npmPackageForId(id);
|
|
125
|
+
if (!spec) {
|
|
126
|
+
console.log(colors.yellow("skip"), id, "(could not resolve npm package)");
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
console.log(colors.cyan("Updating"), id, "←", spec);
|
|
130
|
+
const result = await installFromNpm(spec, targetDir);
|
|
131
|
+
if (!result.ok) {
|
|
132
|
+
console.error(colors.red("error:"), result.error ?? id);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
console.log(colors.green("✓"), id);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
async function npmPackageForId(id) {
|
|
140
|
+
return (await findExtension(id))?.npmPackage;
|
|
141
|
+
}
|
|
142
|
+
//#endregion
|
|
143
|
+
export { createExtensionPublishCommand, createExtensionSearchCommand, createExtensionUpdateCommand };
|
|
144
|
+
|
|
145
|
+
//# sourceMappingURL=extension-marketplace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension-marketplace.js","names":["marketplace.listExtensions","marketplace.searchExtensions","marketplace.fetchRegistry","marketplace.getRegistryUrl","marketplace.findExtension"],"sources":["../../../../src/cli/commands/extension-marketplace.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\n\nimport { Command } from 'commander';\n\nimport { resolveDefaultAgentId } from '../../agent/agent-scope.js';\nimport { loadConfig } from '../../config/loader.js';\nimport { resolveWorkspaceExtensionsDir } from '../../config/paths.js';\nimport { installFromNpm, resolveExtensionsDir } from '../../extensions/install.js';\nimport { getExtensionLockfileManager } from '../../extensions/lockfile.js';\nimport * as marketplace from '../../extensions/marketplace.js';\nimport { normalizeExtensionManifest } from '../../extensions/normalize-manifest.js';\nimport { colors } from '../utils/colors.js';\nimport { getContextWithOpts } from '../index.js';\n\nconst MANIFEST = 'xopc.extension.json';\n\nfunction isRecord(x: unknown): x is Record<string, unknown> {\n return typeof x === 'object' && x !== null && !Array.isArray(x);\n}\n\nfunction hasWorkspaceDeps(pkg: Record<string, unknown>): boolean {\n const deps = pkg.dependencies;\n const dev = pkg.devDependencies;\n const check = (d: unknown) => {\n if (!isRecord(d)) return false;\n return Object.values(d).some((v) => typeof v === 'string' && v.startsWith('workspace:'));\n };\n return check(deps) || check(dev);\n}\n\nexport function createExtensionSearchCommand(): Command {\n return new Command('extension:search')\n .alias('ext:search')\n .description('Search the curated extension registry')\n .argument('[keyword]', 'Search text (omit to list all)', '')\n .option('--category <cat>', 'Filter by category')\n .option('--json', 'JSON output')\n .action(async (keyword: string, opts: { category?: string; json?: boolean }) => {\n try {\n let rows;\n if (opts.category?.trim()) {\n rows = await marketplace.listExtensions(opts.category.trim());\n if (keyword.trim()) {\n const k = keyword.trim().toLowerCase();\n rows = rows.filter(\n (e) =>\n e.id.toLowerCase().includes(k) ||\n e.name.toLowerCase().includes(k) ||\n (e.description ?? '').toLowerCase().includes(k),\n );\n }\n } else if (keyword.trim()) {\n rows = await marketplace.searchExtensions(keyword.trim());\n } else {\n const reg = await marketplace.fetchRegistry();\n rows = reg.extensions;\n }\n\n if (opts.json) {\n console.log(JSON.stringify(rows, null, 2));\n return;\n }\n\n if (rows.length === 0) {\n console.log('No extensions found.');\n return;\n }\n\n console.log(`Registry: ${marketplace.getRegistryUrl()}`);\n console.log('');\n for (const e of rows) {\n const badge = e.verified ? ` ${colors.green('✓')}` : '';\n console.log(`${colors.cyan(e.name)}${badge} ${colors.gray(e.version ?? '')}`);\n console.log(` id: ${e.id} npm: ${e.npmPackage}`);\n if (e.description) console.log(` ${e.description}`);\n if (e.categories?.length) console.log(` categories: ${e.categories.join(', ')}`);\n console.log('');\n }\n } catch (err) {\n console.error(colors.red('Error:'), err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n}\n\nexport function createExtensionPublishCommand(): Command {\n return new Command('extension:publish')\n .alias('ext:publish')\n .description('Publish extension to npm (public)')\n .argument('[directory]', 'Extension root', '.')\n .option('--dry-run', 'npm publish --dry-run', false)\n .option('--access <level>', 'npm access', 'public')\n .action((dir: string, opts: { dryRun: boolean; access: string }) => {\n const root = resolve(dir || '.');\n const manifestPath = join(root, MANIFEST);\n const pkgPath = join(root, 'package.json');\n if (!existsSync(manifestPath) || !existsSync(pkgPath)) {\n console.error(colors.red('error:'), `Need ${MANIFEST} and package.json in ${root}`);\n process.exit(1);\n }\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as Record<string, unknown>;\n if (hasWorkspaceDeps(pkg)) {\n console.error(\n colors.red('error:'),\n 'Remove workspace:* dependencies before publishing.',\n );\n process.exit(1);\n }\n const raw = JSON.parse(readFileSync(manifestPath, 'utf-8')) as Record<string, unknown>;\n const manifest = normalizeExtensionManifest(raw);\n if (!manifest.id) {\n console.error(colors.red('error:'), 'Invalid manifest id');\n process.exit(1);\n }\n } catch (e) {\n console.error(colors.red('error:'), e instanceof Error ? e.message : String(e));\n process.exit(1);\n }\n\n const args = ['publish', `--access=${opts.access}`];\n if (opts.dryRun) args.push('--dry-run');\n console.log(colors.cyan('Running:'), `npm ${args.join(' ')}`);\n try {\n execSync(`npm ${args.join(' ')}`, { cwd: root, stdio: 'inherit' });\n } catch {\n process.exit(1);\n }\n });\n}\n\nexport function createExtensionUpdateCommand(): Command {\n return new Command('extension:update')\n .alias('ext:update')\n .description('Re-install extension(s) from npm using the lockfile / registry')\n .argument('[extensionId]', 'Specific extension id (default: all in lockfile)')\n .option('--global', 'Use global extensions directory', false)\n .action(async (extensionId: string | undefined, opts: { global: boolean }) => {\n const ctx = getContextWithOpts();\n const cfg = loadConfig(ctx.configPath);\n const agentId = resolveDefaultAgentId(cfg);\n const targetDir = opts.global\n ? resolveExtensionsDir(ctx.workspacePath, true)\n : resolveWorkspaceExtensionsDir(cfg, agentId);\n\n const lock = getExtensionLockfileManager();\n const data = await lock.load();\n const ids = extensionId?.trim()\n ? [extensionId.trim()]\n : Object.keys(data.extensions);\n\n if (ids.length === 0) {\n console.log('No extensions in lockfile.');\n return;\n }\n\n for (const id of ids) {\n const entry = data.extensions[id];\n if (!entry) {\n console.log(colors.yellow('skip'), id, '(not in lockfile)');\n continue;\n }\n if (entry.source !== 'npm') {\n console.log(colors.yellow('skip'), id, `(source ${entry.source})`);\n continue;\n }\n const spec = entry.resolved?.trim() || (await npmPackageForId(id));\n if (!spec) {\n console.log(colors.yellow('skip'), id, '(could not resolve npm package)');\n continue;\n }\n console.log(colors.cyan('Updating'), id, '←', spec);\n const result = await installFromNpm(spec, targetDir);\n if (!result.ok) {\n console.error(colors.red('error:'), result.error ?? id);\n process.exit(1);\n }\n console.log(colors.green('✓'), id);\n }\n });\n}\n\nasync function npmPackageForId(id: string): Promise<string | undefined> {\n const found = await marketplace.findExtension(id);\n return found?.npmPackage;\n}\n"],"mappings":"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"}
|