@oh-my-pi/pi-coding-agent 3.21.0 → 3.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/CHANGELOG.md +55 -1
  2. package/docs/sdk.md +47 -50
  3. package/examples/custom-tools/README.md +0 -15
  4. package/examples/hooks/custom-compaction.ts +1 -3
  5. package/examples/sdk/README.md +6 -10
  6. package/package.json +5 -5
  7. package/src/cli/args.ts +9 -6
  8. package/src/core/agent-session.ts +3 -3
  9. package/src/core/custom-commands/bundled/wt/index.ts +3 -0
  10. package/src/core/custom-tools/wrapper.ts +0 -1
  11. package/src/core/extensions/index.ts +1 -6
  12. package/src/core/extensions/wrapper.ts +0 -7
  13. package/src/core/file-mentions.ts +5 -8
  14. package/src/core/sdk.ts +48 -111
  15. package/src/core/session-manager.ts +7 -0
  16. package/src/core/system-prompt.ts +22 -33
  17. package/src/core/tools/ask.ts +14 -7
  18. package/src/core/tools/bash-interceptor.ts +4 -4
  19. package/src/core/tools/bash.ts +19 -9
  20. package/src/core/tools/complete.ts +131 -0
  21. package/src/core/tools/context.ts +7 -0
  22. package/src/core/tools/edit.ts +8 -15
  23. package/src/core/tools/exa/render.ts +4 -16
  24. package/src/core/tools/find.ts +7 -18
  25. package/src/core/tools/git.ts +13 -3
  26. package/src/core/tools/grep.ts +7 -18
  27. package/src/core/tools/index.test.ts +188 -0
  28. package/src/core/tools/index.ts +106 -236
  29. package/src/core/tools/jtd-to-json-schema.ts +274 -0
  30. package/src/core/tools/ls.ts +4 -9
  31. package/src/core/tools/lsp/index.ts +32 -29
  32. package/src/core/tools/lsp/render.ts +7 -28
  33. package/src/core/tools/notebook.ts +3 -5
  34. package/src/core/tools/output.ts +130 -31
  35. package/src/core/tools/read.ts +8 -19
  36. package/src/core/tools/review.ts +0 -18
  37. package/src/core/tools/rulebook.ts +8 -2
  38. package/src/core/tools/task/agents.ts +28 -7
  39. package/src/core/tools/task/artifacts.ts +6 -9
  40. package/src/core/tools/task/discovery.ts +0 -6
  41. package/src/core/tools/task/executor.ts +306 -257
  42. package/src/core/tools/task/index.ts +65 -235
  43. package/src/core/tools/task/name-generator.ts +247 -0
  44. package/src/core/tools/task/render.ts +158 -19
  45. package/src/core/tools/task/types.ts +13 -11
  46. package/src/core/tools/task/worker-protocol.ts +18 -0
  47. package/src/core/tools/task/worker.ts +270 -0
  48. package/src/core/tools/web-fetch.ts +4 -36
  49. package/src/core/tools/web-search/index.ts +2 -1
  50. package/src/core/tools/web-search/render.ts +1 -4
  51. package/src/core/tools/write.ts +7 -15
  52. package/src/discovery/helpers.test.ts +1 -1
  53. package/src/index.ts +5 -16
  54. package/src/main.ts +4 -4
  55. package/src/modes/interactive/theme/theme.ts +4 -4
  56. package/src/prompts/task.md +14 -57
  57. package/src/prompts/tools/output.md +4 -3
  58. package/src/prompts/tools/task.md +70 -0
  59. package/examples/custom-tools/question/index.ts +0 -84
  60. package/examples/custom-tools/subagent/README.md +0 -172
  61. package/examples/custom-tools/subagent/agents/planner.md +0 -37
  62. package/examples/custom-tools/subagent/agents/scout.md +0 -50
  63. package/examples/custom-tools/subagent/agents/worker.md +0 -24
  64. package/examples/custom-tools/subagent/agents.ts +0 -156
  65. package/examples/custom-tools/subagent/commands/implement-and-review.md +0 -10
  66. package/examples/custom-tools/subagent/commands/implement.md +0 -10
  67. package/examples/custom-tools/subagent/commands/scout-and-plan.md +0 -9
  68. package/examples/custom-tools/subagent/index.ts +0 -1002
  69. package/examples/sdk/05-tools.ts +0 -94
  70. package/examples/sdk/12-full-control.ts +0 -95
  71. package/src/prompts/browser.md +0 -71
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Generate memorable two-word task identifiers.
3
+ * Format: AdjectiveNoun (e.g., "SwiftFalcon", "CalmPanda")
4
+ *
5
+ * Dictionaries sourced from unique-names-generator (MIT license).
6
+ * 1202 adjectives × 355 animals = 426,710 combinations.
7
+ */
8
+
9
+ const ADJECTIVES = [
10
+ "able", "above", "absent", "absolute", "abstract", "abundant", "academic", "acceptable",
11
+ "accepted", "accessible", "accurate", "accused", "active", "actual", "acute", "added",
12
+ "additional", "adequate", "adjacent", "administrative", "adorable", "advanced", "adverse",
13
+ "advisory", "aesthetic", "afraid", "aggregate", "aggressive", "agreeable", "agreed",
14
+ "agricultural", "alert", "alive", "alleged", "allied", "alone", "alright", "alternative",
15
+ "amateur", "amazing", "ambitious", "amused", "ancient", "angry", "annoyed", "annual",
16
+ "anonymous", "anxious", "appalling", "apparent", "applicable", "appropriate", "arbitrary",
17
+ "architectural", "armed", "arrogant", "artificial", "artistic", "ashamed", "asleep",
18
+ "assistant", "associated", "atomic", "attractive", "automatic", "autonomous", "available",
19
+ "average", "awake", "aware", "awful", "awkward", "back", "bad", "balanced", "bare", "basic",
20
+ "beautiful", "beneficial", "better", "bewildered", "big", "binding", "biological", "bitter",
21
+ "bizarre", "blank", "blind", "blonde", "bloody", "blushing", "boiling", "bold", "bored",
22
+ "boring", "bottom", "brainy", "brave", "breakable", "breezy", "brief", "bright", "brilliant",
23
+ "broad", "broken", "bumpy", "burning", "busy", "calm", "capable", "capitalist", "careful",
24
+ "casual", "causal", "cautious", "central", "certain", "changing", "characteristic", "charming",
25
+ "cheap", "cheerful", "chemical", "chief", "chilly", "chosen", "chronic", "chubby", "circular",
26
+ "civic", "civil", "civilian", "classic", "classical", "clean", "clear", "clever", "clinical",
27
+ "close", "closed", "cloudy", "clumsy", "coastal", "cognitive", "coherent", "cold", "collective",
28
+ "colonial", "colorful", "colossal", "coloured", "colourful", "combative", "combined",
29
+ "comfortable", "coming", "commercial", "common", "communist", "compact", "comparable",
30
+ "comparative", "compatible", "competent", "competitive", "complete", "complex", "complicated",
31
+ "comprehensive", "compulsory", "conceptual", "concerned", "concrete", "condemned", "confident",
32
+ "confidential", "confused", "conscious", "conservation", "conservative", "considerable",
33
+ "consistent", "constant", "constitutional", "contemporary", "content", "continental",
34
+ "continued", "continuing", "continuous", "controlled", "controversial", "convenient",
35
+ "conventional", "convinced", "convincing", "cooing", "cool", "cooperative", "corporate",
36
+ "correct", "corresponding", "costly", "courageous", "crazy", "creative", "creepy", "criminal",
37
+ "critical", "crooked", "crowded", "crucial", "crude", "cruel", "cuddly", "cultural", "curious",
38
+ "curly", "current", "curved", "cute", "daily", "damaged", "damp", "dangerous", "dark", "dead",
39
+ "deaf", "deafening", "dear", "decent", "decisive", "deep", "defeated", "defensive", "defiant",
40
+ "definite", "deliberate", "delicate", "delicious", "delighted", "delightful", "democratic",
41
+ "dependent", "depressed", "desirable", "desperate", "detailed", "determined", "developed",
42
+ "developing", "devoted", "different", "difficult", "digital", "diplomatic", "direct", "dirty",
43
+ "disabled", "disappointed", "disastrous", "disciplinary", "disgusted", "distant", "distinct",
44
+ "distinctive", "distinguished", "disturbed", "disturbing", "diverse", "divine", "dizzy",
45
+ "domestic", "dominant", "double", "doubtful", "drab", "dramatic", "dreadful", "driving",
46
+ "drunk", "dry", "dual", "due", "dull", "dusty", "dying", "dynamic", "eager", "early", "eastern",
47
+ "easy", "economic", "educational", "eerie", "effective", "efficient", "elaborate", "elated",
48
+ "elderly", "eldest", "electoral", "electric", "electrical", "electronic", "elegant", "eligible",
49
+ "embarrassed", "embarrassing", "emotional", "empirical", "empty", "enchanting", "encouraging",
50
+ "endless", "energetic", "enormous", "enthusiastic", "entire", "entitled", "envious",
51
+ "environmental", "equal", "equivalent", "essential", "established", "estimated", "ethical",
52
+ "ethnic", "eventual", "everyday", "evident", "evil", "evolutionary", "exact", "excellent",
53
+ "exceptional", "excess", "excessive", "excited", "exciting", "exclusive", "existing", "exotic",
54
+ "expected", "expensive", "experienced", "experimental", "explicit", "extended", "extensive",
55
+ "external", "extra", "extraordinary", "extreme", "exuberant", "faint", "fair", "faithful",
56
+ "familiar", "famous", "fancy", "fantastic", "far", "fascinating", "fashionable", "fast", "fat",
57
+ "fatal", "favourable", "favourite", "federal", "fellow", "female", "feminist", "few", "fierce",
58
+ "filthy", "final", "financial", "fine", "firm", "fiscal", "fit", "fixed", "flaky", "flat",
59
+ "flexible", "fluffy", "fluttering", "flying", "following", "fond", "foolish", "foreign",
60
+ "formal", "formidable", "forthcoming", "fortunate", "forward", "fragile", "frail", "frantic",
61
+ "free", "frequent", "fresh", "friendly", "frightened", "front", "frozen", "full", "fun",
62
+ "functional", "fundamental", "funny", "furious", "future", "fuzzy", "gastric", "gay", "general",
63
+ "generous", "genetic", "gentle", "genuine", "geographical", "giant", "gigantic", "given",
64
+ "glad", "glamorous", "gleaming", "global", "glorious", "golden", "good", "gorgeous", "gothic",
65
+ "governing", "graceful", "gradual", "grand", "grateful", "greasy", "great", "grieving", "grim",
66
+ "gross", "grotesque", "growing", "grubby", "grumpy", "guilty", "handicapped", "handsome",
67
+ "happy", "hard", "harsh", "head", "healthy", "heavy", "helpful", "helpless", "hidden", "high",
68
+ "hilarious", "hissing", "historic", "historical", "hollow", "holy", "homeless", "homely",
69
+ "honest", "horizontal", "horrible", "hostile", "hot", "huge", "human", "hungry", "hurt",
70
+ "hushed", "husky", "icy", "ideal", "identical", "ideological", "ill", "illegal", "imaginative",
71
+ "immediate", "immense", "imperial", "implicit", "important", "impossible", "impressed",
72
+ "impressive", "improved", "inadequate", "inappropriate", "inclined", "increased", "increasing",
73
+ "incredible", "independent", "indirect", "individual", "industrial", "inevitable", "influential",
74
+ "informal", "inherent", "initial", "injured", "inland", "inner", "innocent", "innovative",
75
+ "inquisitive", "instant", "institutional", "insufficient", "intact", "integral", "integrated",
76
+ "intellectual", "intelligent", "intense", "intensive", "interested", "interesting", "interim",
77
+ "interior", "intermediate", "internal", "international", "intimate", "invisible", "involved",
78
+ "irrelevant", "isolated", "itchy", "jealous", "jittery", "joint", "jolly", "joyous", "judicial",
79
+ "juicy", "junior", "just", "keen", "key", "kind", "known", "labour", "large", "late", "latin",
80
+ "lazy", "leading", "left", "legal", "legislative", "legitimate", "lengthy", "lesser", "level",
81
+ "lexical", "liable", "liberal", "light", "like", "likely", "limited", "linear", "linguistic",
82
+ "liquid", "literary", "little", "live", "lively", "living", "local", "logical", "lonely",
83
+ "long", "loose", "lost", "loud", "lovely", "low", "loyal", "lucky", "mad", "magic", "magnetic",
84
+ "magnificent", "main", "major", "male", "mammoth", "managerial", "managing", "manual", "many",
85
+ "marginal", "marine", "marked", "married", "marvellous", "marxist", "mass", "massive",
86
+ "mathematical", "mature", "maximum", "mean", "meaningful", "mechanical", "medical", "medieval",
87
+ "melodic", "melted", "mental", "mere", "metropolitan", "mid", "middle", "mighty", "mild",
88
+ "military", "miniature", "minimal", "minimum", "ministerial", "minor", "miserable", "misleading",
89
+ "missing", "misty", "mixed", "moaning", "mobile", "moderate", "modern", "modest", "molecular",
90
+ "monetary", "monthly", "moral", "motionless", "muddy", "multiple", "mushy", "musical", "mute",
91
+ "mutual", "mysterious", "naked", "narrow", "nasty", "national", "native", "natural", "naughty",
92
+ "naval", "near", "nearby", "neat", "necessary", "negative", "neighbouring", "nervous", "net",
93
+ "neutral", "new", "nice", "noble", "noisy", "normal", "northern", "nosy", "notable", "novel",
94
+ "nuclear", "numerous", "nursing", "nutritious", "nutty", "obedient", "objective", "obliged",
95
+ "obnoxious", "obvious", "occasional", "occupational", "odd", "official", "old", "olympic",
96
+ "only", "open", "operational", "opposite", "optimistic", "oral", "ordinary", "organic",
97
+ "organisational", "original", "orthodox", "other", "outdoor", "outer", "outrageous", "outside",
98
+ "outstanding", "overall", "overseas", "overwhelming", "painful", "pale", "panicky", "parallel",
99
+ "parental", "parliamentary", "partial", "particular", "passing", "passive", "past", "patient",
100
+ "payable", "peaceful", "peculiar", "perfect", "permanent", "persistent", "personal", "petite",
101
+ "philosophical", "physical", "plain", "planned", "plastic", "pleasant", "pleased", "poised",
102
+ "polite", "political", "poor", "popular", "positive", "possible", "potential", "powerful",
103
+ "practical", "precious", "precise", "preferred", "pregnant", "preliminary", "premier",
104
+ "prepared", "present", "presidential", "pretty", "previous", "prickly", "primary", "prime",
105
+ "primitive", "principal", "printed", "prior", "private", "probable", "productive", "professional",
106
+ "profitable", "profound", "progressive", "prominent", "promising", "proper", "proposed",
107
+ "prospective", "protective", "proud", "provincial", "psychiatric", "psychological", "public",
108
+ "puny", "pure", "purring", "puzzled", "quaint", "qualified", "quarrelsome", "querulous",
109
+ "quick", "quickest", "quiet", "quintessential", "quixotic", "racial", "radical", "rainy",
110
+ "random", "rapid", "rare", "raspy", "rational", "ratty", "raw", "ready", "real", "realistic",
111
+ "rear", "reasonable", "recent", "reduced", "redundant", "regional", "registered", "regular",
112
+ "regulatory", "related", "relative", "relaxed", "relevant", "reliable", "relieved", "religious",
113
+ "reluctant", "remaining", "remarkable", "remote", "renewed", "representative", "repulsive",
114
+ "required", "resident", "residential", "resonant", "respectable", "respective", "responsible",
115
+ "resulting", "retail", "retired", "revolutionary", "rich", "ridiculous", "right", "rigid",
116
+ "ripe", "rising", "rival", "roasted", "robust", "rolling", "romantic", "rotten", "rough",
117
+ "round", "royal", "rubber", "rude", "ruling", "running", "rural", "sacred", "sad", "safe",
118
+ "salty", "satisfactory", "satisfied", "scared", "scary", "scattered", "scientific", "scornful",
119
+ "scrawny", "screeching", "secondary", "secret", "secure", "select", "selected", "selective",
120
+ "selfish", "semantic", "senior", "sensible", "sensitive", "separate", "serious", "severe",
121
+ "sexual", "shaggy", "shaky", "shallow", "shared", "sharp", "sheer", "shiny", "shivering",
122
+ "shocked", "short", "shrill", "shy", "sick", "significant", "silent", "silky", "silly",
123
+ "similar", "simple", "single", "skilled", "skinny", "sleepy", "slight", "slim", "slimy",
124
+ "slippery", "slow", "small", "smart", "smiling", "smoggy", "smooth", "social", "socialist",
125
+ "soft", "solar", "sole", "solid", "sophisticated", "sore", "sorry", "sound", "sour", "southern",
126
+ "soviet", "spare", "sparkling", "spatial", "special", "specific", "specified", "spectacular",
127
+ "spicy", "spiritual", "splendid", "spontaneous", "sporting", "spotless", "spotty", "square",
128
+ "squealing", "stable", "stale", "standard", "static", "statistical", "statutory", "steady",
129
+ "steep", "sticky", "stiff", "still", "stingy", "stormy", "straight", "straightforward",
130
+ "strange", "strategic", "strict", "striking", "striped", "strong", "structural", "stuck",
131
+ "stupid", "subjective", "subsequent", "substantial", "subtle", "successful", "successive",
132
+ "sudden", "sufficient", "suitable", "sunny", "super", "superb", "superior", "supporting",
133
+ "supposed", "supreme", "sure", "surprised", "surprising", "surrounding", "surviving",
134
+ "suspicious", "sweet", "swift", "symbolic", "sympathetic", "systematic", "tall", "tame",
135
+ "tart", "tasteless", "tasty", "technical", "technological", "teenage", "temporary", "tender",
136
+ "tense", "terrible", "territorial", "testy", "theoretical", "thick", "thin", "thirsty",
137
+ "thorough", "thoughtful", "thoughtless", "thundering", "tight", "tiny", "tired", "top", "total",
138
+ "tough", "toxic", "traditional", "tragic", "tremendous", "tricky", "tropical", "troubled",
139
+ "typical", "ugliest", "ugly", "ultimate", "unable", "unacceptable", "unaware", "uncertain",
140
+ "unchanged", "uncomfortable", "unconscious", "underground", "underlying", "unemployed",
141
+ "uneven", "unexpected", "unfair", "unfortunate", "unhappy", "uniform", "uninterested", "unique",
142
+ "united", "universal", "unknown", "unlikely", "unnecessary", "unpleasant", "unsightly",
143
+ "unusual", "unwilling", "upper", "upset", "uptight", "urban", "urgent", "used", "useful",
144
+ "useless", "usual", "vague", "valid", "valuable", "variable", "varied", "various", "varying",
145
+ "vast", "verbal", "vertical", "vicarious", "vicious", "victorious", "violent", "visible",
146
+ "visiting", "visual", "vital", "vivacious", "vivid", "vocal", "vocational", "voiceless",
147
+ "voluminous", "voluntary", "vulnerable", "wandering", "warm", "wasteful", "watery", "weak",
148
+ "wealthy", "weary", "weekly", "weird", "welcome", "well", "western", "wet", "whispering",
149
+ "whole", "wicked", "wide", "widespread", "wild", "willing", "willowy", "wily", "wise", "wispy",
150
+ "witty", "wonderful", "wooden", "working", "worldwide", "worried", "worrying", "worthwhile",
151
+ "worthy", "written", "wrong", "xenial", "xeric", "yawning", "yearning", "young", "youngest",
152
+ "youthful", "zany", "zealous", "zesty", "zippy",
153
+ ];
154
+
155
+ const NOUNS = [
156
+ "aardvark", "aardwolf", "albatross", "alligator", "alpaca", "amphibian", "anaconda",
157
+ "angelfish", "anglerfish", "ant", "anteater", "antelope", "antlion", "ape", "aphid",
158
+ "armadillo", "asp", "baboon", "badger", "bandicoot", "barnacle", "barracuda", "basilisk",
159
+ "bass", "bat", "bear", "beaver", "bedbug", "bee", "beetle", "bird", "bison", "blackbird",
160
+ "boa", "boar", "bobcat", "bobolink", "bonobo", "booby", "bovid", "bug", "butterfly", "buzzard",
161
+ "camel", "canid", "capybara", "cardinal", "caribou", "carp", "cat", "caterpillar", "catfish",
162
+ "catshark", "cattle", "centipede", "cephalopod", "chameleon", "cheetah", "chickadee", "chicken",
163
+ "chimpanzee", "chinchilla", "chipmunk", "cicada", "clam", "clownfish", "cobra", "cockroach",
164
+ "cod", "condor", "constrictor", "coral", "cougar", "cow", "coyote", "crab", "crane", "crawdad",
165
+ "crayfish", "cricket", "crocodile", "crow", "cuckoo", "damselfly", "deer", "dingo", "dinosaur",
166
+ "dog", "dolphin", "donkey", "dormouse", "dove", "dragon", "dragonfly", "duck", "eagle",
167
+ "earthworm", "earwig", "echidna", "eel", "egret", "elephant", "elk", "emu", "ermine", "falcon",
168
+ "ferret", "finch", "firefly", "fish", "flamingo", "flea", "fly", "flyingfish", "fowl", "fox",
169
+ "frog", "gamefowl", "gazelle", "gecko", "gerbil", "gibbon", "giraffe", "goat", "goldfish",
170
+ "goose", "gopher", "gorilla", "grasshopper", "grouse", "guan", "guanaco", "guineafowl", "gull",
171
+ "guppy", "haddock", "halibut", "hamster", "hare", "harrier", "hawk", "hedgehog", "heron",
172
+ "herring", "hippopotamus", "hookworm", "hornet", "horse", "hoverfly", "hummingbird", "hyena",
173
+ "iguana", "impala", "jackal", "jaguar", "jay", "jellyfish", "kangaroo", "kingfisher", "kite",
174
+ "kiwi", "koala", "koi", "krill", "ladybug", "lamprey", "lark", "leech", "lemming", "lemur",
175
+ "leopard", "leopon", "limpet", "lion", "lizard", "llama", "lobster", "locust", "loon", "louse",
176
+ "lungfish", "lynx", "macaw", "mackerel", "magpie", "mammal", "manatee", "mandrill", "marlin",
177
+ "marmoset", "marmot", "marsupial", "marten", "mastodon", "meadowlark", "meerkat", "mink",
178
+ "minnow", "mite", "mockingbird", "mole", "mollusk", "mongoose", "monkey", "moose", "mosquito",
179
+ "moth", "mouse", "mule", "muskox", "narwhal", "newt", "nightingale", "ocelot", "octopus",
180
+ "opossum", "orangutan", "orca", "ostrich", "otter", "owl", "ox", "panda", "panther", "parakeet",
181
+ "parrot", "parrotfish", "partridge", "peacock", "peafowl", "pelican", "penguin", "perch",
182
+ "pheasant", "pig", "pigeon", "pike", "pinniped", "piranha", "planarian", "platypus", "pony",
183
+ "porcupine", "porpoise", "possum", "prawn", "primate", "ptarmigan", "puffin", "puma", "python",
184
+ "quail", "quelea", "quokka", "rabbit", "raccoon", "rat", "rattlesnake", "raven", "reindeer",
185
+ "reptile", "rhinoceros", "roadrunner", "rodent", "rook", "rooster", "roundworm", "sailfish",
186
+ "salamander", "salmon", "sawfish", "scallop", "scorpion", "seahorse", "shark", "sheep", "shrew",
187
+ "shrimp", "silkworm", "silverfish", "skink", "skunk", "sloth", "slug", "smelt", "snail",
188
+ "snake", "snipe", "sole", "sparrow", "spider", "spoonbill", "squid", "squirrel", "starfish",
189
+ "stingray", "stoat", "stork", "sturgeon", "swallow", "swan", "swift", "swordfish", "swordtail",
190
+ "tahr", "takin", "tapir", "tarantula", "tarsier", "termite", "tern", "thrush", "tick", "tiger",
191
+ "tiglon", "toad", "tortoise", "toucan", "trout", "tuna", "turkey", "turtle", "tyrannosaurus",
192
+ "unicorn", "urial", "vicuna", "viper", "vole", "vulture", "wallaby", "walrus", "warbler",
193
+ "wasp", "weasel", "whale", "whippet", "whitefish", "wildcat", "wildebeest", "wildfowl", "wolf",
194
+ "wolverine", "wombat", "woodpecker", "worm", "wren", "yak", "zebra",
195
+ ];
196
+
197
+ function capitalize(s: string): string {
198
+ return s.charAt(0).toUpperCase() + s.slice(1);
199
+ }
200
+
201
+ let usedNames = new Set<string>();
202
+
203
+ /**
204
+ * Generate a unique two-word identifier (e.g., "SwiftFalcon").
205
+ * Falls back to numeric suffix if all combinations exhausted.
206
+ */
207
+ export function generateTaskName(): string {
208
+ // Try random combinations first (50 attempts)
209
+ for (let attempt = 0; attempt < 50; attempt++) {
210
+ const adj = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
211
+ const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
212
+ const name = `${capitalize(adj)}${capitalize(noun)}`;
213
+ if (!usedNames.has(name)) {
214
+ usedNames.add(name);
215
+ return name;
216
+ }
217
+ }
218
+
219
+ // Exhaustive search if random fails
220
+ for (const adj of ADJECTIVES) {
221
+ for (const noun of NOUNS) {
222
+ const name = `${capitalize(adj)}${capitalize(noun)}`;
223
+ if (!usedNames.has(name)) {
224
+ usedNames.add(name);
225
+ return name;
226
+ }
227
+ }
228
+ }
229
+
230
+ // All 426k combinations used, add numeric suffix
231
+ let counter = 0;
232
+ while (true) {
233
+ const name = `Task${counter}`;
234
+ if (!usedNames.has(name)) {
235
+ usedNames.add(name);
236
+ return name;
237
+ }
238
+ counter++;
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Reset name generator state (for testing).
244
+ */
245
+ export function resetTaskNames(): void {
246
+ usedNames = new Set<string>();
247
+ }
@@ -69,6 +69,118 @@ function formatFindingSummary(findings: ReportFindingDetails[], theme: Theme): s
69
69
  return `${theme.fg("dim", "Findings:")} ${parts.join(theme.sep.dot)}`;
70
70
  }
71
71
 
72
+ function formatJsonScalar(value: unknown): string {
73
+ if (value === null) return "null";
74
+ if (typeof value === "string") return `"${value}"`;
75
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
76
+ return "";
77
+ }
78
+
79
+ function buildTreePrefix(ancestors: boolean[], theme: Theme): string {
80
+ return ancestors.map((hasNext) => (hasNext ? `${theme.tree.vertical} ` : " ")).join("");
81
+ }
82
+
83
+ function renderJsonTreeLines(
84
+ value: unknown,
85
+ theme: Theme,
86
+ maxDepth: number,
87
+ maxLines: number,
88
+ ): { lines: string[]; truncated: boolean } {
89
+ const lines: string[] = [];
90
+ let truncated = false;
91
+
92
+ const iconObject = theme.styledSymbol("icon.folder", "muted");
93
+ const iconArray = theme.styledSymbol("icon.package", "muted");
94
+ const iconScalar = theme.styledSymbol("icon.file", "muted");
95
+
96
+ const pushLine = (line: string) => {
97
+ if (lines.length >= maxLines) {
98
+ truncated = true;
99
+ return false;
100
+ }
101
+ lines.push(line);
102
+ return true;
103
+ };
104
+
105
+ const renderNode = (val: unknown, key: string | undefined, ancestors: boolean[], isLast: boolean, depth: number) => {
106
+ if (lines.length >= maxLines) {
107
+ truncated = true;
108
+ return;
109
+ }
110
+
111
+ const connector = isLast ? theme.tree.last : theme.tree.branch;
112
+ const prefix = `${buildTreePrefix(ancestors, theme)}${theme.fg("dim", connector)} `;
113
+ const scalar = formatJsonScalar(val);
114
+
115
+ if (scalar) {
116
+ const label = key ? theme.fg("muted", key) : theme.fg("muted", "value");
117
+ pushLine(`${prefix}${iconScalar} ${label}: ${theme.fg("dim", scalar)}`);
118
+ return;
119
+ }
120
+
121
+ if (Array.isArray(val)) {
122
+ const header = key ? theme.fg("muted", key) : theme.fg("muted", "array");
123
+ pushLine(`${prefix}${iconArray} ${header}`);
124
+ if (val.length === 0) {
125
+ pushLine(
126
+ `${buildTreePrefix([...ancestors, !isLast], theme)}${theme.fg("dim", theme.tree.hook)} ${theme.fg("dim", "[]")}`,
127
+ );
128
+ return;
129
+ }
130
+ if (depth >= maxDepth) {
131
+ pushLine(
132
+ `${buildTreePrefix([...ancestors, !isLast], theme)}${theme.fg("dim", theme.tree.hook)} ${theme.fg("dim", theme.format.ellipsis)}`,
133
+ );
134
+ return;
135
+ }
136
+ const nextAncestors = [...ancestors, !isLast];
137
+ for (let i = 0; i < val.length; i++) {
138
+ renderNode(val[i], `[${i}]`, nextAncestors, i === val.length - 1, depth + 1);
139
+ if (lines.length >= maxLines) {
140
+ truncated = true;
141
+ return;
142
+ }
143
+ }
144
+ return;
145
+ }
146
+
147
+ if (val && typeof val === "object") {
148
+ const header = key ? theme.fg("muted", key) : theme.fg("muted", "object");
149
+ pushLine(`${prefix}${iconObject} ${header}`);
150
+ const entries = Object.entries(val as Record<string, unknown>);
151
+ if (entries.length === 0) {
152
+ pushLine(
153
+ `${buildTreePrefix([...ancestors, !isLast], theme)}${theme.fg("dim", theme.tree.hook)} ${theme.fg("dim", "{}")}`,
154
+ );
155
+ return;
156
+ }
157
+ if (depth >= maxDepth) {
158
+ pushLine(
159
+ `${buildTreePrefix([...ancestors, !isLast], theme)}${theme.fg("dim", theme.tree.hook)} ${theme.fg("dim", theme.format.ellipsis)}`,
160
+ );
161
+ return;
162
+ }
163
+ const nextAncestors = [...ancestors, !isLast];
164
+ for (let i = 0; i < entries.length; i++) {
165
+ const [childKey, child] = entries[i];
166
+ renderNode(child, childKey, nextAncestors, i === entries.length - 1, depth + 1);
167
+ if (lines.length >= maxLines) {
168
+ truncated = true;
169
+ return;
170
+ }
171
+ }
172
+ return;
173
+ }
174
+
175
+ const label = key ? theme.fg("muted", key) : theme.fg("muted", "value");
176
+ pushLine(`${prefix}${iconScalar} ${label}: ${theme.fg("dim", String(val))}`);
177
+ };
178
+
179
+ renderNode(value, undefined, [], true, 0);
180
+
181
+ return { lines, truncated };
182
+ }
183
+
72
184
  function renderOutputSection(
73
185
  output: string,
74
186
  continuePrefix: string,
@@ -78,11 +190,30 @@ function renderOutputSection(
78
190
  maxExpanded = 10,
79
191
  ): string[] {
80
192
  const lines: string[] = [];
81
- const outputLines = output.split("\n").filter((line) => line.trim());
82
- if (outputLines.length === 0) return lines;
193
+ const trimmedOutput = output.trim();
194
+ if (!trimmedOutput) return lines;
83
195
 
84
196
  lines.push(`${continuePrefix}${theme.fg("dim", "Output")}`);
85
197
 
198
+ if (trimmedOutput.startsWith("{") || trimmedOutput.startsWith("[")) {
199
+ try {
200
+ const parsed = JSON.parse(trimmedOutput);
201
+ const tree = renderJsonTreeLines(parsed, theme, expanded ? 6 : 2, expanded ? 24 : 6);
202
+ if (tree.lines.length > 0) {
203
+ for (const line of tree.lines) {
204
+ lines.push(`${continuePrefix} ${line}`);
205
+ }
206
+ if (tree.truncated) {
207
+ lines.push(`${continuePrefix} ${theme.fg("dim", theme.format.ellipsis)}`);
208
+ }
209
+ return lines;
210
+ }
211
+ } catch {
212
+ // Fall back to raw output
213
+ }
214
+ }
215
+
216
+ const outputLines = output.split("\n").filter((line) => line.trim());
86
217
  const previewCount = expanded ? maxExpanded : maxCollapsed;
87
218
  for (const line of outputLines.slice(0, previewCount)) {
88
219
  lines.push(`${continuePrefix} ${theme.fg("dim", truncate(line, 70, theme.format.ellipsis))}`);
@@ -144,9 +275,8 @@ function renderAgentProgress(
144
275
  ? "error"
145
276
  : "accent";
146
277
 
147
- // Main status line - include index for Output tool ID derivation
148
- const agentId = `${progress.agent}(${progress.index})`;
149
- let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", agentId)}`;
278
+ // Main status line - use taskId for Output tool
279
+ let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", progress.taskId)}`;
150
280
  const description = progress.description?.trim();
151
281
  if (description) {
152
282
  statusLine += ` ${theme.fg("muted", truncate(description, 40, theme.format.ellipsis))}`;
@@ -181,19 +311,29 @@ function renderAgentProgress(
181
311
 
182
312
  lines.push(statusLine);
183
313
 
184
- // Current tool (if running)
185
- if (progress.status === "running" && progress.currentTool) {
186
- let toolLine = `${continuePrefix}${theme.tree.hook} ${theme.fg("muted", progress.currentTool)}`;
187
- if (progress.currentToolArgs) {
188
- toolLine += `: ${theme.fg("dim", truncate(progress.currentToolArgs, 40, theme.format.ellipsis))}`;
189
- }
190
- if (progress.currentToolStartMs) {
191
- const elapsed = Date.now() - progress.currentToolStartMs;
192
- if (elapsed > 5000) {
193
- toolLine += `${theme.sep.dot}${theme.fg("warning", formatDuration(elapsed))}`;
314
+ // Current tool (if running) or most recent completed tool
315
+ if (progress.status === "running") {
316
+ if (progress.currentTool) {
317
+ let toolLine = `${continuePrefix}${theme.tree.hook} ${theme.fg("muted", progress.currentTool)}`;
318
+ if (progress.currentToolArgs) {
319
+ toolLine += `: ${theme.fg("dim", truncate(progress.currentToolArgs, 40, theme.format.ellipsis))}`;
320
+ }
321
+ if (progress.currentToolStartMs) {
322
+ const elapsed = Date.now() - progress.currentToolStartMs;
323
+ if (elapsed > 5000) {
324
+ toolLine += `${theme.sep.dot}${theme.fg("warning", formatDuration(elapsed))}`;
325
+ }
326
+ }
327
+ lines.push(toolLine);
328
+ } else if (progress.recentTools.length > 0) {
329
+ // Show most recent completed tool when idle between tools
330
+ const recent = progress.recentTools[0];
331
+ let toolLine = `${continuePrefix}${theme.tree.hook} ${theme.fg("dim", recent.tool)}`;
332
+ if (recent.args) {
333
+ toolLine += `: ${theme.fg("dim", truncate(recent.args, 40, theme.format.ellipsis))}`;
194
334
  }
335
+ lines.push(toolLine);
195
336
  }
196
- lines.push(toolLine);
197
337
  }
198
338
 
199
339
  // Render extracted tool data inline (e.g., review findings)
@@ -332,9 +472,8 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
332
472
  const iconColor = success ? "success" : "error";
333
473
  const statusText = aborted ? "aborted" : success ? "done" : "failed";
334
474
 
335
- // Main status line - include index for Output tool ID derivation
336
- const agentId = `${result.agent}(${result.index})`;
337
- let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", agentId)} ${formatBadge(statusText, iconColor, theme)}`;
475
+ // Main status line - use taskId for Output tool
476
+ let statusLine = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", result.taskId)} ${formatBadge(statusText, iconColor, theme)}`;
338
477
  const description = result.description?.trim();
339
478
  if (description) {
340
479
  statusLine += ` ${theme.fg("muted", truncate(description, 40, theme.format.ellipsis))}`;
@@ -19,14 +19,11 @@ export const MAX_OUTPUT_LINES = 5000;
19
19
  /** Maximum agents to show in description */
20
20
  export const MAX_AGENTS_IN_DESCRIPTION = 10;
21
21
 
22
- /** Environment variable to inhibit subagent spawning (legacy, still checked for backwards compat) */
23
- export const OMP_NO_SUBAGENTS_ENV = "OMP_NO_SUBAGENTS";
22
+ /** EventBus channel for raw subagent events */
23
+ export const TASK_SUBAGENT_EVENT_CHANNEL = "task:subagent:event";
24
24
 
25
- /** Environment variable containing blocked agent name (self-recursion prevention) */
26
- export const OMP_BLOCKED_AGENT_ENV = "OMP_BLOCKED_AGENT";
27
-
28
- /** Environment variable containing allowed spawn list (propagated to subprocesses) */
29
- export const OMP_SPAWNS_ENV = "OMP_SPAWNS";
25
+ /** EventBus channel for aggregated subagent progress */
26
+ export const TASK_SUBAGENT_PROGRESS_CHANNEL = "task:subagent:progress";
30
27
 
31
28
  /** Single task item for parallel execution */
32
29
  export const taskItemSchema = Type.Object({
@@ -41,6 +38,11 @@ export type TaskItem = Static<typeof taskItemSchema>;
41
38
  /** Task tool parameters */
42
39
  export const taskSchema = Type.Object({
43
40
  context: Type.Optional(Type.String({ description: "Shared context prepended to all task prompts" })),
41
+ output_schema: Type.Optional(
42
+ Type.Any({
43
+ description: "JSON schema for structured subagent output (used by the complete tool)",
44
+ }),
45
+ ),
44
46
  tasks: Type.Array(taskItemSchema, {
45
47
  description: "Tasks to run in parallel",
46
48
  maxItems: MAX_PARALLEL_TASKS,
@@ -81,7 +83,6 @@ export interface AgentDefinition {
81
83
  tools?: string[];
82
84
  spawns?: string[] | "*";
83
85
  model?: string;
84
- recursive?: boolean;
85
86
  source: AgentSource;
86
87
  filePath?: string;
87
88
  }
@@ -89,6 +90,7 @@ export interface AgentDefinition {
89
90
  /** Progress tracking for a single agent */
90
91
  export interface AgentProgress {
91
92
  index: number;
93
+ taskId: string;
92
94
  agent: string;
93
95
  agentSource: AgentSource;
94
96
  status: "pending" | "running" | "completed" | "failed" | "aborted";
@@ -110,6 +112,7 @@ export interface AgentProgress {
110
112
  /** Result from a single agent execution */
111
113
  export interface SingleResult {
112
114
  index: number;
115
+ taskId: string;
113
116
  agent: string;
114
117
  agentSource: AgentSource;
115
118
  task: string;
@@ -123,10 +126,9 @@ export interface SingleResult {
123
126
  modelOverride?: string;
124
127
  error?: string;
125
128
  aborted?: boolean;
126
- jsonlEvents?: string[];
127
- artifactPaths?: { inputPath: string; outputPath: string; jsonlPath?: string };
128
- /** Aggregated usage from the subprocess, if available. */
129
+ /** Aggregated usage from the subprocess, accumulated incrementally from message_end events. */
129
130
  usage?: Usage;
131
+ artifactPaths?: { inputPath: string; outputPath: string; jsonlPath?: string };
130
132
  /** Data extracted by registered subprocess tool handlers (keyed by tool name) */
131
133
  extractedToolData?: Record<string, unknown[]>;
132
134
  /** Output metadata for Output tool integration */
@@ -0,0 +1,18 @@
1
+ import type { AgentEvent } from "@oh-my-pi/pi-agent-core";
2
+
3
+ export interface SubagentWorkerStartPayload {
4
+ cwd: string;
5
+ task: string;
6
+ systemPrompt: string;
7
+ model?: string;
8
+ toolNames?: string[];
9
+ outputSchema?: unknown;
10
+ sessionFile?: string | null;
11
+ spawnsEnv?: string;
12
+ }
13
+
14
+ export type SubagentWorkerRequest = { type: "start"; payload: SubagentWorkerStartPayload } | { type: "abort" };
15
+
16
+ export type SubagentWorkerResponse =
17
+ | { type: "event"; event: AgentEvent }
18
+ | { type: "done"; exitCode: number; durationMs: number; error?: string; aborted?: boolean };