e10-ebuilder-prototype 0.5.5 → 0.5.7
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 +45 -13
- package/dist/archive.d.ts +1 -1
- package/dist/archive.js +3 -0
- package/dist/capture.js +1 -1
- package/dist/common.d.ts +2 -1
- package/dist/common.js +24 -4
- package/dist/form-behavior-runtime.d.mts +1 -0
- package/dist/form-behavior-runtime.mjs +681 -0
- package/dist/form-behavior.d.ts +11 -0
- package/dist/form-behavior.js +169 -0
- package/dist/form-context.d.ts +4 -0
- package/dist/form-context.js +7 -5
- package/dist/form-generation.d.ts +24 -0
- package/dist/form-generation.js +367 -0
- package/dist/form-runtime.mjs +11 -2
- package/dist/host-ledger.d.ts +23 -0
- package/dist/host-ledger.js +183 -0
- package/dist/host-watch.d.ts +102 -0
- package/dist/host-watch.js +112 -0
- package/dist/html-handoff.d.ts +1 -0
- package/dist/html-handoff.js +30 -2
- package/dist/html-inspect.d.ts +2 -2
- package/dist/html-inspect.js +23 -51
- package/dist/html-interact.d.ts +36 -0
- package/dist/html-interact.js +216 -0
- package/dist/html-review-budget.d.ts +9 -0
- package/dist/html-review-budget.js +47 -0
- package/dist/html.d.ts +77 -5
- package/dist/html.js +155 -41
- package/dist/index.js +119 -44
- package/dist/model.d.ts +2 -1
- package/dist/offline-render.d.ts +11 -0
- package/dist/offline-render.js +71 -0
- package/dist/offline-store.mjs +10 -0
- package/dist/runtime-support.d.mts +29 -1
- package/dist/runtime-support.mjs +55 -5
- package/dist/site.js +66 -51
- package/dist/store.js +18 -4
- package/dist/templates/form-guide.md +6 -20
- package/dist/templates/form-task-core.md +45 -5
- package/dist/templates/index.html +5 -3
- package/dist/templates/workflow-guide.md +4 -2
- package/dist/vendor/environment-auth.d.ts +1 -0
- package/dist/vendor/environment-auth.js +4 -4
- package/docs/PROTOCOL.md +341 -39
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,27 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { hostCapabilities } from './runtime-support.mjs';
|
|
4
|
+
import { hostCapabilities, workBuddyHostBlocker, isHostDiagnosticCommand, } from './runtime-support.mjs';
|
|
5
5
|
import { parseArgs } from 'node:util';
|
|
6
6
|
import { CaptureError, errorInfo, VERSION, digest } from './common.js';
|
|
7
|
-
import {
|
|
8
|
-
import { fetchPages, fetchCatalog } from './platform.js';
|
|
9
|
-
import { capture, launchChrome } from './capture.js';
|
|
10
|
-
import { pack } from './archive.js';
|
|
11
|
-
import { inspectHtmlViewports } from './html-inspect.js';
|
|
12
|
-
import { readyHtml, acceptReadyHtml } from './html-handoff.js';
|
|
13
|
-
import { nextHtml, acceptHtml, failHtml, retryHtml, parseHostActiveTokens } from './html.js';
|
|
14
|
-
import { discoverMenus } from './menus.js';
|
|
15
|
-
import { collectForms } from './forms.js';
|
|
16
|
-
import { applicationInput } from './application.js';
|
|
17
|
-
import { readEnvironmentAuthContext, readEnvironmentAuthStatus, readActiveEnvironmentAuthProfile, setEnvironmentAuth, verifyEnvironmentAuth, listEnvironmentAuthProfiles, useEnvironmentAuthProfile, } from './vendor/environment-auth.js';
|
|
7
|
+
import { parseHostEvents, parseHostActiveTokens, updateHostLedger } from './host-ledger.js';
|
|
18
8
|
const spec = {
|
|
19
9
|
json: { type: 'boolean' },
|
|
20
10
|
brief: { type: 'boolean' },
|
|
21
11
|
refill: { type: 'boolean' },
|
|
22
12
|
'accept-ready': { type: 'boolean' },
|
|
23
13
|
'host-active-tokens': { type: 'string' },
|
|
14
|
+
'host-events': { type: 'string' },
|
|
15
|
+
'after-state': { type: 'string' },
|
|
16
|
+
'watch-ms': { type: 'string' },
|
|
24
17
|
summary: { type: 'string' },
|
|
18
|
+
'repair-reason': { type: 'string' },
|
|
19
|
+
steps: { type: 'string' },
|
|
25
20
|
help: { type: 'boolean' },
|
|
26
21
|
version: { type: 'boolean' },
|
|
27
22
|
'app-id': { type: 'string' },
|
|
@@ -44,6 +39,7 @@ const spec = {
|
|
|
44
39
|
reason: { type: 'string' },
|
|
45
40
|
};
|
|
46
41
|
let secret = '';
|
|
42
|
+
let pendingOutput;
|
|
47
43
|
const safeAuth = (s) => ({
|
|
48
44
|
authenticated: s.authenticated,
|
|
49
45
|
status: s.status,
|
|
@@ -58,9 +54,11 @@ run --app-url URL --dir DIR [--json]
|
|
|
58
54
|
init --app-id ID --dir DIR [--allow-temporary-records]
|
|
59
55
|
next|status|discover|collect|capture|pack|retry --dir DIR
|
|
60
56
|
html next --dir DIR (allocate/resume up to concurrency host-AI jobs)
|
|
61
|
-
html inspect --dir DIR --kind page|form --page-id ID --token TOKEN [--width 390]
|
|
57
|
+
html inspect --dir DIR --kind page|form --page-id ID --token TOKEN [--width 390] [--repair-reason TEXT]
|
|
58
|
+
html interact --dir DIR --kind page|form --page-id ID --token TOKEN --steps FILE [--width 390]
|
|
62
59
|
html ready --dir DIR --kind page|form --page-id ID --token TOKEN --summary TEXT
|
|
63
|
-
html next --dir DIR --accept-ready --brief [--host-
|
|
60
|
+
html next --dir DIR --accept-ready --brief [--host-events '[]'] (durable worker identities)
|
|
61
|
+
html watch --dir DIR --host-active-tokens '["TOKEN"]' [--after-state DIGEST --watch-ms 60000]
|
|
64
62
|
html accept --dir DIR --kind page|form --page-id ID --token TOKEN [--refill --brief]
|
|
65
63
|
html fail --dir DIR --kind page|form --page-id ID --token TOKEN --reason TEXT
|
|
66
64
|
html retry --dir DIR [--kind page|form --page-id ID] (retry failures, or regenerate one target)
|
|
@@ -81,23 +79,27 @@ async function main() {
|
|
|
81
79
|
strict: true,
|
|
82
80
|
});
|
|
83
81
|
const command = pos[0];
|
|
82
|
+
const hostEvents = v['host-events'] === undefined ? undefined : parseHostEvents(v['host-events']);
|
|
83
|
+
if ((hostEvents !== undefined && !(command === 'html' && pos[1] === 'next')) ||
|
|
84
|
+
(hostEvents !== undefined && v['host-active-tokens'] !== undefined))
|
|
85
|
+
throw new CaptureError('ARGUMENT_INVALID', '--host-events 只用于 html next,不能与 --host-active-tokens 混用');
|
|
84
86
|
const activeTokens = v['host-active-tokens'] === undefined
|
|
85
87
|
? undefined
|
|
86
88
|
: parseHostActiveTokens(v['host-active-tokens']);
|
|
87
89
|
if (activeTokens !== undefined &&
|
|
88
90
|
!(command === 'html' &&
|
|
89
|
-
(pos[1]
|
|
90
|
-
throw new CaptureError('ARGUMENT_INVALID', '--host-active-tokens 仅用于 html next 或 accept/fail --refill');
|
|
91
|
+
(['next', 'watch'].includes(pos[1]) || (v.refill && ['accept', 'fail'].includes(pos[1])))))
|
|
92
|
+
throw new CaptureError('ARGUMENT_INVALID', '--host-active-tokens 仅用于 html next/watch 或 accept/fail --refill');
|
|
93
|
+
if ((v['after-state'] !== undefined || v['watch-ms'] !== undefined) &&
|
|
94
|
+
!(command === 'html' && pos[1] === 'watch'))
|
|
95
|
+
throw new CaptureError('ARGUMENT_INVALID', '--after-state/--watch-ms 仅用于 html watch');
|
|
96
|
+
if (v['repair-reason'] !== undefined && !(command === 'html' && pos[1] === 'inspect'))
|
|
97
|
+
throw new CaptureError('ARGUMENT_INVALID', '--repair-reason 仅用于 html inspect');
|
|
98
|
+
if (v.steps !== undefined && !(command === 'html' && pos[1] === 'interact'))
|
|
99
|
+
throw new CaptureError('ARGUMENT_INVALID', '--steps 仅用于 html interact');
|
|
91
100
|
const output = (value) => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
else {
|
|
95
|
-
const { next, fix, ...rest } = value;
|
|
96
|
-
console.log(JSON.stringify(rest, null, 2));
|
|
97
|
-
if (fix)
|
|
98
|
-
console.log(`FIX: ${fix}`);
|
|
99
|
-
console.log(`NEXT: ${next || 'none'}`);
|
|
100
|
-
}
|
|
101
|
+
// Final output is committed only after the transaction releases its lock.
|
|
102
|
+
pendingOutput = { value, json: !!v.json };
|
|
101
103
|
};
|
|
102
104
|
if (v.help || (!command && !v.version)) {
|
|
103
105
|
if (v.json)
|
|
@@ -110,7 +112,14 @@ async function main() {
|
|
|
110
112
|
output({ version: VERSION, next: 'none' });
|
|
111
113
|
return;
|
|
112
114
|
}
|
|
115
|
+
const hostBlocker = workBuddyHostBlocker();
|
|
116
|
+
if (hostBlocker && !isHostDiagnosticCommand(pos)) {
|
|
117
|
+
output(hostBlocker);
|
|
118
|
+
process.exitCode = 1;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
113
121
|
if (command === 'doctor') {
|
|
122
|
+
const { launchChrome } = await import('./capture.js');
|
|
114
123
|
const chrome = await launchChrome();
|
|
115
124
|
const version = chrome.version;
|
|
116
125
|
await chrome.close();
|
|
@@ -127,6 +136,7 @@ async function main() {
|
|
|
127
136
|
return;
|
|
128
137
|
}
|
|
129
138
|
if (command === 'auth') {
|
|
139
|
+
const { AUTH_REQUIRED_FIX, readEnvironmentAuthStatus, readActiveEnvironmentAuthProfile, setEnvironmentAuth, verifyEnvironmentAuth, listEnvironmentAuthProfiles, useEnvironmentAuthProfile, } = await import('./vendor/environment-auth.js');
|
|
130
140
|
if (pos[1] === 'set') {
|
|
131
141
|
if (!v['base-url'])
|
|
132
142
|
throw new CaptureError('ARGUMENT_REQUIRED', '需要 --base-url');
|
|
@@ -155,6 +165,7 @@ async function main() {
|
|
|
155
165
|
const status = v.local ? readEnvironmentAuthStatus() : await verifyEnvironmentAuth();
|
|
156
166
|
output({
|
|
157
167
|
...safeAuth(status),
|
|
168
|
+
...(!status.authenticated ? { fix: AUTH_REQUIRED_FIX } : {}),
|
|
158
169
|
next: status.authenticated ? 'run --app-id ID --dir DIR' : 'auth set',
|
|
159
170
|
});
|
|
160
171
|
return;
|
|
@@ -189,6 +200,7 @@ async function main() {
|
|
|
189
200
|
throw new CaptureError('COMMAND_INVALID', `未知命令: ${command}`);
|
|
190
201
|
if (!v.dir)
|
|
191
202
|
throw new CaptureError('ARGUMENT_REQUIRED', '需要 --dir');
|
|
203
|
+
const { Store } = await import('./store.js');
|
|
192
204
|
const store = new Store(v.dir);
|
|
193
205
|
const settings = {};
|
|
194
206
|
for (const [arg, key, min, max] of [
|
|
@@ -209,9 +221,10 @@ async function main() {
|
|
|
209
221
|
const report = async (s) => {
|
|
210
222
|
const { results, htmlResults, formResults, formHtmlResults, ...status } = await store.status(s);
|
|
211
223
|
const failedSources = status.failed + status.collection.failed;
|
|
212
|
-
const
|
|
213
|
-
?
|
|
214
|
-
:
|
|
224
|
+
const auth = ['DISCOVER', 'COLLECT', 'CAPTURE'].includes(status.state)
|
|
225
|
+
? await import('./vendor/environment-auth.js')
|
|
226
|
+
: undefined;
|
|
227
|
+
const local = auth ? auth.readEnvironmentAuthStatus() : { authenticated: true };
|
|
215
228
|
const state = !local.authenticated && ['DISCOVER', 'COLLECT', 'CAPTURE'].includes(status.state)
|
|
216
229
|
? 'WAITING_AUTH'
|
|
217
230
|
: status.state;
|
|
@@ -237,6 +250,7 @@ async function main() {
|
|
|
237
250
|
output({
|
|
238
251
|
...status,
|
|
239
252
|
state,
|
|
253
|
+
...(state === 'WAITING_AUTH' ? { fix: auth.AUTH_REQUIRED_FIX } : {}),
|
|
240
254
|
appId: s.appId,
|
|
241
255
|
directory: store.root,
|
|
242
256
|
effectiveSettings: s.settings,
|
|
@@ -297,8 +311,21 @@ async function main() {
|
|
|
297
311
|
await report(s);
|
|
298
312
|
return;
|
|
299
313
|
}
|
|
300
|
-
if (command === 'html' && [
|
|
301
|
-
const
|
|
314
|
+
if (command === 'html' && pos[1] === 'watch') {
|
|
315
|
+
const { watchHost } = await import('./host-watch.js');
|
|
316
|
+
if (activeTokens === undefined)
|
|
317
|
+
throw new CaptureError('ARGUMENT_REQUIRED', 'html watch 需要 --host-active-tokens');
|
|
318
|
+
if (Object.keys(settings).length || v.refill || v['accept-ready'])
|
|
319
|
+
throw new CaptureError('ARGUMENT_INVALID', 'html watch 只读,不接受队列修改或任务设置');
|
|
320
|
+
output(await watchHost(store, {
|
|
321
|
+
activeTokens,
|
|
322
|
+
afterState: v['after-state'],
|
|
323
|
+
watchMs: v['watch-ms'] === undefined ? undefined : Number(v['watch-ms']),
|
|
324
|
+
}));
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (command === 'html' && ['inspect', 'interact', 'ready'].includes(pos[1])) {
|
|
328
|
+
const ignored = Object.keys(settings).filter((key) => !(['inspect', 'interact'].includes(pos[1]) && key === 'width'));
|
|
302
329
|
if (ignored.length)
|
|
303
330
|
throw new CaptureError('ARGUMENT_INVALID', `html ${pos[1]} 不支持这些参数:${ignored.join(', ')};校对仅支持 --width`);
|
|
304
331
|
const kind = v.kind || 'page';
|
|
@@ -311,10 +338,22 @@ async function main() {
|
|
|
311
338
|
const reviewLock = new Store(path.join(store.meta, 'inspection-locks', digest(`${kind}:${v['page-id']}:${v.token}`)));
|
|
312
339
|
await reviewLock.lock(async () => {
|
|
313
340
|
if (pos[1] === 'ready') {
|
|
341
|
+
const { readyHtml } = await import('./html-handoff.js');
|
|
314
342
|
output(await readyHtml(store, await store.load(), v['page-id'], v.token, kind, v.summary || ''));
|
|
315
343
|
return;
|
|
316
344
|
}
|
|
317
|
-
|
|
345
|
+
if (pos[1] === 'interact') {
|
|
346
|
+
if (!v.steps)
|
|
347
|
+
throw new CaptureError('ARGUMENT_REQUIRED', 'html interact 需要 --steps JSON文件');
|
|
348
|
+
const { interactHtml } = await import('./html-interact.js');
|
|
349
|
+
const result = await interactHtml(store, await store.load(), v['page-id'], v.token, kind, path.resolve(v.steps), settings.width);
|
|
350
|
+
output({ ...result, next: result.ok ? 'host-review' : 'repair-draft' });
|
|
351
|
+
if (!result.ok)
|
|
352
|
+
process.exitCode = 1;
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const { inspectHtmlViewports } = await import('./html-inspect.js');
|
|
356
|
+
const inspection = await inspectHtmlViewports(store, await store.load(), v['page-id'], v.token, kind, settings.width, v['repair-reason']);
|
|
318
357
|
output({ ...inspection, next: inspection.ok ? 'host-review' : 'repair-draft' });
|
|
319
358
|
if (!inspection.ok)
|
|
320
359
|
process.exitCode = 1;
|
|
@@ -325,6 +364,7 @@ async function main() {
|
|
|
325
364
|
let s;
|
|
326
365
|
if (command === 'init' || command === 'run') {
|
|
327
366
|
if (v['app-id'] || v['app-url']) {
|
|
367
|
+
const { applicationInput } = await import('./application.js');
|
|
328
368
|
const input = applicationInput(v['app-url'] || v['app-id']);
|
|
329
369
|
if (v['app-id'] && applicationInput(v['app-id']).appId !== input.appId)
|
|
330
370
|
throw new CaptureError('APP_INPUT_CONFLICT', '应用 ID 与地址不一致');
|
|
@@ -358,16 +398,22 @@ async function main() {
|
|
|
358
398
|
return;
|
|
359
399
|
}
|
|
360
400
|
if (command === 'pack') {
|
|
401
|
+
const { pack } = await import('./archive.js');
|
|
361
402
|
await pack(store, s);
|
|
362
403
|
await report(s);
|
|
363
404
|
return;
|
|
364
405
|
}
|
|
365
406
|
if (command === 'html') {
|
|
407
|
+
const { nextHtml, acceptHtml, failHtml, retryHtml } = await import('./html.js');
|
|
366
408
|
const kind = v.kind || 'page';
|
|
367
409
|
if (kind !== 'page' && kind !== 'form')
|
|
368
410
|
throw new CaptureError('ARGUMENT_INVALID', 'kind 必须为 page 或 form');
|
|
369
411
|
if (pos[1] === 'next') {
|
|
370
|
-
|
|
412
|
+
if (hostEvents !== undefined)
|
|
413
|
+
await updateHostLedger(store, s, hostEvents);
|
|
414
|
+
const handoff = v['accept-ready']
|
|
415
|
+
? await (await import('./html-handoff.js')).acceptReadyHtml(store, s)
|
|
416
|
+
: undefined;
|
|
371
417
|
output({ ...(await nextHtml(store, s, { brief: v.brief, activeTokens })), ...handoff });
|
|
372
418
|
return;
|
|
373
419
|
}
|
|
@@ -394,7 +440,7 @@ async function main() {
|
|
|
394
440
|
return;
|
|
395
441
|
}
|
|
396
442
|
if (command === 'retry')
|
|
397
|
-
await retryHtml(store, s);
|
|
443
|
+
await (await import('./html.js')).retryHtml(store, s);
|
|
398
444
|
const before = await store.status(s);
|
|
399
445
|
const needsCapture = ['capture', 'run', 'retry'].includes(command) &&
|
|
400
446
|
(before.pending > 0 || (command === 'retry' && before.failed > 0));
|
|
@@ -410,14 +456,16 @@ async function main() {
|
|
|
410
456
|
if (command === 'collect' && (!s.formPages || !s.menus))
|
|
411
457
|
throw new CaptureError('CATALOG_REQUIRED', '先执行 discover 获取发布菜单');
|
|
412
458
|
if (needsDiscover || needsCapture || needsCollect) {
|
|
459
|
+
const { readEnvironmentAuthContext } = await import('./vendor/environment-auth.js');
|
|
413
460
|
const auth = readEnvironmentAuthContext();
|
|
414
461
|
secret = auth.eteamsId;
|
|
415
462
|
store.bind(s, auth);
|
|
416
463
|
await store.save(s);
|
|
417
464
|
if (needsDiscover) {
|
|
418
465
|
if (s.menuRequired)
|
|
419
|
-
Object.assign(s, await discoverMenus(auth, s.appId));
|
|
466
|
+
Object.assign(s, await (await import('./menus.js')).discoverMenus(auth, s.appId));
|
|
420
467
|
else {
|
|
468
|
+
const { fetchPages, fetchCatalog } = await import('./platform.js');
|
|
421
469
|
if (!s.pages)
|
|
422
470
|
s.pages = await fetchPages(auth, s.appId);
|
|
423
471
|
if (s.siteRequired && (!s.forms || !s.appName))
|
|
@@ -427,28 +475,55 @@ async function main() {
|
|
|
427
475
|
await store.save(s);
|
|
428
476
|
}
|
|
429
477
|
if (s.menuRequired && ['collect', 'run', 'retry'].includes(command))
|
|
430
|
-
await collectForms(store, s, auth, command === 'retry');
|
|
478
|
+
await (await import('./forms.js')).collectForms(store, s, auth, command === 'retry');
|
|
431
479
|
if (['capture', 'run', 'retry'].includes(command))
|
|
432
|
-
await capture(store, s, auth, {
|
|
480
|
+
await (await import('./capture.js')).capture(store, s, auth, {
|
|
433
481
|
retryFailed: command === 'retry',
|
|
434
482
|
progress: (event) => console.error(JSON.stringify(event)),
|
|
435
483
|
});
|
|
436
484
|
}
|
|
437
485
|
if ((command === 'run' || command === 'retry') && (await store.status(s)).state === 'PACKAGE')
|
|
438
|
-
await pack(store, s);
|
|
486
|
+
await (await import('./archive.js')).pack(store, s);
|
|
439
487
|
await report(s);
|
|
440
488
|
});
|
|
441
489
|
}
|
|
442
|
-
main()
|
|
443
|
-
|
|
490
|
+
main()
|
|
491
|
+
.then(() => {
|
|
492
|
+
if (!pendingOutput)
|
|
493
|
+
return;
|
|
494
|
+
const { value, json } = pendingOutput;
|
|
495
|
+
if (json)
|
|
496
|
+
console.log(JSON.stringify(value));
|
|
497
|
+
else {
|
|
498
|
+
const { next, fix, ...rest } = value;
|
|
499
|
+
console.log(JSON.stringify(rest, null, 2));
|
|
500
|
+
if (fix)
|
|
501
|
+
console.log(`FIX: ${fix}`);
|
|
502
|
+
console.log(`NEXT: ${next || 'none'}`);
|
|
503
|
+
}
|
|
504
|
+
})
|
|
505
|
+
.catch(async (e) => {
|
|
506
|
+
let error = errorInfo(e, secret);
|
|
507
|
+
const guard = error.code.startsWith('SAFE_DELETE_BULK_')
|
|
508
|
+
? error.message.match(/\[safe-delete\]\[SAFE_DELETE_BULK_[A-Z_]+\][^\r\n]*/)?.[0]
|
|
509
|
+
: undefined;
|
|
510
|
+
if (guard) {
|
|
511
|
+
// WorkBuddy parses the first marker in stdout + stderr as raw JSON.
|
|
512
|
+
// A marker escaped inside our JSON hides the actual authorization signal.
|
|
513
|
+
// Preserve the native signal on stderr; structured stdout retains its code.
|
|
514
|
+
console.error(guard);
|
|
515
|
+
error = JSON.parse(JSON.stringify(error).replaceAll('[safe-delete][SAFE_DELETE_BULK_', 'WorkBuddy[SAFE_DELETE_BULK_'));
|
|
516
|
+
}
|
|
444
517
|
const value = {
|
|
445
518
|
ok: false,
|
|
446
519
|
error,
|
|
447
|
-
fix: error.code
|
|
448
|
-
? '
|
|
449
|
-
: error.code === '
|
|
450
|
-
? '
|
|
451
|
-
:
|
|
520
|
+
fix: error.code.startsWith('SAFE_DELETE_BULK_')
|
|
521
|
+
? 'WorkBuddy 宿主删除保护阻塞了命令。停止自动重试并保留草稿/回执/锁;不要手删锁目录、切换删除方式或改安全设置。由宿主正常授权流程处理后,先核对现有回执和原生任务状态,再用原目录恢复;不要把本次当作完成或重新生成页面。'
|
|
522
|
+
: error.code === 'E10_LOGIN_REQUIRED'
|
|
523
|
+
? (await import('./vendor/environment-auth.js')).AUTH_REQUIRED_FIX
|
|
524
|
+
: error.code === 'SETTINGS_MISMATCH'
|
|
525
|
+
? '按 error.details.effectiveSettings 恢复:移除不同参数;截图失败执行 retry,run/capture 不会重试失败项。'
|
|
526
|
+
: '根据 error 修正后用原目录恢复',
|
|
452
527
|
next: 'none',
|
|
453
528
|
};
|
|
454
529
|
if (process.argv.includes('--json'))
|
package/dist/model.d.ts
CHANGED
|
@@ -117,9 +117,10 @@ export interface HtmlResult {
|
|
|
117
117
|
kind?: 'page' | 'form';
|
|
118
118
|
status: 'pending' | 'running' | 'succeeded' | 'failed';
|
|
119
119
|
token: string;
|
|
120
|
+
dispatchName?: string;
|
|
120
121
|
attempt: number;
|
|
121
122
|
sourceSha256: string;
|
|
122
|
-
promptVersion: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
|
123
|
+
promptVersion: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17;
|
|
123
124
|
startedAt: string;
|
|
124
125
|
finishedAt?: string;
|
|
125
126
|
file?: string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Page } from 'playwright-core';
|
|
2
|
+
/** Inspect and pack must execute the same bytes under the same file-origin rules. */
|
|
3
|
+
export declare function loadOfflinePage(page: Page, filename: string, form: boolean): Promise<{
|
|
4
|
+
ok: boolean;
|
|
5
|
+
stage: string;
|
|
6
|
+
ready: boolean;
|
|
7
|
+
errors: string[];
|
|
8
|
+
runtimeErrors: string[];
|
|
9
|
+
details: any;
|
|
10
|
+
url: string;
|
|
11
|
+
}>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { pathToFileURL } from 'node:url';
|
|
2
|
+
import { bounded } from './common.js';
|
|
3
|
+
/** Inspect and pack must execute the same bytes under the same file-origin rules. */
|
|
4
|
+
export async function loadOfflinePage(page, filename, form) {
|
|
5
|
+
const url = pathToFileURL(filename).href;
|
|
6
|
+
const errors = [], runtimeErrors = [];
|
|
7
|
+
let stage = 'navigation';
|
|
8
|
+
page.on('pageerror', (error) => {
|
|
9
|
+
if (runtimeErrors.length < 10)
|
|
10
|
+
runtimeErrors.push(`${error.name}: ${error.message}`.slice(0, 500));
|
|
11
|
+
});
|
|
12
|
+
page.on('websocket', () => errors.push('页面尝试建立网络连接'));
|
|
13
|
+
await page.route('**/*', async (route) => {
|
|
14
|
+
if (route.request().url() === url || /^(data:|about:)/.test(route.request().url()))
|
|
15
|
+
await route.continue();
|
|
16
|
+
else {
|
|
17
|
+
if (errors.length < 10)
|
|
18
|
+
errors.push('页面尝试访问外部或旁路资源');
|
|
19
|
+
await route.abort();
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
await page.addInitScript(() => {
|
|
23
|
+
;
|
|
24
|
+
window.__E10_INSPECTION_ERRORS__ = [];
|
|
25
|
+
const record = (message, url = '', line = 0, column = 0) => {
|
|
26
|
+
const entries = window.__E10_INSPECTION_ERRORS__;
|
|
27
|
+
if (entries.length < 10)
|
|
28
|
+
entries.push({ message: String(message).slice(0, 500), url, line, column });
|
|
29
|
+
};
|
|
30
|
+
addEventListener('error', (e) => {
|
|
31
|
+
if (e.message)
|
|
32
|
+
record(e.message, e.filename, e.lineno, e.colno);
|
|
33
|
+
});
|
|
34
|
+
addEventListener('unhandledrejection', (e) => record(e.reason?.message || String(e.reason)));
|
|
35
|
+
});
|
|
36
|
+
try {
|
|
37
|
+
await page.goto(url, { waitUntil: 'load', timeout: 10000 });
|
|
38
|
+
stage = 'initialization';
|
|
39
|
+
if (form)
|
|
40
|
+
await page.waitForFunction(() => window.__E10_FORM_READY__ === true ||
|
|
41
|
+
window.__E10_INSPECTION_ERRORS__?.length > 0, undefined, { timeout: 10000 });
|
|
42
|
+
stage = 'fonts';
|
|
43
|
+
await bounded(page.evaluate(() => document.fonts.ready), 10000, 'FORM_FONTS_TIMEOUT');
|
|
44
|
+
stage = 'ready';
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
errors.push(`${stage}: ${error instanceof Error ? error.message : String(error)}`.slice(0, 700));
|
|
48
|
+
}
|
|
49
|
+
const evidence = await page
|
|
50
|
+
.evaluate(() => ({
|
|
51
|
+
ready: window.__E10_FORM_READY__ === true,
|
|
52
|
+
details: window.__E10_INSPECTION_ERRORS__ || [],
|
|
53
|
+
}))
|
|
54
|
+
.catch(() => ({ ready: false, details: [] }));
|
|
55
|
+
for (const e of evidence.details)
|
|
56
|
+
if (!runtimeErrors.some((v) => v.includes(e.message)) && runtimeErrors.length < 10)
|
|
57
|
+
runtimeErrors.push(e.message);
|
|
58
|
+
if (runtimeErrors.length)
|
|
59
|
+
errors.push('页面存在 JavaScript 运行错误');
|
|
60
|
+
if (form && !evidence.ready)
|
|
61
|
+
errors.push('表单未完成初始化,检查具体脚本错误及 __E10_FORM_READY__ 设置');
|
|
62
|
+
return {
|
|
63
|
+
ok: !errors.length,
|
|
64
|
+
stage,
|
|
65
|
+
ready: evidence.ready,
|
|
66
|
+
errors,
|
|
67
|
+
runtimeErrors,
|
|
68
|
+
details: evidence.details,
|
|
69
|
+
url,
|
|
70
|
+
};
|
|
71
|
+
}
|
package/dist/offline-store.mjs
CHANGED
|
@@ -44,6 +44,16 @@ export function createOfflineStorage(appId) {
|
|
|
44
44
|
return {
|
|
45
45
|
valid,
|
|
46
46
|
operate(scope, operation, value) {
|
|
47
|
+
if (operation === 'compareSave') {
|
|
48
|
+
check(scope, value?.expected);
|
|
49
|
+
check(scope, value?.next);
|
|
50
|
+
const next = read();
|
|
51
|
+
if (JSON.stringify(next.objects[scope]) !== JSON.stringify(value.expected))
|
|
52
|
+
throw new Error('FORM_CONFLICT: 本地记录已变化,请重新打开后修改');
|
|
53
|
+
next.objects[scope] = clone(value.next);
|
|
54
|
+
persist(next);
|
|
55
|
+
return clone(next.objects[scope]);
|
|
56
|
+
}
|
|
47
57
|
check(scope, value);
|
|
48
58
|
if (!['load', 'save', 'reset'].includes(operation))
|
|
49
59
|
throw new Error('未知本地操作');
|
|
@@ -1,9 +1,37 @@
|
|
|
1
|
-
export function hostCapabilities(env?: NodeJS.ProcessEnv): {
|
|
1
|
+
export function hostCapabilities(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): {
|
|
2
2
|
code?: string | undefined;
|
|
3
3
|
fix?: string | undefined;
|
|
4
4
|
background: string;
|
|
5
|
+
agentTeams: string;
|
|
6
|
+
backgroundTasks: string;
|
|
7
|
+
evidenceSource: string;
|
|
8
|
+
evidenceScope: string;
|
|
5
9
|
verification: string;
|
|
6
10
|
};
|
|
11
|
+
export function workBuddyHostBlocker(env?: NodeJS.ProcessEnv, required?: boolean): {
|
|
12
|
+
ok: boolean;
|
|
13
|
+
blocked: boolean;
|
|
14
|
+
state: string;
|
|
15
|
+
error: {
|
|
16
|
+
code: string;
|
|
17
|
+
message: string;
|
|
18
|
+
};
|
|
19
|
+
userMessage: string;
|
|
20
|
+
fix: string;
|
|
21
|
+
next: string;
|
|
22
|
+
host: {
|
|
23
|
+
code?: string | undefined;
|
|
24
|
+
fix?: string | undefined;
|
|
25
|
+
background: string;
|
|
26
|
+
agentTeams: string;
|
|
27
|
+
backgroundTasks: string;
|
|
28
|
+
evidenceSource: string;
|
|
29
|
+
evidenceScope: string;
|
|
30
|
+
verification: string;
|
|
31
|
+
};
|
|
32
|
+
} | null;
|
|
33
|
+
/** @param {string[]} commands */
|
|
34
|
+
export function isHostDiagnosticCommand(commands: string[]): boolean;
|
|
7
35
|
export function environmentValue(env: any, name: any, platform?: NodeJS.Platform): any;
|
|
8
36
|
export function productStateRoot(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform, homeDirectory?: string): string;
|
|
9
37
|
export function npmEnvironment(env?: NodeJS.ProcessEnv): {
|
package/dist/runtime-support.mjs
CHANGED
|
@@ -11,21 +11,71 @@ const fail = (code, message = code) => Object.assign(new Error(message), { code
|
|
|
11
11
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
12
|
// Inspect only the two capability flags inherited from the host. A reserved CLI
|
|
13
13
|
// token or a saved preference is not proof that a background Agent is running.
|
|
14
|
-
export function hostCapabilities(env = process.env) {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
export function hostCapabilities(env = process.env, platform = process.platform) {
|
|
15
|
+
const flag = (name) => {
|
|
16
|
+
const value = environmentValue(env, name, platform)?.trim() || '';
|
|
17
|
+
return /^(1|true)$/i.test(value) ? true : /^(0|false)$/i.test(value) ? false : undefined;
|
|
18
|
+
};
|
|
19
|
+
const teams = flag('CODEBUDDY_CODE_EXPERIMENTAL_AGENT_TEAMS');
|
|
20
|
+
const disabled = flag('CODEBUDDY_CODE_DISABLE_BACKGROUND_TASKS');
|
|
21
|
+
const agentTeams = teams === true ? 'enabled' : teams === false ? 'disabled' : 'unknown';
|
|
22
|
+
const backgroundTasks = disabled === true ? 'disabled' : disabled === false ? 'allowed' : 'unknown';
|
|
23
|
+
const background = disabled === true || teams === false ? 'disabled' : teams === true ? 'enabled' : 'unknown';
|
|
18
24
|
return {
|
|
19
25
|
background,
|
|
26
|
+
agentTeams,
|
|
27
|
+
backgroundTasks,
|
|
28
|
+
evidenceSource: 'inherited-host-environment',
|
|
29
|
+
evidenceScope: 'current-session',
|
|
20
30
|
verification: 'native-task-id-and-independent-completion-required',
|
|
21
31
|
...(background === 'disabled'
|
|
22
32
|
? {
|
|
23
33
|
code: 'HOST_BACKGROUND_DISABLED',
|
|
24
|
-
fix:
|
|
34
|
+
fix: workBuddyHostFix,
|
|
25
35
|
}
|
|
26
36
|
: {}),
|
|
27
37
|
};
|
|
28
38
|
}
|
|
39
|
+
const workBuddyHostFix = '请在 WorkBuddy 左下角头像 → 设置 → 智能体 → 智能体团队管理中检查智能体团队:若显示“取消禁用”,请点击;若显示“禁用”,表示界面设置已开启,请勿再次点击。然后完全退出并重新打开 WorkBuddy,回到本任务回复“继续”。若界面已开启且重启后仍受阻,可能是界面设置与当前会话能力状态不同步,请向 WorkBuddy 反馈该状态异常,不要反复切换开关。复查通过后会从原任务目录继续。';
|
|
40
|
+
const workBuddyHostReason = '应用原型生成是高并发任务,需要多个智能体同时生成和校对不同页面。未开启智能体团队时,只能逐页串行处理,整体耗时会非常长。为避免长时间等待,必须先开启智能体团队,才能继续生成。';
|
|
41
|
+
// The wrapper requires positive evidence even before runtime installation. Direct
|
|
42
|
+
// CLI use on other hosts remains supported; inherited WorkBuddy flags opt it in.
|
|
43
|
+
export function workBuddyHostBlocker(env = process.env, required = false) {
|
|
44
|
+
const host = hostCapabilities(env);
|
|
45
|
+
const detected = [
|
|
46
|
+
'CODEBUDDY_CODE_EXPERIMENTAL_AGENT_TEAMS',
|
|
47
|
+
'CODEBUDDY_CODE_DISABLE_BACKGROUND_TASKS',
|
|
48
|
+
].some((name) => environmentValue(env, name) !== undefined);
|
|
49
|
+
if (host.background === 'enabled' || (!required && !detected))
|
|
50
|
+
return null;
|
|
51
|
+
const message = host.backgroundTasks === 'disabled'
|
|
52
|
+
? '当前 WorkBuddy 会话报告后台任务不可用,应用原型任务已暂停。'
|
|
53
|
+
: host.agentTeams === 'disabled'
|
|
54
|
+
? '当前 WorkBuddy 会话报告智能体团队未生效,应用原型任务已暂停。'
|
|
55
|
+
: '当前无法确认 WorkBuddy 会话的智能体团队可用,应用原型任务已暂停。';
|
|
56
|
+
return {
|
|
57
|
+
ok: false,
|
|
58
|
+
blocked: true,
|
|
59
|
+
state: 'WAITING_HOST',
|
|
60
|
+
error: {
|
|
61
|
+
code: host.background === 'disabled' ? 'HOST_BACKGROUND_DISABLED' : 'HOST_BACKGROUND_UNVERIFIED',
|
|
62
|
+
message,
|
|
63
|
+
},
|
|
64
|
+
userMessage: `${message}\n\n${workBuddyHostReason}\n\n${workBuddyHostFix}`,
|
|
65
|
+
fix: workBuddyHostFix,
|
|
66
|
+
next: 'enable-agent-teams',
|
|
67
|
+
host,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** @param {string[]} commands */
|
|
71
|
+
export function isHostDiagnosticCommand(commands) {
|
|
72
|
+
const [command, subcommand, action] = commands;
|
|
73
|
+
return (['doctor', 'status', 'next', 'version', '--help', '--version'].includes(command) ||
|
|
74
|
+
(command === 'html' && subcommand === 'watch') ||
|
|
75
|
+
(command === 'auth' &&
|
|
76
|
+
(subcommand === 'status' ||
|
|
77
|
+
(subcommand === 'profile' && ['list', 'current'].includes(action)))));
|
|
78
|
+
}
|
|
29
79
|
export function environmentValue(env, name, platform = process.platform) {
|
|
30
80
|
return platform === 'win32'
|
|
31
81
|
? Object.entries(env).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1]
|