picocode-core 0.9.134 → 0.9.136
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 +1 -1
- package/src/commands.js +53 -8
- package/src/controller.js +17 -9
- package/src/skills.js +14 -2
package/package.json
CHANGED
package/src/commands.js
CHANGED
|
@@ -3,6 +3,8 @@ import { join } from 'node:path'
|
|
|
3
3
|
import { picoHome } from './paths.js'
|
|
4
4
|
import { parseFrontmatter } from './skills.js'
|
|
5
5
|
|
|
6
|
+
const ARGUMENT = /\{\{([A-Za-z][A-Za-z0-9_-]*):(path|text|choice\(([^{}()]*)\))(?:\:([^{}]*))?\}\}/g
|
|
7
|
+
|
|
6
8
|
export function globalCommandsDir() {
|
|
7
9
|
return join(picoHome(), 'commands')
|
|
8
10
|
}
|
|
@@ -11,6 +13,31 @@ export function projectCommandsDir(root) {
|
|
|
11
13
|
return join(root, '.pico', 'commands')
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
function inferredLabel(name) {
|
|
17
|
+
const words = name.replace(/[-_]+/g, ' ').trim()
|
|
18
|
+
return words ? words[0].toUpperCase() + words.slice(1) : name
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function parseCommandArguments(body) {
|
|
22
|
+
const arguments_ = []
|
|
23
|
+
const seen = new Set()
|
|
24
|
+
for (const match of body.matchAll(ARGUMENT)) {
|
|
25
|
+
const [, name, typeSpec, choicesText, explicitLabel] = match
|
|
26
|
+
if (seen.has(name)) continue
|
|
27
|
+
const type = typeSpec.startsWith('choice(') ? 'choice' : typeSpec
|
|
28
|
+
const choices = type === 'choice' ? choicesText.split(',').map((v) => v.trim()).filter(Boolean) : undefined
|
|
29
|
+
if (type === 'choice' && choices.length === 0) continue
|
|
30
|
+
seen.add(name)
|
|
31
|
+
arguments_.push({
|
|
32
|
+
name,
|
|
33
|
+
type,
|
|
34
|
+
label: explicitLabel?.trim() || inferredLabel(name),
|
|
35
|
+
...(choices ? { choices } : {}),
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
return arguments_
|
|
39
|
+
}
|
|
40
|
+
|
|
14
41
|
async function scanDir(dir, source) {
|
|
15
42
|
let names = []
|
|
16
43
|
try {
|
|
@@ -22,24 +49,42 @@ async function scanDir(dir, source) {
|
|
|
22
49
|
for (const file of names) {
|
|
23
50
|
if (!file.endsWith('.md')) continue
|
|
24
51
|
try {
|
|
25
|
-
const
|
|
52
|
+
const text = await readFile(join(dir, file), 'utf-8')
|
|
53
|
+
const { meta, body } = parseFrontmatter(text)
|
|
26
54
|
commands.push({
|
|
27
55
|
name: file.slice(0, -3),
|
|
28
56
|
description: meta.description || '',
|
|
29
57
|
source,
|
|
30
58
|
file: join(dir, file),
|
|
59
|
+
arguments: parseCommandArguments(body),
|
|
31
60
|
})
|
|
32
61
|
} catch {}
|
|
33
62
|
}
|
|
34
63
|
return commands
|
|
35
64
|
}
|
|
36
65
|
|
|
37
|
-
export function expandCommand(body, args) {
|
|
38
|
-
|
|
39
|
-
|
|
66
|
+
export function expandCommand(body, args = '', namedValues = {}) {
|
|
67
|
+
if (args && typeof args === 'object' && !Array.isArray(args)) {
|
|
68
|
+
namedValues = args
|
|
69
|
+
args = ''
|
|
70
|
+
}
|
|
71
|
+
args = String(args ?? '')
|
|
72
|
+
namedValues = namedValues && typeof namedValues === 'object' ? namedValues : {}
|
|
73
|
+
const metadata = new Map(parseCommandArguments(body).map((argument) => [argument.name, argument]))
|
|
74
|
+
const rendered = body.replace(ARGUMENT, (markup, name) => {
|
|
75
|
+
const argument = metadata.get(name)
|
|
76
|
+
if (!argument) return markup
|
|
77
|
+
const value = Object.hasOwn(namedValues, name) ? String(namedValues[name] ?? '') : ''
|
|
78
|
+
if (argument.type === 'choice' && value && !argument.choices.includes(value)) {
|
|
79
|
+
throw new TypeError(`Invalid value for command argument "${name}"`)
|
|
80
|
+
}
|
|
81
|
+
return value
|
|
82
|
+
})
|
|
83
|
+
return rendered.includes('$ARGUMENTS')
|
|
84
|
+
? rendered.replaceAll('$ARGUMENTS', () => args)
|
|
40
85
|
: args
|
|
41
|
-
? `${
|
|
42
|
-
:
|
|
86
|
+
? `${rendered.trim()}\n\n${args}`
|
|
87
|
+
: rendered
|
|
43
88
|
}
|
|
44
89
|
|
|
45
90
|
export async function createCommandIndex(root) {
|
|
@@ -51,11 +96,11 @@ export async function createCommandIndex(root) {
|
|
|
51
96
|
|
|
52
97
|
return {
|
|
53
98
|
list: () => commands,
|
|
54
|
-
async load(name, args = '') {
|
|
99
|
+
async load(name, args = '', namedValues = {}) {
|
|
55
100
|
const command = byName.get(name)
|
|
56
101
|
if (!command) return null
|
|
57
102
|
const { body } = parseFrontmatter(await readFile(command.file, 'utf-8'))
|
|
58
|
-
return expandCommand(body.trim(), args
|
|
103
|
+
return expandCommand(body.trim(), args, namedValues)
|
|
59
104
|
},
|
|
60
105
|
}
|
|
61
106
|
}
|
package/src/controller.js
CHANGED
|
@@ -28,7 +28,8 @@ import { connectOpenAI, openaiCredentials, disconnectOpenAI } from './openai-aut
|
|
|
28
28
|
import { agentScratchDir, ensureDir } from './paths.js'
|
|
29
29
|
import { loadCodexModels } from './codex-models.js'
|
|
30
30
|
import { fuzzyScore } from './fuzzy.js'
|
|
31
|
-
import {
|
|
31
|
+
import { MAX_DELIBERATION_ROUNDS } from './deliberation.js'
|
|
32
|
+
import { buildUserContent, finalizeUserContent, inputTextFromContent, mediaTypeFor } from './attachments.js'
|
|
32
33
|
|
|
33
34
|
export const EFFORT_LEVELS = [
|
|
34
35
|
{ key: null, desc: 'let the provider decide how much to think' },
|
|
@@ -73,8 +74,9 @@ function parallelPrompt(task, agentLimit) {
|
|
|
73
74
|
return `Use parallel agents for the following task: ${task}\n\nFirst call agent_plan. Interpret any agent-count instruction in the user's task semantically and declare that count; if the user gave no count, declare the configured default budget of ${agentLimit}. Then use agent_start within that enforced budget to delegate distinct, focused parts of the task to the configured worker model. Collect workers with agent_collect, critically evaluate their results, and synthesize the final response. When useful and the budget permits, use independent workers to check important disputed or weak conclusions. Do not delegate final synthesis. Do not emit progress updates while agents run; Pico displays agent activity automatically.`
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
function deliberatePrompt(decision) {
|
|
77
|
-
|
|
77
|
+
function deliberatePrompt(decision, rounds) {
|
|
78
|
+
const withRounds = rounds ? ` and rounds set to ${rounds}` : ''
|
|
79
|
+
return `Deliberate on the following decision: ${decision}\n\nImmediately call deliberate with a self-contained brief${withRounds}. Do not research first, start ordinary agents, or approximate the deliberation yourself. The deliberation participants own all supporting research.`
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
function workerSystemPrompt(scratchpad) {
|
|
@@ -364,8 +366,11 @@ export function createController({ boot }) {
|
|
|
364
366
|
}
|
|
365
367
|
|
|
366
368
|
async function executeTurn(text) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
+
// an image the model asked to view arrives with its path in the label;
|
|
370
|
+
// that text must not be scanned for image paths or it attaches twice
|
|
371
|
+
const built = buildUserContent(text, state.attachments)
|
|
372
|
+
const viewed = built.used.length > 0 && built.used.every((placeholder) => deliveredImages.has(placeholder))
|
|
373
|
+
const { content, used } = viewed ? built : finalizeUserContent(text, state.attachments)
|
|
369
374
|
for (const placeholder of used) deliveredImages.delete(placeholder)
|
|
370
375
|
persist(makeEvent('message', { message: { role: 'user', content }, ...(viewed ? { origin: 'view' } : {}) }))
|
|
371
376
|
ensureSession()
|
|
@@ -982,13 +987,16 @@ export function createController({ boot }) {
|
|
|
982
987
|
return true
|
|
983
988
|
}
|
|
984
989
|
|
|
985
|
-
|
|
990
|
+
// rounds, when given, is passed along to the tool call; the tool itself
|
|
991
|
+
// clamps to its 1..5 range
|
|
992
|
+
function sendDeliberate(decision, rounds) {
|
|
986
993
|
if (!decision) {
|
|
987
994
|
flash('usage: /deliberate <decision>')
|
|
988
995
|
return true
|
|
989
996
|
}
|
|
990
997
|
if (!modelAvailable(boot.deliberationModel)) return false
|
|
991
|
-
|
|
998
|
+
const n = Number(rounds)
|
|
999
|
+
send(deliberatePrompt(decision, Number.isInteger(n) && n >= 1 && n <= MAX_DELIBERATION_ROUNDS ? n : null))
|
|
992
1000
|
return true
|
|
993
1001
|
}
|
|
994
1002
|
|
|
@@ -999,8 +1007,8 @@ export function createController({ boot }) {
|
|
|
999
1007
|
send(`Follow these skill instructions now.\n\n${body}`)
|
|
1000
1008
|
}
|
|
1001
1009
|
|
|
1002
|
-
async function sendCommand(name, args) {
|
|
1003
|
-
const text = await boot.commands.load(name, args)
|
|
1010
|
+
async function sendCommand(name, args, namedValues) {
|
|
1011
|
+
const text = await boot.commands.load(name, args, namedValues)
|
|
1004
1012
|
if (!text) return flash(`could not load command ${name}`)
|
|
1005
1013
|
send(text)
|
|
1006
1014
|
}
|
package/src/skills.js
CHANGED
|
@@ -121,8 +121,20 @@ Review $ARGUMENTS for security problems. Focus on input validation and secrets.
|
|
|
121
121
|
\`\`\`
|
|
122
122
|
|
|
123
123
|
\`$ARGUMENTS\` is replaced with whatever follows the command; without the placeholder,
|
|
124
|
-
arguments are appended after the body.
|
|
125
|
-
|
|
124
|
+
arguments are appended after the body. For compact typed form fields, put named
|
|
125
|
+
placeholders directly in the Markdown body:
|
|
126
|
+
|
|
127
|
+
- \`{{file:path:File to review}}\` — path/file picker, named \`file\`
|
|
128
|
+
- \`{{focus:text:Review focus}}\` — text input, named \`focus\`
|
|
129
|
+
- \`{{mode:choice(review,fix):Mode}}\` — choice picker whose selected literal is inserted
|
|
130
|
+
|
|
131
|
+
The final label is optional and inferred from the name (for example, \`{{focus:text}}\`
|
|
132
|
+
is labelled "Focus"). Names start with a letter and may contain letters, digits, \`_\`,
|
|
133
|
+
and \`-\`. Keep choice values short and comma-separated. A placeholder may be repeated.
|
|
134
|
+
Named fields and \`$ARGUMENTS\` may coexist for backwards compatibility.
|
|
135
|
+
|
|
136
|
+
Commands are rescanned every turn, so a new command appears in the slash menu from the
|
|
137
|
+
next message.`
|
|
126
138
|
|
|
127
139
|
export async function createSkillIndex(root) {
|
|
128
140
|
const builtin = [
|