picocode-core 0.9.176 → 0.9.178

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": "picocode-core",
3
- "version": "0.9.176",
3
+ "version": "0.9.178",
4
4
  "description": "The agent runtime behind pico: sessions, tools, subagents, MCP, memory, and model access",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/controller.js CHANGED
@@ -11,7 +11,7 @@ import { createAgentManager } from './agents.js'
11
11
  import { runDeliberation, validateDeliberation } from './deliberation.js'
12
12
  import { deliberationsFromEvents } from './deliberation-history.js'
13
13
  import { compactionPrompt, formatCompactSummary, summarySections, compactionKeepFrom } from './compaction.js'
14
- import { completedToolCalls, TOOL_TRIM_VERSION } from './tool-trimming.js'
14
+ import { completedToolCalls, retrieveToolResult, TOOL_TRIM_VERSION } from './tool-trimming.js'
15
15
  import { createToolset } from './tools/index.js'
16
16
  import { defaultTitle } from './tools/recorder.js'
17
17
  import { scanUserTools } from './user-tools.js'
@@ -603,6 +603,7 @@ export function createController({ boot }) {
603
603
  const context = await createAgentContext(boot, { userTools: userToolScan.tools })
604
604
  const { tools, recorder } = createToolset({
605
605
  ...context.tools,
606
+ toolResult: (id) => retrieveToolResult(state.events, id),
606
607
  sessionId: state.session?.id,
607
608
  sessionFile: state.session?.file,
608
609
  wakeups: boot.wakeups,
@@ -835,7 +836,7 @@ export function createController({ boot }) {
835
836
  const unavailable = ids.filter((id) => !completed.has(id))
836
837
  if (unavailable.length) return flash(`error: tool calls are not completed in current context: ${unavailable.join(', ')}`)
837
838
  const eligible = type === 'tool_trim'
838
- ? ids.filter((id) => state.derived.toolItems.get(id)?.contextCanTrim)
839
+ ? ids.filter((id) => !state.derived.trimmedToolIds.has(id))
839
840
  : ids.filter((id) => state.derived.toolItems.get(id)?.contextTrimmed)
840
841
  if (!eligible.length) return false
841
842
  persist(makeEvent(type, { callIds: eligible, version: TOOL_TRIM_VERSION }))
@@ -1217,6 +1218,7 @@ export function createController({ boot }) {
1217
1218
  return createToolset({
1218
1219
  cwd: boot.cwd,
1219
1220
  env: boot.env,
1221
+ toolResult: (id) => retrieveToolResult(state.events, id),
1220
1222
  hostTools: boot.hostTools ?? [],
1221
1223
  tracker: boot.tracker,
1222
1224
  skills: boot.skills,
package/src/derive.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { continuationMessage } from './compaction.js'
2
2
  import { applySteering } from './steer.js'
3
- import { annotateToolContext, applyToolTrims } from './tool-trimming.js'
3
+ import { annotateToolContext, applyToolTrims, toolResultArchive } from './tool-trimming.js'
4
4
 
5
5
  const DROPPING_MODES = ['both', 'chat', 'summary']
6
6
 
@@ -180,6 +180,7 @@ export function deriveState(events) {
180
180
  toolItems: new Map(),
181
181
  latestCompactIndex,
182
182
  trimmedToolIds: new Set(),
183
+ toolTrimVersions: new Map(),
183
184
  }
184
185
 
185
186
  for (const event of effectiveEvents) {
@@ -259,7 +260,10 @@ export function deriveState(events) {
259
260
  break
260
261
  }
261
262
  case 'tool_trim':
262
- for (const callId of event.data.callIds || []) state.trimmedToolIds.add(callId)
263
+ for (const callId of event.data.callIds || []) {
264
+ state.trimmedToolIds.add(callId)
265
+ state.toolTrimVersions.set(callId, event.data.version?.algorithm || 'tool-trim-v1')
266
+ }
263
267
  state.lastPromptTokens = 0
264
268
  break
265
269
  case 'tool_restore':
@@ -272,6 +276,7 @@ export function deriveState(events) {
272
276
  state.historyEventIds = []
273
277
  state.toolItems = new Map()
274
278
  state.trimmedToolIds = new Set()
279
+ state.toolTrimVersions = new Map()
275
280
  state.lastPromptTokens = 0
276
281
  break
277
282
  case 'context_file':
@@ -299,7 +304,7 @@ export function deriveState(events) {
299
304
  }
300
305
 
301
306
  const effectiveHistory = elideStaleToolResults(state.providerHistory)
302
- state.providerHistory = applyToolTrims(effectiveHistory, state.trimmedToolIds)
307
+ state.providerHistory = applyToolTrims(effectiveHistory, state.trimmedToolIds, state.toolTrimVersions, state.toolItems, toolResultArchive(events).references)
303
308
  annotateToolContext(state, state.providerHistory, state.trimmedToolIds)
304
309
  return state
305
310
  }
@@ -92,7 +92,28 @@ export function completedToolCalls(history) {
92
92
  return new Map([...callsIn(history)].filter(([, entry]) => entry.result))
93
93
  }
94
94
 
95
- export function applyToolTrims(history, trimmedIds) {
95
+ export function toolResultArchive(events) {
96
+ const history = events.filter((event) => event.type === 'message').map((event) => event.data.message)
97
+ const references = new Map()
98
+ for (const message of history) {
99
+ for (const call of message.tool_calls || []) {
100
+ if (!references.has(call.id)) references.set(call.id, `t${references.size + 1}`)
101
+ }
102
+ }
103
+ const results = new Map()
104
+ for (const [callId, entry] of completedToolCalls(history)) {
105
+ results.set(references.get(callId), { callId, name: entry.call.function.name, arguments: entry.call.function.arguments, result: entry.result.content })
106
+ }
107
+ return { references, results }
108
+ }
109
+
110
+ export function retrieveToolResult(events, id) {
111
+ const result = toolResultArchive(events).results.get(id)
112
+ if (!result) throw new Error(`no saved tool result with id ${id}`)
113
+ return structuredClone(result)
114
+ }
115
+
116
+ export function applyToolTrims(history, trimmedIds, versions = new Map(), toolItems = new Map(), references = new Map()) {
96
117
  if (!trimmedIds.size) return history
97
118
  const completed = completedToolCalls(history)
98
119
  const callMessages = new Map()
@@ -106,8 +127,19 @@ export function applyToolTrims(history, trimmedIds) {
106
127
  callMessages.set(entry.callIndex, callMessage)
107
128
  }
108
129
  const call = callMessage.tool_calls.find((candidate) => candidate.id === id)
109
- call.function.arguments = trimArguments(call.function.arguments)
110
- resultMessages.set(entry.resultIndex, { ...entry.result, content: trimResult(entry.result.content) })
130
+ if (versions.get(id) === 'tool-compact-v2') {
131
+ const item = toolItems.get(id)
132
+ let args
133
+ try { args = JSON.parse(call.function.arguments) } catch { args = {} }
134
+ const description = item?.description || args?.description || call.function.name
135
+ call.function.arguments = JSON.stringify({ description })
136
+ const error = item?.status === 'error' ? `error: ${item.error || 'tool failed'}\n` : ''
137
+ const reference = references.get(id)
138
+ resultMessages.set(entry.resultIndex, { ...entry.result, content: `${error}[compacted; retrieve with tool_result(${JSON.stringify({ id: reference })})]` })
139
+ } else {
140
+ call.function.arguments = trimArguments(call.function.arguments)
141
+ resultMessages.set(entry.resultIndex, { ...entry.result, content: trimResult(entry.result.content) })
142
+ }
111
143
  }
112
144
  return history.map((message, index) => callMessages.get(index) || resultMessages.get(index) || message)
113
145
  }
@@ -129,13 +161,12 @@ export function annotateToolContext(state, effectiveHistory, trimmedIds) {
129
161
  item.contextAvailable = !!entry
130
162
  item.contextTrimmed = !!entry && trimmedIds.has(item.callId)
131
163
  item.contextTokens = entry ? entryTokens(entry.call.function.arguments, entry.result.content) : 0
132
- item.contextCanTrim = !!entry && !item.contextTrimmed &&
133
- entryTokens(trimArguments(entry.call.function.arguments), trimResult(entry.result.content)) < item.contextTokens
164
+ item.contextCanTrim = !!entry && !item.contextTrimmed
134
165
  }
135
166
  }
136
167
 
137
168
  export const TOOL_TRIM_VERSION = {
138
- algorithm: ALGORITHM,
169
+ algorithm: 'tool-compact-v2',
139
170
  argumentChars: ARGUMENT_CHARS,
140
171
  resultChars: RESULT_CHARS,
141
172
  stringChars: STRING_CHARS,
@@ -7,7 +7,7 @@ import { createGlob } from './glob.js'
7
7
  import { createGrep } from './grep.js'
8
8
  import { createView } from './view.js'
9
9
 
10
- export function createToolset({ cwd, env, tracker, skills, shells, sessionId, sessionFile, wakeups, memory, agents, deliberations, onAgentsCollected, askUser, mcpTools = [], userTools = [], hostTools = [], signal, maxToolCalls, maxAgentStarts, requireAgentPlan = false, allowNames, onToolUpdate, viewer }) {
10
+ export function createToolset({ toolResult, cwd, env, tracker, skills, shells, sessionId, sessionFile, wakeups, memory, agents, deliberations, onAgentsCollected, askUser, mcpTools = [], userTools = [], hostTools = [], signal, maxToolCalls, maxAgentStarts, requireAgentPlan = false, allowNames, onToolUpdate, viewer }) {
11
11
  const recorder = createRecorder(onToolUpdate)
12
12
  let agentStarts = 0
13
13
  let plannedAgentStarts = requireAgentPlan ? null : maxAgentStarts
@@ -22,6 +22,16 @@ export function createToolset({ cwd, env, tracker, skills, shells, sessionId, se
22
22
  createGrep(deps),
23
23
  ]
24
24
 
25
+ if (toolResult) local.push({
26
+ name: 'tool_result',
27
+ description: 'Retrieve the original arguments and result of a compacted tool call using its retrieval ID. This reads saved data without rerunning the tool or restoring other calls.',
28
+ schema: {
29
+ description: describeParam,
30
+ id: { type: 'string', description: 'retrieval ID shown in the compacted result, such as t42' },
31
+ },
32
+ execute: ({ id }) => toolResult(id),
33
+ })
34
+
25
35
  if (viewer) local.push(createView({ ...deps, viewer }))
26
36
 
27
37
  if (shells) {