picocode-core 0.9.163 → 0.9.164

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.163",
3
+ "version": "0.9.164",
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/agent.js CHANGED
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises'
2
2
  import { execFile } from 'node:child_process'
3
3
  import { promisify } from 'node:util'
4
4
  import { compose, scope, model, noToolsCalled, Inherit, getText } from '@prsm/ai'
5
- import { commitLabel, fileLabel, mediaTypeFor, selectionLabel } from './attachments.js'
5
+ import { commitLabel, elementLabel, fileLabel, mediaTypeFor, selectionLabel } from './attachments.js'
6
6
 
7
7
  const exec = promisify(execFile)
8
8
  // a whole patch rides along only while it is small; a bigger commit
@@ -37,6 +37,7 @@ async function hydratePart(part) {
37
37
  if (part.type === 'file') return { type: 'text', text: fileLabel(part.path) }
38
38
  if (part.type === 'selection') return { type: 'text', text: selectionLabel(part) }
39
39
  if (part.type === 'commit') return hydrateCommit(part)
40
+ if (part.type === 'element') return { type: 'text', text: elementLabel(part) }
40
41
  if (part.type !== 'image' || part.source?.kind !== 'path') return part
41
42
  const mediaType = part.source.mediaType || mediaTypeFor(part.source.path)
42
43
  if (!mediaType) return { type: 'text', text: `[image unavailable: ${part.source.path}]` }
@@ -73,6 +73,9 @@ export function splitTextByImagePaths(text, exists = existsSync) {
73
73
  export const fileLabel = (path) => `[file: ${path}]`
74
74
  export const selectionLabel = (part) => `[selection: ${part.path}:${part.fromLine}-${part.toLine}]\n${part.text}\n[/selection]`
75
75
  export const commitLabel = (part) => `[commit: ${part.hash.slice(0, 7)} ${JSON.stringify(part.subject ?? '')}]`
76
+ // a page element the user picked in a browser: where it is, what it is,
77
+ // and its markup, so the model can find it in the source
78
+ export const elementLabel = (part) => `[element: <${part.tag}> ${part.selector} on ${part.url}${part.component ? ` in component ${part.component}` : ''}]\n${part.html}\n[/element]`
76
79
 
77
80
  // [Image #n] and [File #n] placeholders in the composed text stand for
78
81
  // attachments; they become image, file, or selection parts of the user message
@@ -89,6 +92,8 @@ export function buildUserContent(text, attachments) {
89
92
  ? { type: 'selection', path: attachment.path, text: attachment.text, fromLine: attachment.fromLine, toLine: attachment.toLine, ...(Number.isInteger(attachment.fromColumn) ? { fromColumn: attachment.fromColumn } : {}), ...(Number.isInteger(attachment.toColumn) ? { toColumn: attachment.toColumn } : {}) }
90
93
  : attachment.kind === 'commit'
91
94
  ? { type: 'commit', hash: attachment.hash, subject: attachment.subject, root: attachment.root }
95
+ : attachment.kind === 'element'
96
+ ? { type: 'element', url: attachment.url, selector: attachment.selector, tag: attachment.tag, text: attachment.text, html: attachment.html, rect: attachment.rect, component: attachment.component ?? null }
92
97
  : attachment.kind === 'file'
93
98
  ? { type: 'file', path: attachment.path }
94
99
  : { type: 'image', source: { kind: 'path', path: attachment.path, mediaType: attachment.mediaType } })
@@ -131,6 +136,10 @@ export function inputTextFromContent(content, { attachments, nextId }) {
131
136
  const placeholder = `[File #${nextId()}]`
132
137
  attachments.set(placeholder, { path: part.path, kind: 'file' })
133
138
  out += placeholder
139
+ } else if (part.type === 'element' && part.selector) {
140
+ const placeholder = `[File #${nextId()}]`
141
+ attachments.set(placeholder, { url: part.url, selector: part.selector, tag: part.tag, text: part.text, html: part.html, rect: part.rect, component: part.component ?? null, kind: 'element' })
142
+ out += placeholder
134
143
  } else if (part.type === 'commit' && part.hash) {
135
144
  const placeholder = `[File #${nextId()}]`
136
145
  attachments.set(placeholder, { hash: part.hash, subject: part.subject, root: part.root, kind: 'commit' })
package/src/controller.js CHANGED
@@ -899,6 +899,14 @@ export function createController({ boot }) {
899
899
  return placeholder
900
900
  }
901
901
 
902
+ // an element picked in a browser page; the message carries its markup
903
+ function attachElement({ url, selector, tag, text = '', html = '', rect = null, component = null }) {
904
+ if (!selector || !tag) return null
905
+ const placeholder = `[File #${++state.imageCount}]`
906
+ state.attachments.set(placeholder, { url: String(url ?? ''), selector: String(selector), tag: String(tag), text: String(text).slice(0, 400), html: String(html).slice(0, 4000), rect, component: component ? String(component) : null, kind: 'element' })
907
+ return placeholder
908
+ }
909
+
902
910
  // a project-relative pick: images attach as images, anything else as a
903
911
  // file reference
904
912
  function attachProjectFile(file) {
@@ -1398,6 +1406,7 @@ export function createController({ boot }) {
1398
1406
  attachFile,
1399
1407
  attachSelection,
1400
1408
  attachCommit,
1409
+ attachElement,
1401
1410
  viewImage: deliverImage,
1402
1411
  attachProjectFile,
1403
1412
  detachImage,
package/src/derive.js CHANGED
@@ -101,7 +101,7 @@ function foldMessage(state, event) {
101
101
  if (message.role === 'user') {
102
102
  const text = Array.isArray(message.content)
103
103
  ? message.content
104
- .map((p) => (p.type === 'text' ? p.text : p.type === 'file' ? `[file: ${p.path}]` : p.type === 'selection' ? `[selection: ${p.path}:${p.fromLine}-${p.toLine}]` : p.type === 'commit' ? `[commit: ${String(p.hash).slice(0, 7)}]` : `[image: ${String(p.source?.path || '').split('/').pop() || 'attached'}]`))
104
+ .map((p) => (p.type === 'text' ? p.text : p.type === 'file' ? `[file: ${p.path}]` : p.type === 'selection' ? `[selection: ${p.path}:${p.fromLine}-${p.toLine}]` : p.type === 'commit' ? `[commit: ${String(p.hash).slice(0, 7)}]` : p.type === 'element' ? `[element: ${p.selector}]` : `[image: ${String(p.source?.path || '').split('/').pop() || 'attached'}]`))
105
105
  .join('')
106
106
  : String(message.content)
107
107
  state.transcript.push({ ...base, kind: 'user', text, content: message.content, ...(event.data.origin ? { origin: event.data.origin } : {}) })