pi-ptc-subagents 3.3.1 → 3.3.2

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/CHANGELOG.md CHANGED
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.3.2] - 2026-10-18
9
+
10
+ ### Fixed
11
+
12
+ - **The agent roster and agent discovery now name the same agents.** A `.md` file whose frontmatter
13
+ carried no `name:` was advertised by the roster under its file name and refused by dispatch as an
14
+ unknown agent — in the same message, because the refusal renders that roster. A model told an
15
+ agent exists could not dispatch it, and the self-contradicting refusal made the real cause hard to
16
+ read. Identity is the frontmatter `name:` and nothing else, which is what pi's own subagent loader
17
+ does: it skips a definition whose frontmatter has no string `name`, and never derives identity
18
+ from the file name. The roster now applies the same predicate `discoverAgent` applies, so the
19
+ invariant — for one scope, the names the roster lists are the names a dispatch resolves — holds by
20
+ construction rather than by two patches that happen to agree. A file that exists but cannot be
21
+ read is likewise no longer listed under its file name; it still emits a `console.warn`, so that
22
+ failure remains observable. **Behaviour change:** a file relying on the file-name fallback stops
23
+ appearing in the roster, in the refusal's registry listing, and in the `ptc_subagent` tool
24
+ description. Add a frontmatter `name:` to keep it dispatchable.
25
+
8
26
  ## [3.3.1] - 2026-10-16
9
27
 
10
28
  ### Changed
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import{a as e,i as t,n,o as r,r as i,s as a,t as o}from"./protocol-CfOgxz3u.js";
6
6
 
7
7
  `+o}function ht(){return{text:``,status:`rejected`,started:!1,agentName:`unknown`,durationMs:0,exitCode:-1,reportChannel:`none`,errorMessage:`dispatch depth limit reached`}}function gt(){return{text:``,status:`rejected`,started:!1,agentName:`unknown`,durationMs:0,exitCode:-1,reportChannel:`none`,errorMessage:`dispatch concurrency limit reached`}}function _t(e,t,n={}){e.kill(t,`SIGTERM`);let r=setTimeout(()=>{n.isDone?.()!==!0&&e.kill(t,`SIGKILL`)},5e3);return n.unref===!0&&r.unref(),()=>clearTimeout(r)}function vt(){return{...ht(),errorMessage:`dispatch depth limit reached; next_step: call ptc_task_list to inspect the in-flight background tasks`}}function yt(){return{...gt(),errorMessage:`dispatch concurrency limit reached; next_step: call ptc_task_list to inspect running tasks and ptc_task_stop to free a slot`}}function bt(e){if(e!==void 0&&e.length!==0)return e.includes(`Response was truncated: the model reached its output token limit.`)?`child response was truncated at the model's output token limit (pi exited non-zero)`:void 0}function xt(e){if(e.aborted)return{status:`rejected`,errorMessage:`dispatch cancelled`};if(e.exitCode===0&&e.finalText.length>0)return{status:`fulfilled`};if(e.exitCode===0)return{status:`rejected`,errorMessage:`dispatch produced no final text`};let t=bt(e.stderr);return{status:`rejected`,...t===void 0?{}:{errorMessage:t}}}function St(e,t){let n=e.match(RegExp(`^`+t+`:\\s*(.+)$`,`m`));if(!n)return;let r=(n[1]??``).trim();if(r.length>=2){let e=r[0],t=r[r.length-1];(e===`"`&&t===`"`||e===`'`&&t===`'`)&&(r=r.slice(1,-1))}return r.length===0?void 0:r}function Ct(e,t){let n=e.match(RegExp(`^`+t+`:\\s*(.+)$`,`m`));if(!n)return;let r=(n[1]??``).trim();r.startsWith(`[`)&&r.endsWith(`]`)&&(r=r.slice(1,-1));let i=r.split(`,`).map(e=>e.trim()).filter(e=>e.length>0);return i.length===0?void 0:i}function wt(e){let t=St(e,`childReport`);if(t!==void 0)return t.toLowerCase()!==`false`&&t.toLowerCase()!==`no`}function Tt(e){return e.childReport!==!1}function Et(e){let t=e.match(/^\uFEFF?[ \t]*(?:\r?\n)?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n([\s\S]*))?$/);if(!t)return null;let n=(t[1]??``).replace(/\r\n/g,`
8
8
  `),r=(t[2]??``).replace(/\r\n/g,`
9
- `),i=St(n,`name`);if(!i)return null;let a=St(n,`description`)??``,o=St(n,`model`)??``,s=Ct(n,`tools`),c=wt(n),l={name:i,systemPrompt:r.trim()};return a!==void 0&&(l.description=a),o!==void 0&&(l.model=o),s!==void 0&&(l.tools=s),c!==void 0&&(l.childReport=c),l}function Dt(e,t,n,r){let{userDir:i,projectDir:a}=Ot({cwd:t,agentScope:n,homeDir:r?.homeDir}),o=[];(n===`user`||n===`both`)&&o.push({path:O.join(i,e+`.md`),source:`user`}),(n===`project`||n===`both`)&&o.push({path:O.join(a,e+`.md`),source:`project`});for(let t of o){let n;try{n=C.readFileSync(t.path,`utf-8`)}catch{continue}let r=Et(n);if(r&&r.name===e){let e={name:r.name,source:t.source,systemPrompt:r.systemPrompt};return r.model!==void 0&&(e.model=r.model),r.tools!==void 0&&(e.tools=r.tools),r.childReport!==void 0&&(e.childReport=r.childReport),e}}return null}function Ot(e){let t=e.homeDir??ne.homedir();return{userDir:O.join(t,`.pi`,`agent`,`agents`),projectDir:O.join(e.cwd,`.pi`,`agents`),userRoot:t,projectRoot:e.cwd}}function kt(e){let t=e?.code;return typeof t==`string`?t:String(e)}function At(e){try{return!C.statSync(e).isDirectory()}catch{try{return C.lstatSync(e),!0}catch{return!1}}}function jt(e,t){let n=O.resolve(e),r=O.resolve(t),i=e=>O.dirname(e),a=n,o=!1;for(let e=0;e<=8;e+=1){try{if(C.statSync(a).isDirectory()){o=!0;break}}catch{}if(a===r)break;let e=i(a);if(e===a)break;a=e}if(o){for(let e=n;e!==a&&e!==i(e);e=i(e))if(Mt(e))return!0;return!1}return a===r&&Mt(r)}function Mt(e){try{return C.lstatSync(e).isSymbolicLink()}catch{return!1}}function Nt(e,t,n){let r=kt(n);return r!==`ENOENT`&&r!==`ENOTDIR`||At(e)?!1:!jt(e,t)}function Pt(e){let{userDir:t,projectDir:n,userRoot:r,projectRoot:i}=Ot(e),a=[];(e.agentScope===`user`||e.agentScope===`both`)&&a.push({dir:t,boundary:r,source:`user`}),(e.agentScope===`project`||e.agentScope===`both`)&&a.push({dir:n,boundary:i,source:`project`});let o=/* @__PURE__ */ new Map;for(let{dir:e,boundary:t,source:n}of a){let r;try{r=C.readdirSync(e)}catch(n){if(!Nt(e,t,n)){let t=kt(n);console.warn(`pi.dispatch: cannot read agent directory ${e} (${t}); the agents registered there are not listed`)}continue}for(let t of r){if(!t.endsWith(`.md`))continue;let r=t.slice(0,-3),i=O.join(e,t),a;try{a=C.readFileSync(i,`utf-8`)}catch(e){let t=e instanceof Error?e.message:String(e);console.warn(`pi.dispatch: cannot read agent file ${i}: ${t}; falling back to the filename`),o.has(r)||o.set(r,{name:r,description:``,source:n});continue}let s=Et(a),c=s?.name??r;c.length!==0&&(o.has(c)||o.set(c,{name:c,description:(s?.description??``).trim(),source:n}))}}return[...o.values()]}function Ft(e){return Pt(e).map(e=>e.name).sort()}function It(e){let t=Pt(e),n="`agent` is optional: omit it for a fresh generalist pi with pi's own system prompt and its full toolset. Name a definition to NARROW the child instead.";if(t.length===0)return n+` None are registered here, so there is nothing to name.`;let r=t.map(e=>{let t=e.description.length>0?e.description:`(no description given)`;return`- ${e.name} (${e.source} scope): ${t}`});return n+"\nRegistered definitions — pass `agentScope` matching the scope shown, since it defaults "+`to "user" and does not see the others:
9
+ `),i=St(n,`name`);if(!i)return null;let a=St(n,`description`)??``,o=St(n,`model`)??``,s=Ct(n,`tools`),c=wt(n),l={name:i,systemPrompt:r.trim()};return a!==void 0&&(l.description=a),o!==void 0&&(l.model=o),s!==void 0&&(l.tools=s),c!==void 0&&(l.childReport=c),l}function Dt(e,t,n,r){let{userDir:i,projectDir:a}=Ot({cwd:t,agentScope:n,homeDir:r?.homeDir}),o=[];(n===`user`||n===`both`)&&o.push({path:O.join(i,e+`.md`),source:`user`}),(n===`project`||n===`both`)&&o.push({path:O.join(a,e+`.md`),source:`project`});for(let t of o){let n;try{n=C.readFileSync(t.path,`utf-8`)}catch{continue}let r=Et(n);if(r&&r.name===e){let e={name:r.name,source:t.source,systemPrompt:r.systemPrompt};return r.model!==void 0&&(e.model=r.model),r.tools!==void 0&&(e.tools=r.tools),r.childReport!==void 0&&(e.childReport=r.childReport),e}}return null}function Ot(e){let t=e.homeDir??ne.homedir();return{userDir:O.join(t,`.pi`,`agent`,`agents`),projectDir:O.join(e.cwd,`.pi`,`agents`),userRoot:t,projectRoot:e.cwd}}function kt(e){let t=e?.code;return typeof t==`string`?t:String(e)}function At(e){try{return!C.statSync(e).isDirectory()}catch{try{return C.lstatSync(e),!0}catch{return!1}}}function jt(e,t){let n=O.resolve(e),r=O.resolve(t),i=e=>O.dirname(e),a=n,o=!1;for(let e=0;e<=8;e+=1){try{if(C.statSync(a).isDirectory()){o=!0;break}}catch{}if(a===r)break;let e=i(a);if(e===a)break;a=e}if(o){for(let e=n;e!==a&&e!==i(e);e=i(e))if(Mt(e))return!0;return!1}return a===r&&Mt(r)}function Mt(e){try{return C.lstatSync(e).isSymbolicLink()}catch{return!1}}function Nt(e,t,n){let r=kt(n);return r!==`ENOENT`&&r!==`ENOTDIR`||At(e)?!1:!jt(e,t)}function Pt(e){let{userDir:t,projectDir:n,userRoot:r,projectRoot:i}=Ot(e),a=[];(e.agentScope===`user`||e.agentScope===`both`)&&a.push({dir:t,boundary:r,source:`user`}),(e.agentScope===`project`||e.agentScope===`both`)&&a.push({dir:n,boundary:i,source:`project`});let o=/* @__PURE__ */ new Map;for(let{dir:e,boundary:t,source:n}of a){let r;try{r=C.readdirSync(e)}catch(n){if(!Nt(e,t,n)){let t=kt(n);console.warn(`pi.dispatch: cannot read agent directory ${e} (${t}); the agents registered there are not listed`)}continue}for(let t of r){if(!t.endsWith(`.md`))continue;let r=O.join(e,t),i;try{i=C.readFileSync(r,`utf-8`)}catch(e){let t=e instanceof Error?e.message:String(e);console.warn(`pi.dispatch: cannot read agent file ${r}: ${t}; skipping it`);continue}let a=Et(i);if(!a)continue;let s=a.name;s.length!==0&&(o.has(s)||o.set(s,{name:s,description:(a?.description??``).trim(),source:n}))}}return[...o.values()]}function Ft(e){return Pt(e).map(e=>e.name).sort()}function It(e){let t=Pt(e),n="`agent` is optional: omit it for a fresh generalist pi with pi's own system prompt and its full toolset. Name a definition to NARROW the child instead.";if(t.length===0)return n+` None are registered here, so there is nothing to name.`;let r=t.map(e=>{let t=e.description.length>0?e.description:`(no description given)`;return`- ${e.name} (${e.source} scope): ${t}`});return n+"\nRegistered definitions — pass `agentScope` matching the scope shown, since it defaults "+`to "user" and does not see the others:
10
10
  `+r.join(`
11
11
  `)}function Lt(e){let{userDir:t,projectDir:n}=Ot(e),r=Ft(e);return`registered agents: ${r.length>0?`[`+r.join(`, `)+`]`:`(none)`} — agents load from ${t}/<name>.md (user scope) or ${n}/<name>.md (project scope); register one as an agent markdown file: frontmatter header (\`---\\nname: <agent-name>\\n---\`) followed by the system prompt body`}function Rt(e){return{text:``,status:`rejected`,started:!1,agentName:``,durationMs:0,exitCode:1,reportChannel:`none`,errorMessage:`agent, when given, must be a non-empty string — omit it entirely to dispatch a generalist child; `+Lt(e)}}function zt(e){return e!==void 0&&(typeof e!=`string`||e.trim().length===0)}function Bt(e){return e===void 0}function Vt(e,t,n){if(Bt(e.agent))return{kind:`resolved`,agent:pt};let r=Dt(e.agent,t,n);return r?{kind:`resolved`,agent:r}:{kind:`unknown`,name:e.agent}}function Ht(e,t,n,r=0){return{text:``,status:`rejected`,started:!1,agentName:e,durationMs:r,exitCode:1,reportChannel:`none`,errorMessage:`unknown agent: `+e+` (agentScope=`+t+`, cwd=`+n+`). `+Lt({cwd:n,agentScope:t})}}function Ut(e,t){if(R(t)===`strict`)return Tt(e)?[...ut,Le]:ut;let n=e.tools??[];if(!Tt(e))return n.length>0?n:void 0;if(n.length!==0)return n.includes(`ptc_child_report`)?n:[...n,Le]}function Wt(e,t,n){let r=[`--mode`,`json`,`-p`,`--no-session`],i=e.model??t.model;i&&r.push(`--model`,i);let a=e.thinkingLevel??`off`;r.push(`--thinking`,a);let o=Ut(t,e.readOnly);return o!==void 0&&r.push(`--tools`,o.join(`,`)),r.push(`--append-system-prompt`,n),r.push(`Task: `+e.task),r}let Gt=Kt;async function Kt(e,t){let n=await C.promises.mkdtemp(O.join(ne.tmpdir(),`pi-dispatch-`)),r=e.replace(/[^\w.-]+/g,`_`),i=O.join(n,`prompt-`+r+`.md`);return await C.promises.writeFile(i,t,{encoding:`utf-8`,mode:384}),{dir:n,filePath:i}}function qt(e){try{C.unlinkSync(e.filePath)}catch{}try{C.rmdirSync(e.dir)}catch{}}function Jt(e){if(e.type!==`message_end`||e.message?.role!==`assistant`)return;let t=e.message.content;if(!Array.isArray(t))return;let n;for(let e of t)e&&e.type===`text`&&typeof e.text==`string`&&(n=e.text);return n}function Yt(e,t){if(t.type!==`message_end`)return e;let n=t.message;if(n?.role!==`assistant`||n.usage===void 0)return e;let r=e??{input:0,output:0,cacheRead:0,cacheWrite:0,cost:0,turns:0};return r.input+=n.usage.input??0,r.output+=n.usage.output??0,r.cacheRead+=n.usage.cacheRead??0,r.cacheWrite+=n.usage.cacheWrite??0,r.cost+=n.usage.cost?.total??0,r.turns+=1,r}function Xt(e){let t=[],n;for(let r of e.split(`
12
12
  `)){let e=r.trim();if(n===void 0){let t=/^(`{3,}|~{3,})(.*)$/.exec(e);t&&(n={marker:(t[1]??``)[0],info:(t[2]??``).trim(),body:[]});continue}RegExp(`^`+n.marker+`{3,}\\s*$`).test(e)?(t.push({info:n.info,body:n.body.join(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ptc-subagents",
3
- "version": "3.3.1",
3
+ "version": "3.3.2",
4
4
  "description": "Programmable tool calling (PTC) and subagent fan-out for pi — the model writes JS/TS programs that call pi's tools, or fans out to a fresh pi subprocess per subagent task",
5
5
  "keywords": [
6
6
  "pi",