@logicseek/hello-cc 1.0.5 → 1.1.0-rc.3
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/CHANGELOG.md +72 -0
- package/docs/codex-account.zh-CN.md +38 -0
- package/docs/codex-protocol.md +59 -0
- package/docs/commands.md +9 -0
- package/docs/commands.zh-CN.md +7 -0
- package/docs/macos-process-identity.md +2 -0
- package/docs/native.md +179 -14
- package/docs/native.zh-CN.md +136 -6
- package/docs/web-handoff.zh-CN.md +118 -8
- package/lib/cli/app.mjs +47 -4
- package/lib/cli/commands/coordination.mjs +14 -4
- package/lib/cli/commands/dsh.mjs +1 -1
- package/lib/cli/commands/gc.mjs +22 -15
- package/lib/cli/commands/hook.mjs +19 -3
- package/lib/cli/commands/install.mjs +80 -10
- package/lib/cli/commands/misc.mjs +36 -9
- package/lib/cli/commands/native.mjs +4 -38
- package/lib/cli/commands/peer.mjs +3 -0
- package/lib/cli/commands/run.mjs +13 -6
- package/lib/cli/commands/tmux.mjs +1 -2
- package/lib/cli-runtime.mjs +19 -2
- package/lib/core/coordination/results.mjs +27 -10
- package/lib/core/sessions/hook-root-identity.mjs +47 -0
- package/lib/core/sessions/launch.mjs +7 -1
- package/lib/db/connection.mjs +140 -42
- package/lib/db/schema.mjs +59 -0
- package/lib/diagnostics/codex-protocol.mjs +105 -0
- package/lib/guidance.mjs +21 -36
- package/lib/integrations/codex-account.mjs +143 -0
- package/lib/integrations/codex-interactions.mjs +13 -11
- package/lib/integrations/dsh-collaboration.mjs +6 -12
- package/lib/integrations/dsh-cordis.mjs +17 -13
- package/lib/integrations/dsh.mjs +16 -11
- package/lib/integrations/mcp-elicitation.mjs +116 -0
- package/lib/integrations/mcp-url-elicitation.mjs +25 -0
- package/lib/integrations/native/claude.mjs +24 -6
- package/lib/integrations/native/codex.mjs +38 -7
- package/lib/integrations/native/dsh-acp.mjs +67 -24
- package/lib/integrations/native/index.mjs +4 -1
- package/lib/integrations/native/interactions.mjs +2 -1
- package/lib/integrations/native/jsonrpc.mjs +37 -5
- package/lib/integrations/native/telemetry.mjs +30 -0
- package/lib/process/identity.mjs +4 -0
- package/lib/process/pinned-cwd.mjs +302 -0
- package/lib/process/pinned-guidance.mjs +138 -0
- package/lib/process/pinned-project-state.mjs +105 -0
- package/lib/process/selected-cwd-identity.mjs +95 -0
- package/lib/runtime/buffer-evidence.mjs +2 -1
- package/lib/runtime/client.mjs +9 -0
- package/lib/runtime/native/client.mjs +4 -1
- package/lib/runtime/native/history.mjs +140 -0
- package/lib/runtime/native/launcher.mjs +106 -0
- package/lib/runtime/native/service.mjs +64 -8
- package/lib/runtime/native/store.mjs +83 -58
- package/lib/runtime/paths.mjs +17 -2
- package/lib/runtime/private-file.mjs +76 -0
- package/lib/runtime/private-state.mjs +546 -0
- package/lib/runtime/project-context.mjs +2 -1
- package/lib/runtime/project-path.mjs +92 -20
- package/lib/runtime/project-trust.mjs +79 -0
- package/lib/runtime/projects.mjs +9 -3
- package/lib/runtime/state-migration.mjs +697 -0
- package/lib/runtime/state.mjs +69 -27
- package/lib/shared/automatic-result-key.mjs +6 -0
- package/lib/shared/json-file.mjs +55 -6
- package/lib/shared/redact.mjs +2 -1
- package/lib/ui/help.mjs +6 -4
- package/lib/web/agent-defaults.mjs +110 -0
- package/lib/web/auto-attach.mjs +3 -2
- package/lib/web/browser/contracts.d.ts +10 -0
- package/lib/web/browser/core.mjs +1997 -0
- package/lib/web/browser/project-data.mjs +158 -0
- package/lib/web/browser/session-sync.mjs +82 -0
- package/lib/web/browser-assets.mjs +19 -0
- package/lib/web/buffer-gc-runtime.mjs +2 -2
- package/lib/web/codex-app-server.mjs +111 -37
- package/lib/web/codex-sessions.mjs +61 -12
- package/lib/web/context-files.mjs +82 -0
- package/lib/web/cookie-auth.mjs +51 -3
- package/lib/web/external-sessions.mjs +76 -9
- package/lib/web/http-routes.mjs +238 -25
- package/lib/web/http.mjs +50 -4
- package/lib/web/liveness-reaper.mjs +2 -2
- package/lib/web/native-sessions.mjs +323 -14
- package/lib/web/project-contexts.mjs +236 -44
- package/lib/web/project-file-write.mjs +249 -0
- package/lib/web/project-files.mjs +268 -0
- package/lib/web/pty-sessions.mjs +11 -3
- package/lib/web/runtime-main.mjs +132 -80
- package/lib/web/session-serialize.mjs +87 -8
- package/lib/web/session-state-sync.mjs +98 -0
- package/lib/web/shutdown-diagnostics.mjs +38 -0
- package/lib/web/shutdown-response.mjs +21 -0
- package/lib/web/shutdown-state.mjs +91 -0
- package/lib/web/startup.mjs +103 -35
- package/lib/web/task-results.mjs +19 -19
- package/lib/web/tls.mjs +84 -33
- package/lib/web/tmux-sessions.mjs +73 -9
- package/lib/web/tmux-stream.mjs +36 -8
- package/lib/web/ui-agent-defaults.mjs +149 -0
- package/lib/web/ui-agent-start.mjs +330 -0
- package/lib/web/ui-codex-account.mjs +81 -0
- package/lib/web/ui-codex.mjs +105 -85
- package/lib/web/ui-command-palette.mjs +21 -5
- package/lib/web/ui-file-writes.mjs +220 -0
- package/lib/web/ui-files.mjs +231 -0
- package/lib/web/ui-handoff.mjs +10 -2
- package/lib/web/ui-history.mjs +175 -105
- package/lib/web/ui-i18n.mjs +280 -8
- package/lib/web/ui-interactions.mjs +107 -6
- package/lib/web/ui-native-timeline.mjs +326 -0
- package/lib/web/ui-native.mjs +93 -43
- package/lib/web/ui-review.mjs +5 -3
- package/lib/web/ui-safe-markdown.mjs +79 -0
- package/lib/web/ui-session-tools.mjs +224 -0
- package/lib/web/ui-template.mjs +29 -1968
- package/lib/web/ui-terminal-find.mjs +5 -3
- package/lib/web/ui-workbench.mjs +5 -3
- package/lib/web/ui-workspace.mjs +3 -0
- package/package.json +8 -2
- package/scripts/benchmark-session-sync.mjs +53 -0
- package/scripts/codex-protocol.mjs +88 -0
- package/scripts/dsh-approval-layers-acceptance.mjs +172 -0
- package/scripts/dsh-package-provenance.mjs +118 -0
- package/scripts/mcp-form-live-fixture.mjs +81 -0
- package/scripts/native-bounded-live-acceptance.mjs +193 -0
- package/scripts/native-fixture-root.mjs +49 -0
- package/scripts/native-installed-acceptance.mjs +611 -0
- package/scripts/native-stability-checks.mjs +40 -0
- package/scripts/regression-cookie-expiry.mjs +70 -0
- package/scripts/regression-http-transport.mjs +9 -0
- package/scripts/regression-terminal-marker.mjs +95 -0
- package/scripts/regression.mjs +481 -219
- package/scripts/shim-reentry-probe.mjs +105 -0
- package/scripts/web-acceptance-diagnostics.mjs +90 -0
- package/scripts/web-codex-account-acceptance.mjs +178 -0
- package/scripts/web-file-preview-checks.mjs +302 -0
- package/scripts/web-mcp-form-acceptance.mjs +134 -0
- package/scripts/web-mcp-form-live-acceptance.mjs +368 -0
- package/scripts/web-native-handoff-checks.mjs +128 -0
- package/scripts/web-native-interaction-acceptance.mjs +219 -18
- package/scripts/web-native-interaction-diagnostics.mjs +80 -0
- package/scripts/web-native-model-acceptance.mjs +22 -7
- package/scripts/web-session-tools-checks.mjs +112 -0
- package/scripts/web-workbench-acceptance.mjs +435 -0
- package/scripts/web-workbench-installed-acceptance.mjs +122 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { createSafeMarkdown } from './ui-safe-markdown.mjs';
|
|
2
|
+
import { createFileWrites, fileWritesCss, fileUploadHtml, fileEditorHtml } from './ui-file-writes.mjs';
|
|
3
|
+
|
|
4
|
+
const escape = value => String(value).replace(/[&<>"']/g, character => ({ '&':'&', '<':'<', '>':'>', '"':'"', "'":''' }[character]));
|
|
5
|
+
export function renderFileMarkdown(content) {
|
|
6
|
+
const markdown = createSafeMarkdown(escape), parts = [];
|
|
7
|
+
let prose = [], code = null, fence = '';
|
|
8
|
+
for (const line of String(content).split('\n')) {
|
|
9
|
+
const marker = /^\s{0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
10
|
+
if (!code && marker) {
|
|
11
|
+
if (prose.length) parts.push(markdown(prose.join('\n')));
|
|
12
|
+
prose = []; code = []; fence = marker[1];
|
|
13
|
+
} else if (code && marker && marker[1][0] === fence[0] && marker[1].length >= fence.length && !marker[2].trim()) {
|
|
14
|
+
parts.push('<pre><code>' + escape(code.join('\n')) + '</code></pre>'); code = null;
|
|
15
|
+
} else (code || prose).push(line);
|
|
16
|
+
}
|
|
17
|
+
if (code) parts.push('<pre><code>' + escape(code.join('\n')) + '</code></pre>');
|
|
18
|
+
if (prose.length) parts.push(markdown(prose.join('\n')));
|
|
19
|
+
return parts.join('');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function staticPreviewCss(value) {
|
|
23
|
+
const css = String(value).replace(/\/\*[\s\S]*?\*\//g, '');
|
|
24
|
+
// Escape sequences and resource-bearing CSS are omitted rather than decoded
|
|
25
|
+
// into a second URL language. The preview CSP independently blocks requests.
|
|
26
|
+
const resourceSyntax = /\\|url\s*\(|@import|@font-face|image-set\s*\(|expression\s*\(|behavior\s*:|-moz-binding|https?:/i;
|
|
27
|
+
return resourceSyntax.test(css) ? '' : css;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function staticPreviewNode(node) {
|
|
31
|
+
if (node.nodeType === 3) return escape(node.textContent || '');
|
|
32
|
+
if (node.nodeType !== 1 || (node.namespaceURI && node.namespaceURI !== 'http://www.w3.org/1999/xhtml')) return '';
|
|
33
|
+
const tag = String(node.localName).toLowerCase();
|
|
34
|
+
if (['script','iframe','object','embed','link','meta','base','title','template','noscript','svg','math','form','input','select','textarea','button','audio','video','source'].includes(tag)) return '';
|
|
35
|
+
if (tag === 'style') return '<style>' + staticPreviewCss(node.textContent).replace(/</g, '\\3c ') + '</style>';
|
|
36
|
+
const body = [...(node.childNodes || [])].map(staticPreviewNode).join('');
|
|
37
|
+
if (!['html','head','body','main','article','section','header','footer','nav','aside','div','span','p','h1','h2','h3','h4','h5','h6','ul','ol','li','dl','dt','dd','table','thead','tbody','tfoot','tr','th','td','caption','colgroup','col','pre','code','blockquote','strong','em','b','i','u','s','small','sub','sup','br','hr','img','a','figure','figcaption','details','summary','time'].includes(tag)) return body;
|
|
38
|
+
if (['html','head','body'].includes(tag)) return body;
|
|
39
|
+
const attrs = [];
|
|
40
|
+
for (const attr of [...(node.attributes || [])]) {
|
|
41
|
+
const name = attr.name.toLowerCase();
|
|
42
|
+
if (['id','class','title','alt','width','height','colspan','rowspan','scope','dir','lang'].includes(name)) attrs.push(name + '="' + escape(attr.value) + '"');
|
|
43
|
+
else if (name === 'style') { const css = staticPreviewCss(attr.value); if (css) attrs.push('style="' + escape(css) + '"'); }
|
|
44
|
+
else if (tag === 'img' && name === 'src' && /^data:image\/(?:png|jpeg|gif|webp);base64,[A-Za-z0-9+/=\s]+$/i.test(attr.value)) attrs.push('src="' + escape(attr.value) + '"');
|
|
45
|
+
}
|
|
46
|
+
return '<' + tag + (attrs.length ? ' ' + attrs.join(' ') : '') + '>' + body + (['br','hr','img','col'].includes(tag) ? '' : '</' + tag + '>');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function staticHtmlPreview(content, document) {
|
|
50
|
+
// Template contents are inert: parsing cannot start a subresource fetch or
|
|
51
|
+
// execute a script. Rebuild the output rather than trusting the original DOM.
|
|
52
|
+
const template = document.createElement('template'); template.innerHTML = String(content);
|
|
53
|
+
const body = [...template.content.childNodes].map(staticPreviewNode).join('');
|
|
54
|
+
const csp = "default-src 'none'; script-src 'none'; style-src 'unsafe-inline'; img-src data:; font-src 'none'; connect-src 'none'; media-src 'none'; frame-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'";
|
|
55
|
+
return '<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="' + escape(csp)
|
|
56
|
+
+ '"><meta name="referrer" content="no-referrer"><style>body{margin:20px;overflow-wrap:anywhere}img{max-width:100%;height:auto}pre{white-space:pre-wrap}</style></head><body>' + body + '</body></html>';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const filesCss = `
|
|
60
|
+
${fileWritesCss}
|
|
61
|
+
.files-dialog { width:min(1180px,96vw); height:min(850px,92dvh); max-width:none; display:flex; flex-direction:column; gap:12px; overflow:hidden; }
|
|
62
|
+
.files-project,.files-meta { color:var(--muted); font-size:var(--small-font); overflow-wrap:anywhere; margin:0; }
|
|
63
|
+
.files-layout { flex:1; min-height:0; display:grid; grid-template-columns:minmax(220px,29%) minmax(0,1fr); gap:12px; }
|
|
64
|
+
.files-browser,.files-view { min-width:0; min-height:0; display:flex; flex-direction:column; gap:8px; }
|
|
65
|
+
.files-browser { border-right:1px solid var(--border); padding-right:12px; }
|
|
66
|
+
.files-path-form,.files-toolbar { display:flex; flex-wrap:wrap; align-items:center; gap:6px; }
|
|
67
|
+
.files-path-form input { min-width:0; flex:1; }
|
|
68
|
+
.files-entries { overflow:auto; flex:1; min-height:0; display:flex; flex-direction:column; gap:4px; }
|
|
69
|
+
.files-entries button { height:auto; min-height:36px; width:100%; text-align:left; overflow-wrap:anywhere; flex:none; }
|
|
70
|
+
.files-entries button[aria-current="true"] { background:var(--selection-bg); border-color:var(--accent); }
|
|
71
|
+
.files-preview { flex:1; min-width:0; min-height:0; overflow:auto; border:1px solid var(--border); border-radius:8px; padding:12px; }
|
|
72
|
+
.files-preview pre { margin:0; white-space:pre-wrap; overflow-wrap:anywhere; font:var(--small-font)/1.6 var(--mono); }
|
|
73
|
+
.files-preview img { display:block; max-width:100%; height:auto; margin:auto; }
|
|
74
|
+
.files-preview iframe { border:0; width:100%; height:100%; min-height:350px; background:white; }
|
|
75
|
+
.files-preview:has(iframe) { padding:0; }
|
|
76
|
+
.files-preview .codex-markdown { max-width:850px; margin:auto; }
|
|
77
|
+
#filesBack { display:none; }
|
|
78
|
+
.files-dialog [hidden] { display:none!important; }
|
|
79
|
+
.files-dialog[data-main-window-required="true"] .files-layout { grid-template-columns:1fr; }
|
|
80
|
+
.files-dialog[data-main-window-required="true"] .files-browser,.files-dialog[data-main-window-required="true"] .files-toolbar { display:none; }
|
|
81
|
+
@media(max-width:700px) {
|
|
82
|
+
.files-dialog { width:96vw; height:94dvh; padding:12px; }
|
|
83
|
+
.files-layout { display:flex; }
|
|
84
|
+
.files-browser,.files-view { flex:1; }
|
|
85
|
+
.files-browser { border:0; padding:0; }
|
|
86
|
+
.files-dialog[data-mobile-view="tree"] .files-view { display:none; }
|
|
87
|
+
.files-dialog[data-mobile-view="preview"] .files-browser { display:none; }
|
|
88
|
+
#filesBack { display:inline-flex; }
|
|
89
|
+
.files-toolbar button,.files-entries button { min-height:44px; }
|
|
90
|
+
}
|
|
91
|
+
`;
|
|
92
|
+
|
|
93
|
+
export function filesHtml() {
|
|
94
|
+
return `<div class="dialog-overlay" id="filesDialog" role="dialog" aria-modal="true" aria-labelledby="filesTitle" hidden><section class="dialog files-dialog" id="filesPanel" data-mobile-view="tree">
|
|
95
|
+
<header class="dialog-heading"><h3 id="filesTitle" data-i18n="files.title">Project files</h3><button id="filesClose" type="button" data-i18n-aria="close" aria-label="Close">×</button></header>
|
|
96
|
+
<p class="files-project" id="filesProject"></p><p class="dialog-help" data-i18n="files.readOnly">Preview, upload and edit project files. File contents are not sent to an Agent.</p>
|
|
97
|
+
<div class="files-layout"><section class="files-browser">
|
|
98
|
+
<form class="files-path-form" id="filesPathForm"><input id="filesPath" type="text" autocomplete="off" data-i18n-aria="files.path" aria-label="Project relative directory"><button id="filesGo" type="submit" data-i18n="files.go">Go</button></form>
|
|
99
|
+
<div class="files-toolbar"><button id="filesUp" type="button" data-i18n="files.up">Parent directory</button><button id="filesRefresh" type="button" data-i18n="refresh">Refresh</button></div>
|
|
100
|
+
${fileUploadHtml()}
|
|
101
|
+
<p class="dialog-help" id="filesTreeStatus" role="status"></p><div class="files-entries" id="filesEntries" data-i18n-aria="files.title" aria-label="Project files"></div>
|
|
102
|
+
</section><section class="files-view">
|
|
103
|
+
<div class="files-toolbar"><button id="filesBack" type="button" data-i18n="files.back">Back to files</button><strong id="filesName"></strong><button id="filesSourceBtn" type="button" data-i18n="files.source" aria-pressed="false" hidden>Source</button><button id="filesReload" type="button" data-i18n="refresh" hidden>Refresh</button><a id="filesDownload" data-i18n="files.download" hidden>Download PDF</a></div>
|
|
104
|
+
<p class="files-meta" id="filesMeta"></p><p class="dialog-help" id="filesPreviewStatus" role="status"></p>${fileEditorHtml()}<div class="files-preview" id="filesPreview" tabindex="0"></div>
|
|
105
|
+
</section></div>
|
|
106
|
+
</section></div>`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function installFiles(browser = globalThis) {
|
|
110
|
+
const { window, document } = browser, bridge = window.hccHandoff, byId = id => document.getElementById(id);
|
|
111
|
+
const dialog = byId('filesDialog'), panel = byId('filesPanel'), preview = byId('filesPreview');
|
|
112
|
+
const tr = key => bridge.tr(key);
|
|
113
|
+
let revision = 0, treeSequence = 0, fileSequence = 0, root = '', rootIdentity = '', directory = '', selected = '', value = null, source = false;
|
|
114
|
+
const urls = new Set();
|
|
115
|
+
const writes = createFileWrites({browser,
|
|
116
|
+
context:()=>({root,identity:rootIdentity,directory,path:selected,value,hidden:dialog.hidden,visit:revision+':'+fileSequence}),
|
|
117
|
+
updated:next=>{value=next;render();writes.select();},
|
|
118
|
+
uploaded:path=>{void tree(directory);void openFile(path);}
|
|
119
|
+
});
|
|
120
|
+
const current = visit => !dialog.hidden && visit === revision && root === bridge.projectRoot && rootIdentity === (bridge.projectIdentity || '');
|
|
121
|
+
function release() { for (const url of urls) browser.URL.revokeObjectURL(url); urls.clear(); }
|
|
122
|
+
function clearPreview() {
|
|
123
|
+
writes.suspend();
|
|
124
|
+
preview.replaceChildren(); release(); value = null;
|
|
125
|
+
for (const id of ['filesName','filesMeta','filesPreviewStatus']) byId(id).textContent = '';
|
|
126
|
+
for (const id of ['filesSourceBtn','filesReload','filesDownload']) byId(id).hidden = true;
|
|
127
|
+
byId('filesDownload').removeAttribute('href');
|
|
128
|
+
}
|
|
129
|
+
function blobUrl(content, mime) {
|
|
130
|
+
const bytes = browser.Uint8Array.from(browser.atob(content), character => character.charCodeAt(0));
|
|
131
|
+
const url = browser.URL.createObjectURL(new browser.Blob([bytes], { type:mime })); urls.add(url); return url;
|
|
132
|
+
}
|
|
133
|
+
function render() {
|
|
134
|
+
if (!value) return;
|
|
135
|
+
preview.replaceChildren(); release();
|
|
136
|
+
const download = byId('filesDownload'); download.hidden = true; download.removeAttribute('href');
|
|
137
|
+
byId('filesName').textContent = value.name || value.path;
|
|
138
|
+
byId('filesMeta').textContent = value.path + ' · ' + (Number.isFinite(value.size) ? value.size + ' bytes' : '');
|
|
139
|
+
const textual = value.encoding === 'utf8' && ['text','markdown','html'].includes(value.kind);
|
|
140
|
+
const sourceOnly = value.kind === 'html' && value.truncated;
|
|
141
|
+
byId('filesSourceBtn').hidden = !textual || value.kind === 'text' || sourceOnly;
|
|
142
|
+
byId('filesSourceBtn').setAttribute('aria-pressed',String(source));
|
|
143
|
+
byId('filesSourceBtn').textContent = tr(source ? 'files.rendered' : 'files.source');
|
|
144
|
+
byId('filesReload').hidden = false;
|
|
145
|
+
byId('filesPreviewStatus').textContent = [value.truncated ? tr('files.truncated') : '',
|
|
146
|
+
value.kind === 'html' ? tr('files.htmlLimit') : value.kind === 'pdf' ? tr('files.pdfHelp') : ''].filter(Boolean).join(' ');
|
|
147
|
+
if (textual && (source || sourceOnly || value.kind === 'text')) {
|
|
148
|
+
const pre = document.createElement('pre'); pre.textContent = value.content || ''; preview.appendChild(pre);
|
|
149
|
+
} else if (value.kind === 'markdown' && textual) {
|
|
150
|
+
preview.innerHTML = renderFileMarkdown(value.content || '');
|
|
151
|
+
} else if (value.kind === 'html' && textual) {
|
|
152
|
+
const frame = document.createElement('iframe'); frame.id = 'filesFrame'; frame.title = tr('files.staticHtml');
|
|
153
|
+
frame.setAttribute('sandbox',''); frame.referrerPolicy = 'no-referrer'; frame.srcdoc = staticHtmlPreview(value.content || '',document); preview.appendChild(frame);
|
|
154
|
+
} else if (value.kind === 'image' && value.encoding === 'base64' && /^image\/(png|jpeg|gif|webp)$/.test(value.mime)) {
|
|
155
|
+
const img = document.createElement('img'); img.alt = value.name || value.path; img.src = blobUrl(value.content,value.mime); preview.appendChild(img);
|
|
156
|
+
} else if (value.kind === 'pdf' && value.encoding === 'base64' && value.mime === 'application/pdf' && browser.atob(value.content.slice(0,8)).startsWith('%PDF-')) {
|
|
157
|
+
const url = blobUrl(value.content,'application/pdf');
|
|
158
|
+
const frame = document.createElement('iframe'); frame.id = 'filesFrame'; frame.title = value.name || 'PDF';
|
|
159
|
+
frame.referrerPolicy = 'no-referrer'; frame.src = url; preview.appendChild(frame);
|
|
160
|
+
download.href = url; download.download = value.name || 'document.pdf'; download.hidden = false;
|
|
161
|
+
} else preview.textContent = tr('files.unsupported');
|
|
162
|
+
writes.sync();
|
|
163
|
+
}
|
|
164
|
+
async function tree(path = '') {
|
|
165
|
+
if (bridge.draftScope === 'auxiliary') return;
|
|
166
|
+
const visit = revision, sequence = ++treeSequence;
|
|
167
|
+
directory = path; byId('filesPath').value = path; byId('filesUp').disabled = !path;
|
|
168
|
+
writes.sync();
|
|
169
|
+
byId('filesTreeStatus').textContent = tr('files.loading'); byId('filesEntries').replaceChildren();
|
|
170
|
+
try {
|
|
171
|
+
const result = await bridge.api('/api/files/tree?path=' + encodeURIComponent(path));
|
|
172
|
+
if (!current(visit) || sequence !== treeSequence) return;
|
|
173
|
+
directory = result.path || ''; byId('filesPath').value = directory; byId('filesUp').disabled = !directory;
|
|
174
|
+
writes.sync();
|
|
175
|
+
const entries = Array.isArray(result.entries) ? result.entries : [];
|
|
176
|
+
byId('filesTreeStatus').textContent = result.truncated ? tr('files.treeTruncated') : entries.length ? '' : tr('files.empty');
|
|
177
|
+
for (const entry of entries) {
|
|
178
|
+
if (!['file','directory'].includes(entry.type) || typeof entry.path !== 'string') continue;
|
|
179
|
+
const button = document.createElement('button'); button.type = 'button'; button.dataset.filePath = entry.path; button.dataset.fileType = entry.type;
|
|
180
|
+
button.textContent = (entry.type === 'directory' ? '▸ ' : '') + entry.name;
|
|
181
|
+
button.setAttribute('aria-current',String(entry.path === selected));
|
|
182
|
+
button.addEventListener('click', () => entry.type === 'directory' ? tree(entry.path) : openFile(entry.path));
|
|
183
|
+
byId('filesEntries').appendChild(button);
|
|
184
|
+
}
|
|
185
|
+
} catch (error) { if (current(visit) && sequence === treeSequence) byId('filesTreeStatus').textContent = error.detail || error.message; }
|
|
186
|
+
}
|
|
187
|
+
async function openFile(path) {
|
|
188
|
+
if (bridge.draftScope === 'auxiliary') return;
|
|
189
|
+
const visit = revision, sequence = ++fileSequence;
|
|
190
|
+
selected = path; source = false; clearPreview(); panel.dataset.mobileView = 'preview';
|
|
191
|
+
for (const button of byId('filesEntries').children) button.setAttribute('aria-current',String(button.dataset.filePath === path));
|
|
192
|
+
byId('filesName').textContent = path; byId('filesPreviewStatus').textContent = tr('files.loading');
|
|
193
|
+
byId('filesReload').hidden = false;
|
|
194
|
+
try {
|
|
195
|
+
const result = await bridge.api('/api/files/preview?path=' + encodeURIComponent(path));
|
|
196
|
+
if (!current(visit) || sequence !== fileSequence) return;
|
|
197
|
+
value = result; render(); writes.select();
|
|
198
|
+
} catch (error) { if (current(visit) && sequence === fileSequence) { byId('filesPreviewStatus').textContent = error.detail || error.message; writes.select(); } }
|
|
199
|
+
}
|
|
200
|
+
function closed() { revision++; treeSequence++; fileSequence++; clearPreview(); }
|
|
201
|
+
function reset() { closed(); if (!dialog.hidden) bridge.closeDialog(dialog); }
|
|
202
|
+
function open(path = '') {
|
|
203
|
+
if (bridge.draftScope === 'auxiliary') {
|
|
204
|
+
try {
|
|
205
|
+
if (window.parent !== window && window.parent.hccHandoff?.projectRoot === bridge.projectRoot && window.parent.hccHandoff?.projectIdentity === bridge.projectIdentity && window.parent.hccFiles) {
|
|
206
|
+
window.parent.hccFiles.open(path); return;
|
|
207
|
+
}
|
|
208
|
+
} catch { /* A detached or cross-origin parent cannot accept this request. */ }
|
|
209
|
+
closed(); root = bridge.projectRoot; panel.dataset.mobileView = 'preview';
|
|
210
|
+
panel.dataset.mainWindowRequired = 'true';
|
|
211
|
+
byId('filesProject').textContent = root; preview.textContent = tr('files.mainWindowRequired');
|
|
212
|
+
bridge.openDialog(dialog,byId('filesClose')); return;
|
|
213
|
+
}
|
|
214
|
+
delete panel.dataset.mainWindowRequired;
|
|
215
|
+
closed(); root = bridge.projectRoot; rootIdentity = bridge.projectIdentity || ''; directory = ''; selected = ''; panel.dataset.mobileView = path ? 'preview' : 'tree';
|
|
216
|
+
byId('filesProject').textContent = root; bridge.openDialog(dialog,byId('filesPath'));
|
|
217
|
+
void tree(path ? path.split('/').slice(0,-1).join('/') : '');
|
|
218
|
+
if (path) void openFile(path); else preview.textContent = tr('files.select');
|
|
219
|
+
}
|
|
220
|
+
byId('filesBtn').addEventListener('click',() => open());
|
|
221
|
+
byId('filesClose').addEventListener('click',() => bridge.closeDialog(dialog));
|
|
222
|
+
byId('filesPathForm').addEventListener('submit',event => { event.preventDefault(); void tree(byId('filesPath').value.trim().replace(/^\.\/?$/,'')); });
|
|
223
|
+
byId('filesUp').addEventListener('click',() => tree(directory.split('/').slice(0,-1).join('/')));
|
|
224
|
+
byId('filesRefresh').addEventListener('click',() => tree(directory));
|
|
225
|
+
byId('filesReload').addEventListener('click',() => selected && openFile(selected));
|
|
226
|
+
byId('filesBack').addEventListener('click',() => { panel.dataset.mobileView = 'tree'; byId('filesPath').focus(); });
|
|
227
|
+
byId('filesSourceBtn').addEventListener('click',() => { source = !source; render(); });
|
|
228
|
+
window.addEventListener('hcc:preferences',() => { if (!dialog.hidden) render(); });
|
|
229
|
+
window.addEventListener('pagehide',closed);
|
|
230
|
+
window.hccFiles = { open,closed,reset };
|
|
231
|
+
}
|
package/lib/web/ui-handoff.mjs
CHANGED
|
@@ -74,7 +74,13 @@ export const handoffCss = `
|
|
|
74
74
|
.main > #codexPanel { flex:1; min-height:0; height:auto !important; }
|
|
75
75
|
.handoff-bar { display:flex; flex-wrap:wrap; gap:8px; align-items:center; padding:8px 12px; border-bottom:1px solid var(--border); background:var(--panel); }
|
|
76
76
|
.handoff-status { flex:1 1 220px; font-size:12px; color:var(--muted); }
|
|
77
|
-
.handoff-detail { width:
|
|
77
|
+
.handoff-detail { flex:0 1 auto; min-width:0; font-size:11px; color:var(--muted); overflow-wrap:anywhere; }
|
|
78
|
+
.handoff-detail > summary { cursor:pointer; padding:6px 0; }
|
|
79
|
+
.handoff-detail[open] { flex-basis:100%; }
|
|
80
|
+
.handoff-detail[open] > summary { margin-bottom:4px; }
|
|
81
|
+
.active-identity { display:grid; grid-template-columns:auto minmax(0,1fr); gap:5px 10px; margin:8px 0; }
|
|
82
|
+
.active-identity dt { color:var(--muted); }
|
|
83
|
+
.active-identity dd { margin:0; color:var(--text); font-family:var(--mono); overflow-wrap:anywhere; }
|
|
78
84
|
.handoff-bar button { height:30px; font-size:12px; }
|
|
79
85
|
.terminal-composer { display:grid; gap:6px; padding:10px 12px; border-top:1px solid var(--border); background:var(--panel); }
|
|
80
86
|
.terminal-composer textarea { width:100%; min-height:64px; max-height:160px; resize:vertical; font:inherit; border:1px solid var(--border); border-radius:6px; padding:8px; background:var(--input-bg); color:var(--text); }
|
|
@@ -90,7 +96,7 @@ export function handoffBarHtml() {
|
|
|
90
96
|
<button id="claimControlBtn" type="button" data-i18n="handoff.claim">Take control</button>
|
|
91
97
|
<button id="releaseControlBtn" type="button" data-i18n="handoff.release" hidden>Release control</button>
|
|
92
98
|
<button id="reconnectBtn" type="button" data-i18n="handoff.reconnect">Reconnect</button>
|
|
93
|
-
<details class="handoff-detail"><summary id="handoffHelp">
|
|
99
|
+
<details class="handoff-detail" id="activeDetails"><summary id="handoffHelp">Session details</summary><div id="activeTask" hidden></div><dl class="active-identity" id="activeIdentity"></dl><div id="handoffDetail"></div></details>
|
|
94
100
|
</div>`;
|
|
95
101
|
}
|
|
96
102
|
|
|
@@ -100,3 +106,5 @@ export function terminalComposerHtml() {
|
|
|
100
106
|
<div class="composer-actions"><span class="composer-note" id="draftStatus" role="status" aria-live="polite"></span><button id="reviewDraftBtn" type="button" data-i18n="handoff.review" hidden>Reviewed; keep draft</button><button class="primary" id="sendDraftBtn" type="submit" data-i18n="handoff.send">Send + Enter</button></div>
|
|
101
107
|
</form>`;
|
|
102
108
|
}
|
|
109
|
+
|
|
110
|
+
export function installHandoff() { const window = globalThis.window; window.hccHandoffStore = createHandoffStore((() => { try { return window.localStorage; } catch { return null; } })(), { scope: window.hccUi?.draftScope || window.hccDraftScope || '' }); }
|
package/lib/web/ui-history.mjs
CHANGED
|
@@ -1,118 +1,188 @@
|
|
|
1
|
+
import { nativeTimeline } from './ui-native-timeline.mjs';
|
|
2
|
+
|
|
1
3
|
export function codexHistoryHtml() {
|
|
2
4
|
return `<div class="dialog-overlay" id="historyDialog" role="dialog" aria-modal="true" aria-labelledby="historyTitle" hidden>
|
|
3
5
|
<div class="dialog auxiliary-dialog">
|
|
4
|
-
<header class="dialog-heading"><h3 id="historyTitle" data-i18n="history.title">
|
|
5
|
-
<
|
|
6
|
-
|
|
6
|
+
<header class="dialog-heading"><h3 id="historyTitle" data-i18n="history.title">History</h3><button id="historyClose" type="button" data-i18n-aria="close" aria-label="Close">×</button></header>
|
|
7
|
+
<div class="session-tool-row"><label><span data-i18n="history.source">Source</span> <select id="historySource"><option value="native" data-i18n="history.native">HCC retained history</option><option value="codex" data-i18n="history.codex">Codex saved threads</option></select></label>
|
|
8
|
+
<label id="historyProviderField"><span data-i18n="agent.provider">Agent</span> <select id="historyProvider"><option value="all" data-i18n="history.allProviders">All providers</option><option value="codex">Codex</option><option value="claude">Claude</option><option value="dsh">DeepSeek Harness</option></select></label></div>
|
|
9
|
+
<p class="dialog-help" id="historyHelp"></p><p id="historyNotice" class="dialog-help" role="status" aria-live="polite"></p>
|
|
10
|
+
<p id="historyUnverified" class="dialog-help" role="status" aria-live="polite"></p>
|
|
7
11
|
<div class="history-layout"><div class="history-list"><button id="historyRefresh" type="button" data-i18n="refresh">Refresh</button><div id="historyThreads"></div><button id="historyMore" type="button" data-i18n="history.more" hidden>Load more</button></div>
|
|
8
|
-
<div class="history-preview"><h4 id="historySelected"></h4><
|
|
9
|
-
<
|
|
12
|
+
<div class="history-preview"><h4 id="historySelected"></h4><p id="historyIdentity" class="dialog-help"></p><p id="historyRetention" class="dialog-help"></p>
|
|
13
|
+
<p id="historyResumeReason" class="dialog-help"></p>
|
|
14
|
+
<label class="confirmation-row"><input id="historyConfirmed" type="checkbox"><span id="historyConfirmLabel"></span></label>
|
|
10
15
|
<div class="btns"><button id="historyResume" type="button" data-i18n="history.resume" disabled>Resume in a new executor</button><button id="historyFork" type="button" data-i18n="history.fork" disabled>Fork into a new executor</button></div>
|
|
16
|
+
<div class="session-tool-row" id="historyEventControls" hidden><button id="historyEventsPrevious" type="button" data-i18n="history.previousEvents">Previous events</button><span id="historyEventPage"></span><button id="historyEventsNext" type="button" data-i18n="history.nextEvents">Next events</button><button id="historyEventsReload" type="button" data-i18n="refresh">Refresh</button><label><span data-i18n="history.view">View</span> <select id="historyNativeView"><option value="conversation" data-i18n="history.output">Recorded output</option><option value="trace" data-i18n="history.trace">Event trace</option></select></label></div>
|
|
17
|
+
<div id="historyContent"></div><details id="historyReceipts" hidden><summary data-i18n="history.receipts">Recent delivery receipts</summary><pre id="historyReceiptContent"></pre></details>
|
|
11
18
|
</div></div>
|
|
12
19
|
</div>
|
|
13
20
|
</div>`;
|
|
14
21
|
}
|
|
15
22
|
|
|
16
|
-
export function codexHistoryScript() {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
23
|
+
export function codexHistoryScript() { return '(' + installHistory.toString() + ')(globalThis,(' + nativeTimeline.toString() + '));'; }
|
|
24
|
+
|
|
25
|
+
export function installHistory(browser = globalThis, timeline = nativeTimeline) {
|
|
26
|
+
const { window, document } = browser, byId = id => document.getElementById(id);
|
|
27
|
+
const dialog = byId('historyDialog'), bridge = () => window.hccHandoff;
|
|
28
|
+
const tr = (key, fallback = '') => bridge().tr(key, fallback), esc = value => bridge().esc(String(value ?? ''));
|
|
29
|
+
let root = '', source = 'native', entries = [], cursor = null, selected = null, ready = false, busy = false, generation = 0;
|
|
30
|
+
let unverifiedCount = 0;
|
|
31
|
+
let events = [], deliveries = [], nextAfter = null, pageStarts = [0], page = 0, runtimeAvailable = true;
|
|
32
|
+
const isNative = () => source === 'native';
|
|
33
|
+
const same = (project, version) => project === root && project === bridge().projectRoot && version === generation && !dialog.hidden;
|
|
34
|
+
const route = (path, project = root) => path + (path.includes('?') ? '&' : '?') + 'root=' + encodeURIComponent(project);
|
|
35
|
+
const entryId = entry => isNative() ? entry.peer : entry.id;
|
|
36
|
+
function notice(value) { byId('historyNotice').textContent = value || ''; }
|
|
37
|
+
function liveSources() {
|
|
38
|
+
return (bridge().sessions || []).filter(session => session.status === 'running' && (isNative()
|
|
39
|
+
? session.type === 'native' && [session.id,session.peer_id].includes(selected?.peer)
|
|
40
|
+
: session.binding?.provider === 'codex' && session.binding.provider_session_id === selected?.id));
|
|
41
|
+
}
|
|
42
|
+
function controlsSource(sources) { return sources.every(session => session.id === bridge().active && bridge().canControl); }
|
|
43
|
+
function nativeEligible() { return ready && selected?.resumable === true && selected.status === 'closed' && selected.owned === false && Boolean(selected.owner && selected.sessionId); }
|
|
44
|
+
function updatedLabel(value) {
|
|
45
|
+
if (!value) return '';
|
|
46
|
+
const date = new Date(value);
|
|
47
|
+
return Number.isFinite(date.getTime()) ? date.toLocaleString() : String(value);
|
|
48
|
+
}
|
|
49
|
+
function sync() {
|
|
50
|
+
if (!dialog.hidden && !root && bridge().projectRoot) { root = bridge().projectRoot; void load(); return; }
|
|
51
|
+
const confirmed = byId('historyConfirmed').checked, sources = liveSources();
|
|
52
|
+
const unavailable = busy || !ready || !selected || !confirmed || root !== bridge().projectRoot || Boolean(window.hccAgentStart?.pending);
|
|
53
|
+
byId('historyProviderField').hidden = !isNative();
|
|
54
|
+
byId('historyHelp').textContent = tr(isNative() ? 'history.nativeHelp' : 'history.help');
|
|
55
|
+
byId('historyUnverified').textContent = !isNative() && unverifiedCount > 0
|
|
56
|
+
? tr('history.unverified').replace('{count}', String(unverifiedCount)) : '';
|
|
57
|
+
byId('historyConfirmLabel').textContent = tr(isNative() ? 'history.nativeConfirm' : 'history.confirm');
|
|
58
|
+
byId('historyResume').disabled = unavailable || sources.length > 0 || (isNative() && !nativeEligible());
|
|
59
|
+
byId('historyFork').hidden = isNative(); byId('historyFork').disabled = isNative() || unavailable || !controlsSource(sources);
|
|
60
|
+
byId('historyResumeReason').textContent = isNative() && selected && !nativeEligible()
|
|
61
|
+
? tr('history.resumeReason.' + (selected.resumeReason || 'unavailable'), selected.resumeDetail || tr('history.resumeUnavailable')) : '';
|
|
62
|
+
if (!busy && sources.length) notice(tr(isNative() ? 'history.nativeActive' : controlsSource(sources) ? 'history.stopOriginal' : 'history.sourceControlRequired'));
|
|
63
|
+
byId('historyRefresh').disabled = busy; byId('historyMore').hidden = isNative() || !cursor; byId('historyMore').disabled = busy;
|
|
64
|
+
byId('historyEventControls').hidden = !isNative() || !ready;
|
|
65
|
+
byId('historyEventsPrevious').disabled = busy || page === 0; byId('historyEventsNext').disabled = busy || nextAfter == null;
|
|
66
|
+
byId('historyEventsReload').disabled = busy; byId('historyReceipts').hidden = !isNative() || !ready;
|
|
67
|
+
}
|
|
68
|
+
function clearSelected() {
|
|
69
|
+
selected = null; ready = false; events = []; deliveries = []; nextAfter = null; pageStarts = [0]; page = 0;
|
|
70
|
+
byId('historyConfirmed').checked = false; byId('historyReceipts').open = false;
|
|
71
|
+
for (const id of ['historySelected','historyContent','historyIdentity','historyRetention','historyReceiptContent']) byId(id).textContent = '';
|
|
72
|
+
}
|
|
73
|
+
function renderList() {
|
|
74
|
+
const provider = byId('historyProvider').value || 'all';
|
|
75
|
+
const filtered = isNative() && provider !== 'all' ? entries.filter(entry => entry.provider === provider) : entries;
|
|
76
|
+
const container = byId('historyThreads');
|
|
77
|
+
container.innerHTML = filtered.map(entry => '<button type="button" class="history-thread" data-thread="' + esc(entryId(entry)) + '" aria-pressed="' + (entryId(entry) === (selected && entryId(selected))) + '"><strong>'
|
|
78
|
+
+ esc(isNative() ? entry.peer : entry.name || entry.preview || entry.id) + '</strong><span>'
|
|
79
|
+
+ esc(isNative() ? [entry.provider,entry.status,updatedLabel(entry.updatedAt)].filter(Boolean).join(' · ') : entry.id) + '</span><span>' + esc(entry.cwd || '') + '</span></button>').join('')
|
|
80
|
+
|| '<p class="dialog-help">' + esc(tr(isNative() ? 'history.nativeEmpty' : 'history.empty')) + '</p>';
|
|
81
|
+
container.querySelectorAll('[data-thread]').forEach(button => button.addEventListener('click', () => select(button.dataset.thread))); sync();
|
|
82
|
+
}
|
|
83
|
+
async function load(more = false) {
|
|
84
|
+
if (busy) return;
|
|
85
|
+
if (!root) { notice(tr('history.projectLoading')); return; }
|
|
86
|
+
const project = root, version = ++generation, native = isNative();
|
|
87
|
+
if (!more) { clearSelected(); unverifiedCount = 0; }
|
|
88
|
+
busy = true; notice(tr('history.loading')); sync();
|
|
89
|
+
try {
|
|
90
|
+
const result = await bridge().api(route(native ? '/api/native/history' : '/api/codex/threads' + (more && cursor ? '?cursor=' + encodeURIComponent(cursor) : ''), project));
|
|
91
|
+
if (!same(project,version)) return;
|
|
92
|
+
const incoming = (native ? result.workers : result.threads) || [];
|
|
93
|
+
entries = [...new Map((more ? entries.concat(incoming) : incoming).map(entry => [entryId(entry),entry])).values()];
|
|
94
|
+
cursor = native ? null : result.nextCursor || null; runtimeAvailable = result.runtimeAvailable !== false;
|
|
95
|
+
const hidden = Number(result.unverifiedCount);
|
|
96
|
+
if (!native && Number.isSafeInteger(hidden) && hidden > 0) unverifiedCount += hidden;
|
|
97
|
+
notice(native ? [runtimeAvailable ? '' : tr('history.runtimeOffline'),result.truncated ? tr('history.listTruncated') : ''].filter(Boolean).join(' ') : ''); renderList();
|
|
98
|
+
} catch (error) { if (same(project,version)) notice(error.detail || error.message); }
|
|
99
|
+
finally { if (project === root && version === generation) { busy = false; sync(); } }
|
|
100
|
+
}
|
|
101
|
+
async function select(id, targetPage = 0) {
|
|
102
|
+
if (busy) return;
|
|
103
|
+
const project = root, version = ++generation, native = isNative();
|
|
104
|
+
if (!selected || entryId(selected) !== id) { clearSelected(); selected = entries.find(entry => entryId(entry) === id) || (native ? {peer:id} : {id}); }
|
|
105
|
+
ready = false; byId('historyConfirmed').checked = false;
|
|
106
|
+
byId('historySelected').textContent = selected.name || selected.preview || id; byId('historyContent').textContent = ''; byId('historyReceiptContent').textContent = '';
|
|
107
|
+
busy = true; renderList(); notice(tr('history.loading'));
|
|
108
|
+
try {
|
|
109
|
+
const result = await bridge().api(route(native ? '/api/native/history/' + encodeURIComponent(id) + '?after=' + (pageStarts[targetPage] || 0) : '/api/codex/threads/' + encodeURIComponent(id),project));
|
|
110
|
+
if (!same(project,version)) return;
|
|
111
|
+
selected = (native ? result.worker : result.thread) || selected; ready = true;
|
|
112
|
+
if (native) {
|
|
113
|
+
runtimeAvailable = result.runtimeAvailable !== false;
|
|
114
|
+
events = Array.isArray(result.events) ? result.events : []; deliveries = Array.isArray(result.deliveries) ? result.deliveries : [];
|
|
115
|
+
nextAfter = result.truncated === true && Number.isSafeInteger(result.nextAfter) && result.nextAfter > (pageStarts[targetPage] || 0) ? result.nextAfter : null;
|
|
116
|
+
page = targetPage; if (nextAfter != null) pageStarts[page + 1] = nextAfter; pageStarts.length = page + (nextAfter == null ? 1 : 2);
|
|
117
|
+
}
|
|
118
|
+
renderPreview(); notice(native && !runtimeAvailable ? tr('history.runtimeOffline') : '');
|
|
119
|
+
} catch (error) { if (same(project,version)) notice(error.detail || error.message); }
|
|
120
|
+
finally { if (project === root && version === generation) { busy = false; sync(); } }
|
|
121
|
+
}
|
|
122
|
+
function renderPreview() {
|
|
123
|
+
if (!ready) return;
|
|
124
|
+
if (isNative()) {
|
|
125
|
+
byId('historyIdentity').textContent = [selected.provider,selected.peer,selected.status,selected.sessionId,selected.cwd].filter(Boolean).join(' · ');
|
|
126
|
+
byId('historyRetention').textContent = tr('history.retention');
|
|
127
|
+
byId('historyEventPage').textContent = tr('history.eventPage').replace('{page}',String(page + 1)).replace('{count}',String(events.length))
|
|
128
|
+
+ (events.length ? ' · #' + events[0].id + '–#' + events.at(-1).id : '');
|
|
129
|
+
byId('historyReceiptContent').textContent = JSON.stringify(deliveries,null,2);
|
|
130
|
+
const trace = byId('historyNativeView').value === 'trace';
|
|
131
|
+
byId('historyContent').innerHTML = (trace ? events.map(entry => '<details class="history-turn"><summary>#' + esc(entry.id) + ' · ' + esc((entry.payload || entry).type || '') + '</summary><pre>' + esc(JSON.stringify(entry.payload || entry,null,2)) + '</pre></details>')
|
|
132
|
+
: timeline(events,selected.provider).map(record => '<section class="history-turn"><strong>' + esc(record.role === 'user' ? tr('history.user') : record.role === 'assistant' ? tr('history.assistant') : record.title || record.kind)
|
|
133
|
+
+ '</strong><small> · #' + esc(record.eventId) + (record.status ? ' · ' + esc(record.status) : '') + '</small><pre>'
|
|
134
|
+
+ esc([record.text,record.command,record.input,record.output,record.changes?.length ? JSON.stringify(record.changes,null,2) : ''].filter(Boolean).join('\n\n')) + '</pre></section>')).join('')
|
|
135
|
+
|| '<p class="dialog-help">' + esc(tr('history.noEvents')) + '</p>'; return;
|
|
102
136
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
137
|
+
byId('historyContent').innerHTML = (selected?.turns || []).map(turn => '<section class="history-turn"><strong>' + esc(turn.status || '') + '</strong>' + (turn.items || []).map(item => {
|
|
138
|
+
const content = Array.isArray(item.content) ? item.content.map(part => part.text || '').join('\n') : item.text || item.command || item.aggregatedOutput || '';
|
|
139
|
+
const label = item.type === 'userMessage' ? tr('history.user') : item.type === 'agentMessage' ? tr('history.assistant') : item.type === 'commandExecution' ? tr('history.command') : item.type === 'fileChange' ? tr('history.files') : tr('history.event');
|
|
140
|
+
return '<article><strong>' + esc(label) + '</strong><pre>' + esc(content || (item.changes ? JSON.stringify(item.changes,null,2) : '')) + '</pre></article>';
|
|
141
|
+
}).join('') + '</section>').join('') || '<p class="dialog-help">' + esc(tr('history.noTurns')) + '</p>';
|
|
142
|
+
}
|
|
143
|
+
async function reopen(fork) {
|
|
144
|
+
if (busy || !ready || !selected || !byId('historyConfirmed').checked || root !== bridge().projectRoot) return;
|
|
145
|
+
const sources = liveSources();
|
|
146
|
+
if (isNative()) {
|
|
147
|
+
if (fork || !nativeEligible() || sources.length || window.hccAgentStart?.pending) { sync(); return; }
|
|
148
|
+
const worker = { ...selected }; bridge().closeDialog(dialog); return window.hccAgentStart.resumeNative(worker);
|
|
108
149
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
150
|
+
if (sources.length && (!fork || !controlsSource(sources))) { sync(); return; }
|
|
151
|
+
const project = root, threadId = selected.id, version = ++generation;
|
|
152
|
+
const input = fork ? { confirmed:true, ...(sources.length ? {actionToken:bridge().actionToken,epoch:bridge().epoch} : {}) }
|
|
153
|
+
: {kind:'codex',transport:'app-server',mode:'resume',resume:threadId,handoffConfirmed:true};
|
|
154
|
+
busy = true; sync(); notice(tr('history.opening'));
|
|
155
|
+
try {
|
|
156
|
+
const result = await bridge().api(fork ? route('/api/codex/threads/' + encodeURIComponent(threadId) + '/fork',project) : route('/api/sessions',project),{method:'POST',body:JSON.stringify(input)});
|
|
157
|
+
if (project !== bridge().projectRoot) return;
|
|
158
|
+
await bridge().refreshSessions(); if (!same(project,version)) return;
|
|
159
|
+
bridge().closeDialog(dialog); if (result.session?.id) bridge().openManaged(result.session.id);
|
|
160
|
+
} catch (error) { if (same(project,version)) notice((error.detail || error.message) + (error.code ? ' [' + error.code + ']' : '') + ' ' + tr('history.checkSessions')); }
|
|
161
|
+
finally { if (project === root && version === generation) { busy = false; sync(); } }
|
|
162
|
+
}
|
|
163
|
+
function closed() { generation++; busy = false; ready = false; byId('historyConfirmed').checked = false; }
|
|
164
|
+
function open(nextSource = 'native') {
|
|
165
|
+
root = bridge().projectRoot; generation++; source = nextSource === 'codex' ? 'codex' : 'native'; entries = []; cursor = null; unverifiedCount = 0; busy = false; clearSelected();
|
|
166
|
+
byId('historySource').value = source; byId('historyProvider').value = 'all'; byId('historyNativeView').value = 'conversation';
|
|
167
|
+
bridge().openDialog(dialog,byId('historySource')); renderList(); void load();
|
|
168
|
+
}
|
|
169
|
+
byId('historySource').addEventListener('change',() => {
|
|
170
|
+
generation++; busy = false; source = byId('historySource').value === 'codex' ? 'codex' : 'native'; entries = []; cursor = null; unverifiedCount = 0; clearSelected(); renderList(); void load();
|
|
171
|
+
});
|
|
172
|
+
byId('historyProvider').addEventListener('change',() => {
|
|
173
|
+
// Keep an in-flight list read: its response uses the current filter. A
|
|
174
|
+
// detail read is fenced when its selected provider is left behind.
|
|
175
|
+
if (selected) { generation++; busy = false; clearSelected(); notice(''); }
|
|
176
|
+
renderList();
|
|
177
|
+
});
|
|
178
|
+
byId('historyNativeView').addEventListener('change',renderPreview);
|
|
179
|
+
byId('historyEventsPrevious').addEventListener('click',() => { if (page > 0 && selected) return select(selected.peer,page - 1); });
|
|
180
|
+
byId('historyEventsNext').addEventListener('click',() => { if (nextAfter != null && selected) return select(selected.peer,page + 1); });
|
|
181
|
+
byId('historyEventsReload').addEventListener('click',() => selected && select(selected.peer,page));
|
|
182
|
+
byId('historyClose').addEventListener('click',() => { closed(); bridge().closeDialog(dialog); });
|
|
183
|
+
byId('historyRefresh').addEventListener('click',() => load()); byId('historyMore').addEventListener('click',() => load(true));
|
|
184
|
+
byId('historyConfirmed').addEventListener('change',sync); byId('historyResume').addEventListener('click',() => reopen(false));
|
|
185
|
+
byId('historyFork').addEventListener('click',() => reopen(true));
|
|
186
|
+
window.addEventListener('hcc:preferences',() => { if (!dialog.hidden) { renderList(); renderPreview(); } });
|
|
187
|
+
window.hccHistory = {open,sync,closed,reset() { closed(); if (!dialog.hidden) bridge().closeDialog(dialog); }};
|
|
118
188
|
}
|