cx-viewer 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +2 -2
- package/dist/assets/App-CF-qZ3B6.js +41 -0
- package/dist/assets/{AppHeader-B3hrnZr-.css → AppHeader-BqUGBbBx.css} +2 -2
- package/dist/assets/{AppHeader.module-BMU2vyC4.js → AppHeader.module-BjoBBbCB.js} +194 -190
- package/dist/assets/Mobile-ByhBkyqb.js +10 -0
- package/dist/assets/{_baseUniq-CXWHShmB.js → _baseUniq-5XIuSb_1.js} +1 -1
- package/dist/assets/{arc-qbol0NJo.js → arc-V3mJhKFJ.js} +1 -1
- package/dist/assets/{architectureDiagram-Q4EWVU46-COZebb1N.js → architectureDiagram-Q4EWVU46-22bjxOE1.js} +1 -1
- package/dist/assets/{blockDiagram-DXYQGD6D-COCgzPKy.js → blockDiagram-DXYQGD6D-Brshek1N.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-Bs5XvLEo.js → c4Diagram-AHTNJAMY-DQHIuxkd.js} +1 -1
- package/dist/assets/channel-SmGaOM6g.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-CyGriz9d.js → chunk-4BX2VUAB-BQE7pifU.js} +1 -1
- package/dist/assets/{chunk-4TB4RGXK-DlT_q-fl.js → chunk-4TB4RGXK-DcZwv_PV.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-NHN99ZNC.js → chunk-55IACEB6-Gd_3-vV5.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-fK4f9Na9.js → chunk-EDXVE4YY-BbnYdg9M.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-DL-dQIq1.js → chunk-FMBD7UC4-NSh7z90L.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-6uruB4VY.js → chunk-OYMX7WX6-DdFwjHsc.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-CCWIqLO8.js → chunk-QZHKN3VN-C-1z3DA3.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-Cu84DMJo.js → chunk-YZCP3GAM-w_NiY6dL.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-Bu96zciS.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-Bu96zciS.js +1 -0
- package/dist/assets/clone-u2hQVO7u.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-Dqi4iU3-.js → cose-bilkent-S5V4N54A-bHlM-ydV.js} +1 -1
- package/dist/assets/{dagre-KV5264BT-Dl-EJ9rb.js → dagre-KV5264BT-2cCXOuNw.js} +1 -1
- package/dist/assets/{diagram-5BDNPKRD-BYtyzF0D.js → diagram-5BDNPKRD-C70P_Nd1.js} +1 -1
- package/dist/assets/{diagram-G4DWMVQ6-BuETHFNU.js → diagram-G4DWMVQ6-C2JvwT8U.js} +1 -1
- package/dist/assets/{diagram-MMDJMWI5-Z02oZp6I.js → diagram-MMDJMWI5-C9ygMSLV.js} +1 -1
- package/dist/assets/{diagram-TYMM5635-BpGlKv8l.js → diagram-TYMM5635-x-SMPmqb.js} +1 -1
- package/dist/assets/{erDiagram-SMLLAGMA-C4YngeQh.js → erDiagram-SMLLAGMA-BGIbL82W.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-BWddFffg.js → flowDiagram-DWJPFMVM-Dg0qwguE.js} +1 -1
- package/dist/assets/{ganttDiagram-T4ZO3ILL-Bg0jOD4m.js → ganttDiagram-T4ZO3ILL-XX5qCwKD.js} +1 -1
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-Bz-3sTXZ.js → gitGraphDiagram-UUTBAWPF-DFil4bpA.js} +1 -1
- package/dist/assets/{graph-XiH8DkIu.js → graph-DJ6egHOd.js} +1 -1
- package/dist/assets/{index-BuUsWKGZ.js → index-DYIYWRaO.js} +2 -2
- package/dist/assets/{infoDiagram-42DDH7IO-DLo-lOCE.js → infoDiagram-42DDH7IO-yB7VRrpO.js} +1 -1
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-BsIzT5eO.js → ishikawaDiagram-UXIWVN3A-DZ_lPco0.js} +1 -1
- package/dist/assets/{journeyDiagram-VCZTEJTY-C3bOkKqv.js → journeyDiagram-VCZTEJTY-B-FlPR2W.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-D-rJRNbp.js → kanban-definition-6JOO6SKY-mgJwVxKf.js} +1 -1
- package/dist/assets/{layout-Bqp8Hh1c.js → layout-BjzpJ8ZU.js} +1 -1
- package/dist/assets/{linear-BWRSipGb.js → linear-CqOq1rA7.js} +1 -1
- package/dist/assets/{mermaid.core-CJa7klhs.js → mermaid.core-CZyQhtml.js} +4 -4
- package/dist/assets/{min-CQ_VyzMd.js → min-B_tad8Qz.js} +1 -1
- package/dist/assets/{mindmap-definition-QFDTVHPH-Bnj7DDSx.js → mindmap-definition-QFDTVHPH-CgBeHk8K.js} +1 -1
- package/dist/assets/{pieDiagram-DEJITSTG-CC8a7Ylw.js → pieDiagram-DEJITSTG-CHENa0KJ.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-CrmL1WUV.js → quadrantDiagram-34T5L4WZ-BUwZwCpc.js} +1 -1
- package/dist/assets/{requirementDiagram-MS252O5E-TfkX4u_6.js → requirementDiagram-MS252O5E-H7OaNjNn.js} +1 -1
- package/dist/assets/{sankeyDiagram-XADWPNL6-DMwBbND7.js → sankeyDiagram-XADWPNL6-6UqaFrBf.js} +1 -1
- package/dist/assets/{sequenceDiagram-FGHM5R23-CFghMYsm.js → sequenceDiagram-FGHM5R23-DBgpLSsr.js} +1 -1
- package/dist/assets/{stateDiagram-FHFEXIEX-1H6JIM7l.js → stateDiagram-FHFEXIEX-D_6TWlLl.js} +1 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-Cg06jbzN.js +1 -0
- package/dist/assets/{timeline-definition-GMOUNBTQ-DnLMBlVa.js → timeline-definition-GMOUNBTQ-9_saaHvl.js} +1 -1
- package/dist/assets/{vennDiagram-DHZGUBPP-DoIkY_KL.js → vennDiagram-DHZGUBPP-DMUS1fiT.js} +1 -1
- package/dist/assets/{wardley-RL74JXVD-3n5XmOD5.js → wardley-RL74JXVD-Y-_Nnpu2.js} +1 -1
- package/dist/assets/{wardleyDiagram-NUSXRM2D-UMOq3ofC.js → wardleyDiagram-NUSXRM2D-DIh62HY7.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-CNDuQ5Cg.js → xychartDiagram-5P7HB3ND-B_CLrnpt.js} +1 -1
- package/dist/index.html +1 -1
- package/lib/appserver-bridge.js +108 -9
- package/lib/log-management.js +3 -1
- package/lib/plugin-loader.js +126 -6
- package/lib/sdk-manager.js +55 -0
- package/package.json +1 -1
- package/pty-manager.js +27 -3
- package/server.js +301 -34
- package/dist/assets/App-BzrN42Qt.js +0 -41
- package/dist/assets/Mobile-C9wxQTXV.js +0 -10
- package/dist/assets/channel-DvLUZ3Xf.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-ByKhp2Tk.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-ByKhp2Tk.js +0 -1
- package/dist/assets/clone-DZHpE494.js +0 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-CtXMaKcb.js +0 -1
package/lib/log-management.js
CHANGED
|
@@ -54,7 +54,9 @@ export function listLocalLogs(logDir, currentProjectName) {
|
|
|
54
54
|
}
|
|
55
55
|
} catch { }
|
|
56
56
|
for (const f of files) {
|
|
57
|
-
|
|
57
|
+
// Include active temp sessions so a first-run conversation is visible
|
|
58
|
+
// before it gets finalized to a permanent .jsonl file.
|
|
59
|
+
const match = f.match(/^(.+?)_(\d{8}_\d{6})(?:_temp)?\.jsonl$/);
|
|
58
60
|
if (!match) continue;
|
|
59
61
|
const ts = match[2];
|
|
60
62
|
const filePath = join(projectDir, f);
|
package/lib/plugin-loader.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { readdirSync, existsSync, readFileSync } from 'node:fs';
|
|
1
|
+
import { readdirSync, existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
3
4
|
import { LOG_DIR } from '../findcx.js';
|
|
4
5
|
|
|
5
6
|
// 动态获取(LOG_DIR 可能在运行时被 setLogDir 修改)
|
|
@@ -17,12 +18,56 @@ const HOOK_TYPES = {
|
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
let _plugins = [];
|
|
21
|
+
let _pluginStates = new Map();
|
|
22
|
+
|
|
23
|
+
function normalizeCapabilities(plugin) {
|
|
24
|
+
const capabilities = new Set(Array.isArray(plugin.capabilities) ? plugin.capabilities.filter(Boolean) : []);
|
|
25
|
+
if (plugin.hooks && typeof plugin.hooks.voiceInput === 'function') {
|
|
26
|
+
capabilities.add('voiceInput');
|
|
27
|
+
}
|
|
28
|
+
return Array.from(capabilities);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeVoiceInput(plugin) {
|
|
32
|
+
if (!plugin || typeof plugin !== 'object') return null;
|
|
33
|
+
const voiceInput = plugin.voiceInput;
|
|
34
|
+
if (!voiceInput || typeof voiceInput !== 'object') return null;
|
|
35
|
+
const appKey = typeof voiceInput.appKey === 'string' ? voiceInput.appKey.trim() : '';
|
|
36
|
+
const digestEndpoint = typeof voiceInput.digestEndpoint === 'string' ? voiceInput.digestEndpoint.trim() : '';
|
|
37
|
+
const normalized = {};
|
|
38
|
+
if (appKey) normalized.appKey = appKey;
|
|
39
|
+
if (digestEndpoint) normalized.digestEndpoint = digestEndpoint;
|
|
40
|
+
return Object.keys(normalized).length > 0 ? normalized : {};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getModuleVersion(filePath) {
|
|
44
|
+
try {
|
|
45
|
+
return String(statSync(filePath).mtimeMs);
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function extractNameFromSource(content, fallback) {
|
|
52
|
+
const match = content.match(/name\s*:\s*['"]([^'"]+)['"]/);
|
|
53
|
+
return match ? match[1] : fallback;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function extractCapabilitiesFromSource(content) {
|
|
57
|
+
const match = content.match(/capabilities\s*:\s*\[([^\]]*)\]/);
|
|
58
|
+
if (!match) return [];
|
|
59
|
+
return match[1]
|
|
60
|
+
.split(',')
|
|
61
|
+
.map(s => s.trim().replace(/^['"]|['"]$/g, ''))
|
|
62
|
+
.filter(Boolean);
|
|
63
|
+
}
|
|
20
64
|
|
|
21
65
|
/**
|
|
22
66
|
* 扫描 LOG_DIR/plugins/ 目录,动态 import 每个 .js/.mjs 文件
|
|
23
67
|
*/
|
|
24
68
|
export async function loadPlugins() {
|
|
25
69
|
_plugins = [];
|
|
70
|
+
_pluginStates = new Map();
|
|
26
71
|
|
|
27
72
|
if (!existsSync(getPluginsDir())) return;
|
|
28
73
|
|
|
@@ -48,21 +93,90 @@ export async function loadPlugins() {
|
|
|
48
93
|
|
|
49
94
|
for (const file of files) {
|
|
50
95
|
const filePath = join(getPluginsDir(), file);
|
|
96
|
+
const moduleVersion = getModuleVersion(filePath);
|
|
51
97
|
try {
|
|
52
|
-
const
|
|
98
|
+
const source = readFileSync(filePath, 'utf-8');
|
|
99
|
+
const fileUrl = pathToFileURL(filePath);
|
|
100
|
+
fileUrl.searchParams.set('v', moduleVersion || String(Date.now()));
|
|
101
|
+
const mod = await import(fileUrl.href);
|
|
53
102
|
const plugin = mod.default || mod;
|
|
54
103
|
const name = plugin.name || file;
|
|
104
|
+
const hooks = (plugin.hooks && typeof plugin.hooks === 'object') ? plugin.hooks : {};
|
|
105
|
+
const capabilities = normalizeCapabilities(plugin);
|
|
106
|
+
const voiceInput = normalizeVoiceInput(plugin);
|
|
107
|
+
const canLoad = Object.keys(hooks).length > 0 || capabilities.length > 0;
|
|
55
108
|
|
|
56
109
|
if (disabledPlugins.includes(name)) {
|
|
110
|
+
_pluginStates.set(file, {
|
|
111
|
+
name,
|
|
112
|
+
file,
|
|
113
|
+
hooks: Object.keys(hooks),
|
|
114
|
+
capabilities,
|
|
115
|
+
voiceInput,
|
|
116
|
+
moduleVersion,
|
|
117
|
+
enabled: false,
|
|
118
|
+
loaded: false,
|
|
119
|
+
loadError: null,
|
|
120
|
+
});
|
|
57
121
|
if (SHOULD_LOG) console.error(`[CX Viewer] Plugin "${name}" is disabled, skipping.`);
|
|
58
122
|
continue;
|
|
59
123
|
}
|
|
60
124
|
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
125
|
+
if (!canLoad) {
|
|
126
|
+
_pluginStates.set(file, {
|
|
127
|
+
name: extractNameFromSource(source, name),
|
|
128
|
+
file,
|
|
129
|
+
hooks: [],
|
|
130
|
+
capabilities: extractCapabilitiesFromSource(source),
|
|
131
|
+
voiceInput: null,
|
|
132
|
+
moduleVersion,
|
|
133
|
+
enabled: true,
|
|
134
|
+
loaded: false,
|
|
135
|
+
loadError: 'Plugin must export hooks or capabilities',
|
|
136
|
+
});
|
|
137
|
+
continue;
|
|
64
138
|
}
|
|
139
|
+
|
|
140
|
+
_plugins.push({ name, hooks, file, capabilities, voiceInput, moduleVersion });
|
|
141
|
+
_pluginStates.set(file, {
|
|
142
|
+
name,
|
|
143
|
+
file,
|
|
144
|
+
hooks: Object.keys(hooks),
|
|
145
|
+
capabilities,
|
|
146
|
+
voiceInput,
|
|
147
|
+
moduleVersion,
|
|
148
|
+
enabled: true,
|
|
149
|
+
loaded: true,
|
|
150
|
+
loadError: null,
|
|
151
|
+
});
|
|
152
|
+
if (SHOULD_LOG) console.error(`[CX Viewer] Plugin loaded: ${name} (${file})`);
|
|
65
153
|
} catch (err) {
|
|
154
|
+
try {
|
|
155
|
+
const source = readFileSync(filePath, 'utf-8');
|
|
156
|
+
_pluginStates.set(file, {
|
|
157
|
+
name: extractNameFromSource(source, file),
|
|
158
|
+
file,
|
|
159
|
+
hooks: [],
|
|
160
|
+
capabilities: extractCapabilitiesFromSource(source),
|
|
161
|
+
voiceInput: null,
|
|
162
|
+
moduleVersion,
|
|
163
|
+
enabled: true,
|
|
164
|
+
loaded: false,
|
|
165
|
+
loadError: err.message,
|
|
166
|
+
});
|
|
167
|
+
} catch {
|
|
168
|
+
_pluginStates.set(file, {
|
|
169
|
+
name: file,
|
|
170
|
+
file,
|
|
171
|
+
hooks: [],
|
|
172
|
+
capabilities: [],
|
|
173
|
+
voiceInput: null,
|
|
174
|
+
moduleVersion,
|
|
175
|
+
enabled: true,
|
|
176
|
+
loaded: false,
|
|
177
|
+
loadError: err.message,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
66
180
|
if (SHOULD_LOG) console.error(`[CX Viewer] Failed to load plugin "${file}":`, err.message);
|
|
67
181
|
}
|
|
68
182
|
}
|
|
@@ -133,6 +247,9 @@ export function getPluginsInfo() {
|
|
|
133
247
|
}
|
|
134
248
|
|
|
135
249
|
return files.map(file => {
|
|
250
|
+
const state = _pluginStates.get(file);
|
|
251
|
+
if (state) return state;
|
|
252
|
+
|
|
136
253
|
const loaded = _plugins.find(p => p.file === file);
|
|
137
254
|
let name = file;
|
|
138
255
|
|
|
@@ -155,7 +272,10 @@ export function getPluginsInfo() {
|
|
|
155
272
|
}
|
|
156
273
|
|
|
157
274
|
const hooks = loaded ? Object.keys(loaded.hooks) : [];
|
|
275
|
+
const capabilities = loaded ? loaded.capabilities : [];
|
|
276
|
+
const voiceInput = loaded ? loaded.voiceInput : null;
|
|
158
277
|
const enabled = !disabledPlugins.includes(name);
|
|
159
|
-
|
|
278
|
+
const moduleVersion = loaded?.moduleVersion || getModuleVersion(join(getPluginsDir(), file));
|
|
279
|
+
return { name, file, hooks, capabilities, voiceInput, moduleVersion, enabled, loaded: !!loaded, loadError: null };
|
|
160
280
|
});
|
|
161
281
|
}
|
package/lib/sdk-manager.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* - canUseTool callback for AskUserQuestion + permission approval
|
|
7
7
|
* - Streaming status tracking
|
|
8
8
|
* - Multi-turn conversation via session resume
|
|
9
|
+
* - Completed-entry fallback when a turn only exposes final assistant text via stream events
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
import { sdkToJSONLEntry, buildStreamingStatus } from './sdk-adapter.js';
|
|
@@ -40,6 +41,9 @@ let _streamingContent = [];
|
|
|
40
41
|
let _currentBlockData = null;
|
|
41
42
|
let _streamingRequestId = null;
|
|
42
43
|
let _streamThrottleTimer = null;
|
|
44
|
+
let _pendingFinalContent = null;
|
|
45
|
+
let _pendingFinalRequestMessages = null;
|
|
46
|
+
let _finalEntryEmittedThisTurn = false;
|
|
43
47
|
|
|
44
48
|
// Callbacks registered by server.js
|
|
45
49
|
let _onEntry = null;
|
|
@@ -105,6 +109,9 @@ async function _executeQuery(text) {
|
|
|
105
109
|
// Generate stable timestamp for this turn — all entries share it for dedup
|
|
106
110
|
_turnTimestamp = new Date().toISOString();
|
|
107
111
|
_streamingRequestId = `sdk_${Date.now()}`;
|
|
112
|
+
_pendingFinalContent = null;
|
|
113
|
+
_pendingFinalRequestMessages = null;
|
|
114
|
+
_finalEntryEmittedThisTurn = false;
|
|
108
115
|
|
|
109
116
|
// Accumulate user message BEFORE creating entries (this is the request)
|
|
110
117
|
_accumulatedMessages.push({ role: 'user', content: text });
|
|
@@ -162,6 +169,8 @@ function _processMessage(msg) {
|
|
|
162
169
|
// Main agent message (not sub-agent)
|
|
163
170
|
if (!msg.parent_tool_use_id && msg.message) {
|
|
164
171
|
_resetStreamingState();
|
|
172
|
+
_pendingFinalContent = null;
|
|
173
|
+
_pendingFinalRequestMessages = null;
|
|
165
174
|
|
|
166
175
|
// Snapshot messages BEFORE adding assistant (body.messages = request history, unfiltered)
|
|
167
176
|
const requestMessages = [..._accumulatedMessages];
|
|
@@ -186,6 +195,7 @@ function _processMessage(msg) {
|
|
|
186
195
|
tools: _sdkTools,
|
|
187
196
|
});
|
|
188
197
|
if (_onEntry) _onEntry(entry);
|
|
198
|
+
_finalEntryEmittedThisTurn = true;
|
|
189
199
|
}
|
|
190
200
|
if (msg.session_id) _sessionId = msg.session_id;
|
|
191
201
|
break;
|
|
@@ -213,6 +223,9 @@ function _processMessage(msg) {
|
|
|
213
223
|
break;
|
|
214
224
|
|
|
215
225
|
case 'result':
|
|
226
|
+
if (!_finalEntryEmittedThisTurn && _pendingFinalContent?.length > 0) {
|
|
227
|
+
_emitCompletedEntryFromStream();
|
|
228
|
+
}
|
|
216
229
|
if (msg.session_id) _sessionId = msg.session_id;
|
|
217
230
|
if (_onStreamingStatus) _onStreamingStatus(buildStreamingStatus(false));
|
|
218
231
|
break;
|
|
@@ -281,6 +294,10 @@ function _processStreamEvent(event) {
|
|
|
281
294
|
_pushStreamingEntry();
|
|
282
295
|
}
|
|
283
296
|
} else if (type === 'message_stop') {
|
|
297
|
+
if (_streamingContent.length > 0) {
|
|
298
|
+
_pendingFinalContent = [..._streamingContent];
|
|
299
|
+
_pendingFinalRequestMessages = [..._accumulatedMessages];
|
|
300
|
+
}
|
|
284
301
|
_resetStreamingState();
|
|
285
302
|
}
|
|
286
303
|
}
|
|
@@ -336,6 +353,41 @@ function _resetStreamingState() {
|
|
|
336
353
|
}
|
|
337
354
|
}
|
|
338
355
|
|
|
356
|
+
function _emitCompletedEntryFromStream() {
|
|
357
|
+
if (!_onEntry || !_pendingFinalContent?.length) return;
|
|
358
|
+
|
|
359
|
+
const requestMessages = _pendingFinalRequestMessages || [..._accumulatedMessages];
|
|
360
|
+
const content = _filterInteractiveContent(_pendingFinalContent);
|
|
361
|
+
const syntheticMsg = {
|
|
362
|
+
message: {
|
|
363
|
+
id: _streamingRequestId,
|
|
364
|
+
type: 'message',
|
|
365
|
+
role: 'assistant',
|
|
366
|
+
model: _model,
|
|
367
|
+
content,
|
|
368
|
+
stop_reason: 'end_turn',
|
|
369
|
+
usage: { input_tokens: 0, output_tokens: 0 },
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const entry = sdkToJSONLEntry(syntheticMsg, requestMessages, _model, _projectName, {
|
|
374
|
+
timestamp: _turnTimestamp,
|
|
375
|
+
tools: _sdkTools,
|
|
376
|
+
});
|
|
377
|
+
_onEntry(entry);
|
|
378
|
+
_finalEntryEmittedThisTurn = true;
|
|
379
|
+
|
|
380
|
+
const lastMsg = _accumulatedMessages[_accumulatedMessages.length - 1];
|
|
381
|
+
if (lastMsg && lastMsg.role === 'assistant' && Array.isArray(lastMsg.content)) {
|
|
382
|
+
lastMsg.content = [...lastMsg.content, ...content];
|
|
383
|
+
} else {
|
|
384
|
+
_accumulatedMessages.push({ role: 'assistant', content });
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
_pendingFinalContent = null;
|
|
388
|
+
_pendingFinalRequestMessages = null;
|
|
389
|
+
}
|
|
390
|
+
|
|
339
391
|
/**
|
|
340
392
|
* canUseTool callback — handles AskUserQuestion + permission approval.
|
|
341
393
|
*/
|
|
@@ -447,6 +499,9 @@ function _resetFullState() {
|
|
|
447
499
|
_accumulatedMessages = [];
|
|
448
500
|
_turnTimestamp = null;
|
|
449
501
|
_messageQueue = [];
|
|
502
|
+
_pendingFinalContent = null;
|
|
503
|
+
_pendingFinalRequestMessages = null;
|
|
504
|
+
_finalEntryEmittedThisTurn = false;
|
|
450
505
|
_resetStreamingState();
|
|
451
506
|
_streamingRequestId = null;
|
|
452
507
|
// Reject all pending approvals
|
package/package.json
CHANGED
package/pty-manager.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { resolveNativePath, BINARY_NAME } from './findcx.js';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import { join, dirname } from 'node:path';
|
|
4
|
-
import { chmodSync, statSync } from 'node:fs';
|
|
5
|
-
import { platform, arch } from 'node:os';
|
|
3
|
+
import { join, dirname, basename } from 'node:path';
|
|
4
|
+
import { chmodSync, statSync, mkdirSync, appendFileSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { platform, arch, homedir } from 'node:os';
|
|
6
6
|
import { execSync } from 'node:child_process';
|
|
7
7
|
|
|
8
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -21,6 +21,16 @@ const MAX_BUFFER = 200000;
|
|
|
21
21
|
let batchBuffer = '';
|
|
22
22
|
let batchScheduled = false;
|
|
23
23
|
let _ptyImportForTests = null;
|
|
24
|
+
let outputHistoryPath = null;
|
|
25
|
+
|
|
26
|
+
function ensureOutputHistory({ reset = false } = {}) {
|
|
27
|
+
const historyDir = join(homedir(), '.codex', 'cx-viewer', 'runtime');
|
|
28
|
+
mkdirSync(historyDir, { recursive: true });
|
|
29
|
+
if (reset || !outputHistoryPath) {
|
|
30
|
+
outputHistoryPath = join(historyDir, `terminal-history-${Date.now()}.log`);
|
|
31
|
+
appendFileSync(outputHistoryPath, '', 'utf8');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
24
34
|
|
|
25
35
|
export function _setPtyImportForTests(fn) {
|
|
26
36
|
_ptyImportForTests = fn;
|
|
@@ -78,6 +88,9 @@ function flushBatch() {
|
|
|
78
88
|
if (!batchBuffer) return;
|
|
79
89
|
const chunk = batchBuffer;
|
|
80
90
|
batchBuffer = '';
|
|
91
|
+
if (outputHistoryPath) {
|
|
92
|
+
try { appendFileSync(outputHistoryPath, chunk, 'utf8'); } catch { }
|
|
93
|
+
}
|
|
81
94
|
for (const cb of dataListeners) {
|
|
82
95
|
try { cb(chunk); } catch { }
|
|
83
96
|
}
|
|
@@ -187,6 +200,7 @@ export async function spawnCodex(proxyPort, cwd, extraArgs = [], codexPath = nul
|
|
|
187
200
|
|
|
188
201
|
lastExitCode = null;
|
|
189
202
|
outputBuffer = '';
|
|
203
|
+
ensureOutputHistory({ reset: true });
|
|
190
204
|
currentWorkspacePath = cwd || process.cwd();
|
|
191
205
|
lastWorkspacePath = currentWorkspacePath;
|
|
192
206
|
|
|
@@ -309,6 +323,7 @@ export async function spawnShell() {
|
|
|
309
323
|
|
|
310
324
|
lastExitCode = null;
|
|
311
325
|
currentWorkspacePath = cwd;
|
|
326
|
+
ensureOutputHistory();
|
|
312
327
|
|
|
313
328
|
// Clean env: remove cx-viewer specific vars so child shells don't inherit them
|
|
314
329
|
// (prevents CXVIEWER_PORT leaking to non-cx-viewer Codex instances)
|
|
@@ -403,5 +418,14 @@ export function getCurrentWorkspace() {
|
|
|
403
418
|
}
|
|
404
419
|
|
|
405
420
|
export function getOutputBuffer() {
|
|
421
|
+
if (outputHistoryPath) {
|
|
422
|
+
try {
|
|
423
|
+
return readFileSync(outputHistoryPath, 'utf8');
|
|
424
|
+
} catch { }
|
|
425
|
+
}
|
|
406
426
|
return outputBuffer;
|
|
407
427
|
}
|
|
428
|
+
|
|
429
|
+
export function getOutputHistoryId() {
|
|
430
|
+
return outputHistoryPath ? basename(outputHistoryPath) : null;
|
|
431
|
+
}
|