pi-code 1.0.67 → 1.0.68
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.
|
@@ -7,16 +7,41 @@
|
|
|
7
7
|
* context-imports and the command path-scope guard all go through it).
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import * as os from 'node:os'
|
|
11
|
+
import * as path from 'node:path'
|
|
12
|
+
import { fileURLToPath } from 'node:url'
|
|
13
|
+
|
|
10
14
|
/** The tools that name a file pi-code acts on. */
|
|
11
15
|
const FILE_TOOLS: ReadonlySet<string> = new Set(['read', 'edit', 'write'])
|
|
12
16
|
|
|
17
|
+
/** The spaces pi folds to a plain one before resolving (pi dist/utils/paths). */
|
|
18
|
+
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g
|
|
19
|
+
|
|
20
|
+
/** `target` as pi's file tools read it before resolving against cwd: a leading @ (which
|
|
21
|
+
* some models add) stripped, ~ expanded, a file:// URL decoded, unicode spaces folded.
|
|
22
|
+
* pi does this in resolveToCwd (dist/core/tools/path-utils), which the package does not
|
|
23
|
+
* export. A reader that skips it judges a different file than the one pi opens: a
|
|
24
|
+
* command scoped to `Read(*.md)` allowed `~/secret/notes.md`, read as <cwd>/~/secret. */
|
|
25
|
+
function asPiReadsIt(target: string): string {
|
|
26
|
+
const folded = target.replace(UNICODE_SPACES, ' ')
|
|
27
|
+
const bare = folded.startsWith('@') ? folded.slice(1) : folded
|
|
28
|
+
if (bare === '~') return os.homedir()
|
|
29
|
+
if (bare.startsWith('~/') || (process.platform === 'win32' && bare.startsWith('~\\'))) return path.join(os.homedir(), bare.slice(2))
|
|
30
|
+
if (!bare.startsWith('file://')) return bare
|
|
31
|
+
try {
|
|
32
|
+
return fileURLToPath(bare)
|
|
33
|
+
} catch {
|
|
34
|
+
return bare
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
13
38
|
/** The path a file tool call named, or undefined for any other tool, an errored call,
|
|
14
|
-
* or a call that named none.
|
|
15
|
-
* against their own cwd. */
|
|
39
|
+
* or a call that named none. Normalised as pi normalises it, and still relative when
|
|
40
|
+
* the model gave a relative path: callers resolve it against their own cwd. */
|
|
16
41
|
export function fileToolTarget(event: { toolName: string; input?: unknown; isError?: boolean }): string | undefined {
|
|
17
42
|
if (event.isError === true) return undefined
|
|
18
43
|
if (!FILE_TOOLS.has(event.toolName)) return undefined
|
|
19
44
|
const input = event.input as { path?: unknown; file_path?: unknown } | undefined
|
|
20
45
|
const target = typeof input?.path === 'string' ? input.path : input?.file_path
|
|
21
|
-
return typeof target === 'string' && target.length > 0 ? target : undefined
|
|
46
|
+
return typeof target === 'string' && target.length > 0 ? asPiReadsIt(target) : undefined
|
|
22
47
|
}
|
package/extensions/skills.ts
CHANGED
|
@@ -167,11 +167,37 @@ function refusedByOverride(name: string, ctx: ExtensionContext, trusted: boolean
|
|
|
167
167
|
return { action: 'handled' }
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
/** Forked skills still running in the background, by name. */
|
|
171
|
+
const runningForks = new Set<string>()
|
|
172
|
+
|
|
173
|
+
/** Whether a context: fork skill holds the invoking turn for its result. Claude: "The
|
|
174
|
+
* forked subagent runs in the background: you keep working while it runs, and its result
|
|
175
|
+
* arrives in your conversation when it completes. Set background: false in the frontmatter
|
|
176
|
+
* to instead wait for the result in the turn that invoked the skill." It also waits in
|
|
177
|
+
* non-interactive mode, when CLAUDE_CODE_DISABLE_BACKGROUND_TASKS is 1, and "when you
|
|
178
|
+
* invoke a forked skill while an earlier invocation of the same skill is still running". */
|
|
179
|
+
function forkWaits(name: string, frontmatter: Record<string, unknown>, ctx: ExtensionContext): boolean {
|
|
180
|
+
if (String(frontmatter.background).trim().toLowerCase() === 'false') return true
|
|
181
|
+
return !ctx.hasUI || process.env.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS === '1' || runningForks.has(name)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Start a forked skill in the background and hand its result to the conversation when it
|
|
185
|
+
* completes. The promise has no awaiter, and sendMessage on a pi whose session was
|
|
186
|
+
* replaced meanwhile throws: the catch keeps that from exiting pi, at the cost of the
|
|
187
|
+
* delivery, as a background subagent run's completion notice is lost the same way. */
|
|
188
|
+
function startForkedSkill(pi: ExtensionAPI, ctx: ExtensionContext, fork: { name: string; filePath: string; expanded: string; agentName: string | undefined }): { action: 'handled' } {
|
|
189
|
+
runningForks.add(fork.name)
|
|
190
|
+
ctx.ui.notify(`Skill ${fork.name} is running in a forked subagent in the background; its result arrives here when it completes.`, 'info')
|
|
191
|
+
void runForkedSkill(fork.name, fork.filePath, fork.expanded, fork.agentName)
|
|
192
|
+
.then((result) => pi.sendMessage({ customType: 'skill-fork', content: result.text, display: true }, { triggerTurn: true }))
|
|
193
|
+
.catch(() => {})
|
|
194
|
+
.finally(() => runningForks.delete(fork.name))
|
|
195
|
+
return { action: 'handled' }
|
|
196
|
+
}
|
|
197
|
+
|
|
170
198
|
/** Claude's context: fork run: the expanded skill content becomes the prompt that
|
|
171
|
-
* drives a subagent, without the conversation history.
|
|
172
|
-
*
|
|
173
|
-
* turn (Claude's background: false behavior, which is also what Claude itself
|
|
174
|
-
* does in -p and SDK runs). */
|
|
199
|
+
* drives a subagent, without the conversation history. Never rejects: a failed run
|
|
200
|
+
* is reported in the returned block. */
|
|
175
201
|
async function runForkedSkill(name: string, filePath: string, expanded: string, agentName: string | undefined): Promise<{ action: 'transform'; text: string }> {
|
|
176
202
|
try {
|
|
177
203
|
const output = await runAgent({ prompt: expanded, fullTools: true, ...(agentName ? { agent: agentName } : {}) })
|
|
@@ -218,7 +244,9 @@ async function expandSkillInvocation(pi: ExtensionAPI, rawText: string, ctx: Ext
|
|
|
218
244
|
}
|
|
219
245
|
const expanded = await expandCommand(pi, parsed, args, { cwd: ctx.cwd }, found.filePath, undefined, { allowShell: !shellExecutionDisabled(ctx.cwd, os.homedir(), trusted) })
|
|
220
246
|
if (typeof frontmatter.context === 'string' && frontmatter.context.trim().toLowerCase() === 'fork') {
|
|
221
|
-
|
|
247
|
+
const agentName = typeof frontmatter.agent === 'string' ? frontmatter.agent.trim() : undefined
|
|
248
|
+
if (forkWaits(name, frontmatter, ctx)) return runForkedSkill(name, found.filePath, expanded, agentName)
|
|
249
|
+
return startForkedSkill(pi, ctx, { name, filePath: found.filePath, expanded, agentName })
|
|
222
250
|
}
|
|
223
251
|
return { action: 'transform', text: `<skill name="${name}" location="${found.filePath}">\nReferences are relative to ${found.baseDir}.\n\n${expanded}\n</skill>` }
|
|
224
252
|
}
|
|
@@ -114,12 +114,15 @@ function toNumber(value: string | undefined): number | undefined {
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
/** One rate-limit window from the `anthropic-ratelimit-<prefix>-*` header family,
|
|
117
|
-
* taking
|
|
118
|
-
* `-
|
|
119
|
-
*
|
|
117
|
+
* taking `-utilization` when present, else computing the percentage from `-limit` and
|
|
118
|
+
* `-remaining`. `-utilization` is a 0 to 1 fraction, while Claude documents
|
|
119
|
+
* used_percentage as "from 0 to 100": it is scaled and rounded to one decimal, as
|
|
120
|
+
* Claude Code does (Math.round(utilization * 1000) / 10). resets_at comes from `-reset`
|
|
121
|
+
* when the header is set. Header names vary, so only what is present is read. */
|
|
120
122
|
function readRateLimitWindow(headers: Record<string, string>, prefix: string): RateLimitWindow | undefined {
|
|
121
123
|
const base = `anthropic-ratelimit-${prefix}`
|
|
122
|
-
|
|
124
|
+
const utilization = toNumber(headers[`${base}-utilization`])
|
|
125
|
+
let usedPercentage = utilization === undefined ? undefined : Math.round(utilization * 1000) / 10
|
|
123
126
|
if (usedPercentage === undefined) {
|
|
124
127
|
const limit = toNumber(headers[`${base}-limit`])
|
|
125
128
|
const remaining = toNumber(headers[`${base}-remaining`])
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-code",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.68",
|
|
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, subagents, and goals",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi",
|