openxiangda 2.1.1 → 2.2.0

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 CHANGED
@@ -10,6 +10,8 @@ pnpm openxiangda skill install --workspace . --force
10
10
  pnpm openxiangda dev
11
11
  ```
12
12
 
13
+ 创建和技能安装默认检查内部支持协作所需的 DWS。`support status --json` 查看状态,`support bootstrap` 安装/复用官方完整技能,`support login` 引导 OAuth,`support join` 打开配置中的原邀请链接,由用户在钉钉完成加入。等待接入不阻塞应用工作;离线/CI 可加 `--skip-support`。咨询和持续跟进使用随统一入口安装的 `openxiangda-support` 技能,V1/V2 保持各自项目引擎。
14
+
13
15
  应用导入统一子路径:
14
16
 
15
17
  | 子路径 | 用途 |
@@ -3,17 +3,19 @@ import { flagValue, fail } from './workspace.js';
3
3
  import { update } from './update.js';
4
4
  import { assessMigration } from './migrate.js';
5
5
  import { installDistributionSkills } from './skills.js';
6
+ import { supportOperation } from './support.js';
6
7
 
7
8
  export async function distributionCommand(context, args) {
8
9
  const command = args[0];
9
10
  const skillInstall = command === 'skill' && args[1] === 'install';
10
- if (!['version', 'update', 'changelog', 'migrate'].includes(command) && !skillInstall) return false;
11
+ if (!['version', 'update', 'changelog', 'migrate', 'support'].includes(command) && !skillInstall) return false;
11
12
  validateArguments(command, args.slice(1));
12
13
  if (args.includes('--help') || args.includes('-h')) {
13
- process.stdout.write('openxiangda version [--json]\nopenxiangda update check|install [--target workspace|launcher] [--dry-run] [--json]\nopenxiangda changelog [version] [--json]\nopenxiangda migrate assess --to v2 [--json]\nopenxiangda skill install [--workspace <directory> | --destination <directory>] [--agent codex|claude|qoder|dual] [--force] [--dry-run]\n以上命令支持 --cwd <directory>。新应用默认 V2,项目升级保持原代际。\n');
14
+ process.stdout.write('openxiangda version [--json]\nopenxiangda update check|install [--target workspace|launcher] [--dry-run] [--json]\nopenxiangda changelog [version] [--json]\nopenxiangda migrate assess --to v2 [--json]\nopenxiangda skill install [--workspace <directory> | --destination <directory>] [--agent codex|claude|qoder|dual] [--force] [--dry-run] [--skip-support]\nopenxiangda support status|bootstrap|login|join [--profile <corpId:userId>] [--agent <DWS agent>] [--device] [--force] [--dry-run] [--json]\n以上命令支持 --cwd <directory>。新应用默认 V2,项目升级保持原代际。支持接入等待用户操作时不阻塞应用创建。\n');
14
15
  return true;
15
16
  }
16
17
  let data;
18
+ if (command === 'support') data = await supportOperation(context.packageRoot, args);
17
19
  if (skillInstall) data = await installDistributionSkills(context, args);
18
20
  if (command === 'version') data = {
19
21
  productVersion: context.manifest.version,
@@ -38,8 +40,8 @@ export async function distributionCommand(context, args) {
38
40
  }
39
41
 
40
42
  function validateArguments(command, args) {
41
- const booleans = new Set(['--json', '--help', '-h', ...(command === 'skill' ? ['--force', '--dry-run'] : command === 'update' ? ['--dry-run'] : [])]);
42
- const values = new Set(['--cwd', ...({ update: ['--target', '--registry'], changelog: ['--registry'], migrate: ['--to'], skill: ['--workspace', '--destination', '--dest', '--agent'] }[command] || [])]);
43
+ const booleans = new Set(['--json', '--help', '-h', ...(command === 'skill' ? ['--force', '--dry-run', '--skip-support'] : command === 'support' ? ['--force', '--dry-run', '--device'] : command === 'update' ? ['--dry-run'] : [])]);
44
+ const values = new Set(['--cwd', ...({ update: ['--target', '--registry'], changelog: ['--registry'], migrate: ['--to'], skill: ['--workspace', '--destination', '--dest', '--agent'], support: ['--profile', '--agent'] }[command] || [])]);
43
45
  const seen = new Set(); let positionals = 0;
44
46
  for (let i = 0; i < args.length; i++) {
45
47
  const argument = args[i], key = argument.split('=')[0];
@@ -3,9 +3,14 @@ import { join } from 'node:path';
3
3
  import { spawn } from 'node:child_process';
4
4
  import { discoverWorkspace, flagValue, readJson, resolveEngine } from './workspace.js';
5
5
  import { distributionCommand } from './commands.js';
6
+ import { bootstrapSupport } from './support.js';
6
7
 
7
8
  export async function launch(packageRoot, args = process.argv.slice(2)) {
8
9
  const manifest = readJson(join(packageRoot, 'package.json'));
10
+ if (args[0] === 'support') {
11
+ await distributionCommand({ manifest, packageRoot }, args);
12
+ return;
13
+ }
9
14
  const workspace = discoverWorkspace(flagValue(args, '--cwd') || (args[0] === 'skill' ? flagValue(args, '--workspace') : null) || process.cwd());
10
15
  // Launcher-only updates must also work when a project's dependencies are not yet installed.
11
16
  const launcherUpdate = args[0] === 'update' && flagValue(args, '--target') === 'launcher';
@@ -13,7 +18,7 @@ export async function launch(packageRoot, args = process.argv.slice(2)) {
13
18
  const context = { manifest, packageRoot, workspace, engine };
14
19
  if (await distributionCommand(context, args)) return;
15
20
  if (!args.length || args[0] === '--help' || args[0] === '-h') {
16
- process.stdout.write('统一分发命令:version、update check|install、changelog、migrate assess。\nSkill 安装支持 --workspace、--destination 和 --agent;正文跟随项目版本。\n\n');
21
+ process.stdout.write('统一分发命令:version、update check|install、changelog、migrate assess、support status|bootstrap|login|join。\nSkill 安装支持 --workspace、--destination 和 --agent;正文跟随项目版本。创建和技能刷新默认接入支持协作,离线/CI 可加 --skip-support。\n\n');
17
22
  }
18
23
  const env = { ...process.env };
19
24
  for (const key of ['OPENXIANGDA_DISTRIBUTION_NAME', 'OPENXIANGDA_DISTRIBUTION_VERSION', 'OPENXIANGDA_DOCUMENTATION_ROOT', 'OPENXIANGDA_SKILLS_ROOT']) delete env[key];
@@ -23,7 +28,9 @@ export async function launch(packageRoot, args = process.argv.slice(2)) {
23
28
  env.OPENXIANGDA_DOCUMENTATION_ROOT = join(engine.packageRoot, 'documentation');
24
29
  if (existsSync(join(engine.packageRoot, 'skills'))) env.OPENXIANGDA_SKILLS_ROOT = join(engine.packageRoot, 'skills');
25
30
  }
26
- const forwarded = engine.generation === 'v1' ? removeCwd(args) : args;
31
+ const initializes = args[0] === 'create' || args[0] === 'workspace' && args[1] === 'init' || args[0] === 'skill' && args[1] === 'bootstrap';
32
+ const engineArgs = initializes ? args.filter(argument => argument !== '--skip-support') : args;
33
+ const forwarded = engine.generation === 'v1' ? removeCwd(engineArgs) : engineArgs;
27
34
  const child = spawn(process.execPath, [engine.entry, ...forwarded], {
28
35
  cwd: engine.generation === 'v1' ? workspace?.root || process.cwd() : process.cwd(), env, stdio: 'inherit',
29
36
  });
@@ -36,6 +43,10 @@ export async function launch(packageRoot, args = process.argv.slice(2)) {
36
43
  child.once('exit', (code, signal) => resolve({ code, signal }));
37
44
  });
38
45
  process.exitCode = code ?? (signal === 'SIGINT' ? 130 : 143);
46
+ if (code === 0 && initializes) {
47
+ const support = await bootstrapSupport(packageRoot, args);
48
+ if (support.state !== 'skipped') process.stderr.write(`${JSON.stringify({ operation: 'support bootstrap', data: support })}\n`);
49
+ }
39
50
  } finally { signals.forEach((signal, i) => process.removeListener(signal, handlers[i])); }
40
51
  }
41
52
 
@@ -4,6 +4,7 @@ import { dirname, join, resolve } from 'node:path';
4
4
  import { createRequire } from 'node:module';
5
5
  import { pathToFileURL } from 'node:url';
6
6
  import { fail, flagValue, readJson } from './workspace.js';
7
+ import { bootstrapSupport } from './support.js';
7
8
 
8
9
  export async function installDistributionSkills(context, args) {
9
10
  const workspace = flagValue(args, '--workspace');
@@ -33,6 +34,7 @@ export async function installDistributionSkills(context, args) {
33
34
  }
34
35
  const router = join(source, 'openxiangda');
35
36
  cpSync(join(context.packageRoot, 'launcher-skill/openxiangda'), router, { recursive: true });
37
+ cpSync(join(context.packageRoot, 'launcher-skill/openxiangda-support'), join(source, 'openxiangda-support'), { recursive: true });
36
38
  const file = join(router, 'SKILL.md');
37
39
  writeFileSync(file, readFileSync(file, 'utf8').replaceAll('__OPENXIANGDA_VERSION__', context.manifest.version));
38
40
  const names = readdirSync(source, { withFileTypes: true }).filter(entry => entry.isDirectory()).map(entry => entry.name).sort();
@@ -45,7 +47,8 @@ export async function installDistributionSkills(context, args) {
45
47
  const { refreshWorkspaceGuidance } = await importEngineModule(context.engine.packageRoot, 'openxiangda-skill-kit');
46
48
  guidance = refreshWorkspaceGuidance(resolve(workspace), guidanceTemplate);
47
49
  }
48
- return { destinations, installed: names, engineVersion: context.engine.version, generation: context.engine.generation, dryRun: args.includes('--dry-run'), ...(guidance ? { guidance } : {}) };
50
+ const support = await bootstrapSupport(context.packageRoot, args);
51
+ return { destinations, installed: names, engineVersion: context.engine.version, generation: context.engine.generation, dryRun: args.includes('--dry-run'), support, ...(guidance ? { guidance } : {}) };
49
52
  } finally { rmSync(staging, { recursive: true, force: true }); }
50
53
  }
51
54
 
@@ -0,0 +1,4 @@
1
+ {
2
+ "schemaVersion": "openxiangda.distribution-commands/v1",
3
+ "commands": ["support:status", "support:bootstrap", "support:login", "support:join"]
4
+ }
@@ -0,0 +1,190 @@
1
+ import { cpSync, existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, join } from 'node:path';
4
+ import { spawn } from 'node:child_process';
5
+ import { randomUUID } from 'node:crypto';
6
+ import { fail, flagValue, readJson } from './workspace.js';
7
+ import supportCommands from './support-commands.json' with { type: 'json' };
8
+
9
+ export const SUPPORT_CHANNEL_URL = 'https://evaluate-oss.oss-cn-hangzhou.aliyuncs.com/static/lowcode/support/channel.json';
10
+ export const DWS_STABLE_VERSION = '1.0.61';
11
+ const coreReferences = ['dingtalk-shared/SKILL.md', 'dingtalk-misc/references/profile.md', 'dingtalk-chat/SKILL.md', 'dingtalk-event/SKILL.md'];
12
+
13
+ export async function loadSupportChannel(fetcher = fetch) {
14
+ const response = await fetcher(SUPPORT_CHANNEL_URL, { signal: AbortSignal.timeout(10_000), redirect: 'error' });
15
+ if (!response.ok) fail('SUPPORT_CHANNEL_UNAVAILABLE', `HTTP ${response.status}`);
16
+ const chunks = []; let size = 0;
17
+ for await (const chunk of response.body) {
18
+ size += chunk.length;
19
+ if (size > 16 * 1024) fail('SUPPORT_CHANNEL_TOO_LARGE', '通道配置超过 16 KiB');
20
+ chunks.push(chunk);
21
+ }
22
+ const channel = JSON.parse(Buffer.concat(chunks).toString('utf8'));
23
+ const identifier = value => typeof value === 'string' && value.length > 0 && value.length <= 512 && !/[\s,:]/.test(value);
24
+ if (channel.schemaVersion !== 'openxiangda.support-channel/v1' || !identifier(channel.id) || !identifier(channel.corpId)
25
+ || typeof channel.conversationId !== 'string' || !channel.conversationId || channel.conversationId.length > 1024
26
+ || typeof channel.name !== 'string' || channel.name.length > 256 || !Number.isSafeInteger(channel.revision) || channel.revision < 1) {
27
+ fail('SUPPORT_CHANNEL_INVALID', '通道配置不符合当前契约');
28
+ }
29
+ let invitation;
30
+ try { invitation = new URL(channel.inviteUrl); } catch { fail('SUPPORT_INVITE_URL_INVALID', '邀请地址格式无效'); }
31
+ if (typeof channel.inviteUrl !== 'string' || channel.inviteUrl.length > 8192 || /[\r\n\0]/.test(channel.inviteUrl)
32
+ || !['https:', 'dingtalk:'].includes(invitation.protocol) || invitation.username || invitation.password) {
33
+ fail('SUPPORT_INVITE_URL_INVALID', '邀请地址协议无效');
34
+ }
35
+ return channel;
36
+ }
37
+
38
+ export function selectSupportProfile(profiles, corpId, requested) {
39
+ if (requested) {
40
+ if (!/^[^:\s,]+:[^:\s,]+$/.test(requested)) fail('SUPPORT_PROFILE_INVALID', '使用 profile list 返回的 corpId:userId');
41
+ if (requested.split(':')[0] !== corpId) fail('SUPPORT_PROFILE_ORGANIZATION_MISMATCH', '所选账号不属于支持通道组织');
42
+ return profiles.find(item => item.profile === requested && item.corpId === corpId && `${item.corpId}:${item.userId}` === requested) || null;
43
+ }
44
+ const candidates = profiles.filter(item => item.corpId === corpId && item.isOrgCurrent === true && item.profile === `${item.corpId}:${item.userId}`);
45
+ return candidates.length === 1 ? candidates[0] : null;
46
+ }
47
+
48
+ export function supportVersion(output) {
49
+ const match = /\bdws version v?(\d+)\.(\d+)\.(\d+)(?![\d.-])/.exec(output);
50
+ if (!match) return null;
51
+ const current = match.slice(1).map(Number), minimum = DWS_STABLE_VERSION.split('.').map(Number);
52
+ for (let i = 0; i < 3; i++) {
53
+ if (current[i] > minimum[i]) return current.join('.');
54
+ if (current[i] < minimum[i]) return null;
55
+ }
56
+ return current.join('.');
57
+ }
58
+
59
+ export async function supportOperation(packageRoot, args, overrides = {}) {
60
+ const action = args[1] || 'status';
61
+ if (!supportCommands.commands.includes(`support:${action}`)) fail('SUPPORT_ACTION_INVALID', action);
62
+ const taskHome = overrides.home || homedir();
63
+ const stateFile = overrides.stateFile || join(process.env.XDG_CONFIG_HOME || join(taskHome, '.config'), 'openxiangda', 'support.json');
64
+ const skillsHome = overrides.skillsHome || join(taskHome, '.agents', 'skills');
65
+ const run = overrides.run || runSupportProcess;
66
+ const channelLoader = overrides.loadChannel || loadSupportChannel;
67
+ const requested = flagValue(args, '--profile');
68
+ const target = flagValue(args, '--agent') || 'all';
69
+ if (!/^[a-z][a-z0-9-]{0,63}$/.test(target)) fail('SUPPORT_AGENT_INVALID', target);
70
+ if (args.includes('--dry-run')) return { state: 'planned', action, channelUrl: SUPPORT_CHANNEL_URL, dwsPackage: `dingtalk-workspace-cli@${DWS_STABLE_VERSION}`, skills: 'official-full-multi', target, userActionRequired: ['OAuth', 'DingTalk group join'] };
71
+ const result = { state: 'setup_required', action, channelUrl: SUPPORT_CHANNEL_URL, dws: { available: false }, skills: { ready: false }, nextActions: [] };
72
+ let releaseLock = () => {};
73
+ try {
74
+ if (action !== 'status') {
75
+ mkdirSync(dirname(stateFile), { recursive: true, mode: 0o700 });
76
+ const lock = `${stateFile}.lock`;
77
+ try { mkdirSync(lock, { mode: 0o700 }); } catch (error) { if (error.code === 'EEXIST') fail('SUPPORT_BUSY', `另一个接入操作正在执行;检查 ${lock}`); throw error; }
78
+ releaseLock = () => rmSync(lock, { recursive: true, force: true });
79
+ }
80
+ const saved = existsSync(stateFile) ? readJson(stateFile) : {};
81
+ const persist = patch => {
82
+ const temporary = `${stateFile}.${randomUUID()}.tmp`;
83
+ writeFileSync(temporary, `${JSON.stringify({ ...saved, ...patch, schemaVersion: 'openxiangda.support-state/v1' }, null, 2)}\n`, { mode: 0o600 });
84
+ try { renameSync(temporary, stateFile); } finally { rmSync(temporary, { force: true }); }
85
+ Object.assign(saved, patch);
86
+ };
87
+ let versionResult = await run('dws', ['--version']);
88
+ let version = versionResult.status === 0 ? supportVersion(versionResult.stdout) : null;
89
+ if (!version && action === 'bootstrap') {
90
+ const installed = await run(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['install', '--global', `dingtalk-workspace-cli@${DWS_STABLE_VERSION}`, '--registry', 'https://registry.npmjs.org', '--no-fund', '--no-audit'], { timeout: 120_000 });
91
+ if (installed.status !== 0) return pending(result, 'dws_unavailable', 'SUPPORT_DWS_INSTALL_FAILED', 'openxiangda support bootstrap');
92
+ versionResult = await run('dws', ['--version']);
93
+ version = versionResult.status === 0 ? supportVersion(versionResult.stdout) : null;
94
+ }
95
+ if (!version) return pending(result, 'dws_unavailable', 'SUPPORT_DWS_STABLE_REQUIRED', 'openxiangda support bootstrap');
96
+ result.dws = { available: true, version };
97
+ const referencesPresent = () => coreReferences.every(file => existsSync(join(skillsHome, file)));
98
+ if (action === 'bootstrap') {
99
+ installSupportGuidance(packageRoot, skillsHome);
100
+ if (args.includes('--force') || saved.skillsVersion !== version || saved.skillsTarget !== target || !referencesPresent()) {
101
+ const setup = await run('dws', ['skill', 'setup', '--mode', 'multi', '--target', target, '--yes'], { timeout: 60_000, officialSkills: true });
102
+ if (setup.status !== 0 || !referencesPresent()) return pending(result, 'setup_required', 'SUPPORT_DWS_SKILLS_INCOMPLETE', 'openxiangda support bootstrap --force');
103
+ persist({ skillsVersion: version, skillsTarget: target });
104
+ }
105
+ }
106
+ result.skills = { ready: referencesPresent(), bundle: saved.skillsVersion === version ? 'official-full-multi' : 'not_verified' };
107
+ let channel;
108
+ try { channel = await channelLoader(); } catch (error) { return pending(result, 'channel_unavailable', error.code || 'SUPPORT_CHANNEL_UNAVAILABLE', 'openxiangda support status'); }
109
+ result.channel = { id: channel.id, name: channel.name, revision: channel.revision, corpId: channel.corpId, conversationId: channel.conversationId };
110
+ const json = async command => {
111
+ const output = await run('dws', [...command, '--format', 'json']);
112
+ if (output.status !== 0) return null;
113
+ try { const parsed = JSON.parse(output.stdout); return parsed?.error || parsed?.success === false ? null : parsed; } catch { return null; }
114
+ };
115
+ const listProfiles = async () => {
116
+ const data = await json(['profile', 'list']);
117
+ return Array.isArray(data?.profiles) ? data.profiles : [];
118
+ };
119
+ let profile = selectSupportProfile(await listProfiles(), channel.corpId, requested || saved.profile);
120
+ if (action === 'login') {
121
+ const loginProfile = profile?.profile || requested || channel.corpId;
122
+ const login = await run('dws', ['auth', 'login', '--profile', loginProfile, ...(args.includes('--device') ? ['--device'] : []), '--format', 'json'], { timeout: 180_000, interactive: true });
123
+ if (login.status !== 0) return pending(result, 'authorization_required', 'SUPPORT_AUTH_PENDING', 'openxiangda support login');
124
+ profile = selectSupportProfile(await listProfiles(), channel.corpId, requested || saved.profile);
125
+ }
126
+ if (!profile) return pending(result, 'profile_required', 'SUPPORT_PROFILE_REQUIRED', 'openxiangda support login');
127
+ result.profile = profile.profile;
128
+ const auth = await json(['auth', 'status', '--profile', profile.profile]);
129
+ if (!auth || auth.authenticated !== true || auth.token_valid !== true) return pending(result, 'authorization_required', 'SUPPORT_AUTH_REQUIRED', 'openxiangda support login');
130
+ if (auth.corp_id !== channel.corpId || auth.user_id !== profile.userId) return pending(result, 'profile_required', 'SUPPORT_AUTH_IDENTITY_MISMATCH', 'openxiangda support status --profile <corpId:userId>');
131
+ if (action !== 'status') persist({ profile: profile.profile });
132
+ result.authorization = { verified: true };
133
+ const group = await json(['chat', '+chat-members-list', '--conversation-id', channel.conversationId, '--member-types', 'user', '--page-limit', '1', '--profile', profile.profile]);
134
+ if (group?.conversationId === channel.conversationId && Array.isArray(group.users) && group.users.length > 0 && !(group.failures?.length) && !(group.buckets?.users?.failures?.length)) {
135
+ result.group = { access: 'verified', membership: 'not_independently_verified', organizationRestriction: 'not_independently_verified', complete: group.complete === true };
136
+ result.state = result.skills.ready ? 'ready' : 'setup_required';
137
+ if (!result.skills.ready) result.nextActions = [{ command: 'openxiangda support bootstrap' }];
138
+ return result;
139
+ }
140
+ result.group = { access: 'unverified', membership: 'unverified', organizationRestriction: 'not_independently_verified' };
141
+ if (action === 'join') {
142
+ const opened = overrides.openUrl ? await overrides.openUrl(channel.inviteUrl) : await openSupportUrl(channel.inviteUrl, run);
143
+ result.inviteUrl = channel.inviteUrl;
144
+ result.browserOpened = opened === true;
145
+ return pending(result, 'pending_user_action', 'SUPPORT_JOIN_PENDING', 'openxiangda support status');
146
+ }
147
+ return pending(result, 'group_unavailable', 'SUPPORT_GROUP_ACCESS_UNVERIFIED', 'openxiangda support join');
148
+ } catch (error) {
149
+ return pending(result, 'unavailable', error.code || 'SUPPORT_UNAVAILABLE', 'openxiangda support bootstrap');
150
+ } finally { releaseLock(); }
151
+ }
152
+
153
+ function pending(result, state, code, command) {
154
+ return { ...result, state, code, nextActions: [{ command }] };
155
+ }
156
+
157
+ export function installSupportGuidance(packageRoot, skillsHome) {
158
+ const source = join(packageRoot, 'launcher-skill', 'openxiangda-support');
159
+ mkdirSync(skillsHome, { recursive: true });
160
+ cpSync(source, join(skillsHome, 'openxiangda-support'), { recursive: true });
161
+ }
162
+
163
+ export async function bootstrapSupport(packageRoot, args = [], overrides = {}) {
164
+ if (args.includes('--skip-support') || args.includes('--dry-run') || args.includes('--help') || args.includes('-h')) return { state: 'skipped' };
165
+ return supportOperation(packageRoot, ['support', 'bootstrap', ...(args.includes('--force') ? ['--force'] : [])], overrides);
166
+ }
167
+
168
+ async function openSupportUrl(url, run) {
169
+ const command = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'explorer.exe' : 'xdg-open';
170
+ return (await run(command, [url], { timeout: 10_000 })).status === 0;
171
+ }
172
+
173
+ export function runSupportProcess(command, args, options = {}) {
174
+ return new Promise(resolve => {
175
+ let stdout = '', stderr = '', settled = false, outputSize = 0;
176
+ const env = { ...process.env };
177
+ if (options.officialSkills) delete env.DWS_SKILL_SOURCE;
178
+ const child = spawn(command, args, { env, stdio: options.interactive ? ['inherit', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'], windowsHide: true });
179
+ const timer = setTimeout(() => child.kill('SIGKILL'), options.timeout || 40_000);
180
+ const finish = status => { if (!settled) { settled = true; clearTimeout(timer); resolve({ status, stdout, stderr }); } };
181
+ const collect = stream => chunk => {
182
+ outputSize += chunk.length;
183
+ if (outputSize > 1024 * 1024) { child.kill('SIGKILL'); return; }
184
+ if (stream === 'stdout') stdout += chunk; else stderr += chunk;
185
+ if (options.interactive) process.stderr.write(chunk);
186
+ };
187
+ child.stdout.on('data', collect('stdout')); child.stderr.on('data', collect('stderr'));
188
+ child.once('error', () => finish(-1)); child.once('close', code => finish(code ?? -1));
189
+ });
190
+ }
@@ -15,6 +15,7 @@
15
15
  - 默认读取 AppSpec 当前规格、设计索引、活动变更和阶段缺口。新应用先用自然语言对话主动发现模块,完成本期 PRD、旅程、逐页交互、视觉/原型、权限与架构设计及实际确认,形成内容摘要绑定的评审基线后再制定实施计划和实现业务;参见 docs product-design 和 docs interaction-patterns。既有应用仅修订受影响设计,不把 AI 建议写成用户确认。总纲保存长期规则,本轮变更关联需求、任务、源码与 AC 验收;测试发布前完成设计与计划,生产晋级前保存绑定原测试运行及包摘要的实际验收报告。更新当前规则、验证与发布结果和交接后归档;不得编造确认或通过结果。
16
16
  - 根据变化风险记录业务意图。无行为变化引用已有记录;已有授权和已确认意图不重复向用户请求机械确认。真实角色和浏览器验收与本地测试分别记录。
17
17
  - 失败保留错误码、指针和原候选;查询平台状态后使用允许的恢复操作,不自动重放未知结果。
18
+ - 平台契约、诊断和产品/架构咨询按 openxiangda-support 技能持续跟进已授权案例。使用 support status 查看 DWS 接入状态;待 OAuth、钉钉入群或网络恢复不阻塞独立开发,后台续跑以实际宿主监听/定时配置为准。
18
19
 
19
20
  MCP 使用同一项目 CLI:`pnpm exec openxiangda --mcp-stdio --cwd <workspace>`。先读取 workspace_context,再按任务读取 docs_read 和当前契约。登录、创建及长期 dev 使用 CLI/终端。
20
21
 
@@ -11,10 +11,10 @@ OpenXiangda 2.0 默认生成 React 应用和共享契约。普通 CRUD、标准
11
11
  以下命令的版本占位符由随包资料替换为该根包的精确版本。网站源码阅读者应先确认要使用的发行版本。
12
12
 
13
13
  ```bash
14
- pnpm dlx openxiangda@2.1.1 skill install --force
15
- pnpm dlx openxiangda@2.1.1 auth status --base-url <平台地址> --json
16
- pnpm dlx openxiangda@2.1.1 login --base-url https://platform.example.com
17
- pnpm dlx openxiangda@2.1.1 create my-app --base-url https://platform.example.com
14
+ pnpm dlx openxiangda@2.2.0 skill install --force
15
+ pnpm dlx openxiangda@2.2.0 auth status --base-url <平台地址> --json
16
+ pnpm dlx openxiangda@2.2.0 login --base-url https://platform.example.com
17
+ pnpm dlx openxiangda@2.2.0 create my-app --base-url https://platform.example.com
18
18
  cd my-app
19
19
  pnpm openxiangda context --json
20
20
  pnpm openxiangda dev
@@ -26,6 +26,23 @@ pnpm openxiangda dev
26
26
 
27
27
  进入项目后使用 `pnpm openxiangda`,由项目依赖和锁文件决定版本。查看使用资料运行 `pnpm openxiangda docs`;查看单一主题运行 `pnpm openxiangda docs frontend`。安装到其他 AI 工具时使用 `skill install --destination <Skill根目录>`。
28
28
 
29
+ ### 内部支持协作
30
+
31
+ 统一入口在成功创建应用、V1 workspace init/skill bootstrap 和 skill install 后检查 DWS。缺失时安装审核过的官方稳定版和完整 multi 技能(包括 shared、misc/profile 等跨技能引用),复用可用版本;不会自动登录、加入群或发送消息。支持接入结果独立于应用创建,JSON 创建结果保持原结构,接入进度写 stderr。
32
+
33
+ ```bash
34
+ pnpm openxiangda support status --json
35
+ pnpm openxiangda support bootstrap
36
+ pnpm openxiangda support login
37
+ pnpm openxiangda support join
38
+ ```
39
+
40
+ 首次 OAuth 由用户完成,SSH 使用 `support login --device`。支持侧保存 DWS 精确 profile 指针,多账号可传 `--profile <corpId:userId>`;不修改 DWS 或平台默认账号。通道来自外置配置,`join` 原样打开邀请链接,由用户在钉钉完成加入,再执行 `status` 回读。群可访问、成员身份与组织限制分别报告;钉钉负责真实权限判断,不凭链接推定。
41
+
42
+ 等待授权、入群、网络或安装权限时可以继续独立开发。离线/CI 创建和技能安装可加 `--skip-support`,之后运行 `support bootstrap` 恢复。完整官方技能由 DWS 自身安装/备份;自动接入使用 DWS 的 all 目标检测宿主,应用技能安装的 --agent 只决定应用技能位置。`support bootstrap --force --agent <DWS目标宿主>` 可显式指定 DWS 宿主并刷新。`support --help` 查看命令,支持命令无需应用依赖已安装;独立 V1 引擎没有这些分发命令,应使用新版统一入口,保留原 V1 项目版本。
43
+
44
+ 本地接入指针位于 `${XDG_CONFIG_HOME:-~/.config}/openxiangda/support.json`,只保存 profile 和技能安装回执,不保存 Token。普通支持咨询与持续讨论遵循安装的 `openxiangda-support` 技能及用户授权;后台唤醒须有真实宿主监听或定时任务。
45
+
29
46
  ## 项目结构 {#workspace}
30
47
 
31
48
  ```text
@@ -97,9 +114,9 @@ MCP 的 `docs_read` 可以读取本说明,当前没有独立的源码操作 MC
97
114
  无需本地工作区,使用本 Skill 随包精确版本或已安装的对应 CLI:
98
115
 
99
116
  ```bash
100
- pnpm dlx openxiangda@2.1.1 auth status --base-url <平台> --json
101
- pnpm dlx openxiangda@2.1.1 source resolve <仓库URL> --base-url <平台> --json
102
- pnpm dlx openxiangda@2.1.1 source clone <仓库URL> <新目录> --base-url <平台> --json
117
+ pnpm dlx openxiangda@2.2.0 auth status --base-url <平台> --json
118
+ pnpm dlx openxiangda@2.2.0 source resolve <仓库URL> --base-url <平台> --json
119
+ pnpm dlx openxiangda@2.2.0 source clone <仓库URL> <新目录> --base-url <平台> --json
103
120
  ```
104
121
 
105
122
  登录缺失或站点不匹配时,先按该平台执行 login。resolve 根据平台已经登记的绑定返回
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schemaVersion": "openxiangda.documentation/v1",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "topics": [
5
5
  {
6
6
  "id": "getting-started",
7
7
  "title": "安装与开始开发",
8
8
  "file": "getting-started.md",
9
- "sha256": "5e712efa10651fe9b133f9bc0fe3c6af5b8d5ed655ac8c90c53b09c2109081ac"
9
+ "sha256": "7c4703a4135e868ef4c18c04ec62f25bc1067d9e72fb17414babc9f1a567b862"
10
10
  },
11
11
  {
12
12
  "id": "product-design",
@@ -15,6 +15,8 @@ description: Identify OpenXiangda V1 or V2 workspaces and select the matching pr
15
15
 
16
16
  ## 升级与说明
17
17
 
18
+ - 平台支持协作读取同次安装的 `$openxiangda-support`。创建和技能刷新默认检查 DWS;`openxiangda support status --json` 查看接入状态,`support bootstrap` 补齐官方全套技能,`support login` 完成 OAuth,`support join` 打开配置链接由用户加入。等待用户或网络恢复不阻塞应用开发。支持命令归统一入口,V1/V2 业务和平台登录绑定保持原有所有者。
19
+
18
20
  - `openxiangda update check` 按项目代际检查稳定更新;离线时可继续使用本地能力。
19
21
  - `openxiangda update install --target workspace` 更新本项目同代工具链。升级后检查源码与锁文件差异,并执行项目验证。
20
22
  - `openxiangda update install --target launcher` 更新全局统一入口,需要 Node.js 24。此操作不转换已有项目。
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: openxiangda-support
3
+ description: 在 OpenXiangda 应用开发中接入内部平台支持协作,咨询平台契约、诊断异常、评审产品与架构,并持续跟进已授权的支持案例。普通应用实现继续使用项目对应代际技能。
4
+ ---
5
+
6
+ # OpenXiangda 支持协作
7
+
8
+ ## 接入与恢复
9
+
10
+ 统一入口的 `support status --json` 返回实际接入状态、精确 DWS profile 和当前通道。新应用创建及技能刷新会执行 bootstrap;离线或待授权不会阻塞应用。缺失时执行 `support bootstrap`,由它安装/复用官方稳定 DWS 和全套 multi 技能。旧 V1 项目通过统一入口使用支持命令,业务仍走项目 V1 引擎;不升级项目代际来取得支持。
11
+
12
+ `support login` 引导用户完成 OAuth;SSH 使用 `--device`。多账号时使用用户指定的 `--profile <corpId:userId>` 或目标组织明确的默认账号,后续复用保存的 profile。不得切换全局默认组织或根据姓名选账号。`support join` 原样打开外置配置的邀请链接,用户在钉钉完成加入;不能打开时展示返回的同一地址。随后运行 `support status` 检查群访问。链接已打开、命令退出成功或群可读不等于独立验证了成员身份或组织限制,以返回的各项状态为准。
13
+
14
+ DWS 命令、参数和实际访问由官方 `dingtalk-chat`、`dingtalk-event` 及其 references 负责;账号问题读取 `dingtalk-misc/references/profile.md`。始终带同一 `--profile` 和 `--format json`,按叶子 Schema/Help 补齐未知参数。授权、加入或服务暂不可用时记录待办,继续独立的应用工作。
15
+
16
+ ## 何时讨论
17
+
18
+ 遇到平台契约不明确、文档与实测冲突、需要平台源码/部署/日志证据、复杂权限与数据处理、通用能力取舍或新需求评审时,先读项目当前资料及证据,再在用户已经授权的协作范围内发问。此技能不会单独授予发送消息、操作服务器、修改数据或发布权限。
19
+
20
+ 提问包含目标、现象、已尝试方法、关键证据、需平台判断的问题;从项目 context 补平台、应用、环境及实际版本。未创建应用的能力咨询标记为平台咨询,不伪造 appCode。内部协作可以讨论相关业务逻辑、源码、日志和完整方案;凭据自动遮蔽,不把维护者 localhost 地址作为其他成员的审阅入口。
21
+
22
+ 需要读取应用源码时,提供平台地址及平台内置仓库 URL。支持侧使用正式 `source resolve <仓库URL> --base-url <平台> --json`,再 `source clone <仓库URL> <新目录> --base-url <平台> --json`;记录实际分支与 commit,首次检出不安装或运行项目脚本。使用平台现有身份和 `PLATFORM_ADMIN` 权限事实,不按某个账号名字开例外。旧项目使用 `source setup --import` 保留原仓库,正常解决 Git 冲突;详细契约以当前版本 getting-started 资料为准。
23
+
24
+ ## 评审与持续协作
25
+
26
+ 产品/架构咨询明确真实问题、已有能力、应用或平台归属、跨项目价值、成本与兼容性、失败恢复和验收方法。可以得出现有能力可满足、应用层实现、证据不足、重复需求、平台候选、已安排实施或拒绝/延期;每项结论附证据及下一步责任方。只有关联真实工程任务及容量才称为已安排实施,不口头承诺发布日期,不把客户业务实体当作平台定义。
27
+
28
+ 首问使用官方群消息入口;后续按真实消息 ID 引用回复,沿同一案例追加证据与验证,不重复创建问题。读取回复后补实现/复现结果,明确解决、暂行规避、候选需求或尚有争议;已回答不等于已解决。复杂或高影响操作先给具体对象、差异、影响、恢复方式,在已有授权范围内推进,超出部分才请求决定。
29
+
30
+ 活跃任务可通过官方事件技能等待增量,恢复任务时读取未完成案例。消息 ID、游标、未决项和下一步责任方保留在项目现有交接记录中,不含凭据。技能不会唤醒结束的 AI 会话;后台继续需要宿主真实监听或定时任务,不宣称所有宿主均已接入。
31
+
32
+ 两个 AI 使用不同真实身份。发送请求成功不是消息投递成功;检查真实投递结果,结果不明先回读,不盲目重发。确认、感谢和重复消息不启动新轮;初始上限 6 轮有效往返,连续 2 轮无新证据时暂停并列未决项。结论引用来源和适用版本交给既有案例/知识流程,不另建一套权威路线图。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "OpenXiangda 2.0 的统一命令、应用 SDK、MCP 与中文 AI 技能资料。",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -58,13 +58,13 @@
58
58
  "antd-mobile": "5.42.3",
59
59
  "dayjs": "1.11.18",
60
60
  "docx-preview": "0.3.7",
61
- "openxiangda-cli": "2.2.0",
61
+ "openxiangda-cli": "2.2.1",
62
62
  "openxiangda-contracts": "2.2.0",
63
63
  "openxiangda-devkit-core": "2.2.0",
64
64
  "openxiangda-legacy": "npm:openxiangda@1.0.268",
65
65
  "openxiangda-mcp": "2.0.2",
66
66
  "openxiangda-nest": "2.0.2",
67
- "openxiangda-skill-kit": "2.0.2",
67
+ "openxiangda-skill-kit": "2.0.3",
68
68
  "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
69
69
  },
70
70
  "peerDependencies": {
@@ -130,24 +130,24 @@
130
130
  },
131
131
  "openxiangdaRelease": {
132
132
  "schemaVersion": "openxiangda.release-notes/v1",
133
- "version": "2.1.1",
134
- "title": "OpenXiangda 应用源码托管",
133
+ "version": "2.2.0",
134
+ "title": "OpenXiangda 内部支持协作接入",
135
135
  "status": "reviewed",
136
- "summary": "私有化平台通过共享 Forgejo 管理应用源码。创建应用时自动初始化私有仓库并推送代码,应用管理员自动获得仓库管理权限,开发者使用长期 Git 凭据。",
136
+ "summary": "统一入口为 V1/V2 应用开发提供内部支持协作接入,检查官方 DWS 与技能,复用精确账号,并提供授权、打开群邀请和持续案例跟进入口。",
137
137
  "newFeatures": [
138
- "既有 PLATFORM_ADMIN 自动获得共享 Git 服务全部已有和新建仓库的管理权限。",
139
- "source resolve/clone 支持仅凭平台与仓库 URL、使用当前登录态读取源码,不安装或执行应用脚本。",
140
- "新增 source status、source setup 和 source push 命令,支持初始化恢复和已有工作区接入。",
141
- "创建应用后自动提交并推送初始源码,通过系统凭据管理器保存个人 Git 凭据。",
142
- "配套平台支持首次部署前浏览源码及最近提交,并核验部署关联的提交已经推送。"
138
+ "新增 support status、bootstrap、login、join,应用创建和技能刷新后检查支持通道接入状态。",
139
+ "缺少 DWS 时安装官方稳定版 1.0.61,并安装官方完整 multi 技能;已有可用稳定版本可以复用。",
140
+ "增加跨 V1/V2 支持技能,涵盖平台契约咨询、源码诊断上下文、产品架构评审和已授权案例的引用跟进。",
141
+ "使用外置支持通道配置,按原始邀请链接打开钉钉,由用户完成加入。"
143
142
  ],
144
143
  "fixes": [
145
- "应用源码可直接保留在平台托管仓库中,减少对开发者个人 Git 服务的依赖。",
146
- "显式导入保留原 origin external-source,初始化重试保留已有未提交修改,冲突推送不覆盖远端提交。"
144
+ "支持接入失败或等待用户操作时保留独立状态,不改变应用创建成功结果;进度写入 stderr,保持引擎 JSON 输出。",
145
+ "使用保存的精确 DWS profile,不按显示姓名或列表顺序猜测账号,不切换平台或 DWS 全局登录身份。",
146
+ "命令发现由统一分发层维护,技能校验显式接收分发命令,保持 V2 核心与两代引擎边界。"
147
147
  ],
148
148
  "affectedUsers": [
149
- "使用 OpenXiangda 2.0 创建应用或接入平台源码仓库的开发者。",
150
- "维护内部私有化平台和应用管理员权限的平台管理员。"
149
+ "通过统一入口创建应用、初始化工作区或刷新技能的 V1/V2 开发者。",
150
+ "在内部支持通道中继续已授权平台咨询和故障排查的开发 AI。"
151
151
  ],
152
152
  "compatibility": {
153
153
  "node": ">=24",
@@ -155,26 +155,26 @@
155
155
  "v1",
156
156
  "v2"
157
157
  ],
158
- "v1Policy": "V1 应用保持原代际、版本锁定与登录绑定。",
159
- "platformPolicy": "自动源码托管需要匹配的平台后端、管理前端和网关,并配置 OPENXIANGDA_SOURCE_*;未启用的站点保持原创建行为。",
160
- "gitCredential": "支持 macOS Keychain、Windows Git Credential Manager,以及 Linux 已安装的 Git Credential Manager 或 libsecret。",
158
+ "v1Policy": "V1 项目保持原代际、版本锁定与登录绑定;支持接入由统一分发入口提供。",
159
+ "platformPolicy": "本次工具发布不要求更新平台镜像;源码诊断仍需目标平台已启用正式源码托管并授予当前账号访问权限。",
160
+ "dws": "最低稳定版 1.0.61;OAuth 与加入群需要用户在官方流程中完成。",
161
161
  "releaseChannels": "latest / stable-v2:V2 正式版;legacy-v1:V1 维护版;alpha:预发布。"
162
162
  },
163
163
  "upgradeSteps": [
164
- "平台管理员配置共享 Forgejo、源码入口和专用凭据派生密钥,部署匹配的平台版本;长期保留密钥及 Git 数据。",
165
- " Node.js 24 环境安装统一入口:npm install -g openxiangda@2.1.1。",
166
- "更新 V2 工作区工具链;新应用自动接入,已有应用运行 openxiangda source setup --import 接入。",
167
- "完成修改后运行 openxiangda source push -m 描述,再按现有检查和部署流程交付。"
164
+ " Node.js 24 环境安装统一入口:npm install -g openxiangda@2.2.0。",
165
+ "运行 openxiangda support bootstrap 检查安装与完整技能;离线或 CI 初始化可使用 --skip-support,之后再恢复。",
166
+ "运行 openxiangda support status --json 查看实际状态;需要时执行 support login,SSH 可使用 --device。",
167
+ "尚不能访问支持群时运行 support join,在钉钉完成加入后重新检查状态;已有授权案例继续引用原消息跟进。"
168
168
  ],
169
169
  "knownLimitations": [
170
- "当前仅同步应用管理员新增授权,不同步撤销或删除。",
171
- "导入推送当前分支,不自动迁移全部历史分支、标签及 LFS 对象。",
172
- "制品仍在本地构建;提交存在性核验不构成服务端构建证明。",
173
- "新源码功能不依赖 Studio;Studio 旧代码移除属于独立后续工作。"
170
+ "可读取群仅证明当前账号的访问能力,不能单独证明群成员身份或组织限制;命令返回独立验证状态。",
171
+ "技能不会唤醒已经结束的开发任务,后台继续需要宿主实际监听或定时任务。",
172
+ "安装、授权和群访问依赖网络及组织配置;新的真实用户入群与双方协作须单独现场验收,替身测试不构成真人验收。",
173
+ "本包提供接入与协作指导,常驻支持服务及知识库由各自私有服务独立部署。"
174
174
  ],
175
175
  "issues": [],
176
- "sha256": "f7341eb21e1489c684ee03eed31568063dfad01bfe345f914552bd51b7357f84",
177
- "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.1.1"
176
+ "sha256": "638817e7e1d8c9cb99de6b9d41716828d241e31905c9bd0565cb94e43b9ee991",
177
+ "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.2.0"
178
178
  },
179
179
  "scripts": {
180
180
  "build": "node ../../scripts/prune-package-dist.mjs && tsc -p tsconfig.json && node scripts/copy-assets.mjs",
@@ -0,0 +1,48 @@
1
+ {
2
+ "schemaVersion": "openxiangda.release-notes/v1",
3
+ "version": "2.2.0",
4
+ "title": "OpenXiangda 内部支持协作接入",
5
+ "status": "reviewed",
6
+ "summary": "统一入口为 V1/V2 应用开发提供内部支持协作接入,检查官方 DWS 与技能,复用精确账号,并提供授权、打开群邀请和持续案例跟进入口。",
7
+ "newFeatures": [
8
+ "新增 support status、bootstrap、login、join,应用创建和技能刷新后检查支持通道接入状态。",
9
+ "缺少 DWS 时安装官方稳定版 1.0.61,并安装官方完整 multi 技能;已有可用稳定版本可以复用。",
10
+ "增加跨 V1/V2 支持技能,涵盖平台契约咨询、源码诊断上下文、产品架构评审和已授权案例的引用跟进。",
11
+ "使用外置支持通道配置,按原始邀请链接打开钉钉,由用户完成加入。"
12
+ ],
13
+ "fixes": [
14
+ "支持接入失败或等待用户操作时保留独立状态,不改变应用创建成功结果;进度写入 stderr,保持引擎 JSON 输出。",
15
+ "使用保存的精确 DWS profile,不按显示姓名或列表顺序猜测账号,不切换平台或 DWS 全局登录身份。",
16
+ "命令发现由统一分发层维护,技能校验显式接收分发命令,保持 V2 核心与两代引擎边界。"
17
+ ],
18
+ "affectedUsers": [
19
+ "通过统一入口创建应用、初始化工作区或刷新技能的 V1/V2 开发者。",
20
+ "在内部支持通道中继续已授权平台咨询和故障排查的开发 AI。"
21
+ ],
22
+ "compatibility": {
23
+ "node": ">=24",
24
+ "workspaceGenerations": [
25
+ "v1",
26
+ "v2"
27
+ ],
28
+ "v1Policy": "V1 项目保持原代际、版本锁定与登录绑定;支持接入由统一分发入口提供。",
29
+ "platformPolicy": "本次工具发布不要求更新平台镜像;源码诊断仍需目标平台已启用正式源码托管并授予当前账号访问权限。",
30
+ "dws": "最低稳定版 1.0.61;OAuth 与加入群需要用户在官方流程中完成。",
31
+ "releaseChannels": "latest / stable-v2:V2 正式版;legacy-v1:V1 维护版;alpha:预发布。"
32
+ },
33
+ "upgradeSteps": [
34
+ "在 Node.js 24 环境安装统一入口:npm install -g openxiangda@2.2.0。",
35
+ "运行 openxiangda support bootstrap 检查安装与完整技能;离线或 CI 初始化可使用 --skip-support,之后再恢复。",
36
+ "运行 openxiangda support status --json 查看实际状态;需要时执行 support login,SSH 可使用 --device。",
37
+ "尚不能访问支持群时运行 support join,在钉钉完成加入后重新检查状态;已有授权案例继续引用原消息跟进。"
38
+ ],
39
+ "knownLimitations": [
40
+ "可读取群仅证明当前账号的访问能力,不能单独证明群成员身份或组织限制;命令返回独立验证状态。",
41
+ "技能不会唤醒已经结束的开发任务,后台继续需要宿主实际监听或定时任务。",
42
+ "安装、授权和群访问依赖网络及组织配置;新的真实用户入群与双方协作须单独现场验收,替身测试不构成真人验收。",
43
+ "本包提供接入与协作指导,常驻支持服务及知识库由各自私有服务独立部署。"
44
+ ],
45
+ "issues": [],
46
+ "sha256": "638817e7e1d8c9cb99de6b9d41716828d241e31905c9bd0565cb94e43b9ee991",
47
+ "url": "https://github.com/1377385356/openxiangda/releases/tag/v2.2.0"
48
+ }
@@ -4,7 +4,7 @@
4
4
  {
5
5
  "name": "openxiangda-v2",
6
6
  "description": "使用 OpenXiangda 2.0 从模糊业务想法、已有资料或具体变更出发,通过对话发现模块、完成详细产品设计,再开发、检查和交付应用。维护 1.x 应用时使用对应的 1.x 技能。",
7
- "sha256": "1c60318522970b4531455012d7a85b968bdae19c0b96e7c6a5ff53c1f6ebd981"
7
+ "sha256": "618f4dbe2de1ae08879b04bd192f1feadad24c09b5147f7f222e483e03c8f142"
8
8
  }
9
9
  ]
10
10
  }
@@ -18,10 +18,10 @@ description: 使用 OpenXiangda 2.0 从模糊业务想法、已有资料或具
18
18
  未创建工作区时使用本 Skill 随根包发布的精确版本:
19
19
 
20
20
  ```bash
21
- pnpm dlx openxiangda@2.1.1 auth status --base-url <平台地址> --json
22
- pnpm dlx openxiangda@2.1.1 login --base-url <平台地址>
23
- pnpm dlx openxiangda@2.1.1 create <应用目录> --base-url <同一平台地址>
24
- pnpm dlx openxiangda@2.1.1 skill install --force
21
+ pnpm dlx openxiangda@2.2.0 auth status --base-url <平台地址> --json
22
+ pnpm dlx openxiangda@2.2.0 login --base-url <平台地址>
23
+ pnpm dlx openxiangda@2.2.0 create <应用目录> --base-url <同一平台地址>
24
+ pnpm dlx openxiangda@2.2.0 skill install --force
25
25
  ```
26
26
 
27
27
  创建前把产品要求的目标平台明确带入命令,不从旧登录态推断站点。已有工作区从原绑定恢复,平台不一致时先解决登录与目标,不改 link 文件跨站创建。
@@ -30,6 +30,8 @@ pnpm dlx openxiangda@2.1.1 skill install --force
30
30
 
31
31
  ## 按任务选择资料
32
32
 
33
+ 平台契约疑问、诊断和产品/架构咨询使用同次分发的 `openxiangda-support` 技能。统一入口创建和技能刷新会检查 DWS;用 `support status --json` 查看待授权、待用户入群或可访问状态,独立应用工作继续推进。实际发送与后续操作遵循用户已有授权。
34
+
33
35
  只读当前任务相关专题。以下参考由中文使用文档生成,与 CLI/MCP 正文同源:
34
36
 
35
37
  | 任务 | 参考 |
@@ -11,10 +11,10 @@ OpenXiangda 2.0 默认生成 React 应用和共享契约。普通 CRUD、标准
11
11
  以下命令的版本占位符由随包资料替换为该根包的精确版本。网站源码阅读者应先确认要使用的发行版本。
12
12
 
13
13
  ```bash
14
- pnpm dlx openxiangda@2.1.1 skill install --force
15
- pnpm dlx openxiangda@2.1.1 auth status --base-url <平台地址> --json
16
- pnpm dlx openxiangda@2.1.1 login --base-url https://platform.example.com
17
- pnpm dlx openxiangda@2.1.1 create my-app --base-url https://platform.example.com
14
+ pnpm dlx openxiangda@2.2.0 skill install --force
15
+ pnpm dlx openxiangda@2.2.0 auth status --base-url <平台地址> --json
16
+ pnpm dlx openxiangda@2.2.0 login --base-url https://platform.example.com
17
+ pnpm dlx openxiangda@2.2.0 create my-app --base-url https://platform.example.com
18
18
  cd my-app
19
19
  pnpm openxiangda context --json
20
20
  pnpm openxiangda dev
@@ -26,6 +26,23 @@ pnpm openxiangda dev
26
26
 
27
27
  进入项目后使用 `pnpm openxiangda`,由项目依赖和锁文件决定版本。查看使用资料运行 `pnpm openxiangda docs`;查看单一主题运行 `pnpm openxiangda docs frontend`。安装到其他 AI 工具时使用 `skill install --destination <Skill根目录>`。
28
28
 
29
+ ### 内部支持协作
30
+
31
+ 统一入口在成功创建应用、V1 workspace init/skill bootstrap 和 skill install 后检查 DWS。缺失时安装审核过的官方稳定版和完整 multi 技能(包括 shared、misc/profile 等跨技能引用),复用可用版本;不会自动登录、加入群或发送消息。支持接入结果独立于应用创建,JSON 创建结果保持原结构,接入进度写 stderr。
32
+
33
+ ```bash
34
+ pnpm openxiangda support status --json
35
+ pnpm openxiangda support bootstrap
36
+ pnpm openxiangda support login
37
+ pnpm openxiangda support join
38
+ ```
39
+
40
+ 首次 OAuth 由用户完成,SSH 使用 `support login --device`。支持侧保存 DWS 精确 profile 指针,多账号可传 `--profile <corpId:userId>`;不修改 DWS 或平台默认账号。通道来自外置配置,`join` 原样打开邀请链接,由用户在钉钉完成加入,再执行 `status` 回读。群可访问、成员身份与组织限制分别报告;钉钉负责真实权限判断,不凭链接推定。
41
+
42
+ 等待授权、入群、网络或安装权限时可以继续独立开发。离线/CI 创建和技能安装可加 `--skip-support`,之后运行 `support bootstrap` 恢复。完整官方技能由 DWS 自身安装/备份;自动接入使用 DWS 的 all 目标检测宿主,应用技能安装的 --agent 只决定应用技能位置。`support bootstrap --force --agent <DWS目标宿主>` 可显式指定 DWS 宿主并刷新。`support --help` 查看命令,支持命令无需应用依赖已安装;独立 V1 引擎没有这些分发命令,应使用新版统一入口,保留原 V1 项目版本。
43
+
44
+ 本地接入指针位于 `${XDG_CONFIG_HOME:-~/.config}/openxiangda/support.json`,只保存 profile 和技能安装回执,不保存 Token。普通支持咨询与持续讨论遵循安装的 `openxiangda-support` 技能及用户授权;后台唤醒须有真实宿主监听或定时任务。
45
+
29
46
  ## 项目结构 {#workspace}
30
47
 
31
48
  ```text
@@ -97,9 +114,9 @@ MCP 的 `docs_read` 可以读取本说明,当前没有独立的源码操作 MC
97
114
  无需本地工作区,使用本 Skill 随包精确版本或已安装的对应 CLI:
98
115
 
99
116
  ```bash
100
- pnpm dlx openxiangda@2.1.1 auth status --base-url <平台> --json
101
- pnpm dlx openxiangda@2.1.1 source resolve <仓库URL> --base-url <平台> --json
102
- pnpm dlx openxiangda@2.1.1 source clone <仓库URL> <新目录> --base-url <平台> --json
117
+ pnpm dlx openxiangda@2.2.0 auth status --base-url <平台> --json
118
+ pnpm dlx openxiangda@2.2.0 source resolve <仓库URL> --base-url <平台> --json
119
+ pnpm dlx openxiangda@2.2.0 source clone <仓库URL> <新目录> --base-url <平台> --json
103
120
  ```
104
121
 
105
122
  登录缺失或站点不匹配时,先按该平台执行 login。resolve 根据平台已经登记的绑定返回