pikiloom 0.4.64 → 0.4.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pikiloom",
3
- "version": "0.4.64",
3
+ "version": "0.4.67",
4
4
  "description": "Put the world's smartest AI agents in your pocket. Command local Claude & Gemini via IM. | 让最好用的 IM 变成你电脑上的顶级 Agent 控制台",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,11 +25,13 @@
25
25
  ],
26
26
  "author": "xiaotonng",
27
27
  "license": "MIT",
28
+ "packageManager": "npm@11.6.2",
28
29
  "repository": {
29
30
  "type": "git",
30
31
  "url": "git+https://github.com/xiaotonng/pikiloom.git"
31
32
  },
32
33
  "scripts": {
34
+ "verify:toolchain": "node scripts/verify-toolchain.mjs",
33
35
  "dev": "bash scripts/dev.sh",
34
36
  "command": "set -ae && . ./.env && set +a && tsx src/cli/run.ts",
35
37
  "build:dashboard": "vite build --config dashboard/vite.config.ts",
@@ -45,9 +47,21 @@
45
47
  "engines": {
46
48
  "node": ">=20.0.0"
47
49
  },
50
+ "devEngines": {
51
+ "runtime": {
52
+ "name": "node",
53
+ "version": "22.23.1 || ^25.2.0",
54
+ "onFail": "error"
55
+ },
56
+ "packageManager": {
57
+ "name": "npm",
58
+ "version": "11.6.2",
59
+ "onFail": "error"
60
+ }
61
+ },
48
62
  "devDependencies": {
49
63
  "@tailwindcss/vite": "^4.2.1",
50
- "@types/node": "^25.3.5",
64
+ "@types/node": "22.20.0",
51
65
  "@types/qrcode": "^1.5.6",
52
66
  "@types/react": "^19.2.14",
53
67
  "@types/react-dom": "^19.2.3",
@@ -61,7 +75,7 @@
61
75
  "remark-gfm": "^4.0.1",
62
76
  "tailwindcss": "^4.2.1",
63
77
  "tsx": "^4.21.0",
64
- "typescript": "^5.9.3",
78
+ "typescript": "7.0.2",
65
79
  "vitest": "^4.0.18",
66
80
  "zustand": "^5.0.12"
67
81
  },
@@ -1,9 +1,12 @@
1
1
  import type { AgentDriver, AgentTurnInput, DriverContext, DriverEvent, DriverResult, TuiInput, TuiSpec, NativeSessionInfo } from '../contracts/driver.js';
2
+ import type { UniversalToolCall } from '../protocol/index.js';
2
3
  export declare function codexToolSummary(item: any): {
3
4
  id: string;
4
5
  name: string;
5
6
  summary: string;
6
7
  } | null;
8
+ /** Project one app-server item into the kernel's rich, expandable tool-call shape. */
9
+ export declare function codexToolCall(item: any, fallbackStatus: UniversalToolCall['status']): UniversalToolCall | null;
7
10
  export interface CodexContentState {
8
11
  text: string;
9
12
  reasoning: string;
@@ -34,6 +34,89 @@ const CODEX_TOOL_CALL_TYPES = new Set(['dynamicToolCall', 'mcpToolCall', 'collab
34
34
  // Field-less placeholder summaries — a completed item with only one of these never overrides
35
35
  // a more specific summary cached at item/started.
36
36
  const CODEX_GENERIC_TOOL_SUMMARIES = new Set(['Search web', 'Run shell command', 'Edit files', 'Use tool']);
37
+ const CODEX_TOOL_INPUT_MAX = 4 * 1024;
38
+ const CODEX_TOOL_RESULT_MAX = 12 * 1024;
39
+ function codexToolText(value, max) {
40
+ if (value == null)
41
+ return null;
42
+ let text;
43
+ if (typeof value === 'string')
44
+ text = value;
45
+ else {
46
+ try {
47
+ text = JSON.stringify(value, null, 2);
48
+ }
49
+ catch {
50
+ text = String(value);
51
+ }
52
+ }
53
+ text = text.replace(/\r\n/g, '\n').trim();
54
+ if (!text)
55
+ return null;
56
+ return text.length <= max ? text : `${text.slice(0, max).trimEnd()}\n… (truncated)`;
57
+ }
58
+ function codexToolInput(item) {
59
+ switch (item?.type) {
60
+ case 'commandExecution': {
61
+ const command = Array.isArray(item.command) ? item.command.join(' ') : item.command;
62
+ if (!command)
63
+ return null;
64
+ const detail = { command };
65
+ if (typeof item.cwd === 'string' && item.cwd.trim())
66
+ detail.cwd = item.cwd;
67
+ return codexToolText(detail, CODEX_TOOL_INPUT_MAX);
68
+ }
69
+ case 'fileChange':
70
+ case 'patch':
71
+ return codexToolText(item.changes ?? item.patch ?? item.diff, CODEX_TOOL_INPUT_MAX);
72
+ case 'mcpToolCall':
73
+ case 'dynamicToolCall':
74
+ return codexToolText(item.arguments ?? item.input, CODEX_TOOL_INPUT_MAX);
75
+ case 'collabAgentToolCall':
76
+ return codexToolText({
77
+ prompt: item.prompt ?? undefined,
78
+ model: item.model ?? undefined,
79
+ reasoningEffort: item.reasoningEffort ?? item.reasoning_effort ?? undefined,
80
+ receiverThreadIds: item.receiverThreadIds ?? item.receiver_thread_ids ?? undefined,
81
+ }, CODEX_TOOL_INPUT_MAX);
82
+ case 'webSearch':
83
+ return codexToolText(item.action ?? item.query, CODEX_TOOL_INPUT_MAX);
84
+ default:
85
+ return null;
86
+ }
87
+ }
88
+ function codexToolResult(item) {
89
+ if (item?.type === 'commandExecution') {
90
+ const output = codexToolText(item.aggregatedOutput ?? item.aggregated_output, CODEX_TOOL_RESULT_MAX);
91
+ const exitCode = item.exitCode ?? item.exit_code;
92
+ if (output && typeof exitCode === 'number' && exitCode !== 0)
93
+ return `${output}\n\n(exit code ${exitCode})`;
94
+ if (output)
95
+ return output;
96
+ return typeof exitCode === 'number' && exitCode !== 0 ? `exit code ${exitCode}` : null;
97
+ }
98
+ if (item?.type === 'mcpToolCall') {
99
+ const error = item.error?.message ?? item.error;
100
+ if (error)
101
+ return codexToolText(error, CODEX_TOOL_RESULT_MAX);
102
+ return codexToolText(item.result, CODEX_TOOL_RESULT_MAX);
103
+ }
104
+ if (item?.type === 'dynamicToolCall') {
105
+ return codexToolText(item.contentItems ?? item.content_items, CODEX_TOOL_RESULT_MAX);
106
+ }
107
+ if (item?.type === 'collabAgentToolCall') {
108
+ return codexToolText(item.agentsStates ?? item.agents_states, CODEX_TOOL_RESULT_MAX);
109
+ }
110
+ return null;
111
+ }
112
+ function codexToolStatus(item, fallback) {
113
+ const raw = String(item?.status || '').toLowerCase();
114
+ if (raw === 'failed' || raw === 'declined' || item?.success === false || item?.error)
115
+ return 'failed';
116
+ if (raw === 'completed' || raw === 'done' || item?.success === true)
117
+ return 'done';
118
+ return fallback;
119
+ }
37
120
  export function codexToolSummary(item) {
38
121
  const id = String(item?.id || '');
39
122
  if (!id)
@@ -66,6 +149,27 @@ export function codexToolSummary(item) {
66
149
  }
67
150
  return null;
68
151
  }
152
+ /** Project one app-server item into the kernel's rich, expandable tool-call shape. */
153
+ export function codexToolCall(item, fallbackStatus) {
154
+ const base = codexToolSummary(item);
155
+ if (!base)
156
+ return null;
157
+ return {
158
+ ...base,
159
+ input: codexToolInput(item),
160
+ result: codexToolResult(item),
161
+ status: codexToolStatus(item, fallbackStatus),
162
+ };
163
+ }
164
+ function appendCodexToolOutput(previous, delta) {
165
+ if (typeof delta !== 'string' || !delta)
166
+ return previous ?? null;
167
+ const next = `${previous ?? ''}${delta}`.replace(/\r\n/g, '\n');
168
+ if (next.length <= CODEX_TOOL_RESULT_MAX)
169
+ return next;
170
+ const marker = '… (earlier output truncated)\n';
171
+ return marker + next.slice(-(CODEX_TOOL_RESULT_MAX - marker.length));
172
+ }
69
173
  // codex `item/tool/requestUserInput` params -> a normalized UniversalInteraction
70
174
  // (faithful to the legacy codex driver's toAgentInteraction shape).
71
175
  function codexUserInputToInteraction(params, promptId) {
@@ -153,7 +257,7 @@ export class CodexDriver {
153
257
  const srv = new StdioRpcClient({ command: this.bin, args, env: input.env, label: 'codex app-server' });
154
258
  const state = { text: '', reasoning: '', streamedReasoning: false, msgs: [], thinkParts: [], sessionId: input.sessionId ?? null, input: null, output: null, cached: null, contextUsed: null, contextWindow: null, status: null, error: null, turnId: null };
155
259
  const phases = new Map();
156
- const toolSummaries = new Map();
260
+ const toolCalls = new Map();
157
261
  const deltaItems = new Set();
158
262
  let lastTextItemId = null;
159
263
  let steerRegistered = false;
@@ -218,13 +322,35 @@ export class CodexDriver {
218
322
  const item = params?.item || {};
219
323
  if (item.type === 'agentMessage' && item.id)
220
324
  phases.set(item.id, item.phase || 'final_answer');
221
- const t = codexToolSummary(item);
222
- if (t && !toolSummaries.has(t.id)) {
223
- toolSummaries.set(t.id, t.summary);
224
- ctx.emit({ type: 'tool', call: { id: t.id, name: t.name, summary: t.summary, status: 'running' } });
325
+ const call = codexToolCall(item, 'running');
326
+ if (call && !toolCalls.has(call.id)) {
327
+ toolCalls.set(call.id, call);
328
+ ctx.emit({ type: 'tool', call });
225
329
  }
226
330
  break;
227
331
  }
332
+ case 'item/commandExecution/outputDelta':
333
+ case 'item/fileChange/outputDelta': {
334
+ const id = String(params?.itemId || '');
335
+ const previous = toolCalls.get(id);
336
+ if (!previous)
337
+ break;
338
+ const call = { ...previous, result: appendCodexToolOutput(previous.result, params?.delta) };
339
+ toolCalls.set(id, call);
340
+ ctx.emit({ type: 'tool', call });
341
+ break;
342
+ }
343
+ case 'item/fileChange/patchUpdated': {
344
+ const id = String(params?.itemId || '');
345
+ const projected = codexToolCall({ type: 'fileChange', id, changes: params?.changes }, 'running');
346
+ if (!projected)
347
+ break;
348
+ const previous = toolCalls.get(id);
349
+ const call = previous ? { ...previous, ...projected, result: projected.result ?? previous.result } : projected;
350
+ toolCalls.set(id, call);
351
+ ctx.emit({ type: 'tool', call });
352
+ break;
353
+ }
228
354
  case 'item/agentMessage/delta': {
229
355
  if (!params?.delta)
230
356
  break;
@@ -259,14 +385,26 @@ export class CodexDriver {
259
385
  captureCodexAgentMessage(item, state, deltaItems, phases, ctx.emit);
260
386
  else if (item.type === 'reasoning')
261
387
  captureCodexReasoning(codexReasoningItemText(item), state, ctx.emit);
262
- const t = codexToolSummary(item);
263
- if (t) {
388
+ const projected = codexToolCall(item, 'done');
389
+ if (projected) {
264
390
  // Prefer the completed item's summary when it is specific — webSearch carries its
265
391
  // query only at completion (item/started may be query-less or absent entirely,
266
- // so no `toolSummaries.has` gate: a completed-only tool still gets its row).
267
- const summary = !CODEX_GENERIC_TOOL_SUMMARIES.has(t.summary) ? t.summary : (toolSummaries.get(t.id) || t.summary);
268
- toolSummaries.set(t.id, summary);
269
- ctx.emit({ type: 'tool', call: { id: t.id, name: t.name, summary, status: item.status === 'failed' ? 'failed' : 'done' } });
392
+ // so a completed-only tool still gets its row).
393
+ const previous = toolCalls.get(projected.id);
394
+ const summary = !CODEX_GENERIC_TOOL_SUMMARIES.has(projected.summary)
395
+ ? projected.summary
396
+ : (previous?.summary || projected.summary);
397
+ const call = {
398
+ ...previous,
399
+ ...projected,
400
+ summary,
401
+ input: projected.input ?? previous?.input ?? null,
402
+ // Some app-server versions stream output deltas but omit aggregatedOutput on the
403
+ // terminal item. Preserve the accumulated live result in that case.
404
+ result: projected.result ?? previous?.result ?? null,
405
+ };
406
+ toolCalls.set(call.id, call);
407
+ ctx.emit({ type: 'tool', call });
270
408
  }
271
409
  break;
272
410
  }