@oh-my-pi/subagents 0.5.0 → 0.6.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 (2) hide show
  1. package/package.json +1 -1
  2. package/tools/task/index.ts +55 -47
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/subagents",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Task delegation system with specialized subagents (task, planner, explore, reviewer)",
5
5
  "keywords": [
6
6
  "omp-plugin",
@@ -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
- * - write: (optional) Write results to /tmp/task_{agent}_{i}.md
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 when write=true */
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
- fs.writeFileSync(filePath, prompt, { encoding: "utf-8", mode: 0o600 });
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
- "- write: (optional) If true, results written to /tmp/task_{agent}_{index}.md instead of returned inline",
884
+ '- agentScope: (optional) "user" | "project" | "both" - which agent directories to use',
886
885
  );
886
+ lines.push("");
887
887
  lines.push(
888
- '- agentScope: (optional) "user" | "project" | "both" - which agent directories to use',
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 if write=true
1059
- const outputPaths: string[] = write
1060
- ? params.tasks.map(
1061
- (t, i) => `/tmp/task_${sanitizeAgentName(t.agent)}_${i}.md`,
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 if write=true
1087
- if (write && outputPaths[idx]) {
1088
- const content =
1089
- result.stdout.trim() || result.stderr.trim() || "(no output)";
1090
- try {
1091
- fs.writeFileSync(outputPaths[idx], content, {
1092
- encoding: "utf-8",
1093
- });
1094
- } catch (e) {
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
- if (write && outputPaths[i]) {
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: write ? outputPaths : undefined,
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 writeNote = details.outputPaths ? " → /tmp" : "";
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 writeNote = details.outputPaths ? " → /tmp" : "";
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 +