weaver-work-cli 0.1.8 → 0.1.21
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 +25 -17
- package/dist/cmd/action.js +76 -0
- package/dist/cmd/index.js +2 -0
- package/dist/cmd/refs/index.js +103 -0
- package/dist/cmd/register.js +31 -0
- package/dist/cmd/root.js +1 -0
- package/dist/cmd/setup/index.js +25 -5
- package/dist/cmd/skills/index.js +37 -5
- package/dist/core/connector.js +54 -0
- package/dist/core/context.js +2 -1
- package/dist/core/input.js +48 -0
- package/dist/core/output.js +298 -3
- package/dist/core/refs.js +435 -0
- package/dist/core/state.js +26 -0
- package/dist/internal/e10/auth/commands.js +34 -19
- package/dist/internal/e10/auth/session.js +74 -2
- package/dist/internal/e10/auth/xiaoe.js +359 -94
- package/dist/internal/e10/context.js +18 -6
- package/dist/internal/e10/request-runtime.js +8 -1
- package/dist/internal/skills/install.js +148 -28
- package/dist/internal/skills/reader.js +12 -9
- package/dist/shortcuts/blog/continuation.js +74 -0
- package/dist/shortcuts/blog/errors.js +60 -0
- package/dist/shortcuts/blog/host.js +175 -0
- package/dist/shortcuts/blog/index.js +324 -0
- package/dist/shortcuts/blog/manifest.js +415 -0
- package/dist/shortcuts/blog/operations/comment.js +125 -0
- package/dist/shortcuts/blog/operations/comments.js +31 -0
- package/dist/shortcuts/blog/operations/edit.js +131 -0
- package/dist/shortcuts/blog/operations/export.js +46 -0
- package/dist/shortcuts/blog/operations/like.js +80 -0
- package/dist/shortcuts/blog/operations/publish.js +157 -0
- package/dist/shortcuts/blog/operations/query-user.js +58 -0
- package/dist/shortcuts/blog/operations/query.js +96 -0
- package/dist/shortcuts/blog/operations/registry.js +54 -0
- package/dist/shortcuts/blog/operations/remind.js +176 -0
- package/dist/shortcuts/blog/operations/shared.js +330 -0
- package/dist/shortcuts/blog/operations/types.js +16 -0
- package/dist/shortcuts/blog/operations.js +9 -0
- package/dist/shortcuts/caizhilian/continuation.js +70 -0
- package/dist/shortcuts/caizhilian/errors.js +53 -0
- package/dist/shortcuts/caizhilian/host.js +190 -0
- package/dist/shortcuts/caizhilian/index.js +186 -0
- package/dist/shortcuts/caizhilian/manifest.js +42 -0
- package/dist/shortcuts/caizhilian/operations/api.js +801 -0
- package/dist/shortcuts/caizhilian/operations/confirm.js +75 -0
- package/dist/shortcuts/caizhilian/operations/files.js +306 -0
- package/dist/shortcuts/caizhilian/operations/form.js +170 -0
- package/dist/shortcuts/caizhilian/operations/handlers/collab.js +611 -0
- package/dist/shortcuts/caizhilian/operations/handlers/document.js +890 -0
- package/dist/shortcuts/caizhilian/operations/handlers/file-collection.js +1227 -0
- package/dist/shortcuts/caizhilian/operations/handlers/file-upload.js +192 -0
- package/dist/shortcuts/caizhilian/operations/handlers/fileops.js +570 -0
- package/dist/shortcuts/caizhilian/operations/handlers/folder.js +1024 -0
- package/dist/shortcuts/caizhilian/operations/handlers/search.js +318 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-attach.js +294 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-backup.js +423 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-encrypt.js +238 -0
- package/dist/shortcuts/caizhilian/operations/handlers/setting-storage.js +364 -0
- package/dist/shortcuts/caizhilian/operations/handlers/system.js +106 -0
- package/dist/shortcuts/caizhilian/operations/registry.js +151 -0
- package/dist/shortcuts/caizhilian/operations/shared.js +408 -0
- package/dist/shortcuts/caizhilian/operations/specs.js +698 -0
- package/dist/shortcuts/caizhilian/operations/types.js +57 -0
- package/dist/shortcuts/caizhilian/operations.js +21 -0
- package/dist/shortcuts/contract/continuation.js +70 -0
- package/dist/shortcuts/contract/errors.js +62 -0
- package/dist/shortcuts/contract/host.js +205 -0
- package/dist/shortcuts/contract/index.js +318 -0
- package/dist/shortcuts/contract/manifest.js +596 -0
- package/dist/shortcuts/contract/operations/clause-reference.js +78 -0
- package/dist/shortcuts/contract/operations/form-info.js +371 -0
- package/dist/shortcuts/contract/operations/read.js +118 -0
- package/dist/shortcuts/contract/operations/registry.js +43 -0
- package/dist/shortcuts/contract/operations/request.js +300 -0
- package/dist/shortcuts/contract/operations/shared.js +133 -0
- package/dist/shortcuts/contract/operations/subject.js +157 -0
- package/dist/shortcuts/contract/operations/template.js +81 -0
- package/dist/shortcuts/contract/operations/types.js +7 -0
- package/dist/shortcuts/contract/operations/workflow.js +52 -0
- package/dist/shortcuts/contract/operations/write.js +131 -0
- package/dist/shortcuts/contract/operations.js +8 -0
- package/dist/shortcuts/hrm/index.js +83 -2
- package/dist/shortcuts/hrm/manifest.js +16 -9
- package/dist/shortcuts/hrm/operations/employee.js +160 -30
- package/dist/shortcuts/hrm/operations/org.js +17 -3
- package/dist/shortcuts/hrm/operations/position.js +18 -2
- package/dist/shortcuts/hrm/operations/registry.js +2 -0
- package/dist/shortcuts/hrm/operations/resolve.js +449 -0
- package/dist/shortcuts/index.js +5 -1
- package/dist/shortcuts/invoice/index.js +74 -36
- package/dist/shortcuts/invoice/manifest.js +674 -4
- package/dist/shortcuts/invoice/markdown.js +172 -0
- package/dist/shortcuts/invoice/operations/add.js +3 -3
- package/dist/shortcuts/invoice/operations/delete.js +2 -2
- package/dist/shortcuts/invoice/operations/get.js +10 -2
- package/dist/shortcuts/invoice/operations/import.js +2 -2
- package/dist/shortcuts/invoice/operations/registry.js +8 -1
- package/dist/shortcuts/invoice/operations/reim-auto.js +719 -0
- package/dist/shortcuts/invoice/operations/reim.js +85 -2
- package/dist/shortcuts/invoice/operations/shared.js +75 -8
- package/dist/shortcuts/invoice/operations/update.js +2 -2
- package/dist/shortcuts/invoice/operations/upload-and-validate.js +93 -0
- package/dist/shortcuts/invoice/operations/validate-preview.js +2 -1
- package/dist/shortcuts/invoice/projection.js +328 -0
- package/dist/shortcuts/meeting/host.js +18 -1
- package/dist/shortcuts/meeting/manifest.js +30 -18
- package/dist/shortcuts/meeting/operations/eb/catalog.js +390 -0
- package/dist/shortcuts/meeting/operations/eb/conflict.js +389 -0
- package/dist/shortcuts/meeting/operations/eb/factory.js +389 -0
- package/dist/shortcuts/meeting/operations/eb/index.js +22 -0
- package/dist/shortcuts/meeting/operations/env.js +8 -1
- package/dist/shortcuts/meeting/operations/registry.js +2 -0
- package/dist/shortcuts/meeting/operations/signs.js +216 -2
- package/dist/shortcuts/workflow/endpoints.js +2 -1
- package/dist/shortcuts/workflow/errors.js +8 -2
- package/dist/shortcuts/workflow/host.js +11 -4
- package/dist/shortcuts/workflow/operations/form.js +1016 -590
- package/dist/shortcuts/workflow/operations/read.js +913 -231
- package/dist/shortcuts/workflow/operations/shared.js +21 -0
- package/dist/shortcuts/workflow/operations/write.js +750 -52
- package/dist/shortcuts/workflow/operations.js +36 -10
- package/docs/_catalog.md +6 -0
- package/docs/agent-invoice.md +5 -1
- package/docs/agent-io-channel-design.md +468 -0
- package/docs/agent-skill-install.md +9 -4
- package/docs/blog.md +123 -0
- package/docs/caizhilian.md +304 -0
- package/docs/contract.md +91 -0
- package/docs/e10-auth.md +4 -2
- package/docs/hrm.md +53 -11
- package/docs/invoice.md +5 -1
- package/docs/meeting.md +1 -1
- package/docs/operation-manual.md +15 -3
- package/docs/skill-review-2026-09-19.md +2 -2
- package/package.json +1 -1
- package/scripts/package-skill.mjs +32 -20
- package/scripts/xiaoe_env.py +118 -0
- package/skill-template/business-info.json +113 -14
- package/skill-template/domains/blog.md +24 -0
- package/skill-template/domains/caizhilian.md +22 -0
- package/skill-template/domains/hrm.md +2 -1
- package/skill-template/domains/jinchengda.md +23 -0
- package/skill-template/domains/meeting.md +1 -1
- package/skill-template/domains/workflow.md +1 -1
- package/skill-template/skill-template.md +1 -1
- package/skills/weaver-e10-blog/SKILL.md +111 -0
- package/skills/weaver-e10-blog/references/blog-comment.md +51 -0
- package/skills/weaver-e10-blog/references/blog-comments.md +39 -0
- package/skills/weaver-e10-blog/references/blog-default-permission.md +38 -0
- package/skills/weaver-e10-blog/references/blog-disabled-capabilities.md +11 -0
- package/skills/weaver-e10-blog/references/blog-edit.md +51 -0
- package/skills/weaver-e10-blog/references/blog-export.md +42 -0
- package/skills/weaver-e10-blog/references/blog-like.md +50 -0
- package/skills/weaver-e10-blog/references/blog-publish.md +52 -0
- package/skills/weaver-e10-blog/references/blog-query-user.md +45 -0
- package/skills/weaver-e10-blog/references/blog-query.md +49 -0
- package/skills/weaver-e10-blog/references/blog-remind.md +51 -0
- package/skills/weaver-e10-blog/references/blog-resolve-users.md +38 -0
- package/skills/weaver-e10-blog/references/source-manifest.json +1540 -0
- package/skills/weaver-e10-caizhilian/SKILL.md +217 -0
- package/skills/weaver-e10-caizhilian/references/agent-entry.md +106 -0
- package/skills/weaver-e10-caizhilian/references/collaboration.md +112 -0
- package/skills/weaver-e10-caizhilian/references/document.md +111 -0
- package/skills/weaver-e10-caizhilian/references/file-collection.md +173 -0
- package/skills/weaver-e10-caizhilian/references/file-upload.md +309 -0
- package/skills/weaver-e10-caizhilian/references/files.md +120 -0
- package/skills/weaver-e10-caizhilian/references/folder.md +122 -0
- package/skills/weaver-e10-caizhilian/references/org-browser.md +104 -0
- package/skills/weaver-e10-caizhilian/references/setting-attach.md +440 -0
- package/skills/weaver-e10-caizhilian/references/setting-backup.md +372 -0
- package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +399 -0
- package/skills/weaver-e10-caizhilian/references/setting-storage.md +603 -0
- package/skills/weaver-e10-caizhilian/references/source-manifest.json +5694 -0
- package/skills/weaver-e10-caizhilian/references/withheld.md +93 -0
- package/skills/weaver-e10-caizhilian/references/write-protocol.md +120 -0
- package/skills/weaver-e10-calendar/SKILL.md +4 -3
- package/skills/weaver-e10-ebuilder-form/SKILL.md +4 -9
- package/skills/weaver-e10-ebuilder-form/references/context-routing.md +0 -9
- package/skills/weaver-e10-ebuilder-form/references/custom-api.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/fields-browser.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/list-nlist.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/openapi-read.md +1 -5
- package/skills/weaver-e10-ebuilder-form/references/openapi-write.md +1 -5
- package/skills/weaver-e10-esb/SKILL.md +2 -2
- package/skills/weaver-e10-esb/references/source-manifest.json +9 -9
- package/skills/weaver-e10-hrm/SKILL.md +104 -9
- package/skills/weaver-e10-hrm/references/employee.md +89 -15
- package/skills/weaver-e10-hrm/references/org.md +114 -5
- package/skills/weaver-e10-hrm/references/position.md +14 -7
- package/skills/weaver-e10-hrm/references/source-manifest.json +1 -1
- package/skills/weaver-e10-jinchengda/SKILL.md +167 -0
- package/skills/weaver-e10-jinchengda/references/contract-agent-entry.md +82 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-recommender.md +69 -0
- package/skills/weaver-e10-jinchengda/references/contract-clause-reference-save.md +78 -0
- package/skills/weaver-e10-jinchengda/references/contract-disabled-capabilities.md +27 -0
- package/skills/weaver-e10-jinchengda/references/contract-request-status.md +71 -0
- package/skills/weaver-e10-jinchengda/references/contract-request-write.md +94 -0
- package/skills/weaver-e10-jinchengda/references/contract-signing-subjects.md +89 -0
- package/skills/weaver-e10-jinchengda/references/contract-subject-create.md +77 -0
- package/skills/weaver-e10-jinchengda/references/contract-template-selector.md +66 -0
- package/skills/weaver-e10-jinchengda/references/contract-workflow-discovery.md +94 -0
- package/skills/weaver-e10-jinchengda/references/source-manifest.json +259 -0
- package/skills/weaver-e10-jiuchuanhui/SKILL.md +5 -11
- package/skills/weaver-e10-jucailin/SKILL.md +4 -3
- package/skills/weaver-e10-mail/SKILL.md +4 -11
- package/skills/weaver-e10-meeting/SKILL.md +66 -32
- package/skills/weaver-e10-meeting/references/meeting-change.md +5 -2
- package/skills/weaver-e10-meeting/references/meeting-create.md +2 -1
- package/skills/weaver-e10-meeting/references/meeting-eb.md +114 -0
- package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +9 -8
- package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +35 -7
- package/skills/weaver-e10-meeting/references/meeting-update.md +1 -1
- package/skills/weaver-e10-okr/SKILL.md +6 -11
- package/skills/weaver-e10-okr/references/source-manifest.json +3 -3
- package/skills/weaver-e10-plan/SKILL.md +3 -3
- package/skills/weaver-e10-plan/references/plan-collect-and-compose.md +1 -1
- package/skills/weaver-e10-plan/references/plan-overview-and-routing.md +1 -1
- package/skills/weaver-e10-plan/references/source-manifest.json +13 -13
- package/skills/weaver-e10-qiyecheng/SKILL.md +4 -10
- package/skills/weaver-e10-qiyecheng/references/source-manifest.json +9 -9
- package/skills/weaver-e10-shared/SKILL.md +42 -34
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +16 -43
- package/skills/weaver-e10-shared/references/json-output-contract.md +20 -11
- package/skills/weaver-e10-shared/references/non-interactive-environment.md +21 -14
- package/skills/weaver-e10-shijingran/SKILL.md +2 -1
- package/skills/weaver-e10-shijingran/references/source-manifest.json +9 -9
- package/skills/weaver-e10-skill-maker/SKILL.md +4 -10
- package/skills/weaver-e10-skill-maker/references/business-skill-generation.md +5 -3
- package/skills/weaver-e10-skill-maker/references/module-cli-generation.md +12 -27
- package/skills/weaver-e10-skill-maker/references/module-gap-audit-and-repair.md +17 -5
- package/skills/weaver-e10-skill-maker/references/source-update-detection.md +4 -27
- package/skills/weaver-e10-skill-maker/references/weaver-skill-style.md +1 -1
- package/skills/weaver-e10-wenshuding/SKILL.md +5 -16
- package/skills/weaver-e10-wenshuding/references/source-manifest.json +7 -7
- package/skills/weaver-e10-workflow/SKILL.md +41 -46
- package/skills/weaver-e10-workflow/references/source-manifest.json +39 -75
- package/skills/weaver-e10-workflow/references/workflow-batch.md +90 -29
- package/skills/weaver-e10-workflow/references/workflow-create.md +83 -109
- package/skills/weaver-e10-workflow/references/workflow-memory.md +19 -9
- package/skills/weaver-e10-workflow/references/workflow-operate.md +2 -2
- package/skills/weaver-e10-workflow/references/workflow-query.md +40 -27
- package/skills/weaver-e10-workflow/references/workflow-share.md +18 -2
- package/skills/weaver-e10-workflow/references/workflow-view.md +3 -4
- package/skills/weaver-e10-yepiaotong/SKILL.md +74 -39
- package/skills/weaver-e10-yepiaotong/references/invoice-detail.md +22 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-enterprise-list.md +3 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-output-view.md +135 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-personal-list.md +15 -3
- package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +1 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-summarize.py +217 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-upload-and-validate.md +88 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-validation-preview.md +7 -2
- package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +89 -9
- package/skills/weaver-e10-yepiaotong/references/reim-examples.md +1 -0
- package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +70 -49
- package/skills/weaver-e10-yepiaotong/references/reim-fast-path.md +119 -0
- package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +55 -36
- package/skills/weaver-e10-yepiaotong/references/reim-matching-rules.md +4 -0
- package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +149 -0
- package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +125 -5
- package/skills/weaver-e10-yepiaotong/references/source-manifest.json +430 -63
- package/skills/weaver-e10-yimiaoban/SKILL.md +4 -12
- package/skills/weaver-e10-yimiaoban/references/source-manifest.json +3 -3
- package/skills/weaver-e10-ziguanjia/SKILL.md +2 -1
- package/skills/weaver-e10-ziguanjia/references/ziguanjia-agent-entry.md +3 -3
- package/tools/weaver-skill-detector-1.0.13/SKILL.md +9 -9
- package/tools/weaver-skill-detector-1.0.13/references/checklist.md +3 -3
- package/tools/weaver-skill-detector-1.0.13/scripts/__pycache__/validate_skill.cpython-313.pyc +0 -0
- package/tools/weaver-skill-detector-1.0.13/scripts/validate_skill.py +98 -91
- package/scripts/sync-connector-skills.mjs +0 -172
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { CliError, ExitCode } from '../../core/errors.js';
|
|
2
|
-
import { E10AuthSession } from './auth/session.js';
|
|
3
|
-
import { hasXiaoEEnv, hasXiaoETokenScript, tryXiaoEUnifiedLogin } from './auth/xiaoe.js';
|
|
2
|
+
import { E10AuthSession, getActiveProfile, readProfileConfig } from './auth/session.js';
|
|
3
|
+
import { createXiaoEUnifiedLoginUnavailableError, hasXiaoEEnv, hasXiaoETokenScript, resolveXiaoEPreferredBaseUrl, shouldUseXiaoEBrowserFallback, tryXiaoEUnifiedLogin, } from './auth/xiaoe.js';
|
|
4
4
|
import { E10ApiClient } from './client.js';
|
|
5
5
|
function shouldPreferXiaoEUnifiedLogin(options, env) {
|
|
6
6
|
return !options.auth && !options.profile && (hasXiaoEEnv(env) || hasXiaoETokenScript(env));
|
|
7
7
|
}
|
|
8
|
-
export async function getE10Session(ctx) {
|
|
8
|
+
export async function getE10Session(ctx, load = {}) {
|
|
9
9
|
const options = ctx.getGlobalOptions();
|
|
10
10
|
if (shouldPreferXiaoEUnifiedLogin(options, ctx.env)) {
|
|
11
|
+
const profileBaseUrl = readProfileConfig(getActiveProfile()).baseUrl;
|
|
11
12
|
const xiaoELogin = await tryXiaoEUnifiedLogin({
|
|
12
13
|
env: ctx.env,
|
|
13
14
|
baseUrl: options.baseUrl,
|
|
15
|
+
profileBaseUrl,
|
|
16
|
+
allowBundledEnvScript: true,
|
|
14
17
|
allowTokenScript: true,
|
|
15
18
|
logger: ctx.logger,
|
|
16
19
|
});
|
|
@@ -18,11 +21,20 @@ export async function getE10Session(ctx) {
|
|
|
18
21
|
return xiaoELogin.session;
|
|
19
22
|
return null;
|
|
20
23
|
}
|
|
21
|
-
return E10AuthSession.fromOptions(options, ctx.cwd);
|
|
24
|
+
return E10AuthSession.fromOptions(options, ctx.cwd, load);
|
|
22
25
|
}
|
|
23
|
-
export async function requireE10Session(ctx) {
|
|
24
|
-
const session = await getE10Session(ctx);
|
|
26
|
+
export async function requireE10Session(ctx, load = {}) {
|
|
27
|
+
const session = await getE10Session(ctx, load);
|
|
25
28
|
if (!session) {
|
|
29
|
+
const options = ctx.getGlobalOptions();
|
|
30
|
+
if ((hasXiaoEEnv(ctx.env) || hasXiaoETokenScript(ctx.env)) && !shouldUseXiaoEBrowserFallback()) {
|
|
31
|
+
const baseUrl = resolveXiaoEPreferredBaseUrl({
|
|
32
|
+
explicitBaseUrl: options.baseUrl,
|
|
33
|
+
profileBaseUrl: readProfileConfig(getActiveProfile()).baseUrl,
|
|
34
|
+
env: ctx.env,
|
|
35
|
+
});
|
|
36
|
+
throw createXiaoEUnifiedLoginUnavailableError(baseUrl);
|
|
37
|
+
}
|
|
26
38
|
throw new CliError('E10 auth not found. In XiaoE, run weaver-work-cli auth xiaoe first; if unified login is unavailable, ask the user for the E10 login domain (base URL), then run: weaver-work-cli auth login --base-url <url> --agent_type <agent_type>.', ExitCode.AuthError);
|
|
27
39
|
}
|
|
28
40
|
return session;
|
|
@@ -28,10 +28,12 @@ export class E10RequestRuntime {
|
|
|
28
28
|
session;
|
|
29
29
|
interceptors;
|
|
30
30
|
fetchFn;
|
|
31
|
+
attachSessionTicket;
|
|
31
32
|
constructor(ctx, session, dependencies = {}) {
|
|
32
33
|
this.ctx = ctx;
|
|
33
34
|
this.session = session;
|
|
34
35
|
this.fetchFn = dependencies.fetch || globalThis.fetch;
|
|
36
|
+
this.attachSessionTicket = dependencies.attachSessionTicket === true;
|
|
35
37
|
this.interceptors = [
|
|
36
38
|
...(this.ctx.hooks?.getE10RequestInterceptors?.() || []),
|
|
37
39
|
...(dependencies.interceptors || []),
|
|
@@ -131,8 +133,13 @@ export class E10RequestRuntime {
|
|
|
131
133
|
composeHeaders(request, trustedHeaders = {}) {
|
|
132
134
|
return {
|
|
133
135
|
Accept: 'application/json, text/plain, */*',
|
|
136
|
+
// E10 各端(含 eBuilder 表单运行态)依赖这两个头;缺失时 EB formdata 接口会直接返回
|
|
137
|
+
// 业务失败(CLI 侧表现为 business_error「泛微接口返回业务失败」,且响应无可读 message,
|
|
138
|
+
// 极易被误判为「EB 未配置/环境不对」)。standalone skill 的脚本一直带这两个头。
|
|
139
|
+
langType: 'zh_CN',
|
|
140
|
+
timeZoneOffset: String(new Date().getTimezoneOffset()),
|
|
134
141
|
...request.headers,
|
|
135
|
-
Cookie: this.session.cookieHeader(),
|
|
142
|
+
Cookie: this.attachSessionTicket ? this.session.cookieHeaderWithSessionTicket() : this.session.cookieHeader(),
|
|
136
143
|
eteamsid: this.session.eteamsId,
|
|
137
144
|
'User-Agent': this.session.uaHeader(),
|
|
138
145
|
Origin: this.session.getBaseUrl(),
|
|
@@ -1,13 +1,153 @@
|
|
|
1
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync,
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
|
-
import { basename, relative, resolve, sep } from 'node:path';
|
|
3
|
+
import { basename, posix, relative, resolve, sep, win32 } from 'node:path';
|
|
4
4
|
const SKILL_PREFIX = 'weaver-e10-';
|
|
5
5
|
const LEGACY_SKILL_PREFIX = 'weaver-work-cli-';
|
|
6
6
|
const SHARED_SKILL_NAME = 'weaver-e10-shared';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
function envValue(env, name) {
|
|
8
|
+
const direct = env[name];
|
|
9
|
+
if (typeof direct === 'string' && direct.trim())
|
|
10
|
+
return direct.trim();
|
|
11
|
+
const expected = name.toUpperCase();
|
|
12
|
+
for (const [key, value] of Object.entries(env)) {
|
|
13
|
+
if (key.toUpperCase() === expected && typeof value === 'string' && value.trim())
|
|
14
|
+
return value.trim();
|
|
15
|
+
}
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
function hasEnvFamily(env, prefix, ignoredNames = []) {
|
|
19
|
+
const expected = `${prefix.toUpperCase()}_`;
|
|
20
|
+
const ignored = new Set(ignoredNames.map((name) => name.toUpperCase()));
|
|
21
|
+
return Object.entries(env).some(([key, value]) => (key.toUpperCase().startsWith(expected)
|
|
22
|
+
&& !ignored.has(key.toUpperCase())
|
|
23
|
+
&& typeof value === 'string'
|
|
24
|
+
&& Boolean(value.trim())));
|
|
25
|
+
}
|
|
26
|
+
function normalizedAgentType(value) {
|
|
27
|
+
const normalized = value.trim().toLowerCase();
|
|
28
|
+
if (normalized === 'codex-cli')
|
|
29
|
+
return 'codex';
|
|
30
|
+
if (normalized === 'work-buddy')
|
|
31
|
+
return 'workbuddy';
|
|
32
|
+
return normalized || 'unknown';
|
|
33
|
+
}
|
|
34
|
+
function isKnownSkillsHost(host) {
|
|
35
|
+
return host === 'codex' || host === 'workbuddy';
|
|
36
|
+
}
|
|
37
|
+
export function resolveSkillsTargetPath(value, options = {}) {
|
|
38
|
+
const trimmed = value.trim();
|
|
39
|
+
if (!trimmed)
|
|
40
|
+
throw new Error('skills target path must not be empty');
|
|
41
|
+
const platform = options.platform ?? process.platform;
|
|
42
|
+
const pathApi = platform === 'win32' ? win32 : posix;
|
|
43
|
+
const userHome = options.homeDir ?? homedir();
|
|
44
|
+
if (trimmed === '~')
|
|
45
|
+
return pathApi.resolve(userHome);
|
|
46
|
+
if (/^~[\\/]/u.test(trimmed)) {
|
|
47
|
+
const segments = trimmed.slice(2).split(/[\\/]+/u).filter(Boolean);
|
|
48
|
+
return pathApi.resolve(userHome, ...segments);
|
|
49
|
+
}
|
|
50
|
+
return pathApi.resolve(trimmed);
|
|
51
|
+
}
|
|
52
|
+
function hostSkillsRoot(host, env, options) {
|
|
53
|
+
const platform = options.platform ?? process.platform;
|
|
54
|
+
const pathApi = platform === 'win32' ? win32 : posix;
|
|
55
|
+
const userHome = options.homeDir ?? homedir();
|
|
56
|
+
const envName = host === 'codex' ? 'CODEX_HOME' : 'WORKBUDDY_HOME';
|
|
57
|
+
const configuredHome = envValue(env, envName);
|
|
58
|
+
const hostHome = configuredHome
|
|
59
|
+
? resolveSkillsTargetPath(configuredHome, options)
|
|
60
|
+
: pathApi.resolve(userHome, host === 'codex' ? '.codex' : '.workbuddy');
|
|
61
|
+
return pathApi.resolve(hostHome, 'skills');
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 根据当前进程的环境信号探测宿主。目录存在只代表曾安装过某个宿主,
|
|
65
|
+
* 不能证明当前命令由该宿主调用,因此不在这里参与判定。
|
|
66
|
+
*
|
|
67
|
+
* WEAVER_AGENT_TYPE 是跨宿主的显式信号;没有它时,当前进程的运行时变量优先于
|
|
68
|
+
* 可能被用户长期配置的 *_HOME。两个宿主仍然同时有信号时保持 unknown。
|
|
69
|
+
*/
|
|
70
|
+
export function detectSkillsHost(env = process.env) {
|
|
71
|
+
const explicitAgent = envValue(env, 'WEAVER_AGENT_TYPE');
|
|
72
|
+
if (explicitAgent)
|
|
73
|
+
return normalizedAgentType(explicitAgent);
|
|
74
|
+
const hasCodexRuntime = hasEnvFamily(env, 'CODEX', ['CODEX_HOME']);
|
|
75
|
+
const hasWorkBuddyRuntime = hasEnvFamily(env, 'WORKBUDDY', ['WORKBUDDY_HOME']);
|
|
76
|
+
if (hasCodexRuntime !== hasWorkBuddyRuntime)
|
|
77
|
+
return hasCodexRuntime ? 'codex' : 'workbuddy';
|
|
78
|
+
const hasCodexHome = Boolean(envValue(env, 'CODEX_HOME'));
|
|
79
|
+
const hasWorkBuddyHome = Boolean(envValue(env, 'WORKBUDDY_HOME'));
|
|
80
|
+
if (hasCodexHome !== hasWorkBuddyHome)
|
|
81
|
+
return hasCodexHome ? 'codex' : 'workbuddy';
|
|
82
|
+
return 'unknown';
|
|
83
|
+
}
|
|
84
|
+
export function resolveSkillsRoot(env = process.env, options = {}) {
|
|
85
|
+
const configuredRoot = envValue(env, 'WEAVER_SKILLS_ROOT');
|
|
86
|
+
if (configuredRoot) {
|
|
87
|
+
const explicitAgent = envValue(env, 'WEAVER_AGENT_TYPE');
|
|
88
|
+
const detectedHost = explicitAgent ? normalizedAgentType(explicitAgent) : detectSkillsHost(env);
|
|
89
|
+
return {
|
|
90
|
+
root: resolveSkillsTargetPath(configuredRoot, options),
|
|
91
|
+
host: detectedHost === 'unknown' ? 'explicit' : detectedHost,
|
|
92
|
+
source: 'explicit-env',
|
|
93
|
+
environmentVariable: 'WEAVER_SKILLS_ROOT',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const host = detectSkillsHost(env);
|
|
97
|
+
if (isKnownSkillsHost(host)) {
|
|
98
|
+
const environmentVariable = envValue(env, host === 'codex' ? 'CODEX_HOME' : 'WORKBUDDY_HOME')
|
|
99
|
+
? (host === 'codex' ? 'CODEX_HOME' : 'WORKBUDDY_HOME')
|
|
100
|
+
: undefined;
|
|
101
|
+
return {
|
|
102
|
+
root: hostSkillsRoot(host, env, options),
|
|
103
|
+
host,
|
|
104
|
+
source: environmentVariable ? 'explicit-env' : 'host-detected',
|
|
105
|
+
...(environmentVariable ? { environmentVariable } : {}),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const codexRoot = hostSkillsRoot('codex', env, options);
|
|
109
|
+
const workBuddyRoot = hostSkillsRoot('workbuddy', env, options);
|
|
110
|
+
const pathExists = options.pathExists ?? existsSync;
|
|
111
|
+
if (host === 'unknown') {
|
|
112
|
+
const existing = [
|
|
113
|
+
{ root: codexRoot, host: 'codex' },
|
|
114
|
+
{ root: workBuddyRoot, host: 'workbuddy' },
|
|
115
|
+
].filter((candidate) => pathExists(candidate.root));
|
|
116
|
+
if (existing.length === 1) {
|
|
117
|
+
return { ...existing[0], source: 'filesystem-detected' };
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
root: codexRoot,
|
|
121
|
+
host,
|
|
122
|
+
source: 'fallback',
|
|
123
|
+
requiresExplicitTarget: true,
|
|
124
|
+
reason: existing.length > 1 ? 'ambiguous-host' : 'unknown-host',
|
|
125
|
+
candidates: [codexRoot, workBuddyRoot],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
root: codexRoot,
|
|
130
|
+
host,
|
|
131
|
+
source: 'fallback',
|
|
132
|
+
requiresExplicitTarget: true,
|
|
133
|
+
reason: 'unsupported-host',
|
|
134
|
+
candidates: [codexRoot, workBuddyRoot],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export function defaultCodexSkillsRoot(env = process.env, options = {}) {
|
|
138
|
+
return hostSkillsRoot('codex', env, options);
|
|
139
|
+
}
|
|
140
|
+
export function unresolvedSkillsRootMessage(resolution, targetOption = '--target-dir') {
|
|
141
|
+
const reason = resolution.reason === 'ambiguous-host'
|
|
142
|
+
? '检测到多个可能的宿主目录'
|
|
143
|
+
: resolution.reason === 'unsupported-host'
|
|
144
|
+
? `检测到当前智能体 ${resolution.host},但不知道它的 skills 目录`
|
|
145
|
+
: '未检测到当前智能体及其 skills 目录';
|
|
146
|
+
return [
|
|
147
|
+
`${reason},为避免装错位置,本次未执行安装。`,
|
|
148
|
+
`请使用 ${targetOption} <当前智能体的 skills 根目录>,或设置 WEAVER_SKILLS_ROOT 后重试。`,
|
|
149
|
+
...(resolution.candidates?.length ? [`已知候选目录:${resolution.candidates.join(',')}`] : []),
|
|
150
|
+
].join('\n');
|
|
11
151
|
}
|
|
12
152
|
export function normalizePackagedSkillName(value) {
|
|
13
153
|
const trimmed = value.trim();
|
|
@@ -65,7 +205,7 @@ export function installPackagedSkills(sourceRoot, targetRoot, requested = 'all')
|
|
|
65
205
|
const skillNames = resolvePackagedSkillNames(resolvedSourceRoot, requested);
|
|
66
206
|
const productContent = productMetadataContent(resolvedSourceRoot);
|
|
67
207
|
mkdirSync(resolvedTargetRoot, { recursive: true, mode: 0o700 });
|
|
68
|
-
const installed = skillNames.
|
|
208
|
+
const installed = skillNames.map((name) => {
|
|
69
209
|
const source = resolveInside(resolvedSourceRoot, name);
|
|
70
210
|
const target = resolve(resolvedTargetRoot, basename(name));
|
|
71
211
|
statSync(resolve(source, 'SKILL.md'));
|
|
@@ -81,23 +221,7 @@ export function installPackagedSkills(sourceRoot, targetRoot, requested = 'all')
|
|
|
81
221
|
target,
|
|
82
222
|
files: countFiles(target),
|
|
83
223
|
};
|
|
84
|
-
|
|
85
|
-
return [current];
|
|
86
|
-
const legacyTarget = resolve(resolvedTargetRoot, LEGACY_SHARED_SKILL_NAME);
|
|
87
|
-
rmSync(legacyTarget, { recursive: true, force: true });
|
|
88
|
-
cpSync(source, legacyTarget, {
|
|
89
|
-
recursive: true,
|
|
90
|
-
force: true,
|
|
91
|
-
filter: (entry) => shouldCopy(entry, source, Boolean(productContent)),
|
|
92
|
-
});
|
|
93
|
-
rewriteSkillName(resolve(legacyTarget, 'SKILL.md'), LEGACY_SHARED_SKILL_NAME);
|
|
94
|
-
writeProductMetadata(legacyTarget, productContent);
|
|
95
|
-
return [current, {
|
|
96
|
-
name: LEGACY_SHARED_SKILL_NAME,
|
|
97
|
-
source,
|
|
98
|
-
target: legacyTarget,
|
|
99
|
-
files: countFiles(legacyTarget),
|
|
100
|
-
}];
|
|
224
|
+
return current;
|
|
101
225
|
});
|
|
102
226
|
return {
|
|
103
227
|
sourceRoot: resolvedSourceRoot,
|
|
@@ -106,10 +230,6 @@ export function installPackagedSkills(sourceRoot, targetRoot, requested = 'all')
|
|
|
106
230
|
installed,
|
|
107
231
|
};
|
|
108
232
|
}
|
|
109
|
-
function rewriteSkillName(skillPath, name) {
|
|
110
|
-
const content = readFileSync(skillPath, 'utf8');
|
|
111
|
-
writeFileSync(skillPath, content.replace(/^name:\s*weaver-e10-shared\s*$/mu, `name: ${name}`), 'utf8');
|
|
112
|
-
}
|
|
113
233
|
function productMetadataContent(sourceRoot) {
|
|
114
234
|
const productPath = productMetadataPath(sourceRoot);
|
|
115
235
|
if (!existsSync(productPath))
|
|
@@ -16,13 +16,14 @@ export class SkillReader {
|
|
|
16
16
|
}
|
|
17
17
|
listPath(target) {
|
|
18
18
|
const { name, subpath } = splitTarget(target);
|
|
19
|
-
const
|
|
19
|
+
const skill = this.resolveSkill(name);
|
|
20
|
+
const skillRoot = skill.root;
|
|
20
21
|
const dir = this.resolveInside(skillRoot, subpath);
|
|
21
22
|
const info = statSync(dir);
|
|
22
23
|
if (!info.isDirectory()) {
|
|
23
24
|
throw new Error(`path is a file, not a directory: ${target}`);
|
|
24
25
|
}
|
|
25
|
-
const listed = cleanOutputPath(name, subpath);
|
|
26
|
+
const listed = cleanOutputPath(skill.name, subpath);
|
|
26
27
|
const entries = readdirSync(dir, { withFileTypes: true })
|
|
27
28
|
.map((entry) => ({
|
|
28
29
|
path: cleanOutputPath(listed, entry.name),
|
|
@@ -33,16 +34,15 @@ export class SkillReader {
|
|
|
33
34
|
}
|
|
34
35
|
read(target, pathArg = '') {
|
|
35
36
|
const split = splitTarget(target);
|
|
36
|
-
const
|
|
37
|
+
const skill = this.resolveSkill(split.name);
|
|
37
38
|
const subpath = pathArg || split.subpath || 'SKILL.md';
|
|
38
|
-
const
|
|
39
|
-
const file = this.resolveInside(skillRoot, subpath);
|
|
39
|
+
const file = this.resolveInside(skill.root, subpath);
|
|
40
40
|
const info = statSync(file);
|
|
41
41
|
if (info.isDirectory()) {
|
|
42
|
-
throw new Error(`path is a directory, not a file: ${cleanOutputPath(name, subpath)}`);
|
|
42
|
+
throw new Error(`path is a directory, not a file: ${cleanOutputPath(skill.name, subpath)}`);
|
|
43
43
|
}
|
|
44
44
|
return {
|
|
45
|
-
skill: name,
|
|
45
|
+
skill: skill.name,
|
|
46
46
|
path: cleanOutputPath(subpath),
|
|
47
47
|
content: readFileSync(file, 'utf-8'),
|
|
48
48
|
};
|
|
@@ -64,6 +64,9 @@ export class SkillReader {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
ensureSkill(name) {
|
|
67
|
+
return this.resolveSkill(name).root;
|
|
68
|
+
}
|
|
69
|
+
resolveSkill(name) {
|
|
67
70
|
if (!name || name.includes('/') || name.includes('\\') || name === '.' || name === '..') {
|
|
68
71
|
throw new Error(`unknown skill: ${name}`);
|
|
69
72
|
}
|
|
@@ -73,14 +76,14 @@ export class SkillReader {
|
|
|
73
76
|
if (normalized !== name) {
|
|
74
77
|
const normalizedRoot = this.resolveInside(this.root, normalized);
|
|
75
78
|
if (existsDirectoryWithSkill(normalizedRoot))
|
|
76
|
-
return normalizedRoot;
|
|
79
|
+
return { name: normalized, root: normalizedRoot };
|
|
77
80
|
}
|
|
78
81
|
}
|
|
79
82
|
const info = statSync(skillRoot);
|
|
80
83
|
if (!info.isDirectory())
|
|
81
84
|
throw new Error(`unknown skill: ${name}`);
|
|
82
85
|
statSync(resolve(skillRoot, 'SKILL.md'));
|
|
83
|
-
return skillRoot;
|
|
86
|
+
return { name, root: skillRoot };
|
|
84
87
|
}
|
|
85
88
|
resolveInside(base, subpath = '') {
|
|
86
89
|
const cleaned = subpath.replace(/\\/g, '/');
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { resolve } from 'node:path';
|
|
6
|
+
import { blogValidationError } from './errors.js';
|
|
7
|
+
function stateRoot(ctx) {
|
|
8
|
+
const configured = ctx?.config?.path();
|
|
9
|
+
if (configured)
|
|
10
|
+
return resolve(configured, '..');
|
|
11
|
+
return process.env.WEAVER_WORK_CLI_HOME || process.env.WORK_CLI_HOME || resolve(homedir(), '.weaver-work-cli');
|
|
12
|
+
}
|
|
13
|
+
async function loadKey(ctx) {
|
|
14
|
+
const keysDir = resolve(stateRoot(ctx), 'keys');
|
|
15
|
+
const filename = resolve(keysDir, 'blog-continuation.key');
|
|
16
|
+
await mkdir(keysDir, { recursive: true, mode: 0o700 });
|
|
17
|
+
if (existsSync(filename))
|
|
18
|
+
return readFile(filename);
|
|
19
|
+
const key = randomBytes(32);
|
|
20
|
+
try {
|
|
21
|
+
await writeFile(filename, key, { flag: 'wx', mode: 0o600 });
|
|
22
|
+
if (process.platform !== 'win32')
|
|
23
|
+
await chmod(filename, 0o600).catch(() => undefined);
|
|
24
|
+
return key;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return readFile(filename);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function encode(value) {
|
|
31
|
+
return Buffer.from(JSON.stringify(value)).toString('base64url');
|
|
32
|
+
}
|
|
33
|
+
function signature(payload, key) {
|
|
34
|
+
return createHmac('sha256', key).update(payload).digest('base64url');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* prepare -> apply 确认链的 signed continuation(HMAC-SHA256,默认 10 分钟有效)。
|
|
38
|
+
* 密钥持久化在 `<stateRoot>/keys/blog-continuation.key`,保证多次 CLI 进程调用可校验。
|
|
39
|
+
*/
|
|
40
|
+
export function createBlogContinuationSigner(ctx) {
|
|
41
|
+
return {
|
|
42
|
+
async issue(payload, ttlMs = 10 * 60_000) {
|
|
43
|
+
const issuedAt = Date.now();
|
|
44
|
+
const body = encode({ ...payload, issuedAt, expiresAt: issuedAt + ttlMs });
|
|
45
|
+
const sig = signature(body, await loadKey(ctx));
|
|
46
|
+
return `${body}.${sig}`;
|
|
47
|
+
},
|
|
48
|
+
async verify(token, expectedOperation) {
|
|
49
|
+
if (typeof token !== 'string' || !token.includes('.')) {
|
|
50
|
+
throw blogValidationError('continuation_invalid', 'continuation 无效');
|
|
51
|
+
}
|
|
52
|
+
const [body, provided] = token.split('.');
|
|
53
|
+
const expected = signature(body, await loadKey(ctx));
|
|
54
|
+
const valid = provided.length === expected.length
|
|
55
|
+
&& timingSafeEqual(Buffer.from(provided), Buffer.from(expected));
|
|
56
|
+
if (!valid)
|
|
57
|
+
throw blogValidationError('continuation_invalid', 'continuation 签名校验失败');
|
|
58
|
+
let value;
|
|
59
|
+
try {
|
|
60
|
+
value = JSON.parse(Buffer.from(body, 'base64url').toString('utf8'));
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
throw blogValidationError('continuation_invalid', 'continuation 内容无法解析');
|
|
64
|
+
}
|
|
65
|
+
if (value.operation !== expectedOperation) {
|
|
66
|
+
throw blogValidationError('continuation_mismatch', 'continuation 不属于当前操作');
|
|
67
|
+
}
|
|
68
|
+
if (!Number.isSafeInteger(value.expiresAt) || value.expiresAt < Date.now()) {
|
|
69
|
+
throw blogValidationError('continuation_expired', 'continuation 已过期');
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { CliError, ExitCode } from '../../core/errors.js';
|
|
2
|
+
/** 日报模块结构化错误:type/subtype 驱动 Agent 决策,exitCode 决定进程退出码。 */
|
|
3
|
+
export class BlogCliError extends CliError {
|
|
4
|
+
type;
|
|
5
|
+
subtype;
|
|
6
|
+
code;
|
|
7
|
+
retryable;
|
|
8
|
+
stage;
|
|
9
|
+
details;
|
|
10
|
+
partialData;
|
|
11
|
+
constructor(type, subtype, message, options = {}) {
|
|
12
|
+
super(message, options.exitCode ?? ExitCode.InternalError);
|
|
13
|
+
this.name = 'BlogCliError';
|
|
14
|
+
this.type = type;
|
|
15
|
+
this.subtype = subtype;
|
|
16
|
+
this.code = options.code;
|
|
17
|
+
this.retryable = options.retryable ?? false;
|
|
18
|
+
this.stage = options.stage;
|
|
19
|
+
this.details = options.details;
|
|
20
|
+
this.partialData = options.partialData;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function blogValidationError(subtype, message, details) {
|
|
24
|
+
return new BlogCliError('validation', subtype, message, {
|
|
25
|
+
details,
|
|
26
|
+
exitCode: ExitCode.UsageError,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export function blogAuthError(subtype, message, details) {
|
|
30
|
+
return new BlogCliError('authentication', subtype, message, {
|
|
31
|
+
details,
|
|
32
|
+
exitCode: ExitCode.AuthError,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export function blogNetworkError(subtype, message, options = {}) {
|
|
36
|
+
return new BlogCliError('network', subtype, message, {
|
|
37
|
+
...options,
|
|
38
|
+
retryable: options.retryable ?? true,
|
|
39
|
+
exitCode: ExitCode.NetworkError,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export function blogConfirmationError(action, details) {
|
|
43
|
+
return new BlogCliError('confirmation', 'required', '该操作需要明确确认', {
|
|
44
|
+
details: { action, ...details },
|
|
45
|
+
exitCode: ExitCode.ConfirmationRequired,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export function blogPartialError(message, partialData, details) {
|
|
49
|
+
return new BlogCliError('partial', 'write_uncertain', message, {
|
|
50
|
+
details,
|
|
51
|
+
partialData,
|
|
52
|
+
exitCode: ExitCode.Partial,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
export function isBlogNetworkError(error) {
|
|
56
|
+
return Boolean(error
|
|
57
|
+
&& typeof error === 'object'
|
|
58
|
+
&& 'type' in error
|
|
59
|
+
&& error.type === 'network');
|
|
60
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { ExitCode } from '../../core/errors.js';
|
|
2
|
+
import { requireE10Session } from '../../internal/e10/context.js';
|
|
3
|
+
import { getActiveProfile } from '../../internal/e10/auth/session.js';
|
|
4
|
+
import { createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
|
|
5
|
+
import { BlogCliError, blogAuthError, blogNetworkError, blogValidationError, } from './errors.js';
|
|
6
|
+
const MAX_EXPORT_BYTES = 50 * 1024 * 1024;
|
|
7
|
+
/** E10 登录失效在业务响应体里的 code(实测 -2 既可能是数字也可能是字符串) */
|
|
8
|
+
const SESSION_EXPIRED_CODES = new Set(['-2', -2]);
|
|
9
|
+
function assertPath(requestPath) {
|
|
10
|
+
if (typeof requestPath !== 'string' || !requestPath.startsWith('/') || requestPath.startsWith('//')) {
|
|
11
|
+
throw blogValidationError('path_invalid', '请求路径必须是相对绝对路径');
|
|
12
|
+
}
|
|
13
|
+
if (/^https?:/i.test(requestPath) || /[\r\n]/.test(requestPath) || requestPath.includes('/../')) {
|
|
14
|
+
throw blogValidationError('path_invalid', '请求路径不允许跨域或路径逃逸');
|
|
15
|
+
}
|
|
16
|
+
if (!requestPath.startsWith('/api/')) {
|
|
17
|
+
throw blogValidationError('path_not_allowed', '日报模块只能调用 /api/ 路径');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** 业务码位置在三种响应形态间漂移:顶层 code / res_base.ret / data.res_base.ret */
|
|
21
|
+
function responseCode(value) {
|
|
22
|
+
return value?.code ?? value?.res_base?.ret ?? value?.data?.res_base?.ret;
|
|
23
|
+
}
|
|
24
|
+
function responseMessage(value) {
|
|
25
|
+
if (typeof value?.msg === 'string')
|
|
26
|
+
return value.msg;
|
|
27
|
+
if (typeof value?.message === 'string')
|
|
28
|
+
return value.message;
|
|
29
|
+
if (typeof value?.error === 'string')
|
|
30
|
+
return value.error;
|
|
31
|
+
return '泛微接口返回业务失败';
|
|
32
|
+
}
|
|
33
|
+
function isSessionExpired(value) {
|
|
34
|
+
const code = responseCode(value);
|
|
35
|
+
return code !== undefined && SESSION_EXPIRED_CODES.has(code);
|
|
36
|
+
}
|
|
37
|
+
/** 业务失败判定:fail === true,或 code 存在且不在 {0, 200} 与登录失效码之内 */
|
|
38
|
+
function isBusinessFailure(value) {
|
|
39
|
+
if (value?.fail === true)
|
|
40
|
+
return true;
|
|
41
|
+
const code = responseCode(value);
|
|
42
|
+
if (code === undefined || code === null)
|
|
43
|
+
return false;
|
|
44
|
+
return !SESSION_EXPIRED_CODES.has(code)
|
|
45
|
+
&& code !== 0 && code !== '0' && code !== 200 && code !== '200';
|
|
46
|
+
}
|
|
47
|
+
function inferProfile(ctx) {
|
|
48
|
+
const options = ctx.getGlobalOptions();
|
|
49
|
+
return options.profile || getActiveProfile();
|
|
50
|
+
}
|
|
51
|
+
export function createBlogHost(ctx, dependencies = {}) {
|
|
52
|
+
let e10Session = null;
|
|
53
|
+
let sessionContext = null;
|
|
54
|
+
const requireSession = dependencies.requireSession || requireE10Session;
|
|
55
|
+
async function ensureSession() {
|
|
56
|
+
if (!e10Session) {
|
|
57
|
+
e10Session = await requireSession(ctx);
|
|
58
|
+
sessionContext = {
|
|
59
|
+
baseUrl: e10Session.getBaseUrl(),
|
|
60
|
+
profile: inferProfile(ctx),
|
|
61
|
+
userId: e10Session.userId,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return e10Session;
|
|
65
|
+
}
|
|
66
|
+
async function getSession() {
|
|
67
|
+
await ensureSession();
|
|
68
|
+
return sessionContext;
|
|
69
|
+
}
|
|
70
|
+
async function runtime() {
|
|
71
|
+
return createE10RequestRuntime(ctx, await ensureSession(), {
|
|
72
|
+
interceptors: dependencies.interceptors,
|
|
73
|
+
fetch: dependencies.fetch,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function assertSession(value) {
|
|
77
|
+
if (isSessionExpired(value)) {
|
|
78
|
+
throw blogAuthError('session_expired', 'E10 登录态已失效,请先向用户确认 E10 登录域名,再运行 weaver-work-cli auth login --base-url <域名> --agent_type <当前智能体名称> 完成登录');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function blogRequest(request) {
|
|
82
|
+
const { method = 'POST', path, query, body, operation, timeoutMs = 30_000 } = request;
|
|
83
|
+
assertPath(path);
|
|
84
|
+
const response = await (await runtime()).requestJson({
|
|
85
|
+
module: 'blog',
|
|
86
|
+
operation,
|
|
87
|
+
method,
|
|
88
|
+
path,
|
|
89
|
+
query: query,
|
|
90
|
+
body,
|
|
91
|
+
timeoutMs,
|
|
92
|
+
});
|
|
93
|
+
if (response.status === 401 || response.status === 302) {
|
|
94
|
+
throw blogAuthError('session_expired', 'E10 登录态已失效,请先向用户确认 E10 登录域名,再运行 weaver-work-cli auth login --base-url <域名> --agent_type <当前智能体名称> 完成登录', { httpStatus: response.status });
|
|
95
|
+
}
|
|
96
|
+
if (response.parseError) {
|
|
97
|
+
if (response.status < 200 || response.status >= 300) {
|
|
98
|
+
throw new BlogCliError('api', 'http_error', `泛微接口 HTTP ${response.status}`, {
|
|
99
|
+
code: response.status,
|
|
100
|
+
details: { status: response.status },
|
|
101
|
+
exitCode: ExitCode.GeneralError,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
throw blogNetworkError('protocol', `接口返回不是合法 JSON(HTTP ${response.status})`, { retryable: false });
|
|
105
|
+
}
|
|
106
|
+
if (response.status < 200 || response.status >= 300) {
|
|
107
|
+
throw new BlogCliError('api', 'http_error', `泛微接口 HTTP ${response.status}`, {
|
|
108
|
+
code: response.status,
|
|
109
|
+
details: { status: response.status, message: responseMessage(response.value) },
|
|
110
|
+
exitCode: ExitCode.GeneralError,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
assertSession(response.value);
|
|
114
|
+
if (isBusinessFailure(response.value)) {
|
|
115
|
+
throw new BlogCliError('api', 'business_error', responseMessage(response.value), {
|
|
116
|
+
code: String(responseCode(response.value)),
|
|
117
|
+
details: { response: response.value },
|
|
118
|
+
exitCode: ExitCode.GeneralError,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return response.value;
|
|
122
|
+
}
|
|
123
|
+
async function blogBinaryRequest(request) {
|
|
124
|
+
const { method = 'POST', path, query, body, operation, timeoutMs = 60_000 } = request;
|
|
125
|
+
assertPath(path);
|
|
126
|
+
const response = await (await runtime()).requestBinary({
|
|
127
|
+
module: 'blog',
|
|
128
|
+
operation,
|
|
129
|
+
method,
|
|
130
|
+
path,
|
|
131
|
+
query: query,
|
|
132
|
+
body,
|
|
133
|
+
timeoutMs,
|
|
134
|
+
headers: { Accept: 'application/octet-stream, */*' },
|
|
135
|
+
});
|
|
136
|
+
if (response.status === 401 || response.status === 302) {
|
|
137
|
+
throw blogAuthError('session_expired', 'E10 登录态已失效,请先向用户确认 E10 登录域名,再运行 weaver-work-cli auth login --base-url <域名> --agent_type <当前智能体名称> 完成登录', { httpStatus: response.status });
|
|
138
|
+
}
|
|
139
|
+
const contentType = response.contentType;
|
|
140
|
+
const declaredLength = Number(response.headers.get('content-length'));
|
|
141
|
+
if (Number.isFinite(declaredLength) && declaredLength > MAX_EXPORT_BYTES) {
|
|
142
|
+
throw blogNetworkError('response_too_large', `导出文件超过 ${MAX_EXPORT_BYTES} 字节限制`, { retryable: false });
|
|
143
|
+
}
|
|
144
|
+
const content = response.content;
|
|
145
|
+
const looksLikeJson = /^[\s\r\n]*[[{]/.test(content.subarray(0, 64).toString('utf8'));
|
|
146
|
+
// 导出接口出错时返回 JSON(登录失效 / 业务失败),需在落盘前识别
|
|
147
|
+
if (response.status < 200 || response.status >= 300 || contentType.includes('json') || looksLikeJson) {
|
|
148
|
+
let value = {};
|
|
149
|
+
try {
|
|
150
|
+
value = JSON.parse(content.toString('utf8'));
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
value = {};
|
|
154
|
+
}
|
|
155
|
+
assertSession(value);
|
|
156
|
+
throw new BlogCliError('api', 'export_failed', responseMessage(value) || `导出失败(HTTP ${response.status})`, {
|
|
157
|
+
code: response.status,
|
|
158
|
+
details: { status: response.status, preview: content.subarray(0, 500).toString('utf8') },
|
|
159
|
+
exitCode: ExitCode.GeneralError,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
if (!content.length) {
|
|
163
|
+
throw blogNetworkError('protocol', '导出接口返回空文件', { retryable: false });
|
|
164
|
+
}
|
|
165
|
+
if (content.length > MAX_EXPORT_BYTES) {
|
|
166
|
+
throw blogNetworkError('response_too_large', `导出文件超过 ${MAX_EXPORT_BYTES} 字节限制`, { retryable: false });
|
|
167
|
+
}
|
|
168
|
+
return { content, contentType };
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
blogRequest,
|
|
172
|
+
blogBinaryRequest,
|
|
173
|
+
getSession,
|
|
174
|
+
};
|
|
175
|
+
}
|