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.
@@ -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
- let cwd = session.cwd;
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
- const type = asString(obj["type"]);
146
- const payload = asRecord(obj["payload"]) ?? {};
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 { ...session, cwd: cwd || session.cwd, title: title || session.title };
169
+ return {
170
+ ...session,
171
+ cwd: state.cwd || session.cwd,
172
+ title: state.title || session.title,
173
+ };
165
174
  }
166
- function flushAssistant(messages, pending, model, ts) {
167
- if (pending.length === 0)
175
+ function flushAssistant(state, timestamp) {
176
+ if (state.pending.length === 0)
168
177
  return;
169
- const sawTool = pending.some((b) => b.type === "toolCall");
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: ts,
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
- let text = "";
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
  };
@@ -178,41 +178,34 @@ function sortSessions(sessions) {
178
178
  function sourceLabel(id) {
179
179
  return ADAPTERS[id].label;
180
180
  }
181
- export async function importCommand(args) {
182
- if (args.includes("-h") || args.includes("--help")) {
183
- printHelp();
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 already = 0;
203
- for (const session of limited) {
204
- const prev = index.items[recordKey(session)];
205
- if (prev && !opts.force) {
206
- already += 1;
207
- continue;
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(` 找到 ${limited.length} 段对话` +
213
- (opts.cwd ? `(${formatHomePath(opts.cwd)})` : "") +
214
- (already ? `,其中 ${already} 段以前导过` : "") +
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 (!yes) {
239
- const ok = await confirm(` 导入这 ${pending.length} 段?(回车=好 / n=取消) `);
240
- if (!ok) {
241
- console.log(" 已取消。");
242
- return;
243
- }
233
+ if (assumeYes || await confirm(` 导入这 ${pending.length} 段?(回车=好 / n=取消) `)) {
234
+ return false;
244
235
  }
245
- const summary = runImport(pending, index, opts);
246
- writeJson(importIndexPath(), index);
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 run = (args) => spawnSync("tmux", args, { timeout: 1500, encoding: "utf8" });
116
- if (run(["-V"]).status !== 0)
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
- run(["set-option", "-g", "extended-keys", "on"]);
120
- run(["set-option", "extended-keys", "on"]);
119
+ runTmux(["set-option", "-g", "extended-keys", "on"]);
120
+ runTmux(["set-option", "extended-keys", "on"]);
121
121
  // 3.5+; 3.4 会失败,忽略
122
- run(["set-option", "-g", "extended-keys-format", "csi-u"]);
123
- run(["set-option", "extended-keys-format", "csi-u"]);
124
- const features = run(["show", "-gv", "terminal-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
- run(["set-option", "-ga", "terminal-features", "xterm*:extkeys"]);
126
+ runTmux(["set-option", "-ga", "terminal-features", "xterm*:extkeys"]);
127
127
  }
128
128
  // 已挂上的客户端不会自动重读 terminal-features;直接往当前客户端 tty
129
129
  // 发 modifyOtherKeys,让外层终端立刻开始区分 Shift+Enter。
130
- const tty = run(["display-message", "-p", "#{client_tty}"]).stdout?.trim();
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
- run(["bind-key", "-n", "S-Enter", "send-keys", "-l", "\x1b[13;2u"]);
141
- run(["bind-key", "-n", "C-Enter", "send-keys", "-l", "\x1b[13;5u"]);
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 (ctx) => {
290
- ctx.ui.notify("🗑️ 上下文已清除", "info");
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, version, process.cwd(), width, updateNotice, announcement).map((line) => truncateToWidth(line, width));
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
  };
@@ -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>(items: readonly T[], limit: number, fn: (item: T, index: number) => Promise<void>, signal?: AbortSignal): Promise<void>;
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(items, limit, fn, signal) {
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 fn(items[i], i);
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(result: {
8
- content: Array<{
9
- type: string;
10
- text?: string;
11
- }>;
12
- }, options: {
13
- expanded: boolean;
14
- }, theme: {
15
- fg: (color: any, text: string) => string;
16
- }, context: {
17
- isError: boolean;
18
- }, summaryLine: string): Text;
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;