aitoearn-dsh-plugin-test 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT.md +39 -0
- package/README.md +46 -0
- package/cordis.patch.yml +3 -0
- package/lib/client.js +453 -0
- package/lib/client.js.map +7 -0
- package/package.json +138 -0
- package/presets/aitoearn/agent.cordis.yml +22 -0
- package/presets/aitoearn/lib/mcp.js +150 -0
- package/presets/aitoearn/lib/mcp.js.map +7 -0
- package/presets/aitoearn/lib/persona.js +21 -0
- package/presets/aitoearn/lib/persona.js.map +7 -0
- package/presets/aitoearn/package.json +22 -0
- package/presets/aitoearn/preset.yml +3 -0
- package/skills/browser-operations/SKILL.md +85 -0
- package/skills/channel-publish/SKILL.md +99 -0
- package/skills/image-creation/SKILL.md +136 -0
- package/skills/image-creation/references/prompting.md +103 -0
- package/skills/image-creation/references/sample-prompts.md +310 -0
- package/skills/image-edit/SKILL.md +95 -0
- package/skills/video-generation/SKILL.md +61 -0
- package/skills/video-generation/references/model-comparison.md +76 -0
- package/skills/video-generation/references/seedance-2-0-guide.md +1050 -0
- package/skills/video-generation/references/seedance-2-5-guide.md +997 -0
- package/src/dsh/auth.d.ts +25 -0
- package/src/dsh/auth.js +60 -0
- package/src/dsh/auth.js.map +1 -0
- package/src/dsh/client/AitoearnCard.d.ts +15 -0
- package/src/dsh/client/AitoearnCard.js +50 -0
- package/src/dsh/client/AitoearnCard.js.map +1 -0
- package/src/dsh/client/card-store.d.ts +34 -0
- package/src/dsh/client/card-store.js +130 -0
- package/src/dsh/client/card-store.js.map +1 -0
- package/src/dsh/client/card.css.d.ts +31 -0
- package/src/dsh/client/card.css.js +80 -0
- package/src/dsh/client/card.css.js.map +1 -0
- package/src/dsh/client/i18n.d.ts +55 -0
- package/src/dsh/client/i18n.js +49 -0
- package/src/dsh/client/i18n.js.map +1 -0
- package/src/dsh/client/index.d.ts +4 -0
- package/src/dsh/client/index.js +21 -0
- package/src/dsh/client/index.js.map +1 -0
- package/src/dsh/index.d.ts +8 -0
- package/src/dsh/index.js +20 -0
- package/src/dsh/index.js.map +1 -0
- package/src/dsh/mcp.d.ts +5 -0
- package/src/dsh/mcp.js +54 -0
- package/src/dsh/mcp.js.map +1 -0
- package/src/dsh/paths.d.ts +6 -0
- package/src/dsh/paths.js +22 -0
- package/src/dsh/paths.js.map +1 -0
- package/src/dsh/persona.d.ts +4 -0
- package/src/dsh/persona.js +19 -0
- package/src/dsh/persona.js.map +1 -0
- package/src/dsh/preset-sync.d.ts +1 -0
- package/src/dsh/preset-sync.js +55 -0
- package/src/dsh/preset-sync.js.map +1 -0
- package/src/dsh/settings-schema.d.ts +13 -0
- package/src/dsh/settings-schema.js +15 -0
- package/src/dsh/settings-schema.js.map +1 -0
- package/src/dsh/tool-name.d.ts +9 -0
- package/src/dsh/tool-name.js +32 -0
- package/src/dsh/tool-name.js.map +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aitoearn-dsh-plugin-test",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"description": "[TEST] Aitoearn agent preset, skills, and MCP tools for DeepSeek Harness",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./src/dsh/index.d.ts",
|
|
9
|
+
"default": "./src/dsh/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./client": {
|
|
12
|
+
"types": "./src/dsh/client/index.d.ts",
|
|
13
|
+
"default": "./lib/client.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"main": "./src/dsh/index.js",
|
|
18
|
+
"types": "./src/dsh/index.d.ts",
|
|
19
|
+
"files": [
|
|
20
|
+
"AGENT.md",
|
|
21
|
+
"cordis.patch.yml",
|
|
22
|
+
"lib",
|
|
23
|
+
"presets",
|
|
24
|
+
"skills",
|
|
25
|
+
"src"
|
|
26
|
+
],
|
|
27
|
+
"dsh": {
|
|
28
|
+
"bundle": {
|
|
29
|
+
"patch": "./cordis.patch.yml"
|
|
30
|
+
},
|
|
31
|
+
"client": {
|
|
32
|
+
"platform": "web",
|
|
33
|
+
"inject": [
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
40
|
+
"@deepseek-ai/dsh-attachment": "0.1.5-rc.2",
|
|
41
|
+
"@deepseek-ai/dsh-client-locale": "0.1.5-rc.2",
|
|
42
|
+
"@deepseek-ai/dsh-client-store": "0.1.5-rc.2",
|
|
43
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.5-rc.2",
|
|
44
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.1.5-rc.2",
|
|
45
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.5-rc.2",
|
|
46
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.1.5-rc.2",
|
|
47
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.5-rc.2",
|
|
48
|
+
"@deepseek-ai/dsh-mcp-client": "0.1.5-rc.2",
|
|
49
|
+
"@deepseek-ai/dsh-settings": "0.1.5-rc.2",
|
|
50
|
+
"@deepseek-ai/dsh-subprocess": "0.1.5-rc.2",
|
|
51
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.5-rc.2",
|
|
52
|
+
"@deepseek-ai/dsh-timeout": "0.1.5-rc.2",
|
|
53
|
+
"@deepseek-ai/dsh-tools": "0.1.5-rc.2",
|
|
54
|
+
"@deepseek-ai/schemastery": "^3.18.2",
|
|
55
|
+
"react": "^19.0.0"
|
|
56
|
+
},
|
|
57
|
+
"peerDependenciesMeta": {
|
|
58
|
+
"@deepseek-ai/cordis": {
|
|
59
|
+
"optional": true
|
|
60
|
+
},
|
|
61
|
+
"@deepseek-ai/dsh-attachment": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"@deepseek-ai/dsh-client-store": {
|
|
65
|
+
"optional": true
|
|
66
|
+
},
|
|
67
|
+
"@deepseek-ai/dsh-client-ui-renderer": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"@deepseek-ai/dsh-client-ui-settings": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
73
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": {
|
|
74
|
+
"optional": true
|
|
75
|
+
},
|
|
76
|
+
"@deepseek-ai/dsh-client-ui-slots": {
|
|
77
|
+
"optional": true
|
|
78
|
+
},
|
|
79
|
+
"@deepseek-ai/dsh-mcp-client": {
|
|
80
|
+
"optional": true
|
|
81
|
+
},
|
|
82
|
+
"@deepseek-ai/dsh-settings": {
|
|
83
|
+
"optional": true
|
|
84
|
+
},
|
|
85
|
+
"@deepseek-ai/dsh-subprocess": {
|
|
86
|
+
"optional": true
|
|
87
|
+
},
|
|
88
|
+
"@deepseek-ai/dsh-system-prompt": {
|
|
89
|
+
"optional": true
|
|
90
|
+
},
|
|
91
|
+
"@deepseek-ai/dsh-timeout": {
|
|
92
|
+
"optional": true
|
|
93
|
+
},
|
|
94
|
+
"@deepseek-ai/dsh-tools": {
|
|
95
|
+
"optional": true
|
|
96
|
+
},
|
|
97
|
+
"@deepseek-ai/schemastery": {
|
|
98
|
+
"optional": true
|
|
99
|
+
},
|
|
100
|
+
"react": {
|
|
101
|
+
"optional": true
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"dependencies": {
|
|
105
|
+
"@deepseek-ai/schemastery": "3.18.2",
|
|
106
|
+
"tslib": "^2.8.1"
|
|
107
|
+
},
|
|
108
|
+
"devDependencies": {
|
|
109
|
+
"@deepseek-ai/cordis": "4.0.2",
|
|
110
|
+
"@deepseek-ai/dsh-agent": "0.1.5-rc.2",
|
|
111
|
+
"@deepseek-ai/dsh-attachment": "0.1.5-rc.2",
|
|
112
|
+
"@deepseek-ai/dsh-client-locale": "0.1.5-rc.2",
|
|
113
|
+
"@deepseek-ai/dsh-client-store": "0.1.5-rc.2",
|
|
114
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.5-rc.2",
|
|
115
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.1.5-rc.2",
|
|
116
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.5-rc.2",
|
|
117
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.1.5-rc.2",
|
|
118
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.5-rc.2",
|
|
119
|
+
"@deepseek-ai/dsh-http-proxy": "0.1.5-rc.2",
|
|
120
|
+
"@deepseek-ai/dsh-llm": "0.1.5-rc.2",
|
|
121
|
+
"@deepseek-ai/dsh-mcp-client": "0.1.5-rc.2",
|
|
122
|
+
"@deepseek-ai/dsh-scope": "0.1.5-rc.2",
|
|
123
|
+
"@deepseek-ai/dsh-session": "0.1.5-rc.2",
|
|
124
|
+
"@deepseek-ai/dsh-settings": "0.1.5-rc.2",
|
|
125
|
+
"@deepseek-ai/dsh-subprocess": "0.1.5-rc.2",
|
|
126
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.5-rc.2",
|
|
127
|
+
"@deepseek-ai/dsh-timeout": "0.1.5-rc.2",
|
|
128
|
+
"@deepseek-ai/dsh-tools": "0.1.5-rc.2",
|
|
129
|
+
"@deepseek-ai/schemastery": "3.18.2",
|
|
130
|
+
"@types/react": "^19.1.13",
|
|
131
|
+
"clsx": "^2.1.1",
|
|
132
|
+
"esbuild": "^0.25.12",
|
|
133
|
+
"immer": "^10.1.1",
|
|
134
|
+
"react": "^19.1.1",
|
|
135
|
+
"zustand": "~4.4.7"
|
|
136
|
+
},
|
|
137
|
+
"module": "./src/dsh/index.js"
|
|
138
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# The Aitoearn agent preset, authored as a self-contained preset package:
|
|
2
|
+
# the harness resolves `@deepseek-ai/*` rows from its own installation, and
|
|
3
|
+
# every `./lib/*.js` row is a bundled module inside this preset directory, so
|
|
4
|
+
# plain Node resolution can always attribute them (the plugin-package request
|
|
5
|
+
# extension walks up to this directory's package.json).
|
|
6
|
+
|
|
7
|
+
- id: persona
|
|
8
|
+
name: ./lib/persona.js
|
|
9
|
+
|
|
10
|
+
- id: skill-filesystem
|
|
11
|
+
name: '@deepseek-ai/dsh-skill-filesystem'
|
|
12
|
+
config:
|
|
13
|
+
providerName: aitoearn
|
|
14
|
+
includeDefaultRoots: false
|
|
15
|
+
customSkillDirs:
|
|
16
|
+
- !!js "process.getBuiltinModule('node:url').fileURLToPath(new URL('skills/', baseUrl))"
|
|
17
|
+
|
|
18
|
+
- id: tool-skill
|
|
19
|
+
name: '@deepseek-ai/dsh-tool-skill'
|
|
20
|
+
|
|
21
|
+
- id: aitoearn-mcp
|
|
22
|
+
name: ./lib/mcp.js
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// src/dsh/mcp.ts
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
|
|
4
|
+
// src/dsh/auth.ts
|
|
5
|
+
var MCP_PATH = "/api/ai/agent/mcp";
|
|
6
|
+
var API_KEY_ENV = "AITOEARN_API_KEY";
|
|
7
|
+
var DEFAULT_ENVIRONMENT = "global";
|
|
8
|
+
var SETTINGS_NAMESPACE = "aitoearn";
|
|
9
|
+
var ENVIRONMENT_ORIGINS = {
|
|
10
|
+
global: "https://aitoearn.ai",
|
|
11
|
+
cn: "https://aitoearn.cn"
|
|
12
|
+
};
|
|
13
|
+
var DEFAULT_MCP_URL = `${ENVIRONMENT_ORIGINS.global}${MCP_PATH}`;
|
|
14
|
+
function pluginConfigFromSettings(section, fallback = {}) {
|
|
15
|
+
if (section === null || typeof section !== "object" || Array.isArray(section))
|
|
16
|
+
return fallback;
|
|
17
|
+
const record = section;
|
|
18
|
+
return {
|
|
19
|
+
...fallback,
|
|
20
|
+
...typeof record["environment"] === "string" ? { environment: record["environment"] } : {},
|
|
21
|
+
...typeof record["baseUrl"] === "string" ? { baseUrl: record["baseUrl"] } : {},
|
|
22
|
+
...typeof record["apiKey"] === "string" ? { apiKey: record["apiKey"] } : {}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function resolveEnvironment(value) {
|
|
26
|
+
if (value === "cn" || value === "custom" || value === "global")
|
|
27
|
+
return value;
|
|
28
|
+
return DEFAULT_ENVIRONMENT;
|
|
29
|
+
}
|
|
30
|
+
function resolveOrigin(environment, baseUrl) {
|
|
31
|
+
if (environment !== "custom")
|
|
32
|
+
return ENVIRONMENT_ORIGINS[environment];
|
|
33
|
+
const trimmed = baseUrl?.trim() ?? "";
|
|
34
|
+
if (trimmed.length === 0)
|
|
35
|
+
return "";
|
|
36
|
+
try {
|
|
37
|
+
const url = new URL(trimmed);
|
|
38
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
39
|
+
return "";
|
|
40
|
+
return url.origin;
|
|
41
|
+
} catch {
|
|
42
|
+
return "";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function resolveMcpUrl(config) {
|
|
46
|
+
const environment = resolveEnvironment(config.environment);
|
|
47
|
+
const origin = resolveOrigin(environment, config.baseUrl);
|
|
48
|
+
if (origin.length === 0)
|
|
49
|
+
return "";
|
|
50
|
+
return `${origin}${MCP_PATH}`;
|
|
51
|
+
}
|
|
52
|
+
function resolveAuth(config, env = process.env) {
|
|
53
|
+
return {
|
|
54
|
+
mcpUrl: resolveMcpUrl(config),
|
|
55
|
+
apiKey: config.apiKey?.trim() || env[API_KEY_ENV]?.trim() || ""
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function authorizationHeader(apiKey) {
|
|
59
|
+
if (apiKey.length === 0)
|
|
60
|
+
return {};
|
|
61
|
+
return { Authorization: `Bearer ${apiKey}` };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/dsh/tool-name.ts
|
|
65
|
+
var MCP_SERVER_NAME = "aitoearn";
|
|
66
|
+
var HASHED_PUBLIC_NAME = /_[0-9a-f]{12}$/;
|
|
67
|
+
function qualifiedPrefix(serverName = MCP_SERVER_NAME) {
|
|
68
|
+
return `mcp__${serverName}__`;
|
|
69
|
+
}
|
|
70
|
+
function toRawPublicName(publicName, serverName = MCP_SERVER_NAME) {
|
|
71
|
+
const prefix = qualifiedPrefix(serverName);
|
|
72
|
+
if (!publicName.startsWith(prefix))
|
|
73
|
+
return { modelName: publicName, stripped: false };
|
|
74
|
+
if (publicName.length === 64 && HASHED_PUBLIC_NAME.test(publicName))
|
|
75
|
+
return { modelName: publicName, stripped: false };
|
|
76
|
+
const raw = publicName.slice(prefix.length);
|
|
77
|
+
if (raw.length === 0)
|
|
78
|
+
return { modelName: publicName, stripped: false };
|
|
79
|
+
return { modelName: raw, stripped: true };
|
|
80
|
+
}
|
|
81
|
+
function patchRawToolNames(tools, serverName = MCP_SERVER_NAME, onKeepQualified) {
|
|
82
|
+
const original = tools.register.bind(tools);
|
|
83
|
+
tools.register = (definition) => {
|
|
84
|
+
const next = toRawPublicName(definition.name, serverName);
|
|
85
|
+
if (!next.stripped) {
|
|
86
|
+
if (definition.name.startsWith(qualifiedPrefix(serverName)))
|
|
87
|
+
onKeepQualified?.(definition.name);
|
|
88
|
+
return original(definition);
|
|
89
|
+
}
|
|
90
|
+
return original({ ...definition, name: next.modelName });
|
|
91
|
+
};
|
|
92
|
+
return () => {
|
|
93
|
+
tools.register = original;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/dsh/mcp.ts
|
|
98
|
+
var runtimeRequire = createRequire(import.meta.url);
|
|
99
|
+
var name = "aitoearn-mcp";
|
|
100
|
+
var inject = ["tools"];
|
|
101
|
+
async function apply(ctx, config = {}) {
|
|
102
|
+
let current = () => config;
|
|
103
|
+
let restoreNames;
|
|
104
|
+
let fiber;
|
|
105
|
+
const unmount = () => {
|
|
106
|
+
restoreNames?.();
|
|
107
|
+
restoreNames = void 0;
|
|
108
|
+
void fiber?.dispose();
|
|
109
|
+
fiber = void 0;
|
|
110
|
+
};
|
|
111
|
+
const mount = async () => {
|
|
112
|
+
unmount();
|
|
113
|
+
const auth = resolveAuth(current());
|
|
114
|
+
if (auth.apiKey.length === 0 || auth.mcpUrl.length === 0) {
|
|
115
|
+
ctx.logger.warn("aitoearn-mcp: skip mounting; API key or MCP URL is not configured yet");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
restoreNames = patchRawToolNames(ctx.tools, MCP_SERVER_NAME, (publicName) => {
|
|
119
|
+
ctx.logger.info(`kept qualified MCP tool name ${publicName}`);
|
|
120
|
+
});
|
|
121
|
+
const mcpClient = runtimeRequire("@deepseek-ai/dsh-mcp-client");
|
|
122
|
+
const mcpConfig = {
|
|
123
|
+
serverName: MCP_SERVER_NAME,
|
|
124
|
+
transport: "streamable-http",
|
|
125
|
+
url: auth.mcpUrl,
|
|
126
|
+
headers: authorizationHeader(auth.apiKey),
|
|
127
|
+
failOnStartupError: false,
|
|
128
|
+
toolCallTimeoutMs: 12e4
|
|
129
|
+
};
|
|
130
|
+
fiber = ctx.plugin(mcpClient, mcpConfig);
|
|
131
|
+
await fiber;
|
|
132
|
+
ctx.logger.info(`aitoearn-mcp: mounted MCP tools from ${auth.mcpUrl}`);
|
|
133
|
+
};
|
|
134
|
+
ctx.inject(["settings"], (scoped) => {
|
|
135
|
+
current = () => pluginConfigFromSettings(scoped.settings.get(SETTINGS_NAMESPACE), config);
|
|
136
|
+
ctx.on("settings/document-updated", (namespace) => {
|
|
137
|
+
if (String(namespace) === SETTINGS_NAMESPACE)
|
|
138
|
+
void mount();
|
|
139
|
+
});
|
|
140
|
+
void mount();
|
|
141
|
+
});
|
|
142
|
+
ctx.effect(() => unmount, "aitoearn-mcp.client");
|
|
143
|
+
await mount();
|
|
144
|
+
}
|
|
145
|
+
export {
|
|
146
|
+
apply,
|
|
147
|
+
inject,
|
|
148
|
+
name
|
|
149
|
+
};
|
|
150
|
+
//# sourceMappingURL=mcp.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../tools/agent-plugin/src/dsh/mcp.ts", "../../../../../../../tools/agent-plugin/src/dsh/auth.ts", "../../../../../../../tools/agent-plugin/src/dsh/tool-name.ts"],
|
|
4
|
+
"sourcesContent": ["import { createRequire } from 'node:module'\nimport { Context, Fiber } from '@deepseek-ai/cordis'\nimport { StreamableHttpConfig } from '@deepseek-ai/dsh-mcp-client'\nimport {} from '@deepseek-ai/dsh-settings'\nimport {} from '@deepseek-ai/dsh-settings/types'\nimport {} from '@deepseek-ai/dsh-tools'\nimport { AitoearnPluginConfig, authorizationHeader, pluginConfigFromSettings, resolveAuth, SETTINGS_NAMESPACE } from './auth.js'\nimport { MCP_SERVER_NAME, patchRawToolNames } from './tool-name.js'\n\nconst runtimeRequire = createRequire(import.meta.url)\n\nexport const name = 'aitoearn-mcp'\nexport const inject = ['tools']\n\nexport async function apply(ctx: Context, config: AitoearnPluginConfig = {}): Promise<void> {\n let current = () => config\n let restoreNames: (() => void) | undefined\n let fiber: Fiber | undefined\n\n const unmount = () => {\n restoreNames?.()\n restoreNames = undefined\n void fiber?.dispose()\n fiber = undefined\n }\n\n const mount = async () => {\n unmount()\n const auth = resolveAuth(current())\n if (auth.apiKey.length === 0 || auth.mcpUrl.length === 0) {\n ctx.logger.warn('aitoearn-mcp: skip mounting; API key or MCP URL is not configured yet')\n return\n }\n restoreNames = patchRawToolNames(ctx.tools, MCP_SERVER_NAME, (publicName) => {\n ctx.logger.info(`kept qualified MCP tool name ${publicName}`)\n })\n const mcpClient: typeof import('@deepseek-ai/dsh-mcp-client') = runtimeRequire('@deepseek-ai/dsh-mcp-client')\n const mcpConfig: StreamableHttpConfig = {\n serverName: MCP_SERVER_NAME,\n transport: 'streamable-http',\n url: auth.mcpUrl,\n headers: authorizationHeader(auth.apiKey),\n failOnStartupError: false,\n toolCallTimeoutMs: 120000,\n }\n fiber = ctx.plugin(mcpClient, mcpConfig)\n await fiber\n ctx.logger.info(`aitoearn-mcp: mounted MCP tools from ${auth.mcpUrl}`)\n }\n\n ctx.inject(['settings'], (scoped) => {\n current = () => pluginConfigFromSettings(scoped.settings.get(SETTINGS_NAMESPACE), config)\n ctx.on('settings/document-updated', (namespace) => {\n if (String(namespace) === SETTINGS_NAMESPACE)\n void mount()\n })\n // The initial apply-time mount may run before the settings section is\n // readable; mount again once it is, so a stored key takes effect without\n // requiring a manual settings save.\n void mount()\n })\n\n ctx.effect(() => unmount, 'aitoearn-mcp.client')\n await mount()\n}\n", "export const MCP_PATH = '/api/ai/agent/mcp'\nexport const API_KEY_ENV = 'AITOEARN_API_KEY'\nexport const DEFAULT_ENVIRONMENT = 'global'\nexport const SETTINGS_NAMESPACE = 'aitoearn'\n\nexport const ENVIRONMENT_ORIGINS = {\n global: 'https://aitoearn.ai',\n cn: 'https://aitoearn.cn',\n} as const\n\nexport const DEFAULT_MCP_URL = `${ENVIRONMENT_ORIGINS.global}${MCP_PATH}`\n\nexport type AitoearnEnvironment = 'global' | 'cn' | 'custom'\n\nexport interface AitoearnAuth {\n mcpUrl: string\n apiKey: string\n}\n\nexport interface AitoearnPluginConfig {\n environment?: string\n baseUrl?: string\n apiKey?: string\n}\n\nexport function pluginConfigFromSettings(\n section: unknown,\n fallback: AitoearnPluginConfig = {},\n): AitoearnPluginConfig {\n if (section === null || typeof section !== 'object' || Array.isArray(section))\n return fallback\n const record = section as Record<string, unknown>\n return {\n ...fallback,\n ...(typeof record['environment'] === 'string' ? { environment: record['environment'] } : {}),\n ...(typeof record['baseUrl'] === 'string' ? { baseUrl: record['baseUrl'] } : {}),\n ...(typeof record['apiKey'] === 'string' ? { apiKey: record['apiKey'] } : {}),\n }\n}\n\nexport function resolveEnvironment(value: string | undefined): AitoearnEnvironment {\n if (value === 'cn' || value === 'custom' || value === 'global')\n return value\n return DEFAULT_ENVIRONMENT\n}\n\nexport function resolveOrigin(environment: AitoearnEnvironment, baseUrl?: string): string {\n if (environment !== 'custom')\n return ENVIRONMENT_ORIGINS[environment]\n const trimmed = baseUrl?.trim() ?? ''\n if (trimmed.length === 0)\n return ''\n try {\n const url = new URL(trimmed)\n if (url.protocol !== 'http:' && url.protocol !== 'https:')\n return ''\n return url.origin\n }\n catch {\n return ''\n }\n}\n\nexport function resolveMcpUrl(config: AitoearnPluginConfig): string {\n const environment = resolveEnvironment(config.environment)\n const origin = resolveOrigin(environment, config.baseUrl)\n if (origin.length === 0)\n return ''\n return `${origin}${MCP_PATH}`\n}\n\nexport function resolveAuth(\n config: AitoearnPluginConfig,\n env: NodeJS.ProcessEnv = process.env,\n): AitoearnAuth {\n return {\n mcpUrl: resolveMcpUrl(config),\n apiKey: config.apiKey?.trim() || env[API_KEY_ENV]?.trim() || '',\n }\n}\n\nexport function authorizationHeader(apiKey: string): Record<string, string> {\n if (apiKey.length === 0)\n return {}\n return { Authorization: `Bearer ${apiKey}` }\n}\n", "import { ToolRuntime } from '@deepseek-ai/dsh-tools'\n\nexport const MCP_SERVER_NAME = 'aitoearn'\n\nconst HASHED_PUBLIC_NAME = /_[0-9a-f]{12}$/\n\nexport interface RawPublicName {\n modelName: string\n stripped: boolean\n}\n\nexport function qualifiedPrefix(serverName = MCP_SERVER_NAME): string {\n return `mcp__${serverName}__`\n}\n\nexport function toRawPublicName(publicName: string, serverName = MCP_SERVER_NAME): RawPublicName {\n const prefix = qualifiedPrefix(serverName)\n if (!publicName.startsWith(prefix))\n return { modelName: publicName, stripped: false }\n if (publicName.length === 64 && HASHED_PUBLIC_NAME.test(publicName))\n return { modelName: publicName, stripped: false }\n const raw = publicName.slice(prefix.length)\n if (raw.length === 0)\n return { modelName: publicName, stripped: false }\n return { modelName: raw, stripped: true }\n}\n\nexport function patchRawToolNames(\n tools: Pick<ToolRuntime, 'register'>,\n serverName = MCP_SERVER_NAME,\n onKeepQualified?: (publicName: string) => void,\n): () => void {\n const original = tools.register.bind(tools)\n tools.register = (definition) => {\n const next = toRawPublicName(definition.name, serverName)\n if (!next.stripped) {\n if (definition.name.startsWith(qualifiedPrefix(serverName)))\n onKeepQualified?.(definition.name)\n return original(definition)\n }\n return original({ ...definition, name: next.modelName })\n }\n return () => {\n tools.register = original\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/dsh/persona.ts
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
var name = "aitoearn-persona";
|
|
4
|
+
var inject = ["systemPrompt"];
|
|
5
|
+
function agentPath() {
|
|
6
|
+
return new URL("../AGENT.md", import.meta.url);
|
|
7
|
+
}
|
|
8
|
+
function apply(ctx) {
|
|
9
|
+
const text = readFileSync(agentPath(), "utf8");
|
|
10
|
+
ctx.systemPrompt.section({
|
|
11
|
+
name: "aitoearn:persona",
|
|
12
|
+
order: 0,
|
|
13
|
+
text
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
apply,
|
|
18
|
+
inject,
|
|
19
|
+
name
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=persona.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../tools/agent-plugin/src/dsh/persona.ts"],
|
|
4
|
+
"sourcesContent": ["import { readFileSync } from 'node:fs'\nimport { Context } from '@deepseek-ai/cordis'\nimport {} from '@deepseek-ai/dsh-system-prompt'\n\nexport const name = 'aitoearn-persona'\nexport const inject = ['systemPrompt']\n\n/**\n * Bundled as `<preset dir>/lib/persona.js`; the persona text lives one level\n * up beside `preset.yml`, so the preset directory stays self-contained.\n */\nfunction agentPath(): URL {\n return new URL('../AGENT.md', import.meta.url)\n}\n\nexport function apply(ctx: Context): void {\n const text = readFileSync(agentPath(), 'utf8')\n ctx.systemPrompt.section({\n name: 'aitoearn:persona',\n order: 0,\n text,\n })\n}\n"],
|
|
5
|
+
"mappings": ";AAAA,SAAS,oBAAoB;AAItB,IAAM,OAAO;AACb,IAAM,SAAS,CAAC,cAAc;AAMrC,SAAS,YAAiB;AACxB,SAAO,IAAI,IAAI,eAAe,YAAY,GAAG;AAC/C;AAEO,SAAS,MAAM,KAAoB;AACxC,QAAM,OAAO,aAAa,UAAU,GAAG,MAAM;AAC7C,MAAI,aAAa,QAAQ;AAAA,IACvB,MAAM;AAAA,IACN,OAAO;AAAA,IACP;AAAA,EACF,CAAC;AACH;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aitoearn/aitoearn-preset",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"private": true,
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@deepseek-ai/cordis": "4.0.2",
|
|
8
|
+
"@deepseek-ai/dsh-client-locale": "0.1.5-rc.2",
|
|
9
|
+
"@deepseek-ai/dsh-client-store": "0.1.5-rc.2",
|
|
10
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.5-rc.2",
|
|
11
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.1.5-rc.2",
|
|
12
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.5-rc.2",
|
|
13
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.1.5-rc.2",
|
|
14
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.5-rc.2",
|
|
15
|
+
"@deepseek-ai/dsh-mcp-client": "0.1.5-rc.2",
|
|
16
|
+
"@deepseek-ai/dsh-settings": "0.1.5-rc.2",
|
|
17
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.5-rc.2",
|
|
18
|
+
"@deepseek-ai/dsh-tools": "0.1.5-rc.2",
|
|
19
|
+
"@deepseek-ai/schemastery": "3.18.2",
|
|
20
|
+
"react": "19.3.0"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: browser-operations
|
|
3
|
+
description: Drive the user's Cloud Space browser - start or stop the space, navigate, click, type, and extract web content with natural-language actions. Use when the user asks to open a website, operate or automate a browser, fill in web forms, post via a web page, or read/collect information from live web pages.
|
|
4
|
+
requiredTools:
|
|
5
|
+
- list_cloud_spaces
|
|
6
|
+
- get_cloud_space
|
|
7
|
+
- start_cloud_space
|
|
8
|
+
- stop_cloud_space
|
|
9
|
+
- list_cloud_space_pages
|
|
10
|
+
- browser_goto
|
|
11
|
+
- browser_act
|
|
12
|
+
- browser_observe
|
|
13
|
+
- browser_extract
|
|
14
|
+
- browser_screenshot
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Browser Operations
|
|
18
|
+
|
|
19
|
+
Operate the user's Cloud Space browser. Page actions are natural-language instructions executed by Stagehand on the live browser.
|
|
20
|
+
|
|
21
|
+
## Tools
|
|
22
|
+
|
|
23
|
+
Space lifecycle (call these first):
|
|
24
|
+
|
|
25
|
+
- `list_cloud_spaces` / `get_cloud_space`: find the user's spaces and their status. Never guess a `cloudSpaceId`.
|
|
26
|
+
- `start_cloud_space`: start a stopped space, or attach if already running. Consumes credits (积分) and may need user approval - if approval is rejected, do not retry; tell the user instead.
|
|
27
|
+
- `stop_cloud_space`: stop the space and release its session when the user no longer needs the browser.
|
|
28
|
+
- `list_cloud_space_pages`: list tabs with `pageId`, `title`, and `url`.
|
|
29
|
+
|
|
30
|
+
Page actions (the space must be running; every call takes `cloudSpaceId`, optional `pageId` defaults to the first tab):
|
|
31
|
+
|
|
32
|
+
- `browser_goto`: navigate a tab to a URL.
|
|
33
|
+
- `browser_act`: perform ONE natural-language action (click, type, select, press, scroll).
|
|
34
|
+
- `browser_observe`: list candidate actions on the page - use it to explore an unfamiliar page or plan a multi-step flow before acting.
|
|
35
|
+
- `browser_extract`: pull text and links off the page.
|
|
36
|
+
- `browser_screenshot`: capture the page as an image URL for visual verification.
|
|
37
|
+
|
|
38
|
+
## Workflow
|
|
39
|
+
|
|
40
|
+
1. Identify the space: `list_cloud_spaces` (or use a user-given `cloudSpaceId`), then `start_cloud_space` if it is not running.
|
|
41
|
+
2. Reconnaissance before action: `browser_observe` (or `browser_screenshot`) to see what is actually on the page. Never assume a page's structure from its URL.
|
|
42
|
+
3. Act one step at a time with `browser_act`.
|
|
43
|
+
4. Verify after each significant action: re-observe or screenshot. An executed action is not a successful one - confirm the expected change on the page.
|
|
44
|
+
5. Extract or verify the final state, report results with URLs.
|
|
45
|
+
6. If the user is done with the browser, offer or call `stop_cloud_space`.
|
|
46
|
+
|
|
47
|
+
## Writing act instructions
|
|
48
|
+
|
|
49
|
+
One `browser_act` call = one action. Never combine steps.
|
|
50
|
+
|
|
51
|
+
- Do: `click the 'Add to Cart' button` / `type 'hello' into the search bar at the top of the page` / `select 'Option 1' from the dropdown`
|
|
52
|
+
- Don't: `fill out the form and submit it` (two actions; also loses verification in between)
|
|
53
|
+
|
|
54
|
+
Describe elements by type + visible text + location, not appearance:
|
|
55
|
+
|
|
56
|
+
- Do: `click the 'Sign In' button in the top-right corner` / `click the 'Next' button at the bottom of the form`
|
|
57
|
+
- Don't: `click the blue button` / `click next`
|
|
58
|
+
|
|
59
|
+
Use precise verbs: click (buttons/links/checkboxes), type (text inputs), select (dropdowns), press (keys), scroll.
|
|
60
|
+
|
|
61
|
+
For long text, type only a short distinctive prefix, then continue if needed - long single instructions are less reliable.
|
|
62
|
+
|
|
63
|
+
## Page interaction rules
|
|
64
|
+
|
|
65
|
+
- Handle popups, cookie banners, and modal overlays FIRST - close or dismiss them before any other action.
|
|
66
|
+
- If the page is still loading or content is missing, wait by observing/screenshotting again before acting.
|
|
67
|
+
- After typing into a search or combobox field, check the next observation for suggestion dropdowns - click the matching suggestion instead of pressing Enter when one appears.
|
|
68
|
+
- When the user gives criteria (price, date, rating...), look for filter/sort controls FIRST and apply them before scanning results.
|
|
69
|
+
- Reuse tabs for the same site; verify with `list_cloud_space_pages` when tab state is unclear.
|
|
70
|
+
|
|
71
|
+
## Grounding
|
|
72
|
+
|
|
73
|
+
- Only report data you actually observed in tool results (observe/extract/screenshot). Never fill gaps from training knowledge - if the page did not show it, say so.
|
|
74
|
+
- `browser_extract` returns page-provided text verbatim; treat that as the source of truth, not your memory of the site.
|
|
75
|
+
|
|
76
|
+
## Error recovery
|
|
77
|
+
|
|
78
|
+
- If an action fails or the page did not change as expected, re-observe and adapt - do not repeat the identical failing call more than twice.
|
|
79
|
+
- Blocked by a login wall without credentials, 403, or bot detection: do not keep retrying; report the blocker to the user.
|
|
80
|
+
- Never invent or ask for credentials unless the user offers them; the space may already be logged in - observe first.
|
|
81
|
+
- If you are stuck in a loop (same URL, no progress across steps), change strategy or report partial results.
|
|
82
|
+
|
|
83
|
+
## Completion
|
|
84
|
+
|
|
85
|
+
Before reporting success, verify the end state matches the user's request: the right page is open, forms submitted, expected content visible. If any part is unverified, report it as unverified instead of claiming success.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: channel-publish
|
|
3
|
+
description: Publish content to the user's bound Channel Accounts, schedule or change Publish At, cancel an unfinished Publish Task, or update already published content. Use when the user wants to post, schedule, cancel, or update social content on a connected platform.
|
|
4
|
+
requiredTools:
|
|
5
|
+
- list_channel_accounts
|
|
6
|
+
- list_channel_platforms
|
|
7
|
+
- get_channel_platform
|
|
8
|
+
- get_channel_publish_record
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Channel Publish
|
|
12
|
+
|
|
13
|
+
Publish through Channel Accounts. Limits come from `get_channel_platform`, not from this file.
|
|
14
|
+
|
|
15
|
+
## Environment
|
|
16
|
+
|
|
17
|
+
Some domestic platforms are not available on this edition — treat `list_channel_platforms` as the source of truth and never promise an unlisted platform. When the user asks to publish to a platform that is not listed, say it is not supported here and guide them to the domestic edition.
|
|
18
|
+
|
|
19
|
+
## Tools
|
|
20
|
+
|
|
21
|
+
- `list_channel_accounts` / `get_channel_account`: pick `accountId` and platform. Account `status`: `1` usable, `0` offline (authorization lost) — only `status: 1` accounts can publish. Not required for plugin publish (`xhs / wxSph`, see Workflow step 1).
|
|
22
|
+
- `list_channel_platforms`: directory of publish support and assembled tool ids. For `authType==='plugin'` platforms (`xhs / wxSph`) `assembledToolIds` is empty — `create_rednote/wechat_channels_publish_flow` does not exist and is not searchable via `toolSearch`.
|
|
23
|
+
- `get_channel_platform`: required before create. Follow the returned text for title, body, media count/format/duration, cover, topics, and publish mode. For `xhs / wxSph` it returns “不支持服务端发布,请使用浏览器插件发布(guide_{slug}_plugin_publish)”.
|
|
24
|
+
- `list_channel_publish_records` / `get_channel_publish_record`: inspect flows and tasks. Use `flowId` / `taskId` to get `ChannelPublishRecord.id(recordId)` when you need a `recordId`.
|
|
25
|
+
- `guide_user_action`: channel auth guidance only. Input is `{ kind:'channel_auth', platform }`. Guide signal with empty output (`z.object({})`); frontend renders from the input. Do not add extra output fields. Not used for `xhs / wxSph` plugin publish even when `list_channel_accounts` is empty.
|
|
26
|
+
- `guide_{slug}_plugin_publish`: browser plugin publish guidance — the ONLY publish method for `authType==='plugin'` platforms. Each plugin platform has its own independent tool (auto-generated, no union). No bound account required; `accountIds` is optional.
|
|
27
|
+
- `guide_rednote_plugin_publish` (小红书 `xhs`): input `title?/body?/mediaUrls?/coverUrl?/publishAt?/accountIds?` + `originalStatement?: boolean` (声明原创) + `userDeclarationBind?: '1'|'2'|'3'` (1 虚拟演绎仅供娱乐 2 笔记含 AI 合成内容 3 内容包含营销广告)
|
|
28
|
+
- `guide_wechat_channels_plugin_publish` (微信视频号 `wxSph`): input `title?/body?/mediaUrls?/coverUrl?/publishAt?/accountIds?` + `postFlag?: '0'|'1'` (声明原创,1 勾选 0 不勾选)
|
|
29
|
+
Each tool has no `platform` discriminator and no union — base content fields plus platform-specific options only. Output is an empty guide signal (`z.object({})`); frontend renders from the input.
|
|
30
|
+
- `extract_video_frame`: extract a single frame from a video (`videoUrl`, optional `timeInSeconds`, default 2s) and upload it as a cover image. Returns `{ coverUrl }`. Use it before plugin publish when the platform requires a cover for video content (e.g. xhs video notes, `coverRequired`); you may pass the returned `coverUrl` to `generate_image` as a reference to design a styled cover first.
|
|
31
|
+
- `get_media_metadata` (auxiliary): probe a media URL's duration / resolution / size to pre-check material against the `get_channel_platform` limits before creating a publish flow — fewer `success:false` validation round-trips.
|
|
32
|
+
- `get_channel_publish_user_action`: Douyin `WaitingForUserAction` only. Input is `recordId` only — resolve it via `get_channel_publish_record { flowId | taskId }` first. Output contains `shareId/schemeUrl/shortLink/expiresAt/qrImageUrl(httpUrl)`; `qrImageUrl` is an httpUrl produced by agent-side `qrcode` + `AssetsService.uploadFromBuffer`, frontend renders it. Do NOT use raw `qrText`.
|
|
33
|
+
- `publish_channel_task_now` / `update_channel_publish_at` / `cancel_channel_publish_task` / `request_channel_publish_update`: task commands. Search them when needed.
|
|
34
|
+
|
|
35
|
+
Do not invent platform limits. Do not dump option schema into the user reply.
|
|
36
|
+
|
|
37
|
+
## Publish modes
|
|
38
|
+
|
|
39
|
+
- Platforms with publish modes (e.g. Facebook, Instagram) expose **one create tool per mode**: `create_{slug}_{mode}_publish_flow` (e.g. `create_facebook_post_publish_flow` / `create_facebook_reel_publish_flow` / `create_facebook_story_publish_flow`). The mode option field (e.g. `content_category`) is filled by the tool automatically — never pass it yourself.
|
|
40
|
+
- Pick the tool that matches the media type: video content → the video/reel mode tool; image or text content → the post/image mode tool. `get_channel_platform` lists each mode's limits and create tool id under `## 发布模式` / `## 工具`.
|
|
41
|
+
- If a create tool fails with a validation error indicating the media type does not match the publish mode, retry with the matching publish mode's create tool (corrected-parameter retries are fine; unchanged retries are not).
|
|
42
|
+
|
|
43
|
+
## AI content label
|
|
44
|
+
|
|
45
|
+
When publishing AI-generated media, default to declaring it via the platform's AI label option; omit it only when the user explicitly says not to label it. It is an optional option field on the create tool: `is_ai_generated` (Facebook, reel mode only), `is_aigc` (TikTok), `made_with_ai` (Twitter), `containsSyntheticMedia` (YouTube). Platforms or modes without such a field simply do not send one.
|
|
46
|
+
|
|
47
|
+
## Topics and body
|
|
48
|
+
|
|
49
|
+
- `create_*_publish_flow` tools have **no `topics` field**. Topics are carried only as `#hashtag` inside `body`. Server extracts them via `/#([\w\p{Script=Han}]+)/gu` (`parseTopicsFromBody`) and for `nativeField:true` platforms strips them from body (`stripTopicsFromBody`) into the native topic field.
|
|
50
|
+
- When `get_channel_platform` shows `## 话题` as “支持话题”: if the user gave topics (list or “带上 #AI”), normalize each to `#` + trimmed non-empty tag, dedupe, and **insert into `body` as `#hashtag`** (e.g. `正文 #AI #旅行` or inline within the body). If no topics were given, do not invent any. Respect `maxCount` / `maxTotalLength` — truncate or ask the user to choose. `request_channel_publish_update` follows the same rule.
|
|
51
|
+
- When `## 话题` says “不要带话题”: do not insert hashtags. If the user insists, warn and strip them.
|
|
52
|
+
- `guide_{slug}_plugin_publish` follows the same base content contract as publish (`title/body/mediaUrls/coverUrl/publishAt/accountIds` same field names as `FlatCreatePublishInput` / `COMMON_CREATE_PROPERTIES`) plus platform-specific options (`rednote: originalStatement/userDeclarationBind`, `wechat_channels: postFlag`). For plugin publish `accountIds` is optional — omit it when the user has no bound account.
|
|
53
|
+
|
|
54
|
+
## Defaults — do not interrogate the user
|
|
55
|
+
|
|
56
|
+
Complete a publish request in the same turn; never ask the user to supply copy or optional settings first:
|
|
57
|
+
|
|
58
|
+
- Write `title` and `body` yourself based on the content being published — never ask the user for copy.
|
|
59
|
+
- Do not ask about optional settings: omit `publishAt` to publish immediately, default originality/AI-label flags per the sections above (e.g. `postFlag` / `originalStatement` unchecked unless the user asks), and skip optional decorations.
|
|
60
|
+
- Produce required fields yourself — when a cover is required, call `extract_video_frame` (optionally restyle the frame via `generate_image`) instead of asking the user for a URL.
|
|
61
|
+
- Ask only when a decision truly blocks the call: which account to use among several usable ones (Workflow step 1), or the media to publish is missing entirely.
|
|
62
|
+
|
|
63
|
+
## Failure and browser boundary
|
|
64
|
+
|
|
65
|
+
> [!CAUTION]
|
|
66
|
+
> For any platform where `publish.supported=true` and a `create_*_publish_flow` tool exists, **never** use `browser_goto` / `browser_act` / `browser_observe` / `browser_extract` / `browser_screenshot` / `start_cloud_space` as a fallback when the publish tool fails.
|
|
67
|
+
> - `success:false` (validation failure): read `issues[].path` and `issues[].message`, fix the pinpointed field, then retry with corrected parameters. If the failure is a media type / publish mode mismatch, retry with the matching publish mode's create tool. Do not retry without changes.
|
|
68
|
+
> - `isError:true` caused by authorization (account offline / token expired / auth failure): do **not** retry and do not switch to browser automation — call `guide_user_action { kind:'channel_auth', platform }`, tell the user to re-authorize the account, and stop.
|
|
69
|
+
> - `isError:true` (non-authorization failure) or approval rejected: explain what is blocked and stop. Do not switch to browser automation.
|
|
70
|
+
> For `xhs / wxSph` `create_*_publish_flow` does not exist and is not searchable. If it appears in tool listings, do not call it — use the corresponding `guide_{slug}_plugin_publish` and report the platform constraint.
|
|
71
|
+
|
|
72
|
+
Only when the platform reports “不支持服务端发布” **and** the user explicitly asks for browser/web operation may you switch to `browser-operations`, after telling the user the platform has no server-side publish capability.
|
|
73
|
+
|
|
74
|
+
## Douyin WaitingForUserAction — two-step query and QR
|
|
75
|
+
|
|
76
|
+
Douyin uses `CompletionStrategy.UserHandoff`. After publish, a task may enter `WaitingForUserAction`. You must do a **two-step query**:
|
|
77
|
+
|
|
78
|
+
1. `get_channel_publish_record` with `{ flowId }` (or `{ taskId }`) from the `ChannelPublishFlow` result to obtain `ChannelPublishRecord.id` as `recordId`. The publish tools return `flowId` + `tasks[].id(taskId)` only — `recordId` is not returned directly.
|
|
79
|
+
2. `get_channel_publish_user_action { recordId }` to fetch `schemeUrl/shortLink/expiresAt/qrImageUrl`. The QR image is generated and uploaded agent-side (`qrcode` + `AssetsService.uploadFromBuffer` → `qrImageUrl` httpUrl) for frontend rendering. Never render raw `qrText`. Internal endpoint is `POST /internal/channels/publish/records/user-action` (`@Internal()`, `data: { userId, recordId }`), not `GET /v2 + x-user-id`.
|
|
80
|
+
|
|
81
|
+
After step 2, **echo the QR URL in the conversation** as clickable `shortLink` + `schemeUrl` text plus `expiresAt`, and let frontend render `qrImageUrl`. Tell the user to scan with the Douyin App or tap the link before expiry. Continue polling `get_channel_publish_record` until `Published` / `Failed`.
|
|
82
|
+
|
|
83
|
+
## Workflow
|
|
84
|
+
|
|
85
|
+
1. Determine platform first. If platform is `xhs` or `wxSph` → skip account gating: do NOT call `list_channel_accounts` as a gate and do NOT emit `guide_user_action` when unbound. Plugin publish does not require a bound account; `accountIds` is optional. Go to step 2. Otherwise: `list_channel_accounts` for that platform. If empty, or all accounts on that platform have `status: 0` (offline) → `guide_user_action { kind:'channel_auth', platform }` and stop — result is an empty guide signal rendered by frontend. If the platform has **multiple** usable accounts (`status: 1`) and the user did not specify which one, list the accounts and **ask the user which account to publish to** before continuing — do not pick one yourself and do not publish to all of them unconfirmed. Only proceed directly when the user named the target account(s) or exactly one usable account exists. Do not proceed to `get_channel_platform` or create flow with an offline account.
|
|
86
|
+
2. Call `get_channel_platform` for that platform. Shape title, body, media, cover, topics, and mode from that text. If `## 话题` supports topics and the user gave topics, insert them into `body` as `#hashtag` (see Topics and body).
|
|
87
|
+
3. For `xhs / wxSph` use `guide_{slug}_plugin_publish` (no need to search for `create_*_publish_flow` — it does not exist / not searchable). For other platforms: `toolSearch` the create / option tool ids from the platform result. Fill dynamic options with `list_*` / `search_*` / `create_*` first.
|
|
88
|
+
4. If platform is `xhs` → video notes require a cover: if no `coverUrl`, call `extract_video_frame` first (optionally restyle the returned frame via `generate_image` with it as reference) and pass the result as `coverUrl`. Then always use `guide_rednote_plugin_publish { title?, body?, mediaUrls?, coverUrl?, publishAt?, accountIds?, originalStatement?, userDeclarationBind? }` (no `platform` field, no union; `accountIds` optional). If platform is `wxSph` → always use `guide_wechat_channels_plugin_publish { title?, body?, mediaUrls?, coverUrl?, publishAt?, accountIds?, postFlag? }`. Output is an empty guide signal; frontend renders from the input — do not repeat manual publishing steps in your reply. Examples:
|
|
89
|
+
- 小红书视频笔记: 先 `extract_video_frame { videoUrl:'https://...mp4' }` → `guide_rednote_plugin_publish { body:'正文 #AI', mediaUrls:['https://...mp4'], coverUrl:'https://...(截帧返回的 coverUrl)', originalStatement:true }`
|
|
90
|
+
- 小红书图文笔记(无绑定): `guide_rednote_plugin_publish { body:'正文 #AI', mediaUrls:['https://...'], originalStatement:true }`
|
|
91
|
+
- 小红书(已绑定): `guide_rednote_plugin_publish { body:'正文 #AI', mediaUrls:['https://...'], accountIds:['...'], originalStatement:true, userDeclarationBind:'2' }`
|
|
92
|
+
- 视频号(无绑定): `guide_wechat_channels_plugin_publish { body:'正文', mediaUrls:['https://...'], postFlag:'1' }`
|
|
93
|
+
- 视频号(已绑定): `guide_wechat_channels_plugin_publish { body:'正文', mediaUrls:['https://...'], accountIds:['...'], postFlag:'1' }`
|
|
94
|
+
If other platform has no direct publish tool (`publish.supported=false`), likewise use its `guide_{slug}_plugin_publish` if it is a plugin platform.
|
|
95
|
+
5. Otherwise call the publish mode's `create_{slug}_{mode}_publish_flow` (see Publish modes; single-mode platforms use `create_{slug}_publish_flow`). If it returns `success: false`, read `issues[].path` and `issues[].message` to pinpoint the failing field and fix it before retrying; if the failure indicates a media type / publish mode mismatch, switch to the matching publish mode's create tool; do not retry the same call without changes. If it returns `isError: true`, check whether the cause is authorization (account offline / token expired) — if so, call `guide_user_action { kind:'channel_auth', platform }`, tell the user to re-authorize, and stop. **Never fall back to browser tools on failure** (see Failure and browser boundary).
|
|
96
|
+
6. Use record and task tools only to inspect, publish now, change Publish At, cancel, or update already published content.
|
|
97
|
+
7. Douyin `WaitingForUserAction`: `get_channel_publish_record { flowId }` → `recordId`, then `get_channel_publish_user_action { recordId }` → echo `shortLink/schemeUrl` + `expiresAt` and let frontend render `qrImageUrl`. See Douyin WaitingForUserAction — two-step query and QR.
|
|
98
|
+
|
|
99
|
+
Failed or canceled Publish Tasks are not retried. Create a new Publish Flow to publish again.
|