@ran-sh/dsh-crew 2.2.0 → 2.2.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/.claude-plugin/plugin.json +1 -1
- package/official-web-bridge/lib/client.js +1 -1
- package/official-web-bridge/overlay-entry.mjs +5 -1
- package/official-web-bridge/package.json +1 -1
- package/package.json +1 -1
- package/scripts/setup.mjs +39 -33
- package/scripts/verify-npm-install.mjs +2 -2
- package/src/adaptive-routing.mjs +2 -2
- package/src/ci-evidence.mjs +1 -1
- package/src/client/quick-panel.tsx +6 -2
- package/src/credential-lifecycle.mjs +0 -3
- package/src/credential-purge-state.mjs +1 -5
- package/src/credential-reference.mjs +0 -8
- package/src/crew-workspaces.mjs +1 -1
- package/src/delivery.mjs +2 -2
- package/src/dsh-cli-runtime.mjs +227 -30
- package/src/extension-contract.mjs +1 -1
- package/src/failure-classification.mjs +0 -10
- package/src/hub/entry.mjs +1 -1
- package/src/hub/index.mjs +57 -7
- package/src/i18n.mjs +0 -2
- package/src/install/crew-paths.mjs +16 -11
- package/src/install/crew-skill.mjs +1 -1
- package/src/install/desktop-profile.mjs +168 -0
- package/src/install/harness-config-import.mjs +204 -0
- package/src/install/install-legacy.mjs +3 -3
- package/src/install/npx-lifecycle.mjs +210 -125
- package/src/install/official-web.mjs +1 -1
- package/src/install/windows-startup.mjs +5 -5
- package/src/install/windows-supervisor-adapter.mjs +3 -2
- package/src/install/windows-supervisor-lifecycle.mjs +2 -2
- package/src/install/zcode.mjs +0 -2
- package/src/job-contracts.mjs +1 -1
- package/src/job-identity.mjs +2 -2
- package/src/jobs.mjs +3 -4
- package/src/model-routing.mjs +3 -4
- package/src/model-schedule.mjs +0 -1
- package/src/official-web-bridge.mjs +3 -1
- package/src/policy-legacy.mjs +10 -12
- package/src/provider-inventory.mjs +1 -1
- package/src/provider-layer-migration.mjs +0 -2
- package/src/provider-settings-store.mjs +19 -1
- package/src/readiness-matrix.mjs +1 -1
- package/src/release-in-use.mjs +1 -1
- package/src/role-profiles.mjs +2 -2
- package/src/runtime-controls.mjs +1 -1
- package/src/runtime-identity.mjs +1 -1
- package/src/status-shard.mjs +1 -1
- package/src/supervisor/restart-request.mjs +4 -4
- package/src/workflow-runtime.mjs +1 -2
- package/src/workspace-context.mjs +2 -2
- package/src/workspace-isolation.mjs +4 -4
- package/src/workspace-lock.mjs +2 -3
- package/scripts/policy-probe.mjs +0 -101
- package/scripts/verify-crew-ui-polish.mjs +0 -145
- package/scripts/verify-history-ui.mjs +0 -98
- package/scripts/verify-installer-fix.mjs +0 -26
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
// Isolated browser acceptance of the shipped modules. All HTTP requests are
|
|
2
|
-
// intercepted: no real Crew configuration or host browser profile is accessed.
|
|
3
|
-
import assert from 'node:assert/strict';
|
|
4
|
-
import { createRequire } from 'node:module';
|
|
5
|
-
import { readFileSync, mkdtempSync } from 'node:fs';
|
|
6
|
-
import { tmpdir } from 'node:os';
|
|
7
|
-
import { join, dirname } from 'node:path';
|
|
8
|
-
import { fileURLToPath } from 'node:url';
|
|
9
|
-
|
|
10
|
-
const require = createRequire(import.meta.url);
|
|
11
|
-
const { chromium } = require(process.env.CREW_PLAYWRIGHT_MODULE || 'playwright');
|
|
12
|
-
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
13
|
-
const output = mkdtempSync(join(tmpdir(), 'crew-ui-polish-'));
|
|
14
|
-
const browser = await chromium.launch({ headless: true,
|
|
15
|
-
...(process.env.CREW_BROWSER_EXE ? { executablePath: process.env.CREW_BROWSER_EXE } : {}),
|
|
16
|
-
});
|
|
17
|
-
const errors = [];
|
|
18
|
-
const results = [];
|
|
19
|
-
const baseConfig = {
|
|
20
|
-
subagents_enabled: true, collaboration_mode: 'balanced', main_agent_mode: 'direct-allowed',
|
|
21
|
-
flash_state: 'auto', pro_state: 'auto', isolation: 'worktree', max_parallel: 16,
|
|
22
|
-
default_timeout_seconds: 1800,
|
|
23
|
-
default_tier: 'flash', default_effort: 'max', mode: 'auto',
|
|
24
|
-
vision_enabled: true, imagegen_enabled: false, vision_provider: 'example', imagegen_provider: 'example',
|
|
25
|
-
flash_model_priority: [
|
|
26
|
-
{ provider: 'example-provider', model: 'long-model-name-for-responsive-layout-check' },
|
|
27
|
-
{ provider: 'example-provider', model: 'second-model' },
|
|
28
|
-
], pro_model_priority: [],
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
async function mount(surface, locale = 'zh') {
|
|
32
|
-
const page = await browser.newPage({ viewport: { width: 1000, height: 1000 } });
|
|
33
|
-
page.on('pageerror', error => errors.push(error.message));
|
|
34
|
-
let config = structuredClone(baseConfig);
|
|
35
|
-
const patches = [];
|
|
36
|
-
await page.route('**/*', async route => {
|
|
37
|
-
const request = route.request();
|
|
38
|
-
const url = new URL(request.url());
|
|
39
|
-
if (url.origin !== 'http://127.0.0.1:41790') return route.abort();
|
|
40
|
-
if (url.pathname === '/') return route.fulfill({ contentType: 'text/html', body:
|
|
41
|
-
'<!doctype html><html><head><meta charset="utf-8"><style>body{margin:0;background:#f6f7f9;color:#20252b;font-family:Arial,sans-serif}main{max-width:760px;margin:24px auto;padding:20px;background:white;border-radius:16px}@media(prefers-color-scheme:dark){body{background:#16191d;color:#e5e7eb}main{background:#20242a}}</style></head><body><main id="root"></main></body></html>' });
|
|
42
|
-
const suffix = url.pathname.replace('/_dsh/dsh-crew', '');
|
|
43
|
-
let body = { ok: true };
|
|
44
|
-
if (suffix === '/bridge-status') body = surface === 'quick' ? { ok: true, surface: 'official-bridge' } : { ok: false };
|
|
45
|
-
else if (suffix === '/runtime') body = { ok: true, service: 'dsh-crew-hub', profile: 'dsh-crew', listen_port: 3210, execution_plane: 'hub-3210', runtime_version: 'test', protocol_version: 1 };
|
|
46
|
-
else if (suffix === '/quick-config' && request.method() === 'POST') {
|
|
47
|
-
const patch = request.postDataJSON(); patches.push(patch); config = { ...config, ...patch }; body = { ok: true, config };
|
|
48
|
-
} else if (suffix === '/quick-status' || suffix === '/config') body = { ok: true, config };
|
|
49
|
-
else if (suffix === '/jobs') body = { ok: true, jobs: [] };
|
|
50
|
-
else if (suffix === '/install/status') body = { ok: true, status: { codex: { ready: true, installed: true }, claude: { ready: true, installed: true }, zcode: { ready: true, installed: true } } };
|
|
51
|
-
else if (suffix === '/model-catalog' || suffix === '/providers') body = { ok: true, providers: [], records: [] };
|
|
52
|
-
return route.fulfill({ contentType: 'application/json', body: JSON.stringify(body) });
|
|
53
|
-
});
|
|
54
|
-
await page.goto('http://127.0.0.1:41790/');
|
|
55
|
-
await page.waitForLoadState('networkidle');
|
|
56
|
-
await page.addScriptTag({ content: readFileSync(join(root, 'node_modules/react/umd/react.development.js'), 'utf8') });
|
|
57
|
-
await page.addScriptTag({ content: readFileSync(join(root, 'node_modules/react-dom/umd/react-dom.development.js'), 'utf8') });
|
|
58
|
-
await page.evaluate(locale => {
|
|
59
|
-
const ctx = {
|
|
60
|
-
on: () => () => {}, locale: { getLocale: () => ({ active: locale }) },
|
|
61
|
-
slots: { inject: (_name, callback) => callback(), register: (_meta, render) => { window.crewRoot.render(render()); return () => {}; } },
|
|
62
|
-
};
|
|
63
|
-
window.crewRoot = window.ReactDOM.createRoot(document.getElementById('root'));
|
|
64
|
-
window.__ModuleLoader__ = { load({ factory }) {
|
|
65
|
-
const jsx = (tag, props, key) => window.React.createElement(tag, { ...props, ...(key === undefined ? {} : { key }) });
|
|
66
|
-
factory(id => {
|
|
67
|
-
if (id === 'react') return window.React;
|
|
68
|
-
if (id === 'react-dom' || id === 'react-dom/client') return window.ReactDOM;
|
|
69
|
-
if (id === 'react/jsx-runtime') return { jsx, jsxs: jsx, Fragment: window.React.Fragment };
|
|
70
|
-
throw Error('Unexpected fixture dependency: ' + id);
|
|
71
|
-
}).apply(ctx);
|
|
72
|
-
} };
|
|
73
|
-
}, locale);
|
|
74
|
-
await page.addScriptTag({ content: readFileSync(join(root, surface === 'quick' ? 'official-web-bridge/lib/client.js' : 'lib/client.js'), 'utf8') });
|
|
75
|
-
try { await page.locator('.crew-panel-header').waitFor({ timeout: 10000 }); }
|
|
76
|
-
catch (error) {
|
|
77
|
-
console.error(JSON.stringify({ surface, errors, body: (await page.locator('body').innerText()).slice(0, 1600) }));
|
|
78
|
-
throw error;
|
|
79
|
-
}
|
|
80
|
-
return { page, patches };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
const { page, patches } = await mount('quick');
|
|
85
|
-
await page.getByRole('link', { name: '打开 3210 后台 →' }).waitFor();
|
|
86
|
-
assert.equal(await page.getByRole('link', { name: '打开 3210 后台 →' }).getAttribute('href'), 'http://127.0.0.1:3210/');
|
|
87
|
-
await page.screenshot({ path: join(output, '3080-quick.png'), fullPage: true });
|
|
88
|
-
const worker = page.locator('details.crew-quick-group').filter({ has: page.locator('summary strong', { hasText: 'Worker / Flash' }) });
|
|
89
|
-
await worker.getByRole('button', { name: '下移 long-model-name-for-responsive-layout-check' }).click();
|
|
90
|
-
await page.waitForFunction(() => document.querySelector('.crew-quick-list li .crew-quick-model-name')?.textContent === 'second-model');
|
|
91
|
-
assert.equal(patches.at(-1).flash_model_priority[0].model, 'second-model');
|
|
92
|
-
await worker.getByRole('button', { name: '从优先级移除 second-model' }).click();
|
|
93
|
-
await worker.locator('li').first().waitFor();
|
|
94
|
-
await worker.locator('.crew-quick-add > summary').click();
|
|
95
|
-
await worker.getByRole('textbox', { name: 'Worker / Flash Provider', exact: true }).fill('added-provider');
|
|
96
|
-
await worker.getByRole('textbox', { name: 'Worker / Flash 模型', exact: true }).fill('added-model');
|
|
97
|
-
await worker.getByRole('button', { name: '+ 添加模型', exact: true }).click();
|
|
98
|
-
await worker.getByText('added-model', { exact: true }).waitFor();
|
|
99
|
-
assert.equal(patches.at(-1).flash_model_priority.at(-1).model, 'added-model');
|
|
100
|
-
const reviewer = page.locator('details.crew-quick-group').filter({ has: page.locator('summary strong', { hasText: 'Reviewer / Pro' }) });
|
|
101
|
-
await reviewer.locator(':scope > summary').click();
|
|
102
|
-
await reviewer.locator('.crew-quick-add > summary').click();
|
|
103
|
-
await reviewer.getByRole('textbox', { name: 'Reviewer / Pro Provider', exact: true }).fill('review-provider');
|
|
104
|
-
await reviewer.getByRole('textbox', { name: 'Reviewer / Pro 模型', exact: true }).fill('review-model');
|
|
105
|
-
await reviewer.getByRole('button', { name: '+ 添加模型', exact: true }).click();
|
|
106
|
-
await reviewer.getByText('review-model', { exact: true }).waitFor();
|
|
107
|
-
await page.waitForFunction(() => !document.querySelector('input[type=checkbox]').disabled);
|
|
108
|
-
assert.notEqual(await reviewer.getAttribute('open'), null, 'Pro stays expanded after save rerenders');
|
|
109
|
-
const multimodal = page.locator('details.crew-quick-group').filter({ has: page.locator('summary strong', { hasText: '多模态' }) });
|
|
110
|
-
await multimodal.locator(':scope > summary').click();
|
|
111
|
-
await page.getByRole('checkbox', { name: '启用子 Agent' }).uncheck();
|
|
112
|
-
await page.waitForFunction(() => !document.querySelector('input[type=checkbox]').disabled);
|
|
113
|
-
assert.equal(patches.at(-1).subagents_enabled, false);
|
|
114
|
-
assert.notEqual(await reviewer.getAttribute('open'), null, 'Pro stays expanded after other settings change');
|
|
115
|
-
assert.notEqual(await multimodal.getAttribute('open'), null, 'Multimodal stays expanded after other settings change');
|
|
116
|
-
await worker.locator(':scope > summary').click();
|
|
117
|
-
assert.equal(await worker.getAttribute('open'), null);
|
|
118
|
-
await page.setViewportSize({ width: 420, height: 900 });
|
|
119
|
-
await worker.locator(':scope > summary').click();
|
|
120
|
-
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), true);
|
|
121
|
-
await page.screenshot({ path: join(output, '3080-narrow.png'), fullPage: true });
|
|
122
|
-
await page.emulateMedia({ colorScheme: 'dark' });
|
|
123
|
-
await page.screenshot({ path: join(output, '3080-dark.png'), fullPage: true });
|
|
124
|
-
results.push('quick: reorder/remove/add/toggle/collapse; Pro and multimodal stay open across save rerenders; narrow/dark');
|
|
125
|
-
await page.close();
|
|
126
|
-
|
|
127
|
-
const { page: backend } = await mount('full');
|
|
128
|
-
const backLink = backend.getByRole('link', { name: '打开 3080 官方界面 →' });
|
|
129
|
-
assert.equal(await backLink.getAttribute('href'), 'http://127.0.0.1:3080/');
|
|
130
|
-
await backend.getByRole('button', { name: '全部折叠', exact: true }).click();
|
|
131
|
-
await backend.screenshot({ path: join(output, '3210-backend.png'), fullPage: true });
|
|
132
|
-
await backend.getByRole('button', { name: '全部展开', exact: true }).click();
|
|
133
|
-
assert.equal(await backend.locator('.crew-section-trigger[aria-expanded=false]').count(), 0);
|
|
134
|
-
await backend.getByRole('button', { name: '全部折叠', exact: true }).click();
|
|
135
|
-
await backend.setViewportSize({ width: 420, height: 900 });
|
|
136
|
-
assert.equal(await backend.evaluate(() => document.documentElement.scrollWidth <= innerWidth), true);
|
|
137
|
-
await backend.screenshot({ path: join(output, '3210-narrow.png'), fullPage: true });
|
|
138
|
-
results.push('backend: reciprocal link/expand all/collapse all/narrow');
|
|
139
|
-
await backend.close();
|
|
140
|
-
const { page: english } = await mount('quick', 'en');
|
|
141
|
-
await english.getByRole('button', { name: 'Move down long-model-name-for-responsive-layout-check' }).waitFor();
|
|
142
|
-
await english.close();
|
|
143
|
-
assert.deepEqual(errors, []);
|
|
144
|
-
console.log(JSON.stringify({ ok: true, results, consoleErrors: errors, screenshots: output }));
|
|
145
|
-
} finally { await browser.close(); }
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
// Full browser -> HTTP handler -> controller -> executor -> real TEMP files.
|
|
2
|
-
// Supervisor process control is simulated; never addresses real 3080/3210.
|
|
3
|
-
import assert from 'node:assert/strict';
|
|
4
|
-
import { createRequire } from 'node:module';
|
|
5
|
-
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, rmSync } from 'node:fs';
|
|
6
|
-
import { join, dirname } from 'node:path';
|
|
7
|
-
import { tmpdir } from 'node:os';
|
|
8
|
-
import { fileURLToPath } from 'node:url';
|
|
9
|
-
import { Readable } from 'node:stream';
|
|
10
|
-
import { createHistoryService } from '../src/history/service.mjs';
|
|
11
|
-
import { registerHistoryHttp } from '../src/history/http.mjs';
|
|
12
|
-
import { runHistoryOperation } from '../src/history/operation.mjs';
|
|
13
|
-
import { CREW_SESSIONS_REL } from '../src/install/crew-paths.mjs';
|
|
14
|
-
|
|
15
|
-
const require = createRequire(import.meta.url);
|
|
16
|
-
const { chromium } = require(process.env.CREW_PLAYWRIGHT_MODULE || 'playwright');
|
|
17
|
-
const repo = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
18
|
-
const root = mkdtempSync(join(tmpdir(), 'crew-history-e2e-'));
|
|
19
|
-
const output = mkdtempSync(join(tmpdir(), 'crew-history-ui-shots-'));
|
|
20
|
-
mkdirSync(join(root, 'harness/storages'), { recursive: true });
|
|
21
|
-
mkdirSync(join(root, CREW_SESSIONS_REL, 'example/session-test'), { recursive: true });
|
|
22
|
-
const file = join(root, CREW_SESSIONS_REL, 'example/session-test/session.jsonl');
|
|
23
|
-
writeFileSync(file, 'DISPOSABLE TEST CONVERSATION');
|
|
24
|
-
writeFileSync(join(root, 'harness/storages/workspace.json'), JSON.stringify({ unit: { name: 'workspace', version: 2 }, global: { initialized: true, workspaceIds: ['test-workspace'], archivedSessionIds: [] }, tables: { workspaces: { 'test-workspace': { path: '/example/project', title: 'Disposable workspace', createdAt: '2026-01-01T00:00:00Z', updatedAt: '2026-01-01T00:00:00Z', sessionIds: ['session-test'] } } } }));
|
|
25
|
-
const errors = []; let stopped = false; let operationPromise;
|
|
26
|
-
const pause = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
27
|
-
const service = createHistoryService({ crewRoot: root, runtimeId: 'test', agents: { list: () => [], create: async () => ({}) },
|
|
28
|
-
persistence: { supportsRawArtifacts: true, listSnapshots: async () => existsSync(file) ? [{ header: { id: 'session-test', createdAt: 1767225600000 }, revision: 'r1' }] : [], locate: () => ({ kind: 'jsonl', path: file }) },
|
|
29
|
-
launch: async id => { operationPromise = pause(200).then(() => runHistoryOperation({ crewRoot: root, id,
|
|
30
|
-
acquire: () => ({ ok: true }), release: () => ({ ok: true }), checkFence: () => service.fencedCheck(), assertStopped: () => stopped,
|
|
31
|
-
supervisor: { stopOwnedBackend: async () => { stopped = true; await pause(150); return { ok: true }; }, startOwnedBackend: async () => { await pause(150); stopped = false; return { ok: true }; } }, verifyRunning: async () => !stopped,
|
|
32
|
-
})); operationPromise.catch(error => errors.push(error.message)); },
|
|
33
|
-
});
|
|
34
|
-
let historyHandler;
|
|
35
|
-
registerHistoryHttp({ register: route => { historyHandler = route.handler; return () => {}; } }, service);
|
|
36
|
-
const browser = await chromium.launch({ headless: true, ...(process.env.CREW_BROWSER_EXE ? { executablePath: process.env.CREW_BROWSER_EXE } : {}) });
|
|
37
|
-
try {
|
|
38
|
-
const page = await browser.newPage({ viewport: { width: 1100, height: 1100 } });
|
|
39
|
-
page.on('pageerror', error => errors.push(error.message));
|
|
40
|
-
page.on('dialog', dialog => dialog.accept());
|
|
41
|
-
await page.route('**/*', async route => {
|
|
42
|
-
const request = route.request(); const url = new URL(request.url());
|
|
43
|
-
if (url.origin !== 'http://127.0.0.1:41791') return route.abort();
|
|
44
|
-
if (url.pathname === '/') return route.fulfill({ contentType: 'text/html', body: '<html><head><meta charset="utf-8"></head><body style="font:14px Arial;background:#f5f6f8"><main id="root" style="padding:24px;max-width:800px;margin:auto;background:white"></main></body></html>' });
|
|
45
|
-
if (url.pathname.includes('/history/')) {
|
|
46
|
-
if (stopped) return route.abort();
|
|
47
|
-
const req = Readable.from([Buffer.from(request.postData() ?? '{}')]);
|
|
48
|
-
Object.assign(req, { url: url.pathname, method: request.method(), headers: { host: url.host, origin: url.origin }, socket: { remoteAddress: '127.0.0.1' } });
|
|
49
|
-
let status, body;
|
|
50
|
-
await historyHandler(req, { writeHead: n => { status = n; }, end: s => { body = s; } });
|
|
51
|
-
return route.fulfill({ status, contentType: 'application/json', body });
|
|
52
|
-
}
|
|
53
|
-
const suffix = url.pathname.split('/').pop(); let body = { ok: true };
|
|
54
|
-
if (suffix === 'runtime') body = { ok: true, service: 'dsh-crew-hub', profile: 'dsh-crew', listen_port: 3210 };
|
|
55
|
-
if (suffix === 'bridge-status') body = { ok: false };
|
|
56
|
-
if (suffix === 'config') body = { ok: true, config: { flash_model_priority: [], pro_model_priority: [], default_timeout_seconds: 1800 } };
|
|
57
|
-
if (suffix === 'status') body = { ok: true, status: {} };
|
|
58
|
-
return route.fulfill({ contentType: 'application/json', body: JSON.stringify(body) });
|
|
59
|
-
});
|
|
60
|
-
await page.goto('http://127.0.0.1:41791/'); await page.waitForLoadState('networkidle');
|
|
61
|
-
await page.addScriptTag({ content: readFileSync(join(repo, 'node_modules/react/umd/react.development.js'), 'utf8') });
|
|
62
|
-
await page.addScriptTag({ content: readFileSync(join(repo, 'node_modules/react-dom/umd/react-dom.development.js'), 'utf8') });
|
|
63
|
-
await page.evaluate(() => {
|
|
64
|
-
const root = window.ReactDOM.createRoot(document.getElementById('root'));
|
|
65
|
-
const ctx = { on: () => () => {}, locale: { getLocale: () => ({ active: 'zh' }) }, slots: { inject: (_n, callback) => callback(), register: (_meta, render) => { root.render(render()); return () => {}; } } };
|
|
66
|
-
window.__ModuleLoader__ = { load({ factory }) { const jsx = (tag, props, key) => window.React.createElement(tag, { ...props, ...(key === undefined ? {} : { key }) }); factory(id => {
|
|
67
|
-
if (id === 'react') return window.React;
|
|
68
|
-
if (id === 'react-dom' || id === 'react-dom/client') return window.ReactDOM;
|
|
69
|
-
if (id === 'react/jsx-runtime') return { jsx, jsxs: jsx, Fragment: window.React.Fragment };
|
|
70
|
-
throw Error('Unexpected dependency');
|
|
71
|
-
}).apply(ctx); } };
|
|
72
|
-
});
|
|
73
|
-
await page.addScriptTag({ content: readFileSync(join(repo, 'lib/client.js'), 'utf8') });
|
|
74
|
-
await page.getByText('工作区与会话清理', { exact: true }).click();
|
|
75
|
-
const preview = page.getByRole('button', { name: '预览清理范围', exact: true });
|
|
76
|
-
await preview.click();
|
|
77
|
-
await page.getByText('1 个工作区 · 1 个会话', { exact: true }).first().waitFor();
|
|
78
|
-
const confirm = page.getByRole('button', { name: '确认执行', exact: true });
|
|
79
|
-
assert.equal(await confirm.isEnabled(), false);
|
|
80
|
-
await page.getByRole('checkbox', { name: '我已确认范围,并同意短暂重启 3210' }).check();
|
|
81
|
-
await page.screenshot({ path: join(output, 'preview.png'), fullPage: true });
|
|
82
|
-
await confirm.click(); await page.getByRole('button', { name: '恢复', exact: true }).waitFor({ timeout: 15000 }); await operationPromise;
|
|
83
|
-
assert.equal(existsSync(file), false);
|
|
84
|
-
await page.getByRole('button', { name: '恢复', exact: true }).click();
|
|
85
|
-
await page.waitForFunction(() => [...document.querySelectorAll('[role=status]')].some(el => el.textContent === '已完成'));
|
|
86
|
-
await operationPromise; assert.equal(readFileSync(file, 'utf8'), 'DISPOSABLE TEST CONVERSATION');
|
|
87
|
-
await page.getByRole('combobox', { name: '操作', exact: true }).selectOption('delete');
|
|
88
|
-
await page.getByRole('combobox', { name: '时间范围', exact: true }).selectOption('before');
|
|
89
|
-
await page.getByLabel('创建时间早于', { exact: true }).fill('2027-01-01T00:00');
|
|
90
|
-
await preview.click(); await page.getByRole('checkbox', { name: '我已确认范围,并同意短暂重启 3210' }).check();
|
|
91
|
-
assert.equal(await confirm.isEnabled(), false);
|
|
92
|
-
await page.getByLabel('删除确认:请输入 DELETE', { exact: true }).fill('DELETE'); await confirm.click();
|
|
93
|
-
await page.waitForFunction(() => [...document.querySelectorAll('[role=status]')].some(el => el.textContent === '已完成'));
|
|
94
|
-
await operationPromise; assert.equal(existsSync(file), false); assert.deepEqual(service.archives(), []);
|
|
95
|
-
await page.screenshot({ path: join(output, 'completed.png'), fullPage: true });
|
|
96
|
-
assert.deepEqual(errors, []);
|
|
97
|
-
console.log(JSON.stringify({ ok: true, flows: ['archive all', 'restore', 'delete before date', 'confirmation guards', 'reconnect'], screenshots: output }));
|
|
98
|
-
} finally { await browser.close(); service.dispose(); rmSync(root, { recursive: true, force: true }); }
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
// Live-verify the fixed installer against a fake home: the settings must
|
|
2
|
-
// point at the repository itself as the marketplace root (no parent-dir
|
|
3
|
-
// assumption), permission rules intact, CLI step skipped in test mode.
|
|
4
|
-
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
5
|
-
import { join } from 'node:path';
|
|
6
|
-
import { tmpdir } from 'node:os';
|
|
7
|
-
import { installClaudeCode, uninstallClaudeCode } from '../src/install/install.mjs';
|
|
8
|
-
|
|
9
|
-
const home = mkdtempSync(join(tmpdir(), 'dsh-crew-fix-test-'));
|
|
10
|
-
try {
|
|
11
|
-
const r = await installClaudeCode({ home });
|
|
12
|
-
console.log('actions:', r.actions.slice(0, 3).join(' | '));
|
|
13
|
-
const s = JSON.parse(readFileSync(join(home, '.claude', 'settings.json'), 'utf8'));
|
|
14
|
-
const mp = s.extraKnownMarketplaces['dsh-crew'];
|
|
15
|
-
const cwd = process.cwd().replaceAll('\\', '/');
|
|
16
|
-
const mpPath = String(mp?.source?.path ?? '').replaceAll('\\', '/');
|
|
17
|
-
console.log('marketplace path is repo root:', mpPath === cwd, '->', mpPath);
|
|
18
|
-
console.log('permission rules:', (s.permissions?.allow ?? []).filter((x) => String(x).includes('dsh-crew')).length);
|
|
19
|
-
console.log('cli step skipped in test mode:', r.actions.some((a) => a.includes('test mode')));
|
|
20
|
-
if (mpPath !== cwd) throw new Error('marketplace not pointing at repo root');
|
|
21
|
-
const u = await uninstallClaudeCode({ home });
|
|
22
|
-
console.log('uninstall ok:', u.ok);
|
|
23
|
-
} finally {
|
|
24
|
-
rmSync(home, { recursive: true, force: true });
|
|
25
|
-
}
|
|
26
|
-
console.log('INSTALLER FIX VERIFIED');
|