made-bridge 0.3.3 → 0.3.5
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/package.json +1 -1
- package/public/app.js +57 -5
- package/public/i18n-messages.js +20 -0
- package/public/styles.css +24 -0
- package/public/tasks.html +10 -1
- package/src/agent-sprites.mjs +11 -7
- package/src/jobs.mjs +3 -1
- package/src/server.mjs +9 -0
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -6,7 +6,7 @@ import { t, m, getLocale, setLocale, supportedLocales, applyStaticTranslations,
|
|
|
6
6
|
const $ = (id) => document.getElementById(id);
|
|
7
7
|
const taskPage = document.body.dataset.page === 'tasks';
|
|
8
8
|
const assistantsPage = document.body.dataset.page === 'assistants';
|
|
9
|
-
const state = { jobs: [], sessions: [], providers: [], projects: [], connection: {}, voice: {}, models: {}, pairing: { open: false, pending: [], paired: [] }, tunnel: { status: 'off' }, receiver: { status: 'disabled' }, receiverPasswordVisible: false, token: null, lastSyncedAt: null, loaded: false, error: '', busy: new Set(), pairBusy: false, pairWindowBusy: false, tunnelBusy: false, dismissedPairing: new Set(), selectedSessionId: null, previewProvider: '', previewSessionId: null, sessionFormOpen: false, legacySessionsOpen: false, renamingSession: false, refreshing: false };
|
|
9
|
+
const state = { jobs: [], sessions: [], providers: [], projects: [], connection: {}, voice: {}, models: {}, pairing: { open: false, pending: [], paired: [] }, tunnel: { status: 'off' }, receiver: { status: 'disabled' }, receiverPasswordVisible: false, token: null, lastSyncedAt: null, loaded: false, error: '', busy: new Set(), pairBusy: false, pairWindowBusy: false, tunnelBusy: false, dismissedPairing: new Set(), selectedSessionId: null, previewProvider: '', previewSessionId: null, sessionFormOpen: false, legacySessionsOpen: false, renamingSession: false, refreshing: false, historyOpen: false };
|
|
10
10
|
if (taskPage) {
|
|
11
11
|
try { state.selectedSessionId = sessionStorage.getItem('vibe-selected-session'); } catch { /* Selection works without storage. */ }
|
|
12
12
|
const params = new URLSearchParams(window.location.search);
|
|
@@ -64,6 +64,9 @@ import { t, m, getLocale, setLocale, supportedLocales, applyStaticTranslations,
|
|
|
64
64
|
const canConfirm = (job) => job.status === 'waiting_confirmation';
|
|
65
65
|
const canCancel = (job) => ['waiting_confirmation', 'queued', 'running'].includes(job.status);
|
|
66
66
|
const timeText = (value) => { const date = new Date(value); return Number.isNaN(date.getTime()) ? '' : new Intl.DateTimeFormat(getLocale(), { hour: '2-digit', minute: '2-digit' }).format(date); };
|
|
67
|
+
const turnAnswer = (job) => userFacingJobError(job) || (job.status === 'running' ? job.progress : '') || job.result ||
|
|
68
|
+
(job.status === 'waiting_confirmation' ? t('app.074') :
|
|
69
|
+
job.status === 'queued' ? t('app.075') : t('app.076'));
|
|
67
70
|
const setFormMessage = (message, success = false) => { setUiMessage($('formMessage'), message); $('formMessage').classList.toggle('success', success); };
|
|
68
71
|
let toastTimer;
|
|
69
72
|
function showToast(message, error = false) { const toast = $('toast'); setUiMessage(toast, message); toast.classList.toggle('error', error); toast.classList.add('visible'); clearTimeout(toastTimer); toastTimer = setTimeout(() => toast.classList.remove('visible'), 4200); }
|
|
@@ -314,9 +317,7 @@ import { t, m, getLocale, setLocale, supportedLocales, applyStaticTranslations,
|
|
|
314
317
|
const top = text('div', 'job-top');
|
|
315
318
|
top.append(text('div', 'job-meta', t('app.071', { v0: (timeText(job.createdAt) || t('app.047')) })), text('span', `job-status ${info.css}`, info.label));
|
|
316
319
|
card.append(top, text('div', 'job-instruction', t('app.073', { v0: (job.instruction || t('app.072')) })));
|
|
317
|
-
const answer =
|
|
318
|
-
(job.status === 'waiting_confirmation' ? t('app.074') :
|
|
319
|
-
job.status === 'queued' ? t('app.075') : t('app.076'));
|
|
320
|
+
const answer = turnAnswer(job);
|
|
320
321
|
card.append(text('p', `job-summary turn-answer ${job.error && !job.retryPending ? 'error' : ''}`, t('app.077', { v0: (providerName(job.provider)), v1: (safeString(answer)) })));
|
|
321
322
|
if (job.provider === 'codex' && job.retryPending) {
|
|
322
323
|
card.append(text('p', 'job-retry-notice', t('app.078')));
|
|
@@ -328,6 +329,38 @@ import { t, m, getLocale, setLocale, supportedLocales, applyStaticTranslations,
|
|
|
328
329
|
if (actions.children.length) { const bottom = text('div', 'job-bottom'); bottom.append(actions); card.append(bottom); }
|
|
329
330
|
list.append(card);
|
|
330
331
|
}
|
|
332
|
+
/** 历史会话抽屉:当前任务的全部指令与回复,新的在上。数据来自 /api/state 的 jobs。 */
|
|
333
|
+
function renderHistory() {
|
|
334
|
+
const button = $('historyDrawerButton');
|
|
335
|
+
const overlay = $('historyDrawerOverlay');
|
|
336
|
+
if (!button || !overlay) return;
|
|
337
|
+
const session = selectedSession();
|
|
338
|
+
const jobs = session ? jobsForSession(session.id) : [];
|
|
339
|
+
button.hidden = !session || !!state.error;
|
|
340
|
+
button.setAttribute('aria-expanded', String(state.historyOpen));
|
|
341
|
+
overlay.hidden = !state.historyOpen;
|
|
342
|
+
if (!state.historyOpen) return;
|
|
343
|
+
$('historyDrawerMeta').textContent = session ? `${session.title || t('app.045')} — ${t('app.227')}` : t('app.227');
|
|
344
|
+
const list = $('historyList'); list.replaceChildren();
|
|
345
|
+
if (!jobs.length) {
|
|
346
|
+
list.append(text('p', 'history-empty', t('app.229')));
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
[...jobs].reverse().forEach((job, index) => {
|
|
350
|
+
const info = jobStatus(job);
|
|
351
|
+
const turn = text('article', 'history-turn');
|
|
352
|
+
const top = text('div', 'history-turn-top');
|
|
353
|
+
top.append(
|
|
354
|
+
text('span', 'turn-number', t('app.228', { v0: jobs.length - index })),
|
|
355
|
+
text('span', 'history-turn-time', formatDateTime(job.createdAt)),
|
|
356
|
+
text('span', `job-status ${info.css}`, info.label),
|
|
357
|
+
);
|
|
358
|
+
turn.append(top, text('div', 'history-turn-instruction', job.instruction || t('app.072')));
|
|
359
|
+
const answer = turnAnswer(job);
|
|
360
|
+
if (answer) turn.append(text('p', `history-turn-answer ${job.error && !job.retryPending ? 'error' : ''}`, answer));
|
|
361
|
+
list.append(turn);
|
|
362
|
+
});
|
|
363
|
+
}
|
|
331
364
|
const boardEncoder = new TextEncoder();
|
|
332
365
|
function boardCleanText(value) {
|
|
333
366
|
return safeString(value)
|
|
@@ -912,7 +945,7 @@ import { t, m, getLocale, setLocale, supportedLocales, applyStaticTranslations,
|
|
|
912
945
|
setUiMessage($('firmwareMessage'), error.message || '');
|
|
913
946
|
} finally { firmwareBusy = false; }
|
|
914
947
|
}
|
|
915
|
-
function render() { renderSelects(); renderSessions(); renderJobs(); renderBoard(); renderPairing(); renderTunnel(); renderReceiver(); renderFirmware(); renderConnection(); renderModelSettings(); renderVoiceSettings(); renderProviders(); }
|
|
948
|
+
function render() { renderSelects(); renderSessions(); renderJobs(); renderHistory(); renderBoard(); renderPairing(); renderTunnel(); renderReceiver(); renderFirmware(); renderConnection(); renderModelSettings(); renderVoiceSettings(); renderProviders(); }
|
|
916
949
|
function applyLanguage(locale) {
|
|
917
950
|
const changed = setLocale(locale);
|
|
918
951
|
if (!changed && document.documentElement.lang === locale) {
|
|
@@ -992,6 +1025,25 @@ import { t, m, getLocale, setLocale, supportedLocales, applyStaticTranslations,
|
|
|
992
1025
|
state.legacySessionsOpen = !state.legacySessionsOpen;
|
|
993
1026
|
renderSessions();
|
|
994
1027
|
});
|
|
1028
|
+
$('historyDrawerButton')?.addEventListener('click', () => {
|
|
1029
|
+
state.historyOpen = !state.historyOpen;
|
|
1030
|
+
renderHistory();
|
|
1031
|
+
});
|
|
1032
|
+
$('historyDrawerClose')?.addEventListener('click', () => {
|
|
1033
|
+
state.historyOpen = false;
|
|
1034
|
+
renderHistory();
|
|
1035
|
+
});
|
|
1036
|
+
$('historyDrawerOverlay')?.addEventListener('click', (event) => {
|
|
1037
|
+
if (event.target !== event.currentTarget) return;
|
|
1038
|
+
state.historyOpen = false;
|
|
1039
|
+
renderHistory();
|
|
1040
|
+
});
|
|
1041
|
+
document.addEventListener('keydown', (event) => {
|
|
1042
|
+
if (event.key === 'Escape' && state.historyOpen) {
|
|
1043
|
+
state.historyOpen = false;
|
|
1044
|
+
renderHistory();
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
995
1047
|
$('sessionForm')?.addEventListener('submit', (event) => { event.preventDefault(); createSession(); });
|
|
996
1048
|
$('renameSessionButton')?.addEventListener('click', () => {
|
|
997
1049
|
const session = selectedSession();
|
package/public/i18n-messages.js
CHANGED
|
@@ -900,6 +900,26 @@ export const messages = {
|
|
|
900
900
|
"zh-CN": "创建失败",
|
|
901
901
|
"en": "Creation failed"
|
|
902
902
|
},
|
|
903
|
+
"app.226": {
|
|
904
|
+
"zh-CN": "历史记录",
|
|
905
|
+
"en": "History"
|
|
906
|
+
},
|
|
907
|
+
"app.227": {
|
|
908
|
+
"zh-CN": "这个任务的全部指令与回复,新的在上面。",
|
|
909
|
+
"en": "Every instruction and reply of this task, newest first."
|
|
910
|
+
},
|
|
911
|
+
"app.228": {
|
|
912
|
+
"zh-CN": "第 {v0} 轮",
|
|
913
|
+
"en": "Turn {v0}"
|
|
914
|
+
},
|
|
915
|
+
"app.229": {
|
|
916
|
+
"zh-CN": "这个任务还没有对话记录。",
|
|
917
|
+
"en": "No conversation history for this task yet."
|
|
918
|
+
},
|
|
919
|
+
"app.230": {
|
|
920
|
+
"zh-CN": "关闭",
|
|
921
|
+
"en": "Close"
|
|
922
|
+
},
|
|
903
923
|
"tunnel.official.title": {
|
|
904
924
|
"zh-CN": "官方入口",
|
|
905
925
|
"en": "Official links"
|
package/public/styles.css
CHANGED
|
@@ -443,3 +443,27 @@
|
|
|
443
443
|
.tunnel-service-guide { padding: 12px; }
|
|
444
444
|
.tunnel-official-link { flex: 1 1 auto; }
|
|
445
445
|
}
|
|
446
|
+
|
|
447
|
+
/* 历史会话抽屉:任务页「最新进展」旁的按钮拉开,展示当前任务的全部指令与回复 */
|
|
448
|
+
.history-overlay { position: fixed; inset: 0; z-index: 15; background: rgba(74, 62, 44, .38); backdrop-filter: blur(4px); }
|
|
449
|
+
.history-overlay[hidden] { display: none; }
|
|
450
|
+
.history-drawer { position: absolute; top: 0; right: 0; display: flex; flex-direction: column; width: min(560px, 100%); height: 100%; background: var(--bg); border-left: 1px solid var(--line); box-shadow: -18px 0 48px rgba(74, 62, 44, .22); animation: history-in .22s ease; }
|
|
451
|
+
@keyframes history-in { from { transform: translateX(28px); opacity: 0; } to { transform: none; opacity: 1; } }
|
|
452
|
+
.history-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 22px 22px 16px; border-bottom: 1px solid var(--line); background: var(--panel); }
|
|
453
|
+
.history-head h2 { margin-top: 6px; font-size: var(--font-heading); font-weight: 700; }
|
|
454
|
+
.history-head .panel-intro { margin-top: 6px; }
|
|
455
|
+
.history-head .icon-button { flex: 0 0 auto; }
|
|
456
|
+
.history-list { flex: 1; display: grid; gap: 10px; align-content: start; padding: 18px 22px 30px; overflow-y: auto; }
|
|
457
|
+
.history-empty { color: var(--dim); font-size: var(--font-small); padding: 13px; }
|
|
458
|
+
.history-turn { border: 1px solid var(--line); border-radius: 15px; background: var(--panel); padding: 14px 15px; }
|
|
459
|
+
.history-turn-top { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; color: var(--dim); font-size: var(--font-small); font-weight: 600; }
|
|
460
|
+
.history-turn-time { font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
|
|
461
|
+
.history-turn-instruction { margin: 9px 0 0; font-size: var(--font-body); font-weight: 600; line-height: 1.55; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
462
|
+
.history-turn-answer { margin: 7px 0 0; color: var(--muted); font-size: var(--font-body); line-height: 1.6; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
463
|
+
.history-turn-answer.error { color: var(--red); }
|
|
464
|
+
@media (max-width: 650px) {
|
|
465
|
+
.history-head { padding: 18px 16px 13px; }
|
|
466
|
+
.history-list { padding: 14px 16px 24px; }
|
|
467
|
+
.history-turn { padding: 13px 14px; }
|
|
468
|
+
.history-turn-top { align-items: flex-start; }
|
|
469
|
+
}
|
package/public/tasks.html
CHANGED
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
</section>
|
|
54
54
|
|
|
55
55
|
<section class="panel queue" aria-labelledby="queue-title">
|
|
56
|
-
<div class="panel-head"><div><h2 id="queue-title"><span data-i18n="static.132">最新进展</span></h2><p class="panel-intro"><span data-i18n="static.133">显示这个任务最新一次指令、执行状态和助手回复。</span></p></div></div>
|
|
56
|
+
<div class="panel-head"><div><h2 id="queue-title"><span data-i18n="static.132">最新进展</span></h2><p class="panel-intro"><span data-i18n="static.133">显示这个任务最新一次指令、执行状态和助手回复。</span></p></div><button id="historyDrawerButton" class="small-button" type="button" aria-expanded="false" aria-controls="historyDrawerOverlay" hidden data-i18n="app.226">历史记录</button></div>
|
|
57
57
|
<div id="jobList" class="job-list" aria-live="polite"><div class="empty"><div class="empty-icon" aria-hidden="true">⌁</div><strong><span data-i18n="static.122">正在读取任务</span></strong><span><span data-i18n="static.134">正在连接本机桥接服务…</span></span></div></div>
|
|
58
58
|
</section>
|
|
59
59
|
|
|
@@ -70,6 +70,15 @@
|
|
|
70
70
|
</main>
|
|
71
71
|
<footer class="footer"><span><span data-i18n="static.082">码得 · 多助手桥接</span></span><span id="lastUpdated"><span data-i18n="static.083">等待首次同步</span></span></footer>
|
|
72
72
|
</div>
|
|
73
|
+
<div id="historyDrawerOverlay" class="history-overlay" hidden>
|
|
74
|
+
<aside class="history-drawer" role="dialog" aria-modal="true" aria-labelledby="historyDrawerTitle">
|
|
75
|
+
<div class="history-head">
|
|
76
|
+
<div><span class="section-kicker"><span data-i18n="static.125">当前任务</span></span><h2 id="historyDrawerTitle"><span data-i18n="app.226">历史记录</span></h2><p id="historyDrawerMeta" class="panel-intro"></p></div>
|
|
77
|
+
<button id="historyDrawerClose" class="icon-button" type="button" title="关闭" aria-label="关闭" data-i18n-title="app.230" data-i18n-aria-label="app.230">×</button>
|
|
78
|
+
</div>
|
|
79
|
+
<div id="historyList" class="history-list"></div>
|
|
80
|
+
</aside>
|
|
81
|
+
</div>
|
|
73
82
|
<div id="toast" class="toast" role="status" aria-live="polite"></div>
|
|
74
83
|
<div id="pairingOverlay" class="pairing-overlay" hidden>
|
|
75
84
|
<section class="pairing-dialog" role="dialog" aria-modal="true" aria-labelledby="pairingDialogTitle" aria-describedby="pairingDialogDescription">
|
package/src/agent-sprites.mjs
CHANGED
|
@@ -259,12 +259,14 @@ export function readSprite(packageDir) {
|
|
|
259
259
|
const raw = readManifestFile(spriteDir);
|
|
260
260
|
if (!raw) return null;
|
|
261
261
|
const manifest = validateManifest(raw);
|
|
262
|
-
//
|
|
263
|
-
// GIF
|
|
264
|
-
|
|
262
|
+
// 引用完整性只做提示不做拒绝(2026-10-20 用户拍板):目录里未被 manifest
|
|
263
|
+
// 引用的 PNG/GIF 属于换绑/改形象后忘记删的旧素材,拒整包会让助手形象
|
|
264
|
+
// 整体加载不到(雪球实锤),代价远大于死重。清理责任放在 made-kit skill
|
|
265
|
+
// 层软性强调。manifest 引用不存在的文件(拼写错)仍是硬错误(下方 stat)。
|
|
266
|
+
const gifReferencedHint = new Set();
|
|
265
267
|
if (manifest.actions) {
|
|
266
268
|
for (const action of Object.values(manifest.actions)) {
|
|
267
|
-
if (action && typeof action.gif === 'string')
|
|
269
|
+
if (action && typeof action.gif === 'string') gifReferencedHint.add(action.gif);
|
|
268
270
|
}
|
|
269
271
|
}
|
|
270
272
|
for (const name of fs.readdirSync(spriteDir)) {
|
|
@@ -272,9 +274,11 @@ export function readSprite(packageDir) {
|
|
|
272
274
|
if (lower.endsWith('.png')) {
|
|
273
275
|
const referenced = manifest.layers.some((layer) =>
|
|
274
276
|
(layer.frames !== undefined ? layer.frames : [layer.file]).includes(name));
|
|
275
|
-
if (!referenced)
|
|
276
|
-
|
|
277
|
-
|
|
277
|
+
if (!referenced) {
|
|
278
|
+
console.error(`[plugins] sprite 提示:${path.basename(spriteDir)}/${name} 未被 manifest 引用,不会被下发(建议删除旧素材省空间)`);
|
|
279
|
+
}
|
|
280
|
+
} else if (lower.endsWith('.gif') && !gifReferencedHint.has(name)) {
|
|
281
|
+
console.error(`[plugins] sprite 提示:${path.basename(spriteDir)}/${name} 未被任何 GIF 动作引用,不会被下发(建议删除旧素材省空间)`);
|
|
278
282
|
}
|
|
279
283
|
}
|
|
280
284
|
let total = 0;
|
package/src/jobs.mjs
CHANGED
|
@@ -224,7 +224,9 @@ export class JobStore {
|
|
|
224
224
|
projectFor(job) {
|
|
225
225
|
const configured = this.projects.find((item) => item.id === job.projectId);
|
|
226
226
|
const dir = this.workspacePath(job.vibeSessionId);
|
|
227
|
-
|
|
227
|
+
// 每个会话跑在自己的沙箱目录里,但把选定项目的真实路径一并带给插件
|
|
228
|
+
// (originPath):助手类插件可以只读地回看真正的代码,写入仍留在沙箱。
|
|
229
|
+
return dir ? { ...configured, path: dir, originPath: configured?.path || '' } : configured;
|
|
228
230
|
}
|
|
229
231
|
|
|
230
232
|
createSession(payload) {
|
package/src/server.mjs
CHANGED
|
@@ -1147,6 +1147,15 @@ if (isEntry) {
|
|
|
1147
1147
|
process.once('SIGINT', shutdown);
|
|
1148
1148
|
process.once('SIGTERM', shutdown);
|
|
1149
1149
|
process.once('exit', () => { try { app.tunnel.process?.kill('SIGTERM'); } catch { /* Already exited. */ } });
|
|
1150
|
+
// 无声退出兜底(2026-10-20):桥接器在设备锁屏反复断链场景会无栈消失
|
|
1151
|
+
// (一天三次,2min~25min 不等),未捕获异常/拒绝会静默杀进程。桥接器
|
|
1152
|
+
// 是常驻服务——记录后继续跑,状态机脏了也强过整个掉线。
|
|
1153
|
+
process.on('uncaughtException', (error) => {
|
|
1154
|
+
console.error(`[fatal] uncaughtException(继续运行):${error?.stack || error}`);
|
|
1155
|
+
});
|
|
1156
|
+
process.on('unhandledRejection', (reason) => {
|
|
1157
|
+
console.error(`[fatal] unhandledRejection(继续运行):${reason?.stack || reason}`);
|
|
1158
|
+
});
|
|
1150
1159
|
app.listen().catch((error) => {
|
|
1151
1160
|
console.error(error.message);
|
|
1152
1161
|
process.exitCode = 1;
|