pi-code 0.8.0 → 1.0.0
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/README.md +5 -5
- package/extensions/commands.ts +85 -15
- package/extensions/git-checkpoint.ts +13 -0
- package/extensions/internal/command-file.ts +170 -0
- package/extensions/mcp.ts +75 -36
- package/extensions/memory.ts +35 -7
- package/extensions/question.ts +196 -150
- package/extensions/skills.ts +12 -3
- package/extensions/subagent/README.md +12 -5
- package/extensions/subagent/agents.ts +73 -0
- package/extensions/subagent/background.ts +38 -3
- package/extensions/subagent/index.ts +63 -24
- package/package.json +1 -1
|
@@ -26,8 +26,9 @@ import { type Static, Type } from 'typebox'
|
|
|
26
26
|
import { capForContext } from '../internal/output-guard.js'
|
|
27
27
|
import { isProjectApproved, isProjectApprovedSilently } from '../internal/project-approval.js'
|
|
28
28
|
import { SUBAGENT_CHANNEL } from '../internal/subagent-events.js'
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
29
|
+
import { skillDirs } from '../skills.js'
|
|
30
|
+
import { type AgentConfig, type AgentScope, discoverAgents, resolveModelAlias, withPreloadedSkills } from './agents.js'
|
|
31
|
+
import { activeBackgroundRuns, backgroundStatusText, cancelBackgroundRun, MAX_BACKGROUND_RUNS, resumeBackgroundRun, startBackgroundRun } from './background.js'
|
|
31
32
|
|
|
32
33
|
const MAX_PARALLEL_TASKS = 8
|
|
33
34
|
const MAX_CONCURRENCY = 4
|
|
@@ -259,6 +260,10 @@ interface RunAgentOptions {
|
|
|
259
260
|
onUpdate?: OnUpdateCallback
|
|
260
261
|
makeDetails: (results: SingleResult[]) => SubagentDetails
|
|
261
262
|
onPhase?: SubagentPhaseSink
|
|
263
|
+
/** Skill directories to preload from, resolved where project trust is known. */
|
|
264
|
+
skillRoots?: string[]
|
|
265
|
+
/** Models this user can actually run, for resolving a tier alias. */
|
|
266
|
+
availableModels?: ReadonlyArray<{ id: string }>
|
|
262
267
|
}
|
|
263
268
|
|
|
264
269
|
/** Publishes a child run's start/stop for the hooks extension's SubagentStart/Stop. */
|
|
@@ -294,7 +299,7 @@ async function runSingleAgentInner(options: RunAgentOptions): Promise<SingleResu
|
|
|
294
299
|
}
|
|
295
300
|
}
|
|
296
301
|
|
|
297
|
-
const args = agentInvocationArgs(agent)
|
|
302
|
+
const args = agentInvocationArgs(agent, resolveModelAlias(agent.modelAlias, options.availableModels ?? []))
|
|
298
303
|
|
|
299
304
|
let tmpPromptDir: string | null = null
|
|
300
305
|
let tmpPromptPath: string | null = null
|
|
@@ -321,8 +326,9 @@ async function runSingleAgentInner(options: RunAgentOptions): Promise<SingleResu
|
|
|
321
326
|
}
|
|
322
327
|
|
|
323
328
|
try {
|
|
324
|
-
|
|
325
|
-
|
|
329
|
+
const promptWithSkills = withPreloadedSkills(agent.systemPrompt, agent.skills, options.skillRoots ?? [])
|
|
330
|
+
if (promptWithSkills.trim()) {
|
|
331
|
+
const tmp = await writePromptToTempFile(agent.name, promptWithSkills)
|
|
326
332
|
tmpPromptDir = tmp.dir
|
|
327
333
|
tmpPromptPath = tmp.filePath
|
|
328
334
|
args.push('--append-system-prompt', tmpPromptPath)
|
|
@@ -461,6 +467,7 @@ const SubagentParams = Type.Object({
|
|
|
461
467
|
background: Type.Optional(Type.Boolean({ description: 'Run the single-mode task in the background: returns a run id immediately and a notification arrives when it completes.' })),
|
|
462
468
|
status: Type.Optional(Type.Boolean({ description: 'Set true (alone, no other params) to list background runs instead of running anything.' })),
|
|
463
469
|
cancel: Type.Optional(Type.String({ description: 'Background run id to cancel (from the id returned when it started, or from status).' })),
|
|
470
|
+
resume: Type.Optional(Type.String({ description: 'Finished background run id to continue with a follow-up task; the child keeps everything it already saw. Pass task with it.' })),
|
|
464
471
|
})
|
|
465
472
|
|
|
466
473
|
/**
|
|
@@ -490,6 +497,21 @@ type SubagentParamsStatic = Static<typeof SubagentParams>
|
|
|
490
497
|
type ChainStepParam = Static<typeof ChainItem>
|
|
491
498
|
type TaskItemParam = Static<typeof TaskItem>
|
|
492
499
|
|
|
500
|
+
/** The completion notice a background run sends when it finishes. */
|
|
501
|
+
export function backgroundCompletionText(run: { id: string; agent: string; state: string; turns: number; output?: string }): string {
|
|
502
|
+
const output = capForContext(run.output ?? '') || '(no output)'
|
|
503
|
+
return `Background subagent run ${run.id} (${run.agent}) ${run.state} after ${run.turns} turns.\n\n${output}`
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/** What to tell the model about a resume request. */
|
|
507
|
+
export function resumeResultText(id: string, task: string | undefined, onComplete: (run: { id: string; agent: string; state: string; turns: number; output?: string }) => void): string {
|
|
508
|
+
if (!task) return 'Pass task with resume: the follow-up needs an instruction.'
|
|
509
|
+
const outcome = resumeBackgroundRun(id, task, onComplete)
|
|
510
|
+
if (outcome === 'resumed') return `Resumed background run ${id} with the follow-up task; a notification will arrive on completion.`
|
|
511
|
+
if (outcome === 'still-running') return `Background run ${id} is still running; wait for it or cancel it first.`
|
|
512
|
+
return `Unknown background run: ${id}.\n\n${backgroundStatusText()}`
|
|
513
|
+
}
|
|
514
|
+
|
|
493
515
|
/** What to tell the model about a cancel request. */
|
|
494
516
|
export function cancelResultText(id: string): string {
|
|
495
517
|
const outcome = cancelBackgroundRun(id)
|
|
@@ -506,6 +528,8 @@ interface ModeContext {
|
|
|
506
528
|
onUpdate: OnUpdateCallback | undefined
|
|
507
529
|
makeDetails: MakeDetails
|
|
508
530
|
onPhase?: SubagentPhaseSink
|
|
531
|
+
skillRoots: string[]
|
|
532
|
+
availableModels: ReadonlyArray<{ id: string }>
|
|
509
533
|
}
|
|
510
534
|
|
|
511
535
|
async function checkProjectAgentGate(params: SubagentParamsStatic, agents: AgentConfig[], ctx: ExtensionContext, projectAgentsDir: string | null, gateMode: SubagentMode, makeDetails: MakeDetails): Promise<ToolResult | null> {
|
|
@@ -539,11 +563,14 @@ async function checkProjectAgentGate(params: SubagentParamsStatic, agents: Agent
|
|
|
539
563
|
}
|
|
540
564
|
|
|
541
565
|
/** CLI args shared by foreground and background children, from the agent's config. */
|
|
542
|
-
function agentInvocationArgs(agent: AgentConfig): string[] {
|
|
566
|
+
function agentInvocationArgs(agent: AgentConfig, aliasModel?: string): string[] {
|
|
543
567
|
const args: string[] = ['--mode', 'json', '-p', '--no-session']
|
|
544
|
-
//
|
|
545
|
-
//
|
|
546
|
-
|
|
568
|
+
// A concrete model wins; otherwise a Claude tier alias resolved against the models
|
|
569
|
+
// this user can actually run. pi reads a thinking level from the model pattern's
|
|
570
|
+
// :suffix when a model is pinned, and from --thinking otherwise.
|
|
571
|
+
const model = agent.model ?? aliasModel
|
|
572
|
+
if (model) args.push('--model', agent.effort ? `${model}:${agent.effort}` : model)
|
|
573
|
+
else if (agent.effort) args.push('--thinking', agent.effort)
|
|
547
574
|
if (agent.tools && agent.tools.length > 0) args.push('--tools', agent.tools.join(','))
|
|
548
575
|
if (agent.disallowedTools && agent.disallowedTools.length > 0) args.push('--exclude-tools', agent.disallowedTools.join(','))
|
|
549
576
|
return args
|
|
@@ -570,7 +597,7 @@ function removeTmpPrompt(tmpPrompt: { dir: string; filePath: string } | undefine
|
|
|
570
597
|
}
|
|
571
598
|
}
|
|
572
599
|
|
|
573
|
-
async function runBackgroundMode(params: SubagentParamsStatic, agents: AgentConfig[], defaultCwd: string, pi: ExtensionAPI, makeDetails: MakeDetails): Promise<ToolResult> {
|
|
600
|
+
async function runBackgroundMode(params: SubagentParamsStatic, agents: AgentConfig[], defaultCwd: string, pi: ExtensionAPI, makeDetails: MakeDetails, skillRoots: string[], availableModels: ReadonlyArray<{ id: string }>): Promise<ToolResult> {
|
|
574
601
|
const task = params.task
|
|
575
602
|
const agentName = params.agent
|
|
576
603
|
if (!task || !agentName) {
|
|
@@ -590,10 +617,11 @@ async function runBackgroundMode(params: SubagentParamsStatic, agents: AgentConf
|
|
|
590
617
|
if (activeBackgroundRuns() >= MAX_BACKGROUND_RUNS) {
|
|
591
618
|
return backgroundCapResult(makeDetails)
|
|
592
619
|
}
|
|
593
|
-
const args = agentInvocationArgs(agent)
|
|
620
|
+
const args = agentInvocationArgs(agent, resolveModelAlias(agent.modelAlias, availableModels))
|
|
594
621
|
let tmpPrompt: { dir: string; filePath: string } | undefined
|
|
595
|
-
|
|
596
|
-
|
|
622
|
+
const promptWithSkills = withPreloadedSkills(agent.systemPrompt, agent.skills, skillRoots)
|
|
623
|
+
if (promptWithSkills.trim()) {
|
|
624
|
+
tmpPrompt = await writePromptToTempFile(agent.name, promptWithSkills)
|
|
597
625
|
args.push('--append-system-prompt', tmpPrompt.filePath)
|
|
598
626
|
}
|
|
599
627
|
args.push(`Task: ${task}`)
|
|
@@ -601,15 +629,7 @@ async function runBackgroundMode(params: SubagentParamsStatic, agents: AgentConf
|
|
|
601
629
|
const id = startBackgroundRun(agent.name, task, { command: invocation.command, args: invocation.args, cwd: params.cwd ?? defaultCwd }, (run) => {
|
|
602
630
|
removeTmpPrompt(tmpPrompt)
|
|
603
631
|
pi.events.emit(SUBAGENT_CHANNEL, { phase: 'stop', agentType: run.agent, agentId: run.id })
|
|
604
|
-
|
|
605
|
-
pi.sendMessage(
|
|
606
|
-
{
|
|
607
|
-
customType: 'subagent-background',
|
|
608
|
-
content: `Background subagent run ${run.id} (${run.agent}) ${run.state} after ${run.turns} turns.\n\n${output}`,
|
|
609
|
-
display: true,
|
|
610
|
-
},
|
|
611
|
-
{ triggerTurn: true },
|
|
612
|
-
)
|
|
632
|
+
pi.sendMessage({ customType: 'subagent-background', content: backgroundCompletionText(run), display: true }, { triggerTurn: true })
|
|
613
633
|
})
|
|
614
634
|
if (id === null) {
|
|
615
635
|
// Lost the cap race to a parallel batch: the atomic check inside startBackgroundRun refused.
|
|
@@ -659,6 +679,8 @@ async function runChainMode(chain: ChainStepParam[], mode: ModeContext): Promise
|
|
|
659
679
|
onUpdate: chainUpdate,
|
|
660
680
|
makeDetails: makeDetails('chain'),
|
|
661
681
|
onPhase: mode.onPhase,
|
|
682
|
+
skillRoots: mode.skillRoots,
|
|
683
|
+
availableModels: mode.availableModels,
|
|
662
684
|
})
|
|
663
685
|
results.push(result)
|
|
664
686
|
|
|
@@ -774,6 +796,8 @@ async function runSingleMode(agentName: string, task: string, cwd: string | unde
|
|
|
774
796
|
onUpdate,
|
|
775
797
|
makeDetails: makeDetails('single'),
|
|
776
798
|
onPhase: mode.onPhase,
|
|
799
|
+
skillRoots: mode.skillRoots,
|
|
800
|
+
availableModels: mode.availableModels,
|
|
777
801
|
})
|
|
778
802
|
const isError = result.exitCode !== 0 || result.stopReason === 'error' || result.stopReason === 'aborted'
|
|
779
803
|
if (isError) {
|
|
@@ -1077,6 +1101,10 @@ function renderParallelResult(results: SingleResult[], expanded: boolean, theme:
|
|
|
1077
1101
|
}
|
|
1078
1102
|
|
|
1079
1103
|
export default function subagentExtension(pi: ExtensionAPI) {
|
|
1104
|
+
const notifyBackgroundCompletion = (run: { id: string; agent: string; state: string; turns: number; output?: string }): void => {
|
|
1105
|
+
pi.sendMessage({ customType: 'subagent-background', content: backgroundCompletionText(run), display: true }, { triggerTurn: true })
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1080
1108
|
// Claude surfaces each agent's description so the model can pick one autonomously.
|
|
1081
1109
|
// Rebuilt per turn (agents are rediscovered per invocation too); project agents are
|
|
1082
1110
|
// included only when the project is already approved, read without prompting, since
|
|
@@ -1129,6 +1157,10 @@ export default function subagentExtension(pi: ExtensionAPI) {
|
|
|
1129
1157
|
results,
|
|
1130
1158
|
})
|
|
1131
1159
|
|
|
1160
|
+
if (params.resume) {
|
|
1161
|
+
return { content: [{ type: 'text', text: resumeResultText(params.resume, params.task, notifyBackgroundCompletion) }], details: makeDetails('single')([]) }
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1132
1164
|
if (params.cancel) {
|
|
1133
1165
|
return { content: [{ type: 'text', text: cancelResultText(params.cancel) }], details: makeDetails('single')([]) }
|
|
1134
1166
|
}
|
|
@@ -1157,9 +1189,16 @@ export default function subagentExtension(pi: ExtensionAPI) {
|
|
|
1157
1189
|
const gateResult = await checkProjectAgentGate(params, agents, ctx, discovery.projectAgentsDir, gateMode, makeDetails)
|
|
1158
1190
|
if (gateResult) return gateResult
|
|
1159
1191
|
|
|
1160
|
-
|
|
1192
|
+
// Project skills only preload once the project is approved, matching the
|
|
1193
|
+
// gate the skills extension applies to discovery itself.
|
|
1194
|
+
const skillRoots = skillDirs(ctx.cwd, os.homedir(), isProjectApprovedSilently(ctx))
|
|
1195
|
+
// Tier aliases resolve against what this user is authenticated for; an
|
|
1196
|
+
// unavailable tier still falls back to the session model.
|
|
1197
|
+
const availableModels = ctx.modelRegistry?.getAvailable?.() ?? []
|
|
1198
|
+
|
|
1199
|
+
if (params.background) return runBackgroundMode(params, agents, ctx.cwd, pi, makeDetails, skillRoots, availableModels)
|
|
1161
1200
|
|
|
1162
|
-
const mode: ModeContext = { agents, defaultCwd: ctx.cwd, signal, onUpdate, makeDetails, onPhase: (phase, agentType, agentId) => pi.events.emit(SUBAGENT_CHANNEL, { phase, agentType, agentId }) }
|
|
1201
|
+
const mode: ModeContext = { agents, defaultCwd: ctx.cwd, signal, onUpdate, makeDetails, skillRoots, availableModels, onPhase: (phase, agentType, agentId) => pi.events.emit(SUBAGENT_CHANNEL, { phase, agentType, agentId }) }
|
|
1163
1202
|
|
|
1164
1203
|
if (params.chain?.length) return runChainMode(params.chain, mode)
|
|
1165
1204
|
if (params.tasks?.length) return runParallelMode(params.tasks, mode)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Claude Code experience for the pi coding agent: reads your .claude config (rules, commands, skills, hooks, output styles, MCP servers, agents) and adds todo, checkpoints, memory, web, and subagents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi",
|