u1s1-cli 1.2.3 → 1.2.4
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/dist/agent-setup.js +2 -2
- package/dist/api.d.ts +5 -1
- package/dist/api.js +6 -6
- package/dist/bench-report.d.ts +22 -0
- package/dist/bench-report.js +158 -0
- package/dist/bench-scoring.d.ts +8 -0
- package/dist/bench-scoring.js +101 -0
- package/dist/bench-types.d.ts +50 -0
- package/dist/bench-types.js +1 -0
- package/dist/bench.js +101 -367
- package/dist/brand.d.ts +9 -3
- package/dist/brand.js +12 -12
- package/dist/deploy.js +72 -49
- package/dist/import/claude.d.ts +2 -1
- package/dist/import/claude.js +162 -129
- package/dist/import/codex.d.ts +2 -1
- package/dist/import/codex.js +184 -157
- package/dist/import/index.js +62 -47
- package/dist/index.js +13 -13
- package/dist/style.js +7 -1
- package/dist/subagent.d.ts +6 -1
- package/dist/subagent.js +4 -4
- package/dist/tools.d.ts +18 -12
- package/dist/tools.js +22 -16
- package/dist/usage.js +95 -74
- package/dist/workflow/runner.js +36 -26
- package/dist/workflow/tool.js +67 -55
- package/package.json +1 -1
- package/scripts/render-regression.mjs +3 -1
package/dist/import/codex.js
CHANGED
|
@@ -14,6 +14,7 @@ const CODEX_TOOL_MAP = {
|
|
|
14
14
|
grep_files: "grep",
|
|
15
15
|
grep: "grep",
|
|
16
16
|
};
|
|
17
|
+
const MAX_CODEX_TRANSCRIPT_CHARS = 8_000_000;
|
|
17
18
|
function codexHomes() {
|
|
18
19
|
return uniqueExistingDirs([process.env["CODEX_HOME"], join(homedir(), ".codex")]);
|
|
19
20
|
}
|
|
@@ -128,13 +129,25 @@ function remapToolArgs(name, args) {
|
|
|
128
129
|
function emptyUsage() {
|
|
129
130
|
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
130
131
|
}
|
|
132
|
+
function updateCodexHydration(state, obj) {
|
|
133
|
+
const type = asString(obj["type"]);
|
|
134
|
+
const payload = asRecord(obj["payload"]) ?? {};
|
|
135
|
+
if (!state.cwd && type === "session_meta") {
|
|
136
|
+
state.cwd = asString(payload["cwd"]);
|
|
137
|
+
}
|
|
138
|
+
if (state.title || type !== "event_msg" || asString(payload["type"]) !== "user_message")
|
|
139
|
+
return;
|
|
140
|
+
const text = (asString(payload["message"]) ?? "").trim();
|
|
141
|
+
if (text && !isProbablyInjection(text)) {
|
|
142
|
+
state.title = oneLine(firstMeaningfulLine(text) || text);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
131
145
|
export async function hydrateCodexSession(session) {
|
|
132
146
|
if (session.title && session.cwd)
|
|
133
147
|
return session;
|
|
134
148
|
const stream = createReadStream(session.sourcePath, { encoding: "utf8" });
|
|
135
149
|
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
136
|
-
|
|
137
|
-
let title = session.title;
|
|
150
|
+
const state = { cwd: session.cwd, title: session.title };
|
|
138
151
|
let lines = 0;
|
|
139
152
|
try {
|
|
140
153
|
for await (const line of rl) {
|
|
@@ -142,16 +155,8 @@ export async function hydrateCodexSession(session) {
|
|
|
142
155
|
const obj = parseJsonLine(line);
|
|
143
156
|
if (!obj)
|
|
144
157
|
continue;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (!cwd && type === "session_meta")
|
|
148
|
-
cwd = asString(payload["cwd"]) ?? cwd;
|
|
149
|
-
if (!title && type === "event_msg" && asString(payload["type"]) === "user_message") {
|
|
150
|
-
const text = (asString(payload["message"]) ?? "").trim();
|
|
151
|
-
if (text && !isProbablyInjection(text))
|
|
152
|
-
title = oneLine(firstMeaningfulLine(text) || text);
|
|
153
|
-
}
|
|
154
|
-
if (cwd && title)
|
|
158
|
+
updateCodexHydration(state, obj);
|
|
159
|
+
if (state.cwd && state.title)
|
|
155
160
|
break;
|
|
156
161
|
if (lines > 80)
|
|
157
162
|
break;
|
|
@@ -161,23 +166,181 @@ export async function hydrateCodexSession(session) {
|
|
|
161
166
|
rl.close();
|
|
162
167
|
stream.destroy();
|
|
163
168
|
}
|
|
164
|
-
return {
|
|
169
|
+
return {
|
|
170
|
+
...session,
|
|
171
|
+
cwd: state.cwd || session.cwd,
|
|
172
|
+
title: state.title || session.title,
|
|
173
|
+
};
|
|
165
174
|
}
|
|
166
|
-
function flushAssistant(
|
|
167
|
-
if (pending.length === 0)
|
|
175
|
+
function flushAssistant(state, timestamp) {
|
|
176
|
+
if (state.pending.length === 0)
|
|
168
177
|
return;
|
|
169
|
-
const sawTool = pending.some((
|
|
170
|
-
messages.push({
|
|
178
|
+
const sawTool = state.pending.some((block) => block.type === "toolCall");
|
|
179
|
+
state.messages.push({
|
|
171
180
|
role: "assistant",
|
|
172
|
-
content: pending.splice(0),
|
|
181
|
+
content: state.pending.splice(0),
|
|
173
182
|
provider: "openai",
|
|
174
|
-
model,
|
|
183
|
+
model: state.model,
|
|
175
184
|
api: "openai-codex-responses",
|
|
176
185
|
stopReason: sawTool ? "toolUse" : "stop",
|
|
177
186
|
usage: emptyUsage(),
|
|
178
|
-
timestamp
|
|
187
|
+
timestamp,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
function readCodexTranscript(path) {
|
|
191
|
+
let text;
|
|
192
|
+
try {
|
|
193
|
+
text = readFileSync(path, "utf8");
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
if (text.length <= MAX_CODEX_TRANSCRIPT_CHARS)
|
|
199
|
+
return text;
|
|
200
|
+
const cut = text.length - MAX_CODEX_TRANSCRIPT_CHARS;
|
|
201
|
+
const nextLine = text.indexOf("\n", cut);
|
|
202
|
+
return text.slice(nextLine === -1 ? cut : nextLine + 1);
|
|
203
|
+
}
|
|
204
|
+
function appendCodexUser(state, rawText, timestamp) {
|
|
205
|
+
flushAssistant(state, timestamp);
|
|
206
|
+
const text = truncateText(rawText, MAX_TEXT_CHARS).trim();
|
|
207
|
+
if (!text || isProbablyInjection(text) || text === state.lastUserText)
|
|
208
|
+
return;
|
|
209
|
+
state.lastUserText = text;
|
|
210
|
+
state.title ??= oneLine(firstMeaningfulLine(text) || text);
|
|
211
|
+
state.messages.push({ role: "user", text, timestamp });
|
|
212
|
+
}
|
|
213
|
+
function codexReasoningText(summary) {
|
|
214
|
+
if (!Array.isArray(summary))
|
|
215
|
+
return "";
|
|
216
|
+
const parts = [];
|
|
217
|
+
for (const item of summary) {
|
|
218
|
+
if (typeof item === "string") {
|
|
219
|
+
parts.push(item);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const text = asString(asRecord(item)?.["text"]);
|
|
223
|
+
if (text)
|
|
224
|
+
parts.push(text);
|
|
225
|
+
}
|
|
226
|
+
return parts.join("\n").trim();
|
|
227
|
+
}
|
|
228
|
+
function appendCodexToolCall(state, payload) {
|
|
229
|
+
const id = asString(payload["call_id"]) ?? asString(payload["id"]);
|
|
230
|
+
if (!id)
|
|
231
|
+
return;
|
|
232
|
+
const name = asString(payload["name"]) ?? "unknown";
|
|
233
|
+
const mappedName = mapToolName(name);
|
|
234
|
+
state.pendingTools.set(id, mappedName);
|
|
235
|
+
state.pending.push({
|
|
236
|
+
type: "toolCall",
|
|
237
|
+
id,
|
|
238
|
+
name: mappedName,
|
|
239
|
+
arguments: remapToolArgs(name, argsFromCall(payload)),
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function codexToolOutput(output) {
|
|
243
|
+
if (typeof output === "string")
|
|
244
|
+
return output;
|
|
245
|
+
const record = asRecord(output);
|
|
246
|
+
if (!record)
|
|
247
|
+
return String(output ?? "");
|
|
248
|
+
return asString(record["output"]) ?? JSON.stringify(output);
|
|
249
|
+
}
|
|
250
|
+
function appendCodexToolResult(state, payload, timestamp) {
|
|
251
|
+
flushAssistant(state, timestamp);
|
|
252
|
+
const id = asString(payload["call_id"]) ?? asString(payload["id"]);
|
|
253
|
+
if (!id)
|
|
254
|
+
return;
|
|
255
|
+
state.messages.push({
|
|
256
|
+
role: "toolResult",
|
|
257
|
+
toolCallId: id,
|
|
258
|
+
toolName: state.pendingTools.get(id) ?? "unknown",
|
|
259
|
+
text: truncateText(codexToolOutput(payload["output"]), MAX_TOOL_RESULT_CHARS),
|
|
260
|
+
isError: false,
|
|
261
|
+
timestamp,
|
|
179
262
|
});
|
|
180
263
|
}
|
|
264
|
+
function consumeCodexResponseItem(state, payload, timestamp) {
|
|
265
|
+
const payloadType = asString(payload["type"]);
|
|
266
|
+
const role = asString(payload["role"]);
|
|
267
|
+
if (payloadType === "message" && role === "user") {
|
|
268
|
+
appendCodexUser(state, flattenCodexContent(payload["content"]), timestamp);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (payloadType === "message" && role === "assistant") {
|
|
272
|
+
const text = flattenCodexContent(payload["content"]).trim();
|
|
273
|
+
if (text)
|
|
274
|
+
state.pending.push({ type: "text", text: truncateText(text, MAX_TEXT_CHARS) });
|
|
275
|
+
flushAssistant(state, timestamp);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (payloadType === "reasoning") {
|
|
279
|
+
const thinking = codexReasoningText(payload["summary"]);
|
|
280
|
+
if (thinking) {
|
|
281
|
+
state.pending.push({ type: "thinking", thinking: truncateText(thinking, MAX_TEXT_CHARS) });
|
|
282
|
+
}
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (payloadType === "function_call" || payloadType === "custom_tool_call") {
|
|
286
|
+
appendCodexToolCall(state, payload);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (payloadType === "function_call_output" || payloadType === "custom_tool_call_output") {
|
|
290
|
+
appendCodexToolResult(state, payload, timestamp);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function consumeCodexEntry(state, obj) {
|
|
294
|
+
const type = asString(obj["type"]);
|
|
295
|
+
const payload = asRecord(obj["payload"]) ?? {};
|
|
296
|
+
const timestamp = parseTime(obj["timestamp"]) ?? state.lastTimestamp;
|
|
297
|
+
state.lastTimestamp = timestamp;
|
|
298
|
+
if (type === "session_meta") {
|
|
299
|
+
state.cwd = asString(payload["cwd"]) ?? state.cwd;
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (type === "turn_context") {
|
|
303
|
+
state.model = asString(payload["model"]) ?? state.model;
|
|
304
|
+
state.cwd = asString(payload["cwd"]) ?? state.cwd;
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (type === "event_msg") {
|
|
308
|
+
if (asString(payload["type"]) === "user_message") {
|
|
309
|
+
appendCodexUser(state, asString(payload["message"]) ?? "", timestamp);
|
|
310
|
+
}
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (type === "response_item") {
|
|
314
|
+
consumeCodexResponseItem(state, payload, timestamp);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
export function convertCodexSession(session) {
|
|
318
|
+
const text = readCodexTranscript(session.sourcePath);
|
|
319
|
+
if (text === undefined) {
|
|
320
|
+
return { cwd: session.cwd || process.cwd(), title: session.title, messages: [] };
|
|
321
|
+
}
|
|
322
|
+
const state = {
|
|
323
|
+
messages: [],
|
|
324
|
+
cwd: session.cwd,
|
|
325
|
+
title: session.title,
|
|
326
|
+
model: "codex",
|
|
327
|
+
pending: [],
|
|
328
|
+
pendingTools: new Map(),
|
|
329
|
+
lastTimestamp: session.startedAt ?? Date.now(),
|
|
330
|
+
lastUserText: "",
|
|
331
|
+
};
|
|
332
|
+
for (const line of text.split(/\r?\n/)) {
|
|
333
|
+
const obj = parseJsonLine(line);
|
|
334
|
+
if (obj)
|
|
335
|
+
consumeCodexEntry(state, obj);
|
|
336
|
+
}
|
|
337
|
+
flushAssistant(state, state.lastTimestamp);
|
|
338
|
+
return {
|
|
339
|
+
cwd: state.cwd || session.cwd || process.cwd(),
|
|
340
|
+
title: state.title,
|
|
341
|
+
messages: state.messages,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
181
344
|
export const codexAdapter = {
|
|
182
345
|
id: "codex",
|
|
183
346
|
label: "Codex",
|
|
@@ -221,142 +384,6 @@ export const codexAdapter = {
|
|
|
221
384
|
return found;
|
|
222
385
|
},
|
|
223
386
|
convert(session) {
|
|
224
|
-
|
|
225
|
-
try {
|
|
226
|
-
text = readFileSync(session.sourcePath, "utf8");
|
|
227
|
-
}
|
|
228
|
-
catch {
|
|
229
|
-
return { cwd: session.cwd || process.cwd(), title: session.title, messages: [] };
|
|
230
|
-
}
|
|
231
|
-
// A few Codex rollouts are huge (100MB+). Keep the latest ~8MB of text so import stays usable.
|
|
232
|
-
const maxChars = 8_000_000;
|
|
233
|
-
if (text.length > maxChars) {
|
|
234
|
-
const cut = text.length - maxChars;
|
|
235
|
-
const nl = text.indexOf("\n", cut);
|
|
236
|
-
text = text.slice(nl === -1 ? cut : nl + 1);
|
|
237
|
-
}
|
|
238
|
-
const messages = [];
|
|
239
|
-
let cwd = session.cwd;
|
|
240
|
-
let title = session.title;
|
|
241
|
-
let model = "codex";
|
|
242
|
-
const pending = [];
|
|
243
|
-
const pendingTools = new Map();
|
|
244
|
-
let lastTs = session.startedAt ?? Date.now();
|
|
245
|
-
let lastUserText = "";
|
|
246
|
-
for (const line of text.split(/\r?\n/)) {
|
|
247
|
-
const obj = parseJsonLine(line);
|
|
248
|
-
if (!obj)
|
|
249
|
-
continue;
|
|
250
|
-
const type = asString(obj["type"]);
|
|
251
|
-
const payload = asRecord(obj["payload"]) ?? {};
|
|
252
|
-
const ts = parseTime(obj["timestamp"]) ?? lastTs;
|
|
253
|
-
lastTs = ts;
|
|
254
|
-
if (type === "session_meta") {
|
|
255
|
-
cwd = asString(payload["cwd"]) ?? cwd;
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
if (type === "turn_context") {
|
|
259
|
-
model = asString(payload["model"]) ?? model;
|
|
260
|
-
cwd = asString(payload["cwd"]) ?? cwd;
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
if (type === "event_msg") {
|
|
264
|
-
const et = asString(payload["type"]);
|
|
265
|
-
if (et === "user_message") {
|
|
266
|
-
flushAssistant(messages, pending, model, ts);
|
|
267
|
-
const userText = truncateText(asString(payload["message"]) ?? "", MAX_TEXT_CHARS).trim();
|
|
268
|
-
if (!userText || isProbablyInjection(userText))
|
|
269
|
-
continue;
|
|
270
|
-
if (userText === lastUserText)
|
|
271
|
-
continue;
|
|
272
|
-
lastUserText = userText;
|
|
273
|
-
if (!title)
|
|
274
|
-
title = oneLine(firstMeaningfulLine(userText) || userText);
|
|
275
|
-
messages.push({ role: "user", text: userText, timestamp: ts });
|
|
276
|
-
}
|
|
277
|
-
continue;
|
|
278
|
-
}
|
|
279
|
-
if (type !== "response_item")
|
|
280
|
-
continue;
|
|
281
|
-
const pt = asString(payload["type"]);
|
|
282
|
-
const role = asString(payload["role"]);
|
|
283
|
-
if (pt === "message" && role === "user") {
|
|
284
|
-
flushAssistant(messages, pending, model, ts);
|
|
285
|
-
const userText = flattenCodexContent(payload["content"]).trim();
|
|
286
|
-
if (!userText || isProbablyInjection(userText) || userText === lastUserText)
|
|
287
|
-
continue;
|
|
288
|
-
lastUserText = userText;
|
|
289
|
-
if (!title)
|
|
290
|
-
title = oneLine(firstMeaningfulLine(userText) || userText);
|
|
291
|
-
messages.push({ role: "user", text: userText, timestamp: ts });
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
if (pt === "message" && role === "assistant") {
|
|
295
|
-
const textOut = flattenCodexContent(payload["content"]).trim();
|
|
296
|
-
if (textOut)
|
|
297
|
-
pending.push({ type: "text", text: truncateText(textOut, MAX_TEXT_CHARS) });
|
|
298
|
-
flushAssistant(messages, pending, model, ts);
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
if (pt === "reasoning") {
|
|
302
|
-
const summary = payload["summary"];
|
|
303
|
-
const bits = [];
|
|
304
|
-
if (Array.isArray(summary)) {
|
|
305
|
-
for (const item of summary) {
|
|
306
|
-
if (typeof item === "string")
|
|
307
|
-
bits.push(item);
|
|
308
|
-
else {
|
|
309
|
-
const rec = asRecord(item);
|
|
310
|
-
const t = rec ? asString(rec["text"]) : undefined;
|
|
311
|
-
if (t)
|
|
312
|
-
bits.push(t);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
const thinking = bits.join("\n").trim();
|
|
317
|
-
if (thinking)
|
|
318
|
-
pending.push({ type: "thinking", thinking: truncateText(thinking, MAX_TEXT_CHARS) });
|
|
319
|
-
continue;
|
|
320
|
-
}
|
|
321
|
-
if (pt === "function_call" || pt === "custom_tool_call") {
|
|
322
|
-
const id = asString(payload["call_id"]) ?? asString(payload["id"]) ?? "";
|
|
323
|
-
const name = asString(payload["name"]) ?? "unknown";
|
|
324
|
-
if (!id)
|
|
325
|
-
continue;
|
|
326
|
-
const mapped = mapToolName(name);
|
|
327
|
-
pendingTools.set(id, mapped);
|
|
328
|
-
pending.push({
|
|
329
|
-
type: "toolCall",
|
|
330
|
-
id,
|
|
331
|
-
name: mapped,
|
|
332
|
-
arguments: remapToolArgs(name, argsFromCall(payload)),
|
|
333
|
-
});
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
if (pt === "function_call_output" || pt === "custom_tool_call_output") {
|
|
337
|
-
flushAssistant(messages, pending, model, ts);
|
|
338
|
-
const id = asString(payload["call_id"]) ?? asString(payload["id"]) ?? "";
|
|
339
|
-
if (!id)
|
|
340
|
-
continue;
|
|
341
|
-
const output = payload["output"];
|
|
342
|
-
let textOut = "";
|
|
343
|
-
if (typeof output === "string")
|
|
344
|
-
textOut = output;
|
|
345
|
-
else {
|
|
346
|
-
const rec = asRecord(output);
|
|
347
|
-
textOut = rec ? (asString(rec["output"]) ?? JSON.stringify(output)) : String(output ?? "");
|
|
348
|
-
}
|
|
349
|
-
messages.push({
|
|
350
|
-
role: "toolResult",
|
|
351
|
-
toolCallId: id,
|
|
352
|
-
toolName: pendingTools.get(id) ?? "unknown",
|
|
353
|
-
text: truncateText(textOut, MAX_TOOL_RESULT_CHARS),
|
|
354
|
-
isError: false,
|
|
355
|
-
timestamp: ts,
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
flushAssistant(messages, pending, model, lastTs);
|
|
360
|
-
return { cwd: cwd || session.cwd || process.cwd(), title, messages };
|
|
387
|
+
return convertCodexSession(session);
|
|
361
388
|
},
|
|
362
389
|
};
|
package/dist/import/index.js
CHANGED
|
@@ -178,41 +178,34 @@ function sortSessions(sessions) {
|
|
|
178
178
|
function sourceLabel(id) {
|
|
179
179
|
return ADAPTERS[id].label;
|
|
180
180
|
}
|
|
181
|
-
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
const yes = args.includes("-y") || args.includes("--yes");
|
|
187
|
-
const opts = parseFlags(args.filter((a) => a !== "-y" && a !== "--yes"));
|
|
188
|
-
const index = loadIndex();
|
|
189
|
-
const discovered = sortSessions(await discoverAll(opts));
|
|
190
|
-
const limited = opts.limit ? discovered.slice(0, opts.limit) : discovered;
|
|
191
|
-
if (limited.length === 0) {
|
|
192
|
-
const where = opts.cwd ? `当前目录 ${formatHomePath(opts.cwd)}` : "这台电脑";
|
|
193
|
-
const who = opts.sources.map(sourceLabel).join(" / ");
|
|
194
|
-
console.log("");
|
|
195
|
-
console.log(` 在${where}没找到 ${who} 的历史对话。`);
|
|
196
|
-
if (opts.cwd)
|
|
197
|
-
console.log(" 想全盘扫一遍可以: u1s1 import --all");
|
|
198
|
-
console.log("");
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
181
|
+
function pendingSessions(sessions, index, force) {
|
|
182
|
+
if (force)
|
|
183
|
+
return { pending: sessions, alreadyImported: 0 };
|
|
201
184
|
const pending = [];
|
|
202
|
-
let
|
|
203
|
-
for (const session of
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
pending.push(session);
|
|
185
|
+
let alreadyImported = 0;
|
|
186
|
+
for (const session of sessions) {
|
|
187
|
+
if (index.items[recordKey(session)])
|
|
188
|
+
alreadyImported += 1;
|
|
189
|
+
else
|
|
190
|
+
pending.push(session);
|
|
210
191
|
}
|
|
192
|
+
return { pending, alreadyImported };
|
|
193
|
+
}
|
|
194
|
+
function printNoSessions(opts) {
|
|
195
|
+
const where = opts.cwd ? `当前目录 ${formatHomePath(opts.cwd)}` : "这台电脑";
|
|
196
|
+
const who = opts.sources.map(sourceLabel).join(" / ");
|
|
197
|
+
console.log("");
|
|
198
|
+
console.log(` 在${where}没找到 ${who} 的历史对话。`);
|
|
199
|
+
if (opts.cwd)
|
|
200
|
+
console.log(" 想全盘扫一遍可以: u1s1 import --all");
|
|
201
|
+
console.log("");
|
|
202
|
+
}
|
|
203
|
+
async function printSessionPreview({ sessions, pending, alreadyImported, opts, }) {
|
|
211
204
|
console.log("");
|
|
212
|
-
console.log(` 找到 ${
|
|
213
|
-
(opts.cwd ? `(${formatHomePath(opts.cwd)})` : "")
|
|
214
|
-
(
|
|
215
|
-
"。");
|
|
205
|
+
console.log(` 找到 ${sessions.length} 段对话`
|
|
206
|
+
+ (opts.cwd ? `(${formatHomePath(opts.cwd)})` : "")
|
|
207
|
+
+ (alreadyImported ? `,其中 ${alreadyImported} 段以前导过` : "")
|
|
208
|
+
+ "。");
|
|
216
209
|
const preview = await Promise.all(pending.slice(0, 12).map(hydrateSession));
|
|
217
210
|
for (const session of preview) {
|
|
218
211
|
const title = session.title ? oneLine(session.title, 56) : "(无标题)";
|
|
@@ -223,33 +216,33 @@ export async function importCommand(args) {
|
|
|
223
216
|
if (pending.length > preview.length) {
|
|
224
217
|
console.log(` …还有 ${pending.length - preview.length} 段`);
|
|
225
218
|
}
|
|
219
|
+
}
|
|
220
|
+
async function shouldCancelImport(pending, opts, assumeYes) {
|
|
226
221
|
if (pending.length === 0) {
|
|
227
222
|
console.log("");
|
|
228
223
|
console.log(" 没有新的可导。想重导一遍就加 --force。");
|
|
229
224
|
console.log("");
|
|
230
|
-
return;
|
|
225
|
+
return true;
|
|
231
226
|
}
|
|
232
227
|
if (opts.dryRun) {
|
|
233
228
|
console.log("");
|
|
234
229
|
console.log(` 预演结束,以上 ${pending.length} 段还没真正导入。`);
|
|
235
230
|
console.log("");
|
|
236
|
-
return;
|
|
231
|
+
return true;
|
|
237
232
|
}
|
|
238
|
-
if (
|
|
239
|
-
|
|
240
|
-
if (!ok) {
|
|
241
|
-
console.log(" 已取消。");
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
233
|
+
if (assumeYes || await confirm(` 导入这 ${pending.length} 段?(回车=好 / n=取消) `)) {
|
|
234
|
+
return false;
|
|
244
235
|
}
|
|
245
|
-
|
|
246
|
-
|
|
236
|
+
console.log(" 已取消。");
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
function printImportSummary(summary) {
|
|
247
240
|
console.log("");
|
|
248
|
-
console.log(` ✓ 导入 ${summary.imported} 段`
|
|
249
|
-
(summary.skipped ? `,跳过 ${summary.skipped}` : "")
|
|
250
|
-
(summary.empty ? `,空对话 ${summary.empty}` : "")
|
|
251
|
-
(summary.errors ? `,失败 ${summary.errors}` : "")
|
|
252
|
-
"。");
|
|
241
|
+
console.log(` ✓ 导入 ${summary.imported} 段`
|
|
242
|
+
+ (summary.skipped ? `,跳过 ${summary.skipped}` : "")
|
|
243
|
+
+ (summary.empty ? `,空对话 ${summary.empty}` : "")
|
|
244
|
+
+ (summary.errors ? `,失败 ${summary.errors}` : "")
|
|
245
|
+
+ "。");
|
|
253
246
|
if (summary.imported > 0) {
|
|
254
247
|
console.log(" 进对应项目跑 u1s1,输入 /resume 就能接着聊。");
|
|
255
248
|
}
|
|
@@ -261,6 +254,28 @@ export async function importCommand(args) {
|
|
|
261
254
|
}
|
|
262
255
|
console.log("");
|
|
263
256
|
}
|
|
257
|
+
export async function importCommand(args) {
|
|
258
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
259
|
+
printHelp();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const yes = args.includes("-y") || args.includes("--yes");
|
|
263
|
+
const opts = parseFlags(args.filter((a) => a !== "-y" && a !== "--yes"));
|
|
264
|
+
const index = loadIndex();
|
|
265
|
+
const discovered = sortSessions(await discoverAll(opts));
|
|
266
|
+
const limited = opts.limit ? discovered.slice(0, opts.limit) : discovered;
|
|
267
|
+
if (limited.length === 0) {
|
|
268
|
+
printNoSessions(opts);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const { pending, alreadyImported } = pendingSessions(limited, index, opts.force);
|
|
272
|
+
await printSessionPreview({ sessions: limited, pending, alreadyImported, opts });
|
|
273
|
+
if (await shouldCancelImport(pending, opts, yes))
|
|
274
|
+
return;
|
|
275
|
+
const summary = runImport(pending, index, opts);
|
|
276
|
+
writeJson(importIndexPath(), index);
|
|
277
|
+
printImportSummary(summary);
|
|
278
|
+
}
|
|
264
279
|
function runImport(sessions, index, opts) {
|
|
265
280
|
const items = [];
|
|
266
281
|
let imported = 0;
|
package/dist/index.js
CHANGED
|
@@ -112,22 +112,22 @@ function installPendingUpdate() {
|
|
|
112
112
|
function ensureTmuxKeyboardProtocol() {
|
|
113
113
|
if (!process.env.TMUX)
|
|
114
114
|
return;
|
|
115
|
-
const
|
|
116
|
-
if (
|
|
115
|
+
const runTmux = (args) => spawnSync("tmux", args, { timeout: 1500, encoding: "utf8" });
|
|
116
|
+
if (runTmux(["-V"]).status !== 0)
|
|
117
117
|
return; // tmux 不可用(如沙箱)则跳过
|
|
118
118
|
// 当前会话 + 全局默认都开,避免只改 -g 时已有会话仍是 off
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
runTmux(["set-option", "-g", "extended-keys", "on"]);
|
|
120
|
+
runTmux(["set-option", "extended-keys", "on"]);
|
|
121
121
|
// 3.5+; 3.4 会失败,忽略
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const features =
|
|
122
|
+
runTmux(["set-option", "-g", "extended-keys-format", "csi-u"]);
|
|
123
|
+
runTmux(["set-option", "extended-keys-format", "csi-u"]);
|
|
124
|
+
const features = runTmux(["show", "-gv", "terminal-features"]);
|
|
125
125
|
if (features.status === 0 && !/\bextkeys\b/.test(features.stdout ?? "")) {
|
|
126
|
-
|
|
126
|
+
runTmux(["set-option", "-ga", "terminal-features", "xterm*:extkeys"]);
|
|
127
127
|
}
|
|
128
128
|
// 已挂上的客户端不会自动重读 terminal-features;直接往当前客户端 tty
|
|
129
129
|
// 发 modifyOtherKeys,让外层终端立刻开始区分 Shift+Enter。
|
|
130
|
-
const tty =
|
|
130
|
+
const tty = runTmux(["display-message", "-p", "#{client_tty}"]).stdout?.trim();
|
|
131
131
|
if (tty) {
|
|
132
132
|
try {
|
|
133
133
|
writeFileSync(tty, "\x1b[>4;2m");
|
|
@@ -137,8 +137,8 @@ function ensureTmuxKeyboardProtocol() {
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
// -l: 按字面量发送,避免 send-keys 把转义序列拆成一串普通按键
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
runTmux(["bind-key", "-n", "S-Enter", "send-keys", "-l", "\x1b[13;2u"]);
|
|
141
|
+
runTmux(["bind-key", "-n", "C-Enter", "send-keys", "-l", "\x1b[13;5u"]);
|
|
142
142
|
}
|
|
143
143
|
async function runAgent(cfg, args) {
|
|
144
144
|
cleanupBrandThemes();
|
|
@@ -286,8 +286,8 @@ async function runAgent(cfg, args) {
|
|
|
286
286
|
description: "清除当前对话上下文,开始新会话",
|
|
287
287
|
handler: async (_args, ctx) => {
|
|
288
288
|
await ctx.newSession({
|
|
289
|
-
withSession: async (
|
|
290
|
-
|
|
289
|
+
withSession: async (sessionContext) => {
|
|
290
|
+
sessionContext.ui.notify("🗑️ 上下文已清除", "info");
|
|
291
291
|
},
|
|
292
292
|
});
|
|
293
293
|
},
|
package/dist/style.js
CHANGED
|
@@ -34,7 +34,13 @@ export function applyBrandUi(pi, version) {
|
|
|
34
34
|
return {
|
|
35
35
|
render(width) {
|
|
36
36
|
// pi-tui crashes on lines wider than the terminal, so truncate defensively.
|
|
37
|
-
return renderBrandHeader(theme,
|
|
37
|
+
return renderBrandHeader(theme, {
|
|
38
|
+
version,
|
|
39
|
+
cwd: process.cwd(),
|
|
40
|
+
width,
|
|
41
|
+
notice: updateNotice,
|
|
42
|
+
announcement,
|
|
43
|
+
}).map((line) => truncateToWidth(line, width));
|
|
38
44
|
},
|
|
39
45
|
invalidate() { },
|
|
40
46
|
};
|
package/dist/subagent.d.ts
CHANGED
|
@@ -41,4 +41,9 @@ export declare function runSubagent(opts: SubagentOptions): Promise<SubagentOutc
|
|
|
41
41
|
* 固定并发的工作池:limit 个工人依次领任务跑完一个补位一个。
|
|
42
42
|
* worker 抛错由调用方在 fn 内部兜住;signal 中止后不再领新任务。
|
|
43
43
|
*/
|
|
44
|
-
export declare function runPool<T>(
|
|
44
|
+
export declare function runPool<T>(input: {
|
|
45
|
+
items: readonly T[];
|
|
46
|
+
limit: number;
|
|
47
|
+
run: (item: T, index: number) => Promise<void>;
|
|
48
|
+
signal?: AbortSignal;
|
|
49
|
+
}): Promise<void>;
|
package/dist/subagent.js
CHANGED
|
@@ -119,15 +119,15 @@ export async function runSubagent(opts) {
|
|
|
119
119
|
* 固定并发的工作池:limit 个工人依次领任务跑完一个补位一个。
|
|
120
120
|
* worker 抛错由调用方在 fn 内部兜住;signal 中止后不再领新任务。
|
|
121
121
|
*/
|
|
122
|
-
export async function runPool(
|
|
122
|
+
export async function runPool(input) {
|
|
123
123
|
let next = 0;
|
|
124
124
|
const worker = async () => {
|
|
125
125
|
for (;;) {
|
|
126
126
|
const i = next++;
|
|
127
|
-
if (i >= items.length || signal?.aborted)
|
|
127
|
+
if (i >= input.items.length || input.signal?.aborted)
|
|
128
128
|
return;
|
|
129
|
-
await
|
|
129
|
+
await input.run(input.items[i], i);
|
|
130
130
|
}
|
|
131
131
|
};
|
|
132
|
-
await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
|
|
132
|
+
await Promise.all(Array.from({ length: Math.max(1, Math.min(input.limit, input.items.length)) }, worker));
|
|
133
133
|
}
|
package/dist/tools.d.ts
CHANGED
|
@@ -4,18 +4,24 @@ import { Type } from "typebox";
|
|
|
4
4
|
import type { CliConfig } from "./config.js";
|
|
5
5
|
export declare function truncate(text: string): string;
|
|
6
6
|
/** 联网工具通用渲染:调用行不占位,收起时只显一行摘要,ctrl+o 展开全文,出错显一行 ✗ */
|
|
7
|
-
export declare function compactResultRender(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
7
|
+
export declare function compactResultRender(input: {
|
|
8
|
+
result: {
|
|
9
|
+
content: Array<{
|
|
10
|
+
type: string;
|
|
11
|
+
text?: string;
|
|
12
|
+
}>;
|
|
13
|
+
};
|
|
14
|
+
options: {
|
|
15
|
+
expanded: boolean;
|
|
16
|
+
};
|
|
17
|
+
theme: {
|
|
18
|
+
fg: (color: any, text: string) => string;
|
|
19
|
+
};
|
|
20
|
+
context: {
|
|
21
|
+
isError: boolean;
|
|
22
|
+
};
|
|
23
|
+
summaryLine: string;
|
|
24
|
+
}): Text;
|
|
19
25
|
/** 联网搜索工具:走 u1s1 网关代理,上游 key 不落到用户机器上。 */
|
|
20
26
|
export declare function createSearchTool(cfg: Pick<CliConfig, "baseUrl" | "apiKey">): import("@earendil-works/pi-coding-agent").ToolDefinition<Type.TObject<{
|
|
21
27
|
query: Type.TString;
|