@oh-my-pi/subagents 0.5.1 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/tools/task/index.ts +55 -47
package/package.json
CHANGED
package/tools/task/index.ts
CHANGED
|
@@ -22,10 +22,11 @@
|
|
|
22
22
|
* Parameters:
|
|
23
23
|
* - tasks: Array of {agent, task} to run in parallel
|
|
24
24
|
* - context: (optional) Shared context prepended to all task prompts
|
|
25
|
-
*
|
|
25
|
+
* Results are written to /tmp/pi-task-{runId}/task_{agent}_{i}.md
|
|
26
26
|
* - agentScope: "user" | "project" | "both"
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
+
import * as crypto from "node:crypto";
|
|
29
30
|
import * as fs from "node:fs";
|
|
30
31
|
import * as os from "node:os";
|
|
31
32
|
import * as path from "node:path";
|
|
@@ -162,7 +163,7 @@ interface TaskDetails {
|
|
|
162
163
|
projectAgentsDir: string | null;
|
|
163
164
|
results: SingleResult[];
|
|
164
165
|
totalDurationMs: number;
|
|
165
|
-
/** Output paths
|
|
166
|
+
/** Output file paths */
|
|
166
167
|
outputPaths?: string[];
|
|
167
168
|
/** For streaming progress updates */
|
|
168
169
|
progress?: AgentProgress[];
|
|
@@ -465,7 +466,12 @@ function writePromptToTempFile(
|
|
|
465
466
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-task-agent-"));
|
|
466
467
|
const safeName = agentName.replace(/[^\w.-]+/g, "_");
|
|
467
468
|
const filePath = path.join(tmpDir, `prompt-${safeName}.md`);
|
|
468
|
-
|
|
469
|
+
try {
|
|
470
|
+
fs.writeFileSync(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
|
|
471
|
+
} catch (err) {
|
|
472
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
473
|
+
throw err;
|
|
474
|
+
}
|
|
469
475
|
return { dir: tmpDir, filePath };
|
|
470
476
|
}
|
|
471
477
|
|
|
@@ -639,7 +645,7 @@ async function runSingleAgent(
|
|
|
639
645
|
const lastMsg = messages.findLast(
|
|
640
646
|
(m: any) => m.role === "assistant",
|
|
641
647
|
);
|
|
642
|
-
if (lastMsg?.content) {
|
|
648
|
+
if (lastMsg?.content && Array.isArray(lastMsg.content)) {
|
|
643
649
|
const textParts = lastMsg.content
|
|
644
650
|
.filter((c: any) => c.type === "text")
|
|
645
651
|
.map((c: any) => c.text);
|
|
@@ -768,13 +774,6 @@ const TaskParams = Type.Object({
|
|
|
768
774
|
Type.String({ description: "Shared context prepended to all tasks" }),
|
|
769
775
|
),
|
|
770
776
|
tasks: Type.Array(TaskItem, { description: "Tasks to run in parallel" }),
|
|
771
|
-
write: Type.Optional(
|
|
772
|
-
Type.Boolean({
|
|
773
|
-
description:
|
|
774
|
-
"Write results to /tmp/task_{agent}_{index}.md instead of returning inline",
|
|
775
|
-
default: false,
|
|
776
|
-
}),
|
|
777
|
-
),
|
|
778
777
|
agentScope: Type.Optional(AgentScopeSchema),
|
|
779
778
|
});
|
|
780
779
|
|
|
@@ -882,10 +881,11 @@ function buildDescription(pi: ToolAPI): string {
|
|
|
882
881
|
"- context: (optional) Shared context string prepended to all task prompts - use this to avoid repeating instructions",
|
|
883
882
|
);
|
|
884
883
|
lines.push(
|
|
885
|
-
|
|
884
|
+
'- agentScope: (optional) "user" | "project" | "both" - which agent directories to use',
|
|
886
885
|
);
|
|
886
|
+
lines.push("");
|
|
887
887
|
lines.push(
|
|
888
|
-
|
|
888
|
+
"Results are always written to /tmp/pi-task-{runId}/task_{agent}_{index}.md",
|
|
889
889
|
);
|
|
890
890
|
lines.push("");
|
|
891
891
|
lines.push("Example usage:");
|
|
@@ -945,18 +945,30 @@ function buildDescription(pi: ToolAPI): string {
|
|
|
945
945
|
lines.push(' { "agent": "explore", "task": "Search in src/" },');
|
|
946
946
|
lines.push(' { "agent": "explore", "task": "Search in lib/" },');
|
|
947
947
|
lines.push(' { "agent": "explore", "task": "Search in tests/" }');
|
|
948
|
-
lines.push(" ]
|
|
949
|
-
lines.push(' "write": true');
|
|
948
|
+
lines.push(" ]");
|
|
950
949
|
lines.push("}");
|
|
951
|
-
lines.push(
|
|
952
|
-
"Results written to /tmp/task_explore_0.md, /tmp/task_explore_1.md, /tmp/task_explore_2.md",
|
|
953
|
-
);
|
|
950
|
+
lines.push("Results → /tmp/pi-task-{runId}/task_explore_*.md");
|
|
954
951
|
lines.push("</example>");
|
|
955
952
|
|
|
956
953
|
return lines.join("\n");
|
|
957
954
|
}
|
|
958
955
|
|
|
956
|
+
const NANOID_ALPHABET =
|
|
957
|
+
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
958
|
+
|
|
959
|
+
function nanoid(size = 12): string {
|
|
960
|
+
const bytes = crypto.randomBytes(size);
|
|
961
|
+
let id = "";
|
|
962
|
+
for (let i = 0; i < size; i++) {
|
|
963
|
+
id += NANOID_ALPHABET[bytes[i] % NANOID_ALPHABET.length];
|
|
964
|
+
}
|
|
965
|
+
return id;
|
|
966
|
+
}
|
|
967
|
+
|
|
959
968
|
const factory: CustomToolFactory = (pi) => {
|
|
969
|
+
const runId = nanoid(8);
|
|
970
|
+
const outputDir = path.join(os.tmpdir(), `pi-task-${runId}`);
|
|
971
|
+
|
|
960
972
|
const tool: CustomAgentTool<typeof TaskParams, TaskDetails> = {
|
|
961
973
|
name: "task",
|
|
962
974
|
label: "Task",
|
|
@@ -971,7 +983,6 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
971
983
|
const discovery = discoverAgents(pi.cwd, agentScope);
|
|
972
984
|
const agents = discovery.agents;
|
|
973
985
|
const context = params.context;
|
|
974
|
-
const write = params.write ?? false;
|
|
975
986
|
|
|
976
987
|
if (!params.tasks || params.tasks.length === 0) {
|
|
977
988
|
const available =
|
|
@@ -1055,12 +1066,11 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
1055
1066
|
model: t.model,
|
|
1056
1067
|
}));
|
|
1057
1068
|
|
|
1058
|
-
// Generate output paths
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
: [];
|
|
1069
|
+
// Generate output paths
|
|
1070
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
1071
|
+
const outputPaths = params.tasks.map(
|
|
1072
|
+
(t, i) => path.join(outputDir, `task_${sanitizeAgentName(t.agent)}_${i}.md`),
|
|
1073
|
+
);
|
|
1064
1074
|
|
|
1065
1075
|
const results = await mapWithConcurrencyLimit(
|
|
1066
1076
|
tasksWithContext,
|
|
@@ -1083,17 +1093,15 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
1083
1093
|
},
|
|
1084
1094
|
);
|
|
1085
1095
|
|
|
1086
|
-
// Write output to file
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
result.stderr += `\nFailed to write output: ${e}`;
|
|
1096
|
-
}
|
|
1096
|
+
// Write output to file
|
|
1097
|
+
const content =
|
|
1098
|
+
result.stdout.trim() || result.stderr.trim() || "(no output)";
|
|
1099
|
+
try {
|
|
1100
|
+
fs.writeFileSync(outputPaths[idx], content, {
|
|
1101
|
+
encoding: "utf-8",
|
|
1102
|
+
});
|
|
1103
|
+
} catch (e) {
|
|
1104
|
+
result.stderr += `\nFailed to write output: ${e}`;
|
|
1097
1105
|
}
|
|
1098
1106
|
|
|
1099
1107
|
return result;
|
|
@@ -1108,14 +1116,8 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
1108
1116
|
const status =
|
|
1109
1117
|
r.exitCode === 0 ? "completed" : `failed (exit ${r.exitCode})`;
|
|
1110
1118
|
const output = r.stdout.trim() || r.stderr.trim() || "(no output)";
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
const preview = previewFirstLines(output, 5);
|
|
1114
|
-
return `[${r.agent}] ${status} → ${outputPaths[i]}\n${preview}`;
|
|
1115
|
-
} else {
|
|
1116
|
-
const preview = previewFirstLines(output, 5);
|
|
1117
|
-
return `[${r.agent}] ${status} (${formatDuration(r.durationMs)})\n${preview}`;
|
|
1118
|
-
}
|
|
1119
|
+
const preview = previewFirstLines(output, 5);
|
|
1120
|
+
return `[${r.agent}] ${status} → ${outputPaths[i]}\n${preview}`;
|
|
1119
1121
|
});
|
|
1120
1122
|
|
|
1121
1123
|
return {
|
|
@@ -1130,7 +1132,7 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
1130
1132
|
projectAgentsDir: discovery.projectAgentsDir,
|
|
1131
1133
|
results,
|
|
1132
1134
|
totalDurationMs: totalDuration,
|
|
1133
|
-
outputPaths
|
|
1135
|
+
outputPaths,
|
|
1134
1136
|
},
|
|
1135
1137
|
};
|
|
1136
1138
|
},
|
|
@@ -1166,7 +1168,10 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
1166
1168
|
const completedCount = details.progress.filter(
|
|
1167
1169
|
(p) => p.status === "completed",
|
|
1168
1170
|
).length;
|
|
1169
|
-
const
|
|
1171
|
+
const outputDir = details.outputPaths?.[0]
|
|
1172
|
+
? path.dirname(details.outputPaths[0])
|
|
1173
|
+
: null;
|
|
1174
|
+
const writeNote = outputDir ? ` → ${outputDir}` : "";
|
|
1170
1175
|
|
|
1171
1176
|
let headerText: string;
|
|
1172
1177
|
if (completedCount === count) {
|
|
@@ -1258,7 +1263,10 @@ const factory: CustomToolFactory = (pi) => {
|
|
|
1258
1263
|
const icon = allSuccess
|
|
1259
1264
|
? theme.fg("success", "●")
|
|
1260
1265
|
: theme.fg("warning", "●");
|
|
1261
|
-
const
|
|
1266
|
+
const outputDir = details.outputPaths?.[0]
|
|
1267
|
+
? path.dirname(details.outputPaths[0])
|
|
1268
|
+
: null;
|
|
1269
|
+
const writeNote = outputDir ? ` → ${outputDir}` : "";
|
|
1262
1270
|
|
|
1263
1271
|
let text =
|
|
1264
1272
|
icon +
|