picocode-core 0.9.177 → 0.9.179

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.177",
3
+ "version": "0.9.179",
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,
@@ -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
 
@@ -183,6 +183,14 @@ export function deriveState(events) {
183
183
  toolTrimVersions: new Map(),
184
184
  }
185
185
 
186
+ const references = toolResultArchive(events).references
187
+ const historyTokens = () => Math.ceil(JSON.stringify(applyToolTrims(
188
+ elideStaleToolResults(state.providerHistory), state.trimmedToolIds,
189
+ state.toolTrimVersions, state.toolItems, references,
190
+ )).length / 4)
191
+ let measuredBaseline = null
192
+ let contextEdited = false
193
+
186
194
  for (const event of effectiveEvents) {
187
195
  if (event.type === 'usage') {
188
196
  if (!dropped.has(event.id)) {
@@ -194,6 +202,8 @@ export function deriveState(events) {
194
202
  if (event.data.lastPrompt !== undefined) {
195
203
  state.lastPromptTokens = event.data.lastPrompt
196
204
  state.lastPromptModel = event.data.model
205
+ measuredBaseline = { tokens: event.data.lastPrompt, history: historyTokens() }
206
+ contextEdited = false
197
207
  }
198
208
  }
199
209
  continue
@@ -238,6 +248,8 @@ export function deriveState(events) {
238
248
  if (!canceledUndoTargets.has(event.id)) foldRewind(state, event)
239
249
  break
240
250
  case 'compact': {
251
+ measuredBaseline = null
252
+ contextEdited = false
241
253
  const { summary, keepFrom, sessionFile } = event.data
242
254
  if (keepFrom !== undefined || sessionFile) {
243
255
  const idx = keepFrom ? state.historyEventIds.indexOf(keepFrom) : -1
@@ -260,6 +272,7 @@ export function deriveState(events) {
260
272
  break
261
273
  }
262
274
  case 'tool_trim':
275
+ contextEdited = true
263
276
  for (const callId of event.data.callIds || []) {
264
277
  state.trimmedToolIds.add(callId)
265
278
  state.toolTrimVersions.set(callId, event.data.version?.algorithm || 'tool-trim-v1')
@@ -267,10 +280,13 @@ export function deriveState(events) {
267
280
  state.lastPromptTokens = 0
268
281
  break
269
282
  case 'tool_restore':
283
+ contextEdited = true
270
284
  for (const callId of event.data.callIds || []) state.trimmedToolIds.delete(callId)
271
285
  state.lastPromptTokens = 0
272
286
  break
273
287
  case 'clear':
288
+ measuredBaseline = null
289
+ contextEdited = false
274
290
  state.transcript = []
275
291
  state.providerHistory = []
276
292
  state.historyEventIds = []
@@ -303,8 +319,11 @@ export function deriveState(events) {
303
319
  }
304
320
  }
305
321
 
322
+ state.estimatedPromptTokens = contextEdited && measuredBaseline
323
+ ? Math.max(0, measuredBaseline.tokens + historyTokens() - measuredBaseline.history)
324
+ : null
306
325
  const effectiveHistory = elideStaleToolResults(state.providerHistory)
307
- state.providerHistory = applyToolTrims(effectiveHistory, state.trimmedToolIds, state.toolTrimVersions, state.toolItems)
326
+ state.providerHistory = applyToolTrims(effectiveHistory, state.trimmedToolIds, state.toolTrimVersions, state.toolItems, toolResultArchive(events).references)
308
327
  annotateToolContext(state, state.providerHistory, state.trimmedToolIds)
309
328
  return state
310
329
  }
@@ -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, versions = new Map(), toolItems = new Map()) {
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()
@@ -112,8 +133,9 @@ export function applyToolTrims(history, trimmedIds, versions = new Map(), toolIt
112
133
  try { args = JSON.parse(call.function.arguments) } catch { args = {} }
113
134
  const description = item?.description || args?.description || call.function.name
114
135
  call.function.arguments = JSON.stringify({ description })
115
- const outcome = item?.status === 'error' ? 'error' : item?.status || 'completed'
116
- resultMessages.set(entry.resultIndex, { ...entry.result, content: `[${outcome}; tool arguments and result compacted, originals retained in session log]` })
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 })})]` })
117
139
  } else {
118
140
  call.function.arguments = trimArguments(call.function.arguments)
119
141
  resultMessages.set(entry.resultIndex, { ...entry.result, content: trimResult(entry.result.content) })
@@ -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) {