dsh-agentone 0.5.44 → 1.0.2
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 +23 -0
- package/lib/archive.js +322 -0
- package/lib/client.js +307 -0
- package/lib/defaults-sync.js +11 -1
- package/lib/drive.js +289 -0
- package/lib/index.js +160 -1
- package/lib/page.js +379 -41
- package/lib/proc.js +14 -4
- package/package.json +3 -2
- package/skills/agentone-drive/SKILL.md +71 -0
- package/skills/agentone-drive/scripts/drive.mjs +336 -0
package/lib/index.js
CHANGED
|
@@ -34,6 +34,10 @@ import { applyPlatformModel, removePlatformModel } from './model.js';
|
|
|
34
34
|
import { installPlatformSkill, listPlatformSkills, removePlatformSkill } from './skill.js';
|
|
35
35
|
import { applyPlatformSkillhub, removePlatformSkillhub } from './skillhub.js';
|
|
36
36
|
import { createProfilePlugins, validatePackage, PROTECTED_PACKAGES } from './profile-plugins.js';
|
|
37
|
+
import {
|
|
38
|
+
createDriveService, withChineseLocale, describeDrivePermission, parseLarkJson, loadDriveCache,
|
|
39
|
+
} from './drive.js';
|
|
40
|
+
import { loadArchivePref, setArchiveEnabled, syncArchiveFeature } from './archive.js';
|
|
37
41
|
import { isDesktopHost } from './desktop.js';
|
|
38
42
|
import { createDefaultsSync } from './defaults-sync.js';
|
|
39
43
|
import { canonicalSkillSlug, localSkillState } from './skill-state.js';
|
|
@@ -691,6 +695,42 @@ export function apply(ctx, config) {
|
|
|
691
695
|
`dsh-agentone: route ${path}`,
|
|
692
696
|
);
|
|
693
697
|
|
|
698
|
+
// 飞书云盘寻址(/agentone/api/drive/*)共享的 lark-cli 门与服务实例。
|
|
699
|
+
// 门返回非 null 表示不可用(state 给前端渲染引导),为 null 才继续寻址。
|
|
700
|
+
// withLatest=false:门是热路径(tab 每次打开都过),不查 npm latest。
|
|
701
|
+
const driveGate = async () => {
|
|
702
|
+
const detail = await probeCliDetail('lark-cli', { withLatest: false })
|
|
703
|
+
.then(withCliUpgradeGuard).catch(() => null);
|
|
704
|
+
if (!detail || detail.state === 'missing') return { state: 'cli_missing' };
|
|
705
|
+
if (detail.unconfigured) return { state: 'cli_unconfigured' };
|
|
706
|
+
if (detail.state !== 'authorized') return { state: 'cli_unauthorized' };
|
|
707
|
+
return null;
|
|
708
|
+
};
|
|
709
|
+
const driveService = createDriveService({
|
|
710
|
+
run: (args, options) => runCliTool('lark-cli', args, options),
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* 「云盘没权限」的第四态:前三态只管装/配/授权,已授权但缺云盘 scope 时
|
|
715
|
+
* 寻址会抛 LarkError。这里把它映射成独立 state + 责任方(用户可自助重新
|
|
716
|
+
* 授权 / 只能管理员开通),而不是把 lark-cli 的英文原文当 502 丢给用户。
|
|
717
|
+
* 返回 null 表示不是权限问题(按普通失败处理)。
|
|
718
|
+
*/
|
|
719
|
+
const drivePermissionState = async (error) => {
|
|
720
|
+
const mapped = await describeDrivePermission(error, {
|
|
721
|
+
// auth scopes:应用侧已开通的 scope 清单(输出无 ok 壳,parseLarkJson
|
|
722
|
+
// 会原样返回对象);失败时 describeDrivePermission 按可自助处理。
|
|
723
|
+
runAppScopes: async () => {
|
|
724
|
+
const { stdout } = await runCliTool('lark-cli', ['auth', 'scopes', '--json']);
|
|
725
|
+
return parseLarkJson(stdout)?.userScopes || null;
|
|
726
|
+
},
|
|
727
|
+
});
|
|
728
|
+
if (mapped) {
|
|
729
|
+
log?.info?.(`[dsh-agentone] drive permission gap: ${mapped.state} (${mapped.missing_scopes.join(',') || 'unspecified'})`);
|
|
730
|
+
}
|
|
731
|
+
return mapped;
|
|
732
|
+
};
|
|
733
|
+
|
|
694
734
|
register('/agentone/', (request, response) => {
|
|
695
735
|
if (request.method !== 'GET') {
|
|
696
736
|
response.writeHead(405, { allow: 'GET' });
|
|
@@ -1057,7 +1097,7 @@ export function apply(ctx, config) {
|
|
|
1057
1097
|
invalidateStatusCache();
|
|
1058
1098
|
sendJson(response, 200, { ok: true, installed: result?.installed ?? 0, failed: result?.failed ?? 0 });
|
|
1059
1099
|
} catch (error) {
|
|
1060
|
-
sendJson(response, 502, { error: error?.message || '
|
|
1100
|
+
sendJson(response, 502, { error: error?.message || '同步技能失败' });
|
|
1061
1101
|
}
|
|
1062
1102
|
});
|
|
1063
1103
|
|
|
@@ -1548,6 +1588,121 @@ export function apply(ctx, config) {
|
|
|
1548
1588
|
}
|
|
1549
1589
|
});
|
|
1550
1590
|
|
|
1591
|
+
register('/agentone/api/drive/home', async (request, response) => {
|
|
1592
|
+
if (request.method !== 'GET') {
|
|
1593
|
+
response.writeHead(405, { allow: 'GET' });
|
|
1594
|
+
response.end();
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
try {
|
|
1598
|
+
// lark-cli 三态门与 CLI 页同口径(missing / unconfigured / unauthorized),
|
|
1599
|
+
// 前端按 state 渲染引导;寻址细节见 lib/drive.js 头注释。
|
|
1600
|
+
const gate = await driveGate();
|
|
1601
|
+
if (gate) {
|
|
1602
|
+
sendJson(response, 200, gate);
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
const folder = await driveService.resolveAgentOneFolder({ config });
|
|
1606
|
+
// 带上中文界面参数:缓存里可能是修复前存的裸链接,统一在出口补
|
|
1607
|
+
sendJson(response, 200, { state: 'ready', ...folder, url: withChineseLocale(folder.url) });
|
|
1608
|
+
} catch (error) {
|
|
1609
|
+
const permission = await drivePermissionState(error);
|
|
1610
|
+
if (permission) {
|
|
1611
|
+
sendJson(response, 200, permission);
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
log?.warn?.(`[dsh-agentone] drive home failed: ${error?.message || error}`);
|
|
1615
|
+
sendJson(response, 502, { error: '获取云盘文件夹失败,请稍后重试' });
|
|
1616
|
+
}
|
|
1617
|
+
});
|
|
1618
|
+
|
|
1619
|
+
register('/agentone/api/drive/home/resolve', async (request, response) => {
|
|
1620
|
+
if (request.method !== 'POST') {
|
|
1621
|
+
response.writeHead(405, { allow: 'POST' });
|
|
1622
|
+
response.end();
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
try {
|
|
1626
|
+
const gate = await driveGate();
|
|
1627
|
+
if (gate) {
|
|
1628
|
+
sendJson(response, 200, gate);
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
const body = await readJsonBody(request);
|
|
1632
|
+
const adoptUrl = String(body.adopt_url || '').trim().slice(0, 1000);
|
|
1633
|
+
const folder = await driveService.resolveAgentOneFolder({ config, force: true, adoptUrl });
|
|
1634
|
+
sendJson(response, 200, { state: 'ready', ...folder, url: withChineseLocale(folder.url) });
|
|
1635
|
+
} catch (error) {
|
|
1636
|
+
const permission = await drivePermissionState(error);
|
|
1637
|
+
if (permission) {
|
|
1638
|
+
sendJson(response, 200, permission);
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
log?.warn?.(`[dsh-agentone] drive resolve failed: ${error?.message || error}`);
|
|
1642
|
+
sendJson(response, 502, { error: '定位云盘文件夹失败,请稍后重试' });
|
|
1643
|
+
}
|
|
1644
|
+
});
|
|
1645
|
+
|
|
1646
|
+
// 归档开关:AI 产出的飞书云文档是否自动归入云盘文件夹。
|
|
1647
|
+
// 开 = 注入 AGENTS.md 指令块 + 安装归档技能;关 = 撤销两者。
|
|
1648
|
+
register('/agentone/api/drive/archive', async (request, response) => {
|
|
1649
|
+
if (request.method === 'GET') {
|
|
1650
|
+
try {
|
|
1651
|
+
const pref = await loadArchivePref(config);
|
|
1652
|
+
sendJson(response, 200, { enabled: pref.enabled });
|
|
1653
|
+
} catch (error) {
|
|
1654
|
+
log?.warn?.(`[dsh-agentone] read archive pref failed: ${error?.message || error}`);
|
|
1655
|
+
sendJson(response, 502, { error: '读取归档开关失败,请稍后重试' });
|
|
1656
|
+
}
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
if (request.method !== 'POST') {
|
|
1660
|
+
response.writeHead(405, { allow: 'GET, POST' });
|
|
1661
|
+
response.end();
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
try {
|
|
1665
|
+
const body = await readJsonBody(request);
|
|
1666
|
+
const result = await setArchiveEnabled(config, body.enabled === true, log);
|
|
1667
|
+
if (!result.ok) {
|
|
1668
|
+
// 同名技能目录被用户占用:不改开关,明确告知处理方式
|
|
1669
|
+
sendJson(response, 409, { error: result.error });
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
sendJson(response, 200, { enabled: result.enabled });
|
|
1673
|
+
} catch (error) {
|
|
1674
|
+
log?.warn?.(`[dsh-agentone] set archive failed: ${error?.message || error}`);
|
|
1675
|
+
sendJson(response, 502, { error: '设置归档开关失败,请稍后重试' });
|
|
1676
|
+
}
|
|
1677
|
+
});
|
|
1678
|
+
|
|
1679
|
+
// 云盘页签自动刷新:轮询根清单指纹(10s 节流缓存,多个客户端共享)。
|
|
1680
|
+
// 指纹直接用 drive.json 缓存的 token,不做可用性探测(省一次 API 调用);
|
|
1681
|
+
// 缓存缺失 / lark-cli 不可用 / 任何失败都返回 rev:null,前端按「未知,不动」。
|
|
1682
|
+
let driveRevCheckedAt = 0;
|
|
1683
|
+
let driveRevValue = null;
|
|
1684
|
+
register('/agentone/api/drive/rev', async (request, response) => {
|
|
1685
|
+
if (request.method !== 'GET') {
|
|
1686
|
+
response.writeHead(405, { allow: 'GET' });
|
|
1687
|
+
response.end();
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
try {
|
|
1691
|
+
if (Date.now() - driveRevCheckedAt >= 10_000) {
|
|
1692
|
+
driveRevCheckedAt = Date.now();
|
|
1693
|
+
const cache = await loadDriveCache(config);
|
|
1694
|
+
const token = cache?.agentone_folder?.token;
|
|
1695
|
+
driveRevValue = token ? await driveService.fingerprintFolder(token) : null;
|
|
1696
|
+
}
|
|
1697
|
+
sendJson(response, 200, { rev: driveRevValue });
|
|
1698
|
+
} catch (error) {
|
|
1699
|
+
// 失败同样走 10s 节流(时间戳已前移),rev 归 null 让前端静默不动
|
|
1700
|
+
driveRevValue = null;
|
|
1701
|
+
log?.warn?.(`[dsh-agentone] drive rev failed: ${error?.message || error}`);
|
|
1702
|
+
sendJson(response, 200, { rev: null });
|
|
1703
|
+
}
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1551
1706
|
register('/agentone/api/plan', async (request, response) => {
|
|
1552
1707
|
if (request.method !== 'GET') {
|
|
1553
1708
|
response.writeHead(405, { allow: 'GET' });
|
|
@@ -1643,6 +1798,10 @@ export function apply(ctx, config) {
|
|
|
1643
1798
|
ensureLanguageDefault(config, log).catch((error) => {
|
|
1644
1799
|
log.warn(`[dsh-agentone] language default check failed: ${error?.message || error}`);
|
|
1645
1800
|
});
|
|
1801
|
+
// 云盘归档开关落盘在 storeDir:启动按开关收敛(用户改过 AGENTS.md / 技能目录也对齐)
|
|
1802
|
+
syncArchiveFeature(config, log).catch((error) => {
|
|
1803
|
+
log.warn(`[dsh-agentone] drive archive sync failed: ${error?.message || error}`);
|
|
1804
|
+
});
|
|
1646
1805
|
(async () => {
|
|
1647
1806
|
// 托管实例先把 Hub 注入的凭据落盘,再走既有的「已登录」启动流程。
|
|
1648
1807
|
await bootstrapInstanceCredentials(config, log).catch((error) => {
|