@oyasmi/pipiclaw 0.3.1 → 0.3.3

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.
Files changed (57) hide show
  1. package/README.md +55 -0
  2. package/dist/agent.d.ts +1 -1
  3. package/dist/agent.d.ts.map +1 -1
  4. package/dist/agent.js +67 -9
  5. package/dist/agent.js.map +1 -1
  6. package/dist/dingtalk.d.ts +1 -1
  7. package/dist/dingtalk.d.ts.map +1 -1
  8. package/dist/dingtalk.js +26 -4
  9. package/dist/dingtalk.js.map +1 -1
  10. package/dist/events.d.ts.map +1 -1
  11. package/dist/events.js +11 -0
  12. package/dist/events.js.map +1 -1
  13. package/dist/main.d.ts.map +1 -1
  14. package/dist/main.js +106 -39
  15. package/dist/main.js.map +1 -1
  16. package/dist/paths.d.ts +2 -0
  17. package/dist/paths.d.ts.map +1 -1
  18. package/dist/paths.js +2 -0
  19. package/dist/paths.js.map +1 -1
  20. package/dist/prompt-builder.d.ts +4 -1
  21. package/dist/prompt-builder.d.ts.map +1 -1
  22. package/dist/prompt-builder.js +30 -1
  23. package/dist/prompt-builder.js.map +1 -1
  24. package/dist/sandbox.d.ts +1 -0
  25. package/dist/sandbox.d.ts.map +1 -1
  26. package/dist/sandbox.js +56 -13
  27. package/dist/sandbox.js.map +1 -1
  28. package/dist/store.d.ts.map +1 -1
  29. package/dist/store.js +47 -7
  30. package/dist/store.js.map +1 -1
  31. package/dist/sub-agents.d.ts +47 -0
  32. package/dist/sub-agents.d.ts.map +1 -0
  33. package/dist/sub-agents.js +228 -0
  34. package/dist/sub-agents.js.map +1 -0
  35. package/dist/tools/bash.d.ts +4 -1
  36. package/dist/tools/bash.d.ts.map +1 -1
  37. package/dist/tools/bash.js +3 -2
  38. package/dist/tools/bash.js.map +1 -1
  39. package/dist/tools/edit.d.ts.map +1 -1
  40. package/dist/tools/edit.js +2 -6
  41. package/dist/tools/edit.js.map +1 -1
  42. package/dist/tools/index.d.ts +10 -1
  43. package/dist/tools/index.d.ts.map +1 -1
  44. package/dist/tools/index.js +15 -1
  45. package/dist/tools/index.js.map +1 -1
  46. package/dist/tools/subagent.d.ts +52 -0
  47. package/dist/tools/subagent.d.ts.map +1 -0
  48. package/dist/tools/subagent.js +245 -0
  49. package/dist/tools/subagent.js.map +1 -0
  50. package/dist/tools/write-content.d.ts +5 -0
  51. package/dist/tools/write-content.d.ts.map +1 -0
  52. package/dist/tools/write-content.js +30 -0
  53. package/dist/tools/write-content.js.map +1 -0
  54. package/dist/tools/write.d.ts.map +1 -1
  55. package/dist/tools/write.js +2 -10
  56. package/dist/tools/write.js.map +1 -1
  57. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sub-agents.js","sourceRoot":"","sources":["../src/sub-agents.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,4BAA4B,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,uBAAuB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAU,CAAC;AAC3E,MAAM,uBAAuB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAU,CAAC;AAC1D,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAC7B,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAClC,MAAM,yBAAyB,GAAG,GAAG,CAAC;AACtC,MAAM,wBAAwB,GAAG,GAAG,CAAC;AACrC,MAAM,wBAAwB,GAAG,KAAK,CAAC;AACvC,MAAM,8BAA8B,GAAG,KAAK,CAAC;AAqC7C,SAAS,kBAAkB,CAAC,KAAa,EAAE,QAAgB,EAAE,KAAa,EAAsB;IAC/F,IAAI,KAAK,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC9B,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,OAAO,GAAG,KAAK,YAAY,QAAQ,oBAAoB,KAAK,CAAC,MAAM,IAAI,CAAC;AAAA,CACxE;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAY,EAAsB;IACtE,OAAO,kBAAkB,CAAC,IAAI,EAAE,wBAAwB,EAAE,gBAAgB,CAAC,CAAC;AAAA,CAC5E;AAED,MAAM,UAAU,eAAe,CAAC,YAAoB,EAAU;IAC7D,OAAO,IAAI,CAAC,YAAY,EAAE,mBAAmB,CAAC,CAAC;AAAA,CAC/C;AAED,SAAS,cAAc,CAAC,GAAuB,EAAiD;IAC/F,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;QACzB,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,uBAAuB,CAAC,EAAE,CAAC;IAChD,CAAC;IAED,MAAM,MAAM,GAAG,GAAG;SAChB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;SAC5B,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAEtC,OAAO,iBAAiB,CAAC,MAAM,CAAC,CAAC;AAAA,CACjC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAA4B,EAAiD;IAC9G,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpC,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,uBAAuB,CAAC,EAAE,CAAC;IAChD,CAAC;IAED,MAAM,KAAK,GAAuB,EAAE,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAChC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YACzC,SAAS;QACV,CAAC;QACD,IAAI,CAAC,uBAAuB,CAAC,QAAQ,CAAC,UAA8B,CAAC,EAAE,CAAC;YACvE,OAAO;gBACN,KAAK,EAAE,EAAE;gBACT,KAAK,EAAE,iBAAiB,UAAU,qBAAqB,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;aAC3F,CAAC;QACH,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACrB,KAAK,CAAC,IAAI,CAAC,UAA8B,CAAC,CAAC;IAC5C,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,uBAAuB,CAAC,EAAE,CAAC;AAAA,CAC1E;AAED,SAAS,oBAAoB,CAAC,GAAuB,EAAE,QAAgB,EAAuC;IAC7G,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;QACzB,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IAC5B,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;QAC7C,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,0BAA0B,GAAG,oBAAoB,QAAQ,EAAE,EAAE,CAAC;IAClG,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAAA,CACzB;AAED,SAAS,uBAAuB,CAAC,KAAyB,EAAE,QAAgB,EAAU;IACrF,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QAClE,OAAO,QAAQ,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AAAA,CACzB;AAED,SAAS,qBAAqB,CAC7B,QAAgB,EAChB,eAA6B,EACY;IACzC,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,4BAA4B,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;IACrF,IAAI,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACzB,CAAC;IACD,IAAI,SAAS,EAAE,CAAC;QACf,OAAO,EAAE,KAAK,EAAE,oBAAoB,QAAQ,uCAAuC,EAAE,CAAC;IACvF,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,oBAAoB,QAAQ,yCAAyC,EAAE,CAAC;AAAA,CACxF;AAED,MAAM,UAAU,iBAAiB,CAAC,YAAoB,EAAE,eAA6B,EAA2B;IAC/G,MAAM,SAAS,GAAG,eAAe,CAAC,YAAY,CAAC,CAAC;IAChD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAChD,CAAC;IAED,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,MAAM,GAAqB,EAAE,CAAC;IACpC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,MAAM,OAAO,GAAG,WAAW,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;SAC7D,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,cAAc,EAAE,CAAC,CAAC;SAC3F,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAE/C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,OAAO,GAAG,EAAE,CAAC;QACjB,IAAI,CAAC;YACJ,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC3C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,QAAQ,CAAC,IAAI,CACZ,GAAG,KAAK,CAAC,IAAI,0BAA0B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAChG,CAAC;YACF,SAAS;QACV,CAAC;QAED,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAyB,OAAO,CAAC,CAAC;QAChF,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;QACtC,MAAM,WAAW,GAAG,WAAW,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;QAEpD,IAAI,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YAC3B,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,+DAA+D,CAAC,CAAC;YAC5F,SAAS;QACV,CAAC;QAED,IAAI,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,+BAA+B,IAAI,WAAW,CAAC,CAAC;YAC3E,SAAS;QACV,CAAC;QAED,MAAM,SAAS,GAAG,cAAc,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACpD,IAAI,SAAS,CAAC,KAAK,EAAE,CAAC;YACrB,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC;YACnD,SAAS;QACV,CAAC;QAED,MAAM,QAAQ,GAAG,oBAAoB,CAAC,WAAW,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;QAC/E,MAAM,YAAY,GAAG,oBAAoB,CAAC,WAAW,CAAC,YAAY,EAAE,sBAAsB,CAAC,CAAC;QAC5F,MAAM,cAAc,GAAG,oBAAoB,CAAC,WAAW,CAAC,cAAc,EAAE,yBAAyB,CAAC,CAAC;QACnG,MAAM,cAAc,GAAG,oBAAoB,CAAC,WAAW,CAAC,cAAc,EAAE,wBAAwB,CAAC,CAAC;QAElG,KAAK,MAAM,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC,OAAO,EAAE,cAAc,CAAC,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;YAChH,IAAI,OAAO,EAAE,CAAC;gBACb,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC;YAC5C,CAAC;QACF,CAAC;QAED,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC;QAC3C,IAAI,KAA6B,CAAC;QAClC,IAAI,QAAQ,EAAE,CAAC;YACd,MAAM,QAAQ,GAAG,qBAAqB,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;YAClE,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;gBACrB,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;gBAClD,SAAS;YACV,CAAC;YACD,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC;QACxB,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YAClB,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,4BAA4B,CAAC,CAAC;YACzD,SAAS;QACV,CAAC;QAED,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpB,MAAM,CAAC,IAAI,CAAC;YACX,IAAI;YACJ,WAAW;YACX,YAAY,EAAE,IAAI;YAClB,KAAK,EAAE,SAAS,CAAC,KAAK;YACtB,KAAK;YACL,QAAQ,EAAE,QAAQ,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YACvE,QAAQ,EAAE,QAAQ,CAAC,KAAK;YACxB,YAAY,EAAE,YAAY,CAAC,KAAK;YAChC,cAAc,EAAE,cAAc,CAAC,KAAK;YACpC,cAAc,EAAE,cAAc,CAAC,KAAK;YACpC,QAAQ;YACR,MAAM,EAAE,YAAY;SACpB,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAAA,CACvC;AAED,MAAM,UAAU,qBAAqB,CACpC,eAA6B,EAC7B,YAAwB,EACxB,gBAAkC,EAClC,SAAsC,EACQ;IAC9C,MAAM,UAAU,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAClH,IAAI,SAAS,CAAC,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,gBAAgB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QAChH,OAAO,EAAE,KAAK,EAAE,sBAAsB,SAAS,CAAC,KAAK,4BAA4B,SAAS,GAAG,EAAE,CAAC;IACjG,CAAC;IAED,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC,SAAS,CAAC,YAAY,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;QAChF,OAAO,EAAE,KAAK,EAAE,mEAAmE,EAAE,CAAC;IACvF,CAAC;IAED,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK;QAC5B,CAAC,CAAC,iBAAiB,CAAC,SAAS,CAAC,KAAK,CAAC;QACpC,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC,GAAG,uBAAuB,CAAC,EAAE,CAAC;IAChE,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;IAC/B,CAAC;IAED,IAAI,KAAK,GAAG,UAAU,EAAE,KAAK,CAAC;IAC9B,IAAI,QAAQ,GAAG,UAAU,EAAE,QAAQ,CAAC;IACpC,IAAI,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,qBAAqB,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,eAAe,CAAC,CAAC;QAChF,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACrB,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC;QAClC,CAAC;QACD,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC;QACvB,QAAQ,GAAG,oBAAoB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACjD,CAAC;IAED,MAAM,QAAQ,GAAG,uBAAuB,CAAC,SAAS,CAAC,QAAQ,EAAE,UAAU,EAAE,QAAQ,IAAI,iBAAiB,CAAC,CAAC;IACxG,MAAM,YAAY,GAAG,uBAAuB,CAC3C,SAAS,CAAC,YAAY,EACtB,UAAU,EAAE,YAAY,IAAI,sBAAsB,CAClD,CAAC;IACF,MAAM,cAAc,GAAG,uBAAuB,CAC7C,SAAS,CAAC,cAAc,EACxB,UAAU,EAAE,cAAc,IAAI,yBAAyB,CACvD,CAAC;IACF,MAAM,cAAc,GAAG,uBAAuB,CAC7C,SAAS,CAAC,cAAc,EACxB,UAAU,EAAE,cAAc,IAAI,wBAAwB,CACtD,CAAC;IAEF,MAAM,YAAY,GAAG,SAAS,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,UAAU,EAAE,YAAY,IAAI,EAAE,CAAC;IACtF,IAAI,CAAC,YAAY,EAAE,CAAC;QACnB,OAAO,EAAE,KAAK,EAAE,0CAA0C,EAAE,CAAC;IAC9D,CAAC;IACD,IAAI,SAAS,CAAC,YAAY,EAAE,IAAI,EAAE,EAAE,CAAC;QACpC,MAAM,iBAAiB,GAAG,kBAAkB,CAC3C,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,EAC7B,8BAA8B,EAC9B,+BAA+B,CAC/B,CAAC;QACF,IAAI,iBAAiB,EAAE,CAAC;YACvB,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC;QACrC,CAAC;IACF,CAAC;IAED,OAAO;QACN,MAAM,EAAE;YACP,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,UAAU,EAAE,IAAI,IAAI,kBAAkB;YACtE,WAAW,EAAE,UAAU,EAAE,WAAW,IAAI,kBAAkB;YAC1D,YAAY;YACZ,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,KAAK,EAAE,KAAK,IAAI,YAAY;YAC5B,QAAQ,EAAE,QAAQ,IAAI,oBAAoB,CAAC,KAAK,IAAI,YAAY,CAAC;YACjE,QAAQ;YACR,YAAY;YACZ,cAAc;YACd,cAAc;YACd,QAAQ,EAAE,UAAU,EAAE,QAAQ;YAC9B,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ;SAC5C;KACD,CAAC;AAAA,CACF;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAwB,EAAE,QAAQ,GAAW,EAAE,EAAU;IAC3F,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,MAAM,CAAC;IACf,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;IACrG,IAAI,MAAM,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC/B,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,MAAM,CAAC,MAAM,GAAG,QAAQ,OAAO,CAAC;AAAA,CAC1E","sourcesContent":["import type { Api, Model } from \"@mariozechner/pi-ai\";\nimport { parseFrontmatter } from \"@mariozechner/pi-coding-agent\";\nimport { existsSync, readdirSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { findExactModelReferenceMatch, formatModelReference } from \"./model-utils.js\";\nimport { SUB_AGENTS_DIR_NAME } from \"./paths.js\";\n\nconst ALLOWED_SUB_AGENT_TOOLS = [\"read\", \"bash\", \"edit\", \"write\"] as const;\nconst DEFAULT_SUB_AGENT_TOOLS = [\"read\", \"bash\"] as const;\nconst DEFAULT_MAX_TURNS = 24;\nconst DEFAULT_MAX_TOOL_CALLS = 48;\nconst DEFAULT_MAX_WALL_TIME_SEC = 300;\nconst DEFAULT_BASH_TIMEOUT_SEC = 120;\nconst MAX_SUB_AGENT_TASK_CHARS = 12000;\nconst MAX_INLINE_SYSTEM_PROMPT_CHARS = 16000;\n\nexport type SubAgentToolName = (typeof ALLOWED_SUB_AGENT_TOOLS)[number];\n\nexport interface SubAgentConfig {\n\tname: string;\n\tdescription: string;\n\tsystemPrompt: string;\n\ttools: SubAgentToolName[];\n\tmodel?: Model<Api>;\n\tmodelRef?: string;\n\tmaxTurns: number;\n\tmaxToolCalls: number;\n\tmaxWallTimeSec: number;\n\tbashTimeoutSec: number;\n\tfilePath?: string;\n\tsource: \"predefined\" | \"inline\";\n}\n\nexport interface SubAgentDiscoveryResult {\n\tdirectory: string;\n\tagents: SubAgentConfig[];\n\twarnings: string[];\n}\n\nexport interface SubAgentInvocationOverrides {\n\tagent?: string;\n\tname?: string;\n\tsystemPrompt?: string;\n\ttools?: string[];\n\tmodel?: string;\n\tmaxTurns?: number;\n\tmaxToolCalls?: number;\n\tmaxWallTimeSec?: number;\n\tbashTimeoutSec?: number;\n}\n\nfunction validateTextLength(value: string, maxChars: number, label: string): string | undefined {\n\tif (value.length <= maxChars) {\n\t\treturn undefined;\n\t}\n\treturn `${label} exceeds ${maxChars} characters (got ${value.length}).`;\n}\n\nexport function validateSubAgentTask(task: string): string | undefined {\n\treturn validateTextLength(task, MAX_SUB_AGENT_TASK_CHARS, \"Sub-agent task\");\n}\n\nexport function getSubAgentsDir(workspaceDir: string): string {\n\treturn join(workspaceDir, SUB_AGENTS_DIR_NAME);\n}\n\nfunction parseToolNames(raw: string | undefined): { tools: SubAgentToolName[]; error?: string } {\n\tif (!raw || !raw.trim()) {\n\t\treturn { tools: [...DEFAULT_SUB_AGENT_TOOLS] };\n\t}\n\n\tconst values = raw\n\t\t.split(\",\")\n\t\t.map((value) => value.trim())\n\t\t.filter((value) => value.length > 0);\n\n\treturn validateToolNames(values);\n}\n\nexport function validateToolNames(values: string[] | undefined): { tools: SubAgentToolName[]; error?: string } {\n\tif (!values || values.length === 0) {\n\t\treturn { tools: [...DEFAULT_SUB_AGENT_TOOLS] };\n\t}\n\n\tconst tools: SubAgentToolName[] = [];\n\tconst seen = new Set<string>();\n\tfor (const value of values) {\n\t\tconst normalized = value.trim();\n\t\tif (!normalized || seen.has(normalized)) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (!ALLOWED_SUB_AGENT_TOOLS.includes(normalized as SubAgentToolName)) {\n\t\t\treturn {\n\t\t\t\ttools: [],\n\t\t\t\terror: `Unknown tool \"${normalized}\". Allowed tools: ${ALLOWED_SUB_AGENT_TOOLS.join(\", \")}`,\n\t\t\t};\n\t\t}\n\t\tseen.add(normalized);\n\t\ttools.push(normalized as SubAgentToolName);\n\t}\n\n\treturn { tools: tools.length > 0 ? tools : [...DEFAULT_SUB_AGENT_TOOLS] };\n}\n\nfunction parsePositiveInteger(raw: string | undefined, fallback: number): { value: number; warning?: string } {\n\tif (!raw || !raw.trim()) {\n\t\treturn { value: fallback };\n\t}\n\n\tconst parsed = Number.parseInt(raw.trim(), 10);\n\tif (!Number.isFinite(parsed) || parsed <= 0) {\n\t\treturn { value: fallback, warning: `Invalid numeric value \"${raw}\", using default ${fallback}` };\n\t}\n\n\treturn { value: parsed };\n}\n\nfunction resolvePositiveOverride(value: number | undefined, fallback: number): number {\n\tif (!Number.isFinite(value) || value === undefined || value <= 0) {\n\t\treturn fallback;\n\t}\n\treturn Math.floor(value);\n}\n\nfunction resolveModelReference(\n\tmodelRef: string,\n\tavailableModels: Model<Api>[],\n): { model?: Model<Api>; error?: string } {\n\tconst { match, ambiguous } = findExactModelReferenceMatch(modelRef, availableModels);\n\tif (match) {\n\t\treturn { model: match };\n\t}\n\tif (ambiguous) {\n\t\treturn { error: `Model reference \"${modelRef}\" is ambiguous. Use provider/modelId.` };\n\t}\n\treturn { error: `Model reference \"${modelRef}\" was not found among available models.` };\n}\n\nexport function discoverSubAgents(workspaceDir: string, availableModels: Model<Api>[]): SubAgentDiscoveryResult {\n\tconst directory = getSubAgentsDir(workspaceDir);\n\tif (!existsSync(directory)) {\n\t\treturn { directory, agents: [], warnings: [] };\n\t}\n\n\tconst warnings: string[] = [];\n\tconst agents: SubAgentConfig[] = [];\n\tconst seenNames = new Set<string>();\n\tconst entries = readdirSync(directory, { withFileTypes: true })\n\t\t.filter((entry) => entry.name.endsWith(\".md\") && (entry.isFile() || entry.isSymbolicLink()))\n\t\t.sort((a, b) => a.name.localeCompare(b.name));\n\n\tfor (const entry of entries) {\n\t\tconst filePath = join(directory, entry.name);\n\t\tlet content = \"\";\n\t\ttry {\n\t\t\tcontent = readFileSync(filePath, \"utf-8\");\n\t\t} catch (error) {\n\t\t\twarnings.push(\n\t\t\t\t`${entry.name}: failed to read file (${error instanceof Error ? error.message : String(error)})`,\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst { frontmatter, body } = parseFrontmatter<Record<string, string>>(content);\n\t\tconst name = frontmatter.name?.trim();\n\t\tconst description = frontmatter.description?.trim();\n\n\t\tif (!name || !description) {\n\t\t\twarnings.push(`${entry.name}: missing required frontmatter fields \"name\" or \"description\"`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (seenNames.has(name)) {\n\t\t\twarnings.push(`${entry.name}: duplicate sub-agent name \"${name}\" ignored`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst toolParse = parseToolNames(frontmatter.tools);\n\t\tif (toolParse.error) {\n\t\t\twarnings.push(`${entry.name}: ${toolParse.error}`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst maxTurns = parsePositiveInteger(frontmatter.maxTurns, DEFAULT_MAX_TURNS);\n\t\tconst maxToolCalls = parsePositiveInteger(frontmatter.maxToolCalls, DEFAULT_MAX_TOOL_CALLS);\n\t\tconst maxWallTimeSec = parsePositiveInteger(frontmatter.maxWallTimeSec, DEFAULT_MAX_WALL_TIME_SEC);\n\t\tconst bashTimeoutSec = parsePositiveInteger(frontmatter.bashTimeoutSec, DEFAULT_BASH_TIMEOUT_SEC);\n\n\t\tfor (const warning of [maxTurns.warning, maxToolCalls.warning, maxWallTimeSec.warning, bashTimeoutSec.warning]) {\n\t\t\tif (warning) {\n\t\t\t\twarnings.push(`${entry.name}: ${warning}`);\n\t\t\t}\n\t\t}\n\n\t\tconst modelRef = frontmatter.model?.trim();\n\t\tlet model: Model<Api> | undefined;\n\t\tif (modelRef) {\n\t\t\tconst resolved = resolveModelReference(modelRef, availableModels);\n\t\t\tif (!resolved.model) {\n\t\t\t\twarnings.push(`${entry.name}: ${resolved.error}`);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tmodel = resolved.model;\n\t\t}\n\n\t\tif (!body.trim()) {\n\t\t\twarnings.push(`${entry.name}: empty system prompt body`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tseenNames.add(name);\n\t\tagents.push({\n\t\t\tname,\n\t\t\tdescription,\n\t\t\tsystemPrompt: body,\n\t\t\ttools: toolParse.tools,\n\t\t\tmodel,\n\t\t\tmodelRef: modelRef || (model ? formatModelReference(model) : undefined),\n\t\t\tmaxTurns: maxTurns.value,\n\t\t\tmaxToolCalls: maxToolCalls.value,\n\t\t\tmaxWallTimeSec: maxWallTimeSec.value,\n\t\t\tbashTimeoutSec: bashTimeoutSec.value,\n\t\t\tfilePath,\n\t\t\tsource: \"predefined\",\n\t\t});\n\t}\n\n\treturn { directory, agents, warnings };\n}\n\nexport function resolveSubAgentConfig(\n\tavailableModels: Model<Api>[],\n\tcurrentModel: Model<Api>,\n\tpredefinedAgents: SubAgentConfig[],\n\toverrides: SubAgentInvocationOverrides,\n): { config?: SubAgentConfig; error?: string } {\n\tconst baseConfig = overrides.agent ? predefinedAgents.find((agent) => agent.name === overrides.agent) : undefined;\n\tif (overrides.agent && !baseConfig) {\n\t\tconst available = predefinedAgents.length > 0 ? predefinedAgents.map((agent) => agent.name).join(\", \") : \"none\";\n\t\treturn { error: `Unknown sub-agent \"${overrides.agent}\". Available sub-agents: ${available}.` };\n\t}\n\n\tif (!baseConfig && (!overrides.systemPrompt || !overrides.systemPrompt.trim())) {\n\t\treturn { error: 'Provide either \"agent\" or \"systemPrompt\" to define the sub-agent.' };\n\t}\n\n\tconst tools = overrides.tools\n\t\t? validateToolNames(overrides.tools)\n\t\t: { tools: baseConfig?.tools ?? [...DEFAULT_SUB_AGENT_TOOLS] };\n\tif (tools.error) {\n\t\treturn { error: tools.error };\n\t}\n\n\tlet model = baseConfig?.model;\n\tlet modelRef = baseConfig?.modelRef;\n\tif (overrides.model?.trim()) {\n\t\tconst resolved = resolveModelReference(overrides.model.trim(), availableModels);\n\t\tif (!resolved.model) {\n\t\t\treturn { error: resolved.error };\n\t\t}\n\t\tmodel = resolved.model;\n\t\tmodelRef = formatModelReference(resolved.model);\n\t}\n\n\tconst maxTurns = resolvePositiveOverride(overrides.maxTurns, baseConfig?.maxTurns ?? DEFAULT_MAX_TURNS);\n\tconst maxToolCalls = resolvePositiveOverride(\n\t\toverrides.maxToolCalls,\n\t\tbaseConfig?.maxToolCalls ?? DEFAULT_MAX_TOOL_CALLS,\n\t);\n\tconst maxWallTimeSec = resolvePositiveOverride(\n\t\toverrides.maxWallTimeSec,\n\t\tbaseConfig?.maxWallTimeSec ?? DEFAULT_MAX_WALL_TIME_SEC,\n\t);\n\tconst bashTimeoutSec = resolvePositiveOverride(\n\t\toverrides.bashTimeoutSec,\n\t\tbaseConfig?.bashTimeoutSec ?? DEFAULT_BASH_TIMEOUT_SEC,\n\t);\n\n\tconst systemPrompt = overrides.systemPrompt?.trim() || baseConfig?.systemPrompt || \"\";\n\tif (!systemPrompt) {\n\t\treturn { error: \"Sub-agent system prompt cannot be empty.\" };\n\t}\n\tif (overrides.systemPrompt?.trim()) {\n\t\tconst promptLengthError = validateTextLength(\n\t\t\toverrides.systemPrompt.trim(),\n\t\t\tMAX_INLINE_SYSTEM_PROMPT_CHARS,\n\t\t\t\"Inline sub-agent systemPrompt\",\n\t\t);\n\t\tif (promptLengthError) {\n\t\t\treturn { error: promptLengthError };\n\t\t}\n\t}\n\n\treturn {\n\t\tconfig: {\n\t\t\tname: overrides.name?.trim() || baseConfig?.name || \"dynamic-subagent\",\n\t\t\tdescription: baseConfig?.description || \"Inline sub-agent\",\n\t\t\tsystemPrompt,\n\t\t\ttools: tools.tools,\n\t\t\tmodel: model ?? currentModel,\n\t\t\tmodelRef: modelRef ?? formatModelReference(model ?? currentModel),\n\t\t\tmaxTurns,\n\t\t\tmaxToolCalls,\n\t\t\tmaxWallTimeSec,\n\t\t\tbashTimeoutSec,\n\t\t\tfilePath: baseConfig?.filePath,\n\t\t\tsource: baseConfig ? \"predefined\" : \"inline\",\n\t\t},\n\t};\n}\n\nexport function formatSubAgentList(agents: SubAgentConfig[], maxItems: number = 12): string {\n\tif (agents.length === 0) {\n\t\treturn \"none\";\n\t}\n\n\tconst listed = agents.slice(0, maxItems).map((agent) => `- \\`${agent.name}\\`: ${agent.description}`);\n\tif (agents.length <= maxItems) {\n\t\treturn listed.join(\"\\n\");\n\t}\n\n\treturn `${listed.join(\"\\n\")}\\n- ... and ${agents.length - maxItems} more`;\n}\n"]}
@@ -5,6 +5,9 @@ declare const bashSchema: import("@sinclair/typebox").TObject<{
5
5
  command: import("@sinclair/typebox").TString;
6
6
  timeout: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
7
7
  }>;
8
- export declare function createBashTool(executor: Executor): AgentTool<typeof bashSchema>;
8
+ export interface BashToolOptions {
9
+ defaultTimeoutSeconds?: number;
10
+ }
11
+ export declare function createBashTool(executor: Executor, options?: BashToolOptions): AgentTool<typeof bashSchema>;
9
12
  export {};
10
13
  //# sourceMappingURL=bash.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAE7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAW9C,QAAA,MAAM,UAAU;;;;EAId,CAAC;AAOH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAoE/E","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { createWriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport type { Executor } from \"../sandbox.js\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateTail } from \"./truncate.js\";\n\n/**\n * Generate a unique temp file path for bash output\n */\nfunction getTempFilePath(): string {\n\tconst id = randomBytes(8).toString(\"hex\");\n\treturn join(tmpdir(), `pipiclaw-bash-${id}.log`);\n}\n\nconst bashSchema = Type.Object({\n\tlabel: Type.String({ description: \"Brief description of what this command does (shown to user)\" }),\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n});\n\ninterface BashToolDetails {\n\ttruncation?: TruncationResult;\n\tfullOutputPath?: string;\n}\n\nexport function createBashTool(executor: Executor): AgentTool<typeof bashSchema> {\n\treturn {\n\t\tname: \"bash\",\n\t\tlabel: \"bash\",\n\t\tdescription: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,\n\t\tparameters: bashSchema,\n\t\texecute: async (\n\t\t\t_toolCallId: string,\n\t\t\t{ command, timeout }: { label: string; command: string; timeout?: number },\n\t\t\tsignal?: AbortSignal,\n\t\t) => {\n\t\t\t// Track output for potential temp file writing\n\t\t\tlet tempFilePath: string | undefined;\n\t\t\tlet tempFileStream: ReturnType<typeof createWriteStream> | undefined;\n\n\t\t\tconst result = await executor.exec(command, { timeout, signal });\n\t\t\tlet output = \"\";\n\t\t\tif (result.stdout) output += result.stdout;\n\t\t\tif (result.stderr) {\n\t\t\t\tif (output) output += \"\\n\";\n\t\t\t\toutput += result.stderr;\n\t\t\t}\n\n\t\t\tconst totalBytes = Buffer.byteLength(output, \"utf-8\");\n\n\t\t\t// Write to temp file if output exceeds limit\n\t\t\tif (totalBytes > DEFAULT_MAX_BYTES) {\n\t\t\t\ttempFilePath = getTempFilePath();\n\t\t\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\t\t\ttempFileStream.write(output);\n\t\t\t\ttempFileStream.end();\n\t\t\t}\n\n\t\t\t// Apply tail truncation\n\t\t\tconst truncation = truncateTail(output);\n\t\t\tlet outputText = truncation.content || \"(no output)\";\n\n\t\t\t// Build details with truncation info\n\t\t\tlet details: BashToolDetails | undefined;\n\n\t\t\tif (truncation.truncated) {\n\t\t\t\tdetails = {\n\t\t\t\t\ttruncation,\n\t\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t\t};\n\n\t\t\t\t// Build actionable notice\n\t\t\t\tconst startLine = truncation.totalLines - truncation.outputLines + 1;\n\t\t\t\tconst endLine = truncation.totalLines;\n\n\t\t\t\tif (truncation.lastLinePartial) {\n\t\t\t\t\t// Edge case: last line alone > 50KB\n\t\t\t\t\tconst lastLineSize = formatSize(Buffer.byteLength(output.split(\"\\n\").pop() || \"\", \"utf-8\"));\n\t\t\t\t\toutputText += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;\n\t\t\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;\n\t\t\t\t} else {\n\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (result.code !== 0) {\n\t\t\t\tthrow new Error(`${outputText}\\n\\nCommand exited with code ${result.code}`.trim());\n\t\t\t}\n\n\t\t\treturn { content: [{ type: \"text\", text: outputText }], details };\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAE7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAW9C,QAAA,MAAM,UAAU;;;;EAId,CAAC;AAOH,MAAM,WAAW,eAAe;IAC/B,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,GAAE,eAAoB,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAqE9G","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { createWriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport type { Executor } from \"../sandbox.js\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateTail } from \"./truncate.js\";\n\n/**\n * Generate a unique temp file path for bash output\n */\nfunction getTempFilePath(): string {\n\tconst id = randomBytes(8).toString(\"hex\");\n\treturn join(tmpdir(), `pipiclaw-bash-${id}.log`);\n}\n\nconst bashSchema = Type.Object({\n\tlabel: Type.String({ description: \"Brief description of what this command does (shown to user)\" }),\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n});\n\ninterface BashToolDetails {\n\ttruncation?: TruncationResult;\n\tfullOutputPath?: string;\n}\n\nexport interface BashToolOptions {\n\tdefaultTimeoutSeconds?: number;\n}\n\nexport function createBashTool(executor: Executor, options: BashToolOptions = {}): AgentTool<typeof bashSchema> {\n\treturn {\n\t\tname: \"bash\",\n\t\tlabel: \"bash\",\n\t\tdescription: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,\n\t\tparameters: bashSchema,\n\t\texecute: async (\n\t\t\t_toolCallId: string,\n\t\t\t{ command, timeout }: { label: string; command: string; timeout?: number },\n\t\t\tsignal?: AbortSignal,\n\t\t) => {\n\t\t\t// Track output for potential temp file writing\n\t\t\tlet tempFilePath: string | undefined;\n\t\t\tlet tempFileStream: ReturnType<typeof createWriteStream> | undefined;\n\n\t\t\tconst effectiveTimeout = timeout ?? options.defaultTimeoutSeconds;\n\t\t\tconst result = await executor.exec(command, { timeout: effectiveTimeout, signal });\n\t\t\tlet output = \"\";\n\t\t\tif (result.stdout) output += result.stdout;\n\t\t\tif (result.stderr) {\n\t\t\t\tif (output) output += \"\\n\";\n\t\t\t\toutput += result.stderr;\n\t\t\t}\n\n\t\t\tconst totalBytes = Buffer.byteLength(output, \"utf-8\");\n\n\t\t\t// Write to temp file if output exceeds limit\n\t\t\tif (totalBytes > DEFAULT_MAX_BYTES) {\n\t\t\t\ttempFilePath = getTempFilePath();\n\t\t\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\t\t\ttempFileStream.write(output);\n\t\t\t\ttempFileStream.end();\n\t\t\t}\n\n\t\t\t// Apply tail truncation\n\t\t\tconst truncation = truncateTail(output);\n\t\t\tlet outputText = truncation.content || \"(no output)\";\n\n\t\t\t// Build details with truncation info\n\t\t\tlet details: BashToolDetails | undefined;\n\n\t\t\tif (truncation.truncated) {\n\t\t\t\tdetails = {\n\t\t\t\t\ttruncation,\n\t\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t\t};\n\n\t\t\t\t// Build actionable notice\n\t\t\t\tconst startLine = truncation.totalLines - truncation.outputLines + 1;\n\t\t\t\tconst endLine = truncation.totalLines;\n\n\t\t\t\tif (truncation.lastLinePartial) {\n\t\t\t\t\t// Edge case: last line alone > 50KB\n\t\t\t\t\tconst lastLineSize = formatSize(Buffer.byteLength(output.split(\"\\n\").pop() || \"\", \"utf-8\"));\n\t\t\t\t\toutputText += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;\n\t\t\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;\n\t\t\t\t} else {\n\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (result.code !== 0) {\n\t\t\t\tthrow new Error(`${outputText}\\n\\nCommand exited with code ${result.code}`.trim());\n\t\t\t}\n\n\t\t\treturn { content: [{ type: \"text\", text: outputText }], details };\n\t\t},\n\t};\n}\n"]}
@@ -16,7 +16,7 @@ const bashSchema = Type.Object({
16
16
  command: Type.String({ description: "Bash command to execute" }),
17
17
  timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
18
18
  });
19
- export function createBashTool(executor) {
19
+ export function createBashTool(executor, options = {}) {
20
20
  return {
21
21
  name: "bash",
22
22
  label: "bash",
@@ -26,7 +26,8 @@ export function createBashTool(executor) {
26
26
  // Track output for potential temp file writing
27
27
  let tempFilePath;
28
28
  let tempFileStream;
29
- const result = await executor.exec(command, { timeout, signal });
29
+ const effectiveTimeout = timeout ?? options.defaultTimeoutSeconds;
30
+ const result = await executor.exec(command, { timeout: effectiveTimeout, signal });
30
31
  let output = "";
31
32
  if (result.stdout)
32
33
  output += result.stdout;
@@ -1 +1 @@
1
- {"version":3,"file":"bash.js","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { createWriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport type { Executor } from \"../sandbox.js\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateTail } from \"./truncate.js\";\n\n/**\n * Generate a unique temp file path for bash output\n */\nfunction getTempFilePath(): string {\n\tconst id = randomBytes(8).toString(\"hex\");\n\treturn join(tmpdir(), `pipiclaw-bash-${id}.log`);\n}\n\nconst bashSchema = Type.Object({\n\tlabel: Type.String({ description: \"Brief description of what this command does (shown to user)\" }),\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n});\n\ninterface BashToolDetails {\n\ttruncation?: TruncationResult;\n\tfullOutputPath?: string;\n}\n\nexport function createBashTool(executor: Executor): AgentTool<typeof bashSchema> {\n\treturn {\n\t\tname: \"bash\",\n\t\tlabel: \"bash\",\n\t\tdescription: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,\n\t\tparameters: bashSchema,\n\t\texecute: async (\n\t\t\t_toolCallId: string,\n\t\t\t{ command, timeout }: { label: string; command: string; timeout?: number },\n\t\t\tsignal?: AbortSignal,\n\t\t) => {\n\t\t\t// Track output for potential temp file writing\n\t\t\tlet tempFilePath: string | undefined;\n\t\t\tlet tempFileStream: ReturnType<typeof createWriteStream> | undefined;\n\n\t\t\tconst result = await executor.exec(command, { timeout, signal });\n\t\t\tlet output = \"\";\n\t\t\tif (result.stdout) output += result.stdout;\n\t\t\tif (result.stderr) {\n\t\t\t\tif (output) output += \"\\n\";\n\t\t\t\toutput += result.stderr;\n\t\t\t}\n\n\t\t\tconst totalBytes = Buffer.byteLength(output, \"utf-8\");\n\n\t\t\t// Write to temp file if output exceeds limit\n\t\t\tif (totalBytes > DEFAULT_MAX_BYTES) {\n\t\t\t\ttempFilePath = getTempFilePath();\n\t\t\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\t\t\ttempFileStream.write(output);\n\t\t\t\ttempFileStream.end();\n\t\t\t}\n\n\t\t\t// Apply tail truncation\n\t\t\tconst truncation = truncateTail(output);\n\t\t\tlet outputText = truncation.content || \"(no output)\";\n\n\t\t\t// Build details with truncation info\n\t\t\tlet details: BashToolDetails | undefined;\n\n\t\t\tif (truncation.truncated) {\n\t\t\t\tdetails = {\n\t\t\t\t\ttruncation,\n\t\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t\t};\n\n\t\t\t\t// Build actionable notice\n\t\t\t\tconst startLine = truncation.totalLines - truncation.outputLines + 1;\n\t\t\t\tconst endLine = truncation.totalLines;\n\n\t\t\t\tif (truncation.lastLinePartial) {\n\t\t\t\t\t// Edge case: last line alone > 50KB\n\t\t\t\t\tconst lastLineSize = formatSize(Buffer.byteLength(output.split(\"\\n\").pop() || \"\", \"utf-8\"));\n\t\t\t\t\toutputText += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;\n\t\t\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;\n\t\t\t\t} else {\n\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (result.code !== 0) {\n\t\t\t\tthrow new Error(`${outputText}\\n\\nCommand exited with code ${result.code}`.trim());\n\t\t\t}\n\n\t\t\treturn { content: [{ type: \"text\", text: outputText }], details };\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"bash.js","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,UAAU,EAAyB,YAAY,EAAE,MAAM,eAAe,CAAC;AAEtH;;GAEG;AACH,SAAS,eAAe,GAAW;IAClC,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC1C,OAAO,IAAI,CAAC,MAAM,EAAE,EAAE,iBAAiB,EAAE,MAAM,CAAC,CAAC;AAAA,CACjD;AAED,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC;IAC9B,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,6DAA6D,EAAE,CAAC;IAClG,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,yBAAyB,EAAE,CAAC;IAChE,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,mDAAmD,EAAE,CAAC,CAAC;CACzG,CAAC,CAAC;AAWH,MAAM,UAAU,cAAc,CAAC,QAAkB,EAAE,OAAO,GAAoB,EAAE,EAAgC;IAC/G,OAAO;QACN,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,MAAM;QACb,WAAW,EAAE,mHAAmH,iBAAiB,aAAa,iBAAiB,GAAG,IAAI,0HAA0H;QAChT,UAAU,EAAE,UAAU;QACtB,OAAO,EAAE,KAAK,EACb,WAAmB,EACnB,EAAE,OAAO,EAAE,OAAO,EAAwD,EAC1E,MAAoB,EACnB,EAAE,CAAC;YACJ,+CAA+C;YAC/C,IAAI,YAAgC,CAAC;YACrC,IAAI,cAAgE,CAAC;YAErE,MAAM,gBAAgB,GAAG,OAAO,IAAI,OAAO,CAAC,qBAAqB,CAAC;YAClE,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,CAAC,CAAC;YACnF,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,MAAM,CAAC,MAAM;gBAAE,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC;YAC3C,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBACnB,IAAI,MAAM;oBAAE,MAAM,IAAI,IAAI,CAAC;gBAC3B,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC;YACzB,CAAC;YAED,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAEtD,6CAA6C;YAC7C,IAAI,UAAU,GAAG,iBAAiB,EAAE,CAAC;gBACpC,YAAY,GAAG,eAAe,EAAE,CAAC;gBACjC,cAAc,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAAC;gBACjD,cAAc,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;gBAC7B,cAAc,CAAC,GAAG,EAAE,CAAC;YACtB,CAAC;YAED,wBAAwB;YACxB,MAAM,UAAU,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;YACxC,IAAI,UAAU,GAAG,UAAU,CAAC,OAAO,IAAI,aAAa,CAAC;YAErD,qCAAqC;YACrC,IAAI,OAAoC,CAAC;YAEzC,IAAI,UAAU,CAAC,SAAS,EAAE,CAAC;gBAC1B,OAAO,GAAG;oBACT,UAAU;oBACV,cAAc,EAAE,YAAY;iBAC5B,CAAC;gBAEF,0BAA0B;gBAC1B,MAAM,SAAS,GAAG,UAAU,CAAC,UAAU,GAAG,UAAU,CAAC,WAAW,GAAG,CAAC,CAAC;gBACrE,MAAM,OAAO,GAAG,UAAU,CAAC,UAAU,CAAC;gBAEtC,IAAI,UAAU,CAAC,eAAe,EAAE,CAAC;oBAChC,oCAAoC;oBACpC,MAAM,YAAY,GAAG,UAAU,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;oBAC5F,UAAU,IAAI,qBAAqB,UAAU,CAAC,UAAU,CAAC,WAAW,CAAC,YAAY,OAAO,aAAa,YAAY,mBAAmB,YAAY,GAAG,CAAC;gBACrJ,CAAC;qBAAM,IAAI,UAAU,CAAC,WAAW,KAAK,OAAO,EAAE,CAAC;oBAC/C,UAAU,IAAI,sBAAsB,SAAS,IAAI,OAAO,OAAO,UAAU,CAAC,UAAU,kBAAkB,YAAY,GAAG,CAAC;gBACvH,CAAC;qBAAM,CAAC;oBACP,UAAU,IAAI,sBAAsB,SAAS,IAAI,OAAO,OAAO,UAAU,CAAC,UAAU,KAAK,UAAU,CAAC,iBAAiB,CAAC,yBAAyB,YAAY,GAAG,CAAC;gBAChK,CAAC;YACF,CAAC;YAED,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBACvB,MAAM,IAAI,KAAK,CAAC,GAAG,UAAU,gCAAgC,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;YACpF,CAAC;YAED,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC;QAAA,CAClE;KACD,CAAC;AAAA,CACF","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { createWriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport type { Executor } from \"../sandbox.js\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateTail } from \"./truncate.js\";\n\n/**\n * Generate a unique temp file path for bash output\n */\nfunction getTempFilePath(): string {\n\tconst id = randomBytes(8).toString(\"hex\");\n\treturn join(tmpdir(), `pipiclaw-bash-${id}.log`);\n}\n\nconst bashSchema = Type.Object({\n\tlabel: Type.String({ description: \"Brief description of what this command does (shown to user)\" }),\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n});\n\ninterface BashToolDetails {\n\ttruncation?: TruncationResult;\n\tfullOutputPath?: string;\n}\n\nexport interface BashToolOptions {\n\tdefaultTimeoutSeconds?: number;\n}\n\nexport function createBashTool(executor: Executor, options: BashToolOptions = {}): AgentTool<typeof bashSchema> {\n\treturn {\n\t\tname: \"bash\",\n\t\tlabel: \"bash\",\n\t\tdescription: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,\n\t\tparameters: bashSchema,\n\t\texecute: async (\n\t\t\t_toolCallId: string,\n\t\t\t{ command, timeout }: { label: string; command: string; timeout?: number },\n\t\t\tsignal?: AbortSignal,\n\t\t) => {\n\t\t\t// Track output for potential temp file writing\n\t\t\tlet tempFilePath: string | undefined;\n\t\t\tlet tempFileStream: ReturnType<typeof createWriteStream> | undefined;\n\n\t\t\tconst effectiveTimeout = timeout ?? options.defaultTimeoutSeconds;\n\t\t\tconst result = await executor.exec(command, { timeout: effectiveTimeout, signal });\n\t\t\tlet output = \"\";\n\t\t\tif (result.stdout) output += result.stdout;\n\t\t\tif (result.stderr) {\n\t\t\t\tif (output) output += \"\\n\";\n\t\t\t\toutput += result.stderr;\n\t\t\t}\n\n\t\t\tconst totalBytes = Buffer.byteLength(output, \"utf-8\");\n\n\t\t\t// Write to temp file if output exceeds limit\n\t\t\tif (totalBytes > DEFAULT_MAX_BYTES) {\n\t\t\t\ttempFilePath = getTempFilePath();\n\t\t\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\t\t\ttempFileStream.write(output);\n\t\t\t\ttempFileStream.end();\n\t\t\t}\n\n\t\t\t// Apply tail truncation\n\t\t\tconst truncation = truncateTail(output);\n\t\t\tlet outputText = truncation.content || \"(no output)\";\n\n\t\t\t// Build details with truncation info\n\t\t\tlet details: BashToolDetails | undefined;\n\n\t\t\tif (truncation.truncated) {\n\t\t\t\tdetails = {\n\t\t\t\t\ttruncation,\n\t\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t\t};\n\n\t\t\t\t// Build actionable notice\n\t\t\t\tconst startLine = truncation.totalLines - truncation.outputLines + 1;\n\t\t\t\tconst endLine = truncation.totalLines;\n\n\t\t\t\tif (truncation.lastLinePartial) {\n\t\t\t\t\t// Edge case: last line alone > 50KB\n\t\t\t\t\tconst lastLineSize = formatSize(Buffer.byteLength(output.split(\"\\n\").pop() || \"\", \"utf-8\"));\n\t\t\t\t\toutputText += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;\n\t\t\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;\n\t\t\t\t} else {\n\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (result.code !== 0) {\n\t\t\t\tthrow new Error(`${outputText}\\n\\nCommand exited with code ${result.code}`.trim());\n\t\t\t}\n\n\t\t\treturn { content: [{ type: \"text\", text: outputText }], details };\n\t\t},\n\t};\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../src/tools/edit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAG7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAsF9C,QAAA,MAAM,UAAU;;;;;EAKd,CAAC;AAEH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAiE/E","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport * as Diff from \"diff\";\nimport type { Executor } from \"../sandbox.js\";\nimport { shellEscape } from \"../shell-escape.js\";\n\n/**\n * Generate a unified diff string with line numbers and context\n */\nfunction generateDiffString(oldContent: string, newContent: string, contextLines = 4): string {\n\tconst parts = Diff.diffLines(oldContent, newContent);\n\tconst output: string[] = [];\n\n\tconst oldLines = oldContent.split(\"\\n\");\n\tconst newLines = newContent.split(\"\\n\");\n\tconst maxLineNum = Math.max(oldLines.length, newLines.length);\n\tconst lineNumWidth = String(maxLineNum).length;\n\n\tlet oldLineNum = 1;\n\tlet newLineNum = 1;\n\tlet lastWasChange = false;\n\n\tfor (let i = 0; i < parts.length; i++) {\n\t\tconst part = parts[i];\n\t\tconst raw = part.value.split(\"\\n\");\n\t\tif (raw[raw.length - 1] === \"\") {\n\t\t\traw.pop();\n\t\t}\n\n\t\tif (part.added || part.removed) {\n\t\t\tfor (const line of raw) {\n\t\t\t\tif (part.added) {\n\t\t\t\t\tconst lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(`+${lineNum} ${line}`);\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t} else {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(`-${lineNum} ${line}`);\n\t\t\t\t\toldLineNum++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastWasChange = true;\n\t\t} else {\n\t\t\tconst nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n\t\t\tif (lastWasChange || nextPartIsChange) {\n\t\t\t\tlet linesToShow = raw;\n\t\t\t\tlet skipStart = 0;\n\t\t\t\tlet skipEnd = 0;\n\n\t\t\t\tif (!lastWasChange) {\n\t\t\t\t\tskipStart = Math.max(0, raw.length - contextLines);\n\t\t\t\t\tlinesToShow = raw.slice(skipStart);\n\t\t\t\t}\n\n\t\t\t\tif (!nextPartIsChange && linesToShow.length > contextLines) {\n\t\t\t\t\tskipEnd = linesToShow.length - contextLines;\n\t\t\t\t\tlinesToShow = linesToShow.slice(0, contextLines);\n\t\t\t\t}\n\n\t\t\t\tif (skipStart > 0) {\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n\t\t\t\t}\n\n\t\t\t\tfor (const line of linesToShow) {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(` ${lineNum} ${line}`);\n\t\t\t\t\toldLineNum++;\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t}\n\n\t\t\t\tif (skipEnd > 0) {\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n\t\t\t\t}\n\n\t\t\t\toldLineNum += skipStart + skipEnd;\n\t\t\t\tnewLineNum += skipStart + skipEnd;\n\t\t\t} else {\n\t\t\t\toldLineNum += raw.length;\n\t\t\t\tnewLineNum += raw.length;\n\t\t\t}\n\n\t\t\tlastWasChange = false;\n\t\t}\n\t}\n\n\treturn output.join(\"\\n\");\n}\n\nconst editSchema = Type.Object({\n\tlabel: Type.String({ description: \"Brief description of the edit you're making (shown to user)\" }),\n\tpath: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n\toldText: Type.String({ description: \"Exact text to find and replace (must match exactly)\" }),\n\tnewText: Type.String({ description: \"New text to replace the old text with\" }),\n});\n\nexport function createEditTool(executor: Executor): AgentTool<typeof editSchema> {\n\treturn {\n\t\tname: \"edit\",\n\t\tlabel: \"edit\",\n\t\tdescription:\n\t\t\t\"Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits.\",\n\t\tparameters: editSchema,\n\t\texecute: async (\n\t\t\t_toolCallId: string,\n\t\t\t{ path, oldText, newText }: { label: string; path: string; oldText: string; newText: string },\n\t\t\tsignal?: AbortSignal,\n\t\t) => {\n\t\t\t// Read the file\n\t\t\tconst readResult = await executor.exec(`cat ${shellEscape(path)}`, { signal });\n\t\t\tif (readResult.code !== 0) {\n\t\t\t\tthrow new Error(readResult.stderr || `File not found: ${path}`);\n\t\t\t}\n\n\t\t\tconst content = readResult.stdout;\n\n\t\t\t// Check if old text exists\n\t\t\tif (!content.includes(oldText)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Count occurrences\n\t\t\tconst occurrences = content.split(oldText).length - 1;\n\n\t\t\tif (occurrences > 1) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Perform replacement\n\t\t\tconst index = content.indexOf(oldText);\n\t\t\tconst newContent = content.substring(0, index) + newText + content.substring(index + oldText.length);\n\n\t\t\tif (content === newContent) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Write the file back\n\t\t\tconst writeResult = await executor.exec(`printf '%s' ${shellEscape(newContent)} > ${shellEscape(path)}`, {\n\t\t\t\tsignal,\n\t\t\t});\n\t\t\tif (writeResult.code !== 0) {\n\t\t\t\tthrow new Error(writeResult.stderr || `Failed to write file: ${path}`);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: `Successfully replaced text in ${path}. Changed ${oldText.length} characters to ${newText.length} characters.`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tdetails: { diff: generateDiffString(content, newContent) },\n\t\t\t};\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../src/tools/edit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAG7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAuF9C,QAAA,MAAM,UAAU;;;;;EAKd,CAAC;AAEH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CA4D/E","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport * as Diff from \"diff\";\nimport type { Executor } from \"../sandbox.js\";\nimport { shellEscape } from \"../shell-escape.js\";\nimport { writeContent } from \"./write-content.js\";\n\n/**\n * Generate a unified diff string with line numbers and context\n */\nfunction generateDiffString(oldContent: string, newContent: string, contextLines = 4): string {\n\tconst parts = Diff.diffLines(oldContent, newContent);\n\tconst output: string[] = [];\n\n\tconst oldLines = oldContent.split(\"\\n\");\n\tconst newLines = newContent.split(\"\\n\");\n\tconst maxLineNum = Math.max(oldLines.length, newLines.length);\n\tconst lineNumWidth = String(maxLineNum).length;\n\n\tlet oldLineNum = 1;\n\tlet newLineNum = 1;\n\tlet lastWasChange = false;\n\n\tfor (let i = 0; i < parts.length; i++) {\n\t\tconst part = parts[i];\n\t\tconst raw = part.value.split(\"\\n\");\n\t\tif (raw[raw.length - 1] === \"\") {\n\t\t\traw.pop();\n\t\t}\n\n\t\tif (part.added || part.removed) {\n\t\t\tfor (const line of raw) {\n\t\t\t\tif (part.added) {\n\t\t\t\t\tconst lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(`+${lineNum} ${line}`);\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t} else {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(`-${lineNum} ${line}`);\n\t\t\t\t\toldLineNum++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastWasChange = true;\n\t\t} else {\n\t\t\tconst nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n\t\t\tif (lastWasChange || nextPartIsChange) {\n\t\t\t\tlet linesToShow = raw;\n\t\t\t\tlet skipStart = 0;\n\t\t\t\tlet skipEnd = 0;\n\n\t\t\t\tif (!lastWasChange) {\n\t\t\t\t\tskipStart = Math.max(0, raw.length - contextLines);\n\t\t\t\t\tlinesToShow = raw.slice(skipStart);\n\t\t\t\t}\n\n\t\t\t\tif (!nextPartIsChange && linesToShow.length > contextLines) {\n\t\t\t\t\tskipEnd = linesToShow.length - contextLines;\n\t\t\t\t\tlinesToShow = linesToShow.slice(0, contextLines);\n\t\t\t\t}\n\n\t\t\t\tif (skipStart > 0) {\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n\t\t\t\t}\n\n\t\t\t\tfor (const line of linesToShow) {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(` ${lineNum} ${line}`);\n\t\t\t\t\toldLineNum++;\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t}\n\n\t\t\t\tif (skipEnd > 0) {\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n\t\t\t\t}\n\n\t\t\t\toldLineNum += skipStart + skipEnd;\n\t\t\t\tnewLineNum += skipStart + skipEnd;\n\t\t\t} else {\n\t\t\t\toldLineNum += raw.length;\n\t\t\t\tnewLineNum += raw.length;\n\t\t\t}\n\n\t\t\tlastWasChange = false;\n\t\t}\n\t}\n\n\treturn output.join(\"\\n\");\n}\n\nconst editSchema = Type.Object({\n\tlabel: Type.String({ description: \"Brief description of the edit you're making (shown to user)\" }),\n\tpath: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n\toldText: Type.String({ description: \"Exact text to find and replace (must match exactly)\" }),\n\tnewText: Type.String({ description: \"New text to replace the old text with\" }),\n});\n\nexport function createEditTool(executor: Executor): AgentTool<typeof editSchema> {\n\treturn {\n\t\tname: \"edit\",\n\t\tlabel: \"edit\",\n\t\tdescription:\n\t\t\t\"Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits.\",\n\t\tparameters: editSchema,\n\t\texecute: async (\n\t\t\t_toolCallId: string,\n\t\t\t{ path, oldText, newText }: { label: string; path: string; oldText: string; newText: string },\n\t\t\tsignal?: AbortSignal,\n\t\t) => {\n\t\t\t// Read the file\n\t\t\tconst readResult = await executor.exec(`cat ${shellEscape(path)}`, { signal });\n\t\t\tif (readResult.code !== 0) {\n\t\t\t\tthrow new Error(readResult.stderr || `File not found: ${path}`);\n\t\t\t}\n\n\t\t\tconst content = readResult.stdout;\n\n\t\t\t// Check if old text exists\n\t\t\tif (!content.includes(oldText)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Count occurrences\n\t\t\tconst occurrences = content.split(oldText).length - 1;\n\n\t\t\tif (occurrences > 1) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Perform replacement\n\t\t\tconst index = content.indexOf(oldText);\n\t\t\tconst newContent = content.substring(0, index) + newText + content.substring(index + oldText.length);\n\n\t\t\tif (content === newContent) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Write the file back\n\t\t\tawait writeContent(executor, path, newContent, signal);\n\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: `Successfully replaced text in ${path}. Changed ${oldText.length} characters to ${newText.length} characters.`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tdetails: { diff: generateDiffString(content, newContent) },\n\t\t\t};\n\t\t},\n\t};\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import * as Diff from "diff";
3
3
  import { shellEscape } from "../shell-escape.js";
4
+ import { writeContent } from "./write-content.js";
4
5
  /**
5
6
  * Generate a unified diff string with line numbers and context
6
7
  */
@@ -108,12 +109,7 @@ export function createEditTool(executor) {
108
109
  throw new Error(`No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`);
109
110
  }
110
111
  // Write the file back
111
- const writeResult = await executor.exec(`printf '%s' ${shellEscape(newContent)} > ${shellEscape(path)}`, {
112
- signal,
113
- });
114
- if (writeResult.code !== 0) {
115
- throw new Error(writeResult.stderr || `Failed to write file: ${path}`);
116
- }
112
+ await writeContent(executor, path, newContent, signal);
117
113
  return {
118
114
  content: [
119
115
  {
@@ -1 +1 @@
1
- {"version":3,"file":"edit.js","sourceRoot":"","sources":["../../src/tools/edit.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport * as Diff from \"diff\";\nimport type { Executor } from \"../sandbox.js\";\nimport { shellEscape } from \"../shell-escape.js\";\n\n/**\n * Generate a unified diff string with line numbers and context\n */\nfunction generateDiffString(oldContent: string, newContent: string, contextLines = 4): string {\n\tconst parts = Diff.diffLines(oldContent, newContent);\n\tconst output: string[] = [];\n\n\tconst oldLines = oldContent.split(\"\\n\");\n\tconst newLines = newContent.split(\"\\n\");\n\tconst maxLineNum = Math.max(oldLines.length, newLines.length);\n\tconst lineNumWidth = String(maxLineNum).length;\n\n\tlet oldLineNum = 1;\n\tlet newLineNum = 1;\n\tlet lastWasChange = false;\n\n\tfor (let i = 0; i < parts.length; i++) {\n\t\tconst part = parts[i];\n\t\tconst raw = part.value.split(\"\\n\");\n\t\tif (raw[raw.length - 1] === \"\") {\n\t\t\traw.pop();\n\t\t}\n\n\t\tif (part.added || part.removed) {\n\t\t\tfor (const line of raw) {\n\t\t\t\tif (part.added) {\n\t\t\t\t\tconst lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(`+${lineNum} ${line}`);\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t} else {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(`-${lineNum} ${line}`);\n\t\t\t\t\toldLineNum++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastWasChange = true;\n\t\t} else {\n\t\t\tconst nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n\t\t\tif (lastWasChange || nextPartIsChange) {\n\t\t\t\tlet linesToShow = raw;\n\t\t\t\tlet skipStart = 0;\n\t\t\t\tlet skipEnd = 0;\n\n\t\t\t\tif (!lastWasChange) {\n\t\t\t\t\tskipStart = Math.max(0, raw.length - contextLines);\n\t\t\t\t\tlinesToShow = raw.slice(skipStart);\n\t\t\t\t}\n\n\t\t\t\tif (!nextPartIsChange && linesToShow.length > contextLines) {\n\t\t\t\t\tskipEnd = linesToShow.length - contextLines;\n\t\t\t\t\tlinesToShow = linesToShow.slice(0, contextLines);\n\t\t\t\t}\n\n\t\t\t\tif (skipStart > 0) {\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n\t\t\t\t}\n\n\t\t\t\tfor (const line of linesToShow) {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(` ${lineNum} ${line}`);\n\t\t\t\t\toldLineNum++;\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t}\n\n\t\t\t\tif (skipEnd > 0) {\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n\t\t\t\t}\n\n\t\t\t\toldLineNum += skipStart + skipEnd;\n\t\t\t\tnewLineNum += skipStart + skipEnd;\n\t\t\t} else {\n\t\t\t\toldLineNum += raw.length;\n\t\t\t\tnewLineNum += raw.length;\n\t\t\t}\n\n\t\t\tlastWasChange = false;\n\t\t}\n\t}\n\n\treturn output.join(\"\\n\");\n}\n\nconst editSchema = Type.Object({\n\tlabel: Type.String({ description: \"Brief description of the edit you're making (shown to user)\" }),\n\tpath: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n\toldText: Type.String({ description: \"Exact text to find and replace (must match exactly)\" }),\n\tnewText: Type.String({ description: \"New text to replace the old text with\" }),\n});\n\nexport function createEditTool(executor: Executor): AgentTool<typeof editSchema> {\n\treturn {\n\t\tname: \"edit\",\n\t\tlabel: \"edit\",\n\t\tdescription:\n\t\t\t\"Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits.\",\n\t\tparameters: editSchema,\n\t\texecute: async (\n\t\t\t_toolCallId: string,\n\t\t\t{ path, oldText, newText }: { label: string; path: string; oldText: string; newText: string },\n\t\t\tsignal?: AbortSignal,\n\t\t) => {\n\t\t\t// Read the file\n\t\t\tconst readResult = await executor.exec(`cat ${shellEscape(path)}`, { signal });\n\t\t\tif (readResult.code !== 0) {\n\t\t\t\tthrow new Error(readResult.stderr || `File not found: ${path}`);\n\t\t\t}\n\n\t\t\tconst content = readResult.stdout;\n\n\t\t\t// Check if old text exists\n\t\t\tif (!content.includes(oldText)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Count occurrences\n\t\t\tconst occurrences = content.split(oldText).length - 1;\n\n\t\t\tif (occurrences > 1) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Perform replacement\n\t\t\tconst index = content.indexOf(oldText);\n\t\t\tconst newContent = content.substring(0, index) + newText + content.substring(index + oldText.length);\n\n\t\t\tif (content === newContent) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Write the file back\n\t\t\tconst writeResult = await executor.exec(`printf '%s' ${shellEscape(newContent)} > ${shellEscape(path)}`, {\n\t\t\t\tsignal,\n\t\t\t});\n\t\t\tif (writeResult.code !== 0) {\n\t\t\t\tthrow new Error(writeResult.stderr || `Failed to write file: ${path}`);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: `Successfully replaced text in ${path}. Changed ${oldText.length} characters to ${newText.length} characters.`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tdetails: { diff: generateDiffString(content, newContent) },\n\t\t\t};\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"edit.js","sourceRoot":"","sources":["../../src/tools/edit.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport * as Diff from \"diff\";\nimport type { Executor } from \"../sandbox.js\";\nimport { shellEscape } from \"../shell-escape.js\";\nimport { writeContent } from \"./write-content.js\";\n\n/**\n * Generate a unified diff string with line numbers and context\n */\nfunction generateDiffString(oldContent: string, newContent: string, contextLines = 4): string {\n\tconst parts = Diff.diffLines(oldContent, newContent);\n\tconst output: string[] = [];\n\n\tconst oldLines = oldContent.split(\"\\n\");\n\tconst newLines = newContent.split(\"\\n\");\n\tconst maxLineNum = Math.max(oldLines.length, newLines.length);\n\tconst lineNumWidth = String(maxLineNum).length;\n\n\tlet oldLineNum = 1;\n\tlet newLineNum = 1;\n\tlet lastWasChange = false;\n\n\tfor (let i = 0; i < parts.length; i++) {\n\t\tconst part = parts[i];\n\t\tconst raw = part.value.split(\"\\n\");\n\t\tif (raw[raw.length - 1] === \"\") {\n\t\t\traw.pop();\n\t\t}\n\n\t\tif (part.added || part.removed) {\n\t\t\tfor (const line of raw) {\n\t\t\t\tif (part.added) {\n\t\t\t\t\tconst lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(`+${lineNum} ${line}`);\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t} else {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(`-${lineNum} ${line}`);\n\t\t\t\t\toldLineNum++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastWasChange = true;\n\t\t} else {\n\t\t\tconst nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n\t\t\tif (lastWasChange || nextPartIsChange) {\n\t\t\t\tlet linesToShow = raw;\n\t\t\t\tlet skipStart = 0;\n\t\t\t\tlet skipEnd = 0;\n\n\t\t\t\tif (!lastWasChange) {\n\t\t\t\t\tskipStart = Math.max(0, raw.length - contextLines);\n\t\t\t\t\tlinesToShow = raw.slice(skipStart);\n\t\t\t\t}\n\n\t\t\t\tif (!nextPartIsChange && linesToShow.length > contextLines) {\n\t\t\t\t\tskipEnd = linesToShow.length - contextLines;\n\t\t\t\t\tlinesToShow = linesToShow.slice(0, contextLines);\n\t\t\t\t}\n\n\t\t\t\tif (skipStart > 0) {\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n\t\t\t\t}\n\n\t\t\t\tfor (const line of linesToShow) {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(` ${lineNum} ${line}`);\n\t\t\t\t\toldLineNum++;\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t}\n\n\t\t\t\tif (skipEnd > 0) {\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n\t\t\t\t}\n\n\t\t\t\toldLineNum += skipStart + skipEnd;\n\t\t\t\tnewLineNum += skipStart + skipEnd;\n\t\t\t} else {\n\t\t\t\toldLineNum += raw.length;\n\t\t\t\tnewLineNum += raw.length;\n\t\t\t}\n\n\t\t\tlastWasChange = false;\n\t\t}\n\t}\n\n\treturn output.join(\"\\n\");\n}\n\nconst editSchema = Type.Object({\n\tlabel: Type.String({ description: \"Brief description of the edit you're making (shown to user)\" }),\n\tpath: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n\toldText: Type.String({ description: \"Exact text to find and replace (must match exactly)\" }),\n\tnewText: Type.String({ description: \"New text to replace the old text with\" }),\n});\n\nexport function createEditTool(executor: Executor): AgentTool<typeof editSchema> {\n\treturn {\n\t\tname: \"edit\",\n\t\tlabel: \"edit\",\n\t\tdescription:\n\t\t\t\"Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits.\",\n\t\tparameters: editSchema,\n\t\texecute: async (\n\t\t\t_toolCallId: string,\n\t\t\t{ path, oldText, newText }: { label: string; path: string; oldText: string; newText: string },\n\t\t\tsignal?: AbortSignal,\n\t\t) => {\n\t\t\t// Read the file\n\t\t\tconst readResult = await executor.exec(`cat ${shellEscape(path)}`, { signal });\n\t\t\tif (readResult.code !== 0) {\n\t\t\t\tthrow new Error(readResult.stderr || `File not found: ${path}`);\n\t\t\t}\n\n\t\t\tconst content = readResult.stdout;\n\n\t\t\t// Check if old text exists\n\t\t\tif (!content.includes(oldText)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Count occurrences\n\t\t\tconst occurrences = content.split(oldText).length - 1;\n\n\t\t\tif (occurrences > 1) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Perform replacement\n\t\t\tconst index = content.indexOf(oldText);\n\t\t\tconst newContent = content.substring(0, index) + newText + content.substring(index + oldText.length);\n\n\t\t\tif (content === newContent) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Write the file back\n\t\t\tawait writeContent(executor, path, newContent, signal);\n\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: `Successfully replaced text in ${path}. Changed ${oldText.length} characters to ${newText.length} characters.`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tdetails: { diff: generateDiffString(content, newContent) },\n\t\t\t};\n\t\t},\n\t};\n}\n"]}
@@ -1,4 +1,13 @@
1
1
  import type { AgentTool } from "@mariozechner/pi-agent-core";
2
+ import type { Api, Model } from "@mariozechner/pi-ai";
2
3
  import type { Executor } from "../sandbox.js";
3
- export declare function createPipiclawTools(executor: Executor): AgentTool<any>[];
4
+ export interface CreatePipiclawToolsOptions {
5
+ executor: Executor;
6
+ getCurrentModel: () => Model<Api>;
7
+ getAvailableModels: () => Model<Api>[];
8
+ resolveApiKey: (model: Model<Api>) => Promise<string>;
9
+ workspaceDir: string;
10
+ }
11
+ export declare function createPipiclawBaseTools(executor: Executor): AgentTool<any>[];
12
+ export declare function createPipiclawTools(options: CreatePipiclawToolsOptions): AgentTool<any>[];
4
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAM9C,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,CAExE","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport type { Executor } from \"../sandbox.js\";\nimport { createBashTool } from \"./bash.js\";\nimport { createEditTool } from \"./edit.js\";\nimport { createReadTool } from \"./read.js\";\nimport { createWriteTool } from \"./write.js\";\n\nexport function createPipiclawTools(executor: Executor): AgentTool<any>[] {\n\treturn [createReadTool(executor), createBashTool(executor), createEditTool(executor), createWriteTool(executor)];\n}\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAO9C,MAAM,WAAW,0BAA0B;IAC1C,QAAQ,EAAE,QAAQ,CAAC;IACnB,eAAe,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;IAClC,kBAAkB,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;IACvC,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACtD,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,CAE5E;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,CAYzF","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport type { Api, Model } from \"@mariozechner/pi-ai\";\nimport type { Executor } from \"../sandbox.js\";\nimport { createBashTool } from \"./bash.js\";\nimport { createEditTool } from \"./edit.js\";\nimport { createReadTool } from \"./read.js\";\nimport { createSubAgentTool } from \"./subagent.js\";\nimport { createWriteTool } from \"./write.js\";\n\nexport interface CreatePipiclawToolsOptions {\n\texecutor: Executor;\n\tgetCurrentModel: () => Model<Api>;\n\tgetAvailableModels: () => Model<Api>[];\n\tresolveApiKey: (model: Model<Api>) => Promise<string>;\n\tworkspaceDir: string;\n}\n\nexport function createPipiclawBaseTools(executor: Executor): AgentTool<any>[] {\n\treturn [createReadTool(executor), createBashTool(executor), createEditTool(executor), createWriteTool(executor)];\n}\n\nexport function createPipiclawTools(options: CreatePipiclawToolsOptions): AgentTool<any>[] {\n\tconst baseTools = createPipiclawBaseTools(options.executor);\n\treturn [\n\t\t...baseTools,\n\t\tcreateSubAgentTool({\n\t\t\texecutor: options.executor,\n\t\t\tgetCurrentModel: options.getCurrentModel,\n\t\t\tgetAvailableModels: options.getAvailableModels,\n\t\t\tresolveApiKey: options.resolveApiKey,\n\t\t\tworkspaceDir: options.workspaceDir,\n\t\t}),\n\t];\n}\n"]}
@@ -1,8 +1,22 @@
1
1
  import { createBashTool } from "./bash.js";
2
2
  import { createEditTool } from "./edit.js";
3
3
  import { createReadTool } from "./read.js";
4
+ import { createSubAgentTool } from "./subagent.js";
4
5
  import { createWriteTool } from "./write.js";
5
- export function createPipiclawTools(executor) {
6
+ export function createPipiclawBaseTools(executor) {
6
7
  return [createReadTool(executor), createBashTool(executor), createEditTool(executor), createWriteTool(executor)];
7
8
  }
9
+ export function createPipiclawTools(options) {
10
+ const baseTools = createPipiclawBaseTools(options.executor);
11
+ return [
12
+ ...baseTools,
13
+ createSubAgentTool({
14
+ executor: options.executor,
15
+ getCurrentModel: options.getCurrentModel,
16
+ getAvailableModels: options.getAvailableModels,
17
+ resolveApiKey: options.resolveApiKey,
18
+ workspaceDir: options.workspaceDir,
19
+ }),
20
+ ];
21
+ }
8
22
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,UAAU,mBAAmB,CAAC,QAAkB,EAAoB;IACzE,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC;AAAA,CACjH","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport type { Executor } from \"../sandbox.js\";\nimport { createBashTool } from \"./bash.js\";\nimport { createEditTool } from \"./edit.js\";\nimport { createReadTool } from \"./read.js\";\nimport { createWriteTool } from \"./write.js\";\n\nexport function createPipiclawTools(executor: Executor): AgentTool<any>[] {\n\treturn [createReadTool(executor), createBashTool(executor), createEditTool(executor), createWriteTool(executor)];\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAU7C,MAAM,UAAU,uBAAuB,CAAC,QAAkB,EAAoB;IAC7E,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC;AAAA,CACjH;AAED,MAAM,UAAU,mBAAmB,CAAC,OAAmC,EAAoB;IAC1F,MAAM,SAAS,GAAG,uBAAuB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5D,OAAO;QACN,GAAG,SAAS;QACZ,kBAAkB,CAAC;YAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,eAAe,EAAE,OAAO,CAAC,eAAe;YACxC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;YAC9C,aAAa,EAAE,OAAO,CAAC,aAAa;YACpC,YAAY,EAAE,OAAO,CAAC,YAAY;SAClC,CAAC;KACF,CAAC;AAAA,CACF","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport type { Api, Model } from \"@mariozechner/pi-ai\";\nimport type { Executor } from \"../sandbox.js\";\nimport { createBashTool } from \"./bash.js\";\nimport { createEditTool } from \"./edit.js\";\nimport { createReadTool } from \"./read.js\";\nimport { createSubAgentTool } from \"./subagent.js\";\nimport { createWriteTool } from \"./write.js\";\n\nexport interface CreatePipiclawToolsOptions {\n\texecutor: Executor;\n\tgetCurrentModel: () => Model<Api>;\n\tgetAvailableModels: () => Model<Api>[];\n\tresolveApiKey: (model: Model<Api>) => Promise<string>;\n\tworkspaceDir: string;\n}\n\nexport function createPipiclawBaseTools(executor: Executor): AgentTool<any>[] {\n\treturn [createReadTool(executor), createBashTool(executor), createEditTool(executor), createWriteTool(executor)];\n}\n\nexport function createPipiclawTools(options: CreatePipiclawToolsOptions): AgentTool<any>[] {\n\tconst baseTools = createPipiclawBaseTools(options.executor);\n\treturn [\n\t\t...baseTools,\n\t\tcreateSubAgentTool({\n\t\t\texecutor: options.executor,\n\t\t\tgetCurrentModel: options.getCurrentModel,\n\t\t\tgetAvailableModels: options.getAvailableModels,\n\t\t\tresolveApiKey: options.resolveApiKey,\n\t\t\tworkspaceDir: options.workspaceDir,\n\t\t}),\n\t];\n}\n"]}
@@ -0,0 +1,52 @@
1
+ import { type AgentTool } from "@mariozechner/pi-agent-core";
2
+ import type { Api, Model } from "@mariozechner/pi-ai";
3
+ import type { Executor } from "../sandbox.js";
4
+ declare const subagentSchema: import("@sinclair/typebox").TObject<{
5
+ label: import("@sinclair/typebox").TString;
6
+ agent: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
7
+ name: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
8
+ task: import("@sinclair/typebox").TString;
9
+ systemPrompt: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
10
+ tools: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
11
+ model: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
12
+ maxTurns: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
13
+ maxToolCalls: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
14
+ maxWallTimeSec: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
15
+ bashTimeoutSec: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
16
+ }>;
17
+ interface UsageTotals {
18
+ input: number;
19
+ output: number;
20
+ cacheRead: number;
21
+ cacheWrite: number;
22
+ total: number;
23
+ cost: {
24
+ input: number;
25
+ output: number;
26
+ cacheRead: number;
27
+ cacheWrite: number;
28
+ total: number;
29
+ };
30
+ }
31
+ export interface SubAgentToolDetails {
32
+ agent: string;
33
+ source: "predefined" | "inline";
34
+ model: string;
35
+ tools: string[];
36
+ turns: number;
37
+ toolCalls: number;
38
+ durationMs: number;
39
+ failed: boolean;
40
+ failureReason?: string;
41
+ usage: UsageTotals;
42
+ }
43
+ export interface SubAgentToolOptions {
44
+ executor: Executor;
45
+ getCurrentModel: () => Model<Api>;
46
+ getAvailableModels: () => Model<Api>[];
47
+ resolveApiKey: (model: Model<Api>) => Promise<string>;
48
+ workspaceDir: string;
49
+ }
50
+ export declare function createSubAgentTool(options: SubAgentToolOptions): AgentTool<typeof subagentSchema, SubAgentToolDetails>;
51
+ export {};
52
+ //# sourceMappingURL=subagent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subagent.d.ts","sourceRoot":"","sources":["../../src/tools/subagent.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6C,KAAK,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxG,OAAO,KAAK,EAAE,GAAG,EAAoB,KAAK,EAAe,MAAM,qBAAqB,CAAC;AAIrF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAa9C,QAAA,MAAM,cAAc;;;;;;;;;;;;EAsBlB,CAAC;AAEH,UAAU,WAAW;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE;QACL,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;KACd,CAAC;CACF;AAED,MAAM,WAAW,mBAAmB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,YAAY,GAAG,QAAQ,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,WAAW,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,EAAE,QAAQ,CAAC;IACnB,eAAe,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;IAClC,kBAAkB,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;IACvC,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACtD,YAAY,EAAE,MAAM,CAAC;CACrB;AA8GD,wBAAgB,kBAAkB,CACjC,OAAO,EAAE,mBAAmB,GAC1B,SAAS,CAAC,OAAO,cAAc,EAAE,mBAAmB,CAAC,CA+JvD","sourcesContent":["import { Agent, type AgentEvent, type AgentMessage, type AgentTool } from \"@mariozechner/pi-agent-core\";\nimport type { Api, AssistantMessage, Model, TextContent } from \"@mariozechner/pi-ai\";\nimport { convertToLlm } from \"@mariozechner/pi-coding-agent\";\nimport { Type } from \"@sinclair/typebox\";\nimport { formatModelReference } from \"../model-utils.js\";\nimport type { Executor } from \"../sandbox.js\";\nimport {\n\tdiscoverSubAgents,\n\tformatSubAgentList,\n\tresolveSubAgentConfig,\n\ttype SubAgentConfig,\n\tvalidateSubAgentTask,\n} from \"../sub-agents.js\";\nimport { createBashTool } from \"./bash.js\";\nimport { createEditTool } from \"./edit.js\";\nimport { createReadTool } from \"./read.js\";\nimport { createWriteTool } from \"./write.js\";\n\nconst subagentSchema = Type.Object({\n\tlabel: Type.String({ description: \"Brief description of what this sub-agent task does (shown to user)\" }),\n\tagent: Type.Optional(Type.String({ description: \"Name of a predefined sub-agent from workspaceDir/sub-agents/\" })),\n\tname: Type.Optional(Type.String({ description: \"Optional display name for an inline sub-agent\" })),\n\ttask: Type.String({ description: \"Complete task description for the sub-agent\" }),\n\tsystemPrompt: Type.Optional(\n\t\tType.String({\n\t\t\tdescription: \"Optional inline system prompt for a temporary sub-agent. Use when no predefined agent fits.\",\n\t\t}),\n\t),\n\ttools: Type.Optional(Type.Array(Type.String(), { description: \"Optional tool whitelist for the sub-agent\" })),\n\tmodel: Type.Optional(\n\t\tType.String({ description: \"Optional exact model reference. Defaults to the parent's current model.\" }),\n\t),\n\tmaxTurns: Type.Optional(Type.Number({ description: \"Optional maximum assistant turns for this sub-agent\" })),\n\tmaxToolCalls: Type.Optional(Type.Number({ description: \"Optional maximum tool calls for this sub-agent\" })),\n\tmaxWallTimeSec: Type.Optional(\n\t\tType.Number({ description: \"Optional wall time budget in seconds for this sub-agent\" }),\n\t),\n\tbashTimeoutSec: Type.Optional(\n\t\tType.Number({ description: \"Optional default timeout in seconds for bash commands inside this sub-agent\" }),\n\t),\n});\n\ninterface UsageTotals {\n\tinput: number;\n\toutput: number;\n\tcacheRead: number;\n\tcacheWrite: number;\n\ttotal: number;\n\tcost: {\n\t\tinput: number;\n\t\toutput: number;\n\t\tcacheRead: number;\n\t\tcacheWrite: number;\n\t\ttotal: number;\n\t};\n}\n\nexport interface SubAgentToolDetails {\n\tagent: string;\n\tsource: \"predefined\" | \"inline\";\n\tmodel: string;\n\ttools: string[];\n\tturns: number;\n\ttoolCalls: number;\n\tdurationMs: number;\n\tfailed: boolean;\n\tfailureReason?: string;\n\tusage: UsageTotals;\n}\n\nexport interface SubAgentToolOptions {\n\texecutor: Executor;\n\tgetCurrentModel: () => Model<Api>;\n\tgetAvailableModels: () => Model<Api>[];\n\tresolveApiKey: (model: Model<Api>) => Promise<string>;\n\tworkspaceDir: string;\n}\n\nfunction createEmptyUsageTotals(): UsageTotals {\n\treturn {\n\t\tinput: 0,\n\t\toutput: 0,\n\t\tcacheRead: 0,\n\t\tcacheWrite: 0,\n\t\ttotal: 0,\n\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t};\n}\n\nfunction isAssistantMessage(message: AgentMessage): message is AssistantMessage {\n\treturn typeof message === \"object\" && message !== null && \"role\" in message && message.role === \"assistant\";\n}\n\nfunction extractAssistantText(message: AssistantMessage): string {\n\treturn message.content\n\t\t.filter((part): part is Extract<AssistantMessage[\"content\"][number], TextContent> => part.type === \"text\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\\n\")\n\t\t.trim();\n}\n\nfunction getLastAssistantMessage(messages: AgentMessage[]): AssistantMessage | null {\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tconst message = messages[i];\n\t\tif (isAssistantMessage(message)) {\n\t\t\treturn message;\n\t\t}\n\t}\n\treturn null;\n}\n\nfunction extractLabelFromArgs(args: unknown): string | null {\n\tif (!args || typeof args !== \"object\" || !(\"label\" in args)) {\n\t\treturn null;\n\t}\n\tconst label = (args as { label?: unknown }).label;\n\treturn typeof label === \"string\" && label.trim() ? label.trim() : null;\n}\n\nfunction formatStatus(agentName: string, text: string): string {\n\treturn `Subagent ${agentName}: ${text}`;\n}\n\nfunction buildFailureText(config: SubAgentConfig, reason: string, lastAssistantText: string): string {\n\tconst trimmedLastText = lastAssistantText.trim();\n\tif (!trimmedLastText) {\n\t\treturn `Sub-agent ${config.name} failed: ${reason}`;\n\t}\n\treturn `Sub-agent ${config.name} failed: ${reason}\\n\\nLast output:\\n${trimmedLastText}`;\n}\n\nfunction createToolSet(executor: Executor, bashTimeoutSec: number): AgentTool<any>[] {\n\treturn [\n\t\tcreateReadTool(executor),\n\t\tcreateBashTool(executor, { defaultTimeoutSeconds: bashTimeoutSec }),\n\t\tcreateEditTool(executor),\n\t\tcreateWriteTool(executor),\n\t];\n}\n\nfunction filterToolsByName(allTools: AgentTool<any>[], names: string[]): AgentTool<any>[] {\n\tconst allowed = new Set(names);\n\treturn allTools.filter((tool) => allowed.has(tool.name));\n}\n\nfunction createDetails(\n\tconfig: SubAgentConfig,\n\tusage: UsageTotals,\n\tturns: number,\n\ttoolCalls: number,\n\tdurationMs: number,\n\tfailed: boolean,\n\tfailureReason?: string,\n): SubAgentToolDetails {\n\treturn {\n\t\tagent: config.name,\n\t\tsource: config.source,\n\t\tmodel: formatModelReference(config.model!),\n\t\ttools: [...config.tools],\n\t\tturns,\n\t\ttoolCalls,\n\t\tdurationMs,\n\t\tfailed,\n\t\tfailureReason,\n\t\tusage: {\n\t\t\t...usage,\n\t\t\tcost: { ...usage.cost },\n\t\t},\n\t};\n}\n\nfunction linkAbortSignals(parentSignal: AbortSignal | undefined, childController: AbortController): () => void {\n\tif (!parentSignal) {\n\t\treturn () => {};\n\t}\n\n\tconst abortChild = () => childController.abort(parentSignal.reason);\n\tif (parentSignal.aborted) {\n\t\tabortChild();\n\t\treturn () => {};\n\t}\n\n\tparentSignal.addEventListener(\"abort\", abortChild, { once: true });\n\treturn () => parentSignal.removeEventListener(\"abort\", abortChild);\n}\n\nexport function createSubAgentTool(\n\toptions: SubAgentToolOptions,\n): AgentTool<typeof subagentSchema, SubAgentToolDetails> {\n\treturn {\n\t\tname: \"subagent\",\n\t\tlabel: \"subagent\",\n\t\tdescription:\n\t\t\t\"Delegate a task to a sub-agent with an isolated context. You may use a predefined sub-agent from workspaceDir/sub-agents/ or define a temporary inline sub-agent by providing systemPrompt/tools/model parameters. Sub-agents never receive the subagent tool, so they cannot create nested agents.\",\n\t\tparameters: subagentSchema,\n\t\texecute: async (_toolCallId, params, signal, onUpdate) => {\n\t\t\tconst availableModels = options.getAvailableModels();\n\t\t\tconst discovery = discoverSubAgents(options.workspaceDir, availableModels);\n\t\t\tconst currentModel = options.getCurrentModel();\n\t\t\tconst taskLengthError = validateSubAgentTask(params.task);\n\t\t\tif (taskLengthError) {\n\t\t\t\tthrow new Error(taskLengthError);\n\t\t\t}\n\t\t\tconst invocation = resolveSubAgentConfig(availableModels, currentModel, discovery.agents, params);\n\t\t\tif (!invocation.config) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`${invocation.error}\\n\\nAvailable predefined sub-agents:\\n${formatSubAgentList(discovery.agents)}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst config = invocation.config;\n\t\t\tconst apiKey = await options.resolveApiKey(config.model!);\n\t\t\tconst startedAt = Date.now();\n\t\t\tconst usage = createEmptyUsageTotals();\n\t\t\tlet assistantTurns = 0;\n\t\t\tlet toolCalls = 0;\n\t\t\tlet failureReason: string | undefined;\n\t\t\tlet lastUpdateText = \"\";\n\n\t\t\tconst emitUpdate = (text: string) => {\n\t\t\t\tconst nextText = text.trim();\n\t\t\t\tif (!nextText || nextText === lastUpdateText) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlastUpdateText = nextText;\n\t\t\t\tonUpdate?.({\n\t\t\t\t\tcontent: [{ type: \"text\", text: nextText }],\n\t\t\t\t\tdetails: createDetails(\n\t\t\t\t\t\tconfig,\n\t\t\t\t\t\tusage,\n\t\t\t\t\t\tassistantTurns,\n\t\t\t\t\t\ttoolCalls,\n\t\t\t\t\t\tDate.now() - startedAt,\n\t\t\t\t\t\tBoolean(failureReason),\n\t\t\t\t\t\tfailureReason,\n\t\t\t\t\t),\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tconst worker = new Agent({\n\t\t\t\tinitialState: {\n\t\t\t\t\tsystemPrompt: config.systemPrompt,\n\t\t\t\t\tmodel: config.model!,\n\t\t\t\t\tthinkingLevel: \"off\",\n\t\t\t\t\ttools: filterToolsByName(createToolSet(options.executor, config.bashTimeoutSec), config.tools),\n\t\t\t\t},\n\t\t\t\tconvertToLlm,\n\t\t\t\tgetApiKey: async () => apiKey,\n\t\t\t});\n\n\t\t\tconst childController = new AbortController();\n\t\t\tconst unlinkAbortSignals = linkAbortSignals(signal, childController);\n\t\t\tconst wallClockTimer = setTimeout(() => {\n\t\t\t\tfailureReason = `Wall time budget exceeded (${config.maxWallTimeSec}s)`;\n\t\t\t\tworker.abort();\n\t\t\t}, config.maxWallTimeSec * 1000);\n\n\t\t\tconst unsubscribe = worker.subscribe((event: AgentEvent) => {\n\t\t\t\tif (event.type === \"message_end\" && isAssistantMessage(event.message)) {\n\t\t\t\t\tassistantTurns++;\n\t\t\t\t\tconst messageUsage = event.message.usage;\n\t\t\t\t\tusage.input += messageUsage.input;\n\t\t\t\t\tusage.output += messageUsage.output;\n\t\t\t\t\tusage.cacheRead += messageUsage.cacheRead;\n\t\t\t\t\tusage.cacheWrite += messageUsage.cacheWrite;\n\t\t\t\t\tusage.total += messageUsage.totalTokens;\n\t\t\t\t\tusage.cost.input += messageUsage.cost.input;\n\t\t\t\t\tusage.cost.output += messageUsage.cost.output;\n\t\t\t\t\tusage.cost.cacheRead += messageUsage.cost.cacheRead;\n\t\t\t\t\tusage.cost.cacheWrite += messageUsage.cost.cacheWrite;\n\t\t\t\t\tusage.cost.total += messageUsage.cost.total;\n\t\t\t\t}\n\n\t\t\t\tif (event.type === \"tool_execution_start\") {\n\t\t\t\t\ttoolCalls++;\n\t\t\t\t\tconst label = extractLabelFromArgs(event.args) || event.toolName;\n\t\t\t\t\temitUpdate(formatStatus(config.name, label));\n\t\t\t\t\tif (toolCalls > config.maxToolCalls) {\n\t\t\t\t\t\tfailureReason = `Tool call budget exceeded (${config.maxToolCalls})`;\n\t\t\t\t\t\temitUpdate(formatStatus(config.name, \"tool budget reached\"));\n\t\t\t\t\t\tworker.abort();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (\n\t\t\t\t\tevent.type === \"turn_end\" &&\n\t\t\t\t\tisAssistantMessage(event.message) &&\n\t\t\t\t\tevent.toolResults.length > 0 &&\n\t\t\t\t\tassistantTurns >= config.maxTurns\n\t\t\t\t) {\n\t\t\t\t\tfailureReason = `Turn budget exceeded (${config.maxTurns})`;\n\t\t\t\t\temitUpdate(formatStatus(config.name, \"turn budget reached\"));\n\t\t\t\t\tworker.abort();\n\t\t\t\t}\n\t\t\t});\n\n\t\t\temitUpdate(formatStatus(config.name, \"started\"));\n\n\t\t\ttry {\n\t\t\t\tif (childController.signal.aborted) {\n\t\t\t\t\tthrow new Error(\"Sub-agent aborted\");\n\t\t\t\t}\n\n\t\t\t\tconst abortWorker = () => worker.abort();\n\t\t\t\tchildController.signal.addEventListener(\"abort\", abortWorker, { once: true });\n\t\t\t\ttry {\n\t\t\t\t\tawait worker.prompt(params.task);\n\t\t\t\t\tawait worker.waitForIdle();\n\t\t\t\t} finally {\n\t\t\t\t\tchildController.signal.removeEventListener(\"abort\", abortWorker);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tunsubscribe();\n\t\t\t\tunlinkAbortSignals();\n\t\t\t\tclearTimeout(wallClockTimer);\n\t\t\t}\n\n\t\t\tif (signal?.aborted) {\n\t\t\t\tthrow new Error(\"Sub-agent aborted\");\n\t\t\t}\n\n\t\t\tconst lastAssistantMessage = getLastAssistantMessage(worker.state.messages);\n\t\t\tconst durationMs = Date.now() - startedAt;\n\t\t\tif (!lastAssistantMessage) {\n\t\t\t\tfailureReason = failureReason || \"Sub-agent returned no assistant message\";\n\t\t\t\temitUpdate(formatStatus(config.name, \"failed\"));\n\t\t\t\tthrow new Error(`Sub-agent ${config.name} failed: ${failureReason}`);\n\t\t\t}\n\n\t\t\tconst finalText = extractAssistantText(lastAssistantMessage);\n\t\t\tconst effectiveFailureReason =\n\t\t\t\tfailureReason ||\n\t\t\t\t(lastAssistantMessage.stopReason === \"error\" || lastAssistantMessage.stopReason === \"aborted\"\n\t\t\t\t\t? lastAssistantMessage.errorMessage || `Sub-agent stopped with ${lastAssistantMessage.stopReason}`\n\t\t\t\t\t: undefined);\n\n\t\t\tif (effectiveFailureReason) {\n\t\t\t\temitUpdate(formatStatus(config.name, \"failed\"));\n\t\t\t\tthrow new Error(buildFailureText(config, effectiveFailureReason, finalText));\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: finalText || `(Sub-agent ${config.name} completed with no text output)` }],\n\t\t\t\tdetails: createDetails(config, usage, assistantTurns, toolCalls, durationMs, false),\n\t\t\t};\n\t\t},\n\t};\n}\n"]}