@bacnh85/pi-subagent 0.5.0 → 0.7.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.
@@ -7,134 +7,10 @@
7
7
 
8
8
  import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
9
9
  import { matchesKey, Key, truncateToWidth, Markdown } from "@earendil-works/pi-tui";
10
- import type { Message } from "@earendil-works/pi-ai";
11
10
 
12
- import { type SubAgentResult, isFailedResult, getResultOutput, getFinalOutput } from "./runner.ts";
13
- import { formatUsageStats } from "./render.ts";
11
+ import { isFailedResult, getFinalOutput } from "./runner.ts";
12
+ import { formatToolCall, getDisplayItems, formatUsageStats } from "./render.ts";
14
13
  import type { SubagentThread } from "./threads.ts";
15
- import * as os from "node:os";
16
-
17
- // ---------------------------------------------------------------------------
18
- // Safe type guards for tool-call arguments
19
- // ---------------------------------------------------------------------------
20
-
21
- function asString(value: unknown, fallback = "..."): string {
22
- return typeof value === "string" ? value : fallback;
23
- }
24
-
25
- function asNumber(value: unknown): number | undefined {
26
- return typeof value === "number" && Number.isFinite(value) ? value : undefined;
27
- }
28
-
29
- function asRecord(value: unknown): Record<string, unknown> {
30
- return value && typeof value === "object" && !Array.isArray(value)
31
- ? (value as Record<string, unknown>)
32
- : {};
33
- }
34
-
35
- // ---------------------------------------------------------------------------
36
- // Tool-call formatting (same as render.ts)
37
- // ---------------------------------------------------------------------------
38
-
39
- function formatToolCall(
40
- toolName: string,
41
- args: Record<string, unknown>,
42
- themeFg: (color: string, text: string) => string,
43
- ): string {
44
- const shortenPath = (p: string) => {
45
- const home = os.homedir();
46
- return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
47
- };
48
-
49
- switch (toolName) {
50
- case "bash": {
51
- const command = asString(args.command);
52
- const preview = command.length > 60 ? `${command.slice(0, 60)}...` : command;
53
- return themeFg("muted", "$ ") + themeFg("toolOutput", preview);
54
- }
55
- case "read": {
56
- const rawPath = asString(args.file_path ?? args.path);
57
- const filePath = shortenPath(rawPath);
58
- const offset = asNumber(args.offset);
59
- const limit = asNumber(args.limit);
60
- let text = themeFg("accent", filePath);
61
- if (offset !== undefined || limit !== undefined) {
62
- const startLine = offset ?? 1;
63
- const endLine = limit !== undefined ? startLine + limit - 1 : "";
64
- text += themeFg("warning", `:${startLine}${endLine ? `-${endLine}` : ""}`);
65
- }
66
- return themeFg("muted", "read ") + text;
67
- }
68
- case "write": {
69
- const rawPath = asString(args.file_path ?? args.path);
70
- const filePath = shortenPath(rawPath);
71
- const content = asString(args.content, "");
72
- const lines = content.split("\n").length;
73
- let text = themeFg("muted", "write ") + themeFg("accent", filePath);
74
- if (lines > 1) text += themeFg("dim", ` (${lines} lines)`);
75
- return text;
76
- }
77
- case "edit": {
78
- const rawPath = asString(args.file_path ?? args.path);
79
- return themeFg("muted", "edit ") + themeFg("accent", shortenPath(rawPath));
80
- }
81
- case "ls": {
82
- const rawPath = asString(args.path, ".");
83
- return themeFg("muted", "ls ") + themeFg("accent", shortenPath(rawPath));
84
- }
85
- case "find": {
86
- const pattern = asString(args.pattern, "*");
87
- const rawPath = asString(args.path, ".");
88
- return (
89
- themeFg("muted", "find ") +
90
- themeFg("accent", pattern) +
91
- themeFg("dim", ` in ${shortenPath(rawPath)}`)
92
- );
93
- }
94
- case "grep": {
95
- const pattern = asString(args.pattern);
96
- const rawPath = asString(args.path, ".");
97
- return (
98
- themeFg("muted", "grep ") +
99
- themeFg("accent", `/${pattern}/`) +
100
- themeFg("dim", ` in ${shortenPath(rawPath)}`)
101
- );
102
- }
103
- default: {
104
- const argsStr = JSON.stringify(args);
105
- const preview = argsStr.length > 50 ? `${argsStr.slice(0, 50)}...` : argsStr;
106
- return themeFg("accent", toolName) + themeFg("dim", ` ${preview}`);
107
- }
108
- }
109
- }
110
-
111
- // ---------------------------------------------------------------------------
112
- // Display helpers
113
- // ---------------------------------------------------------------------------
114
-
115
- type DisplayItem =
116
- | { type: "text"; text: string }
117
- | { type: "toolCall"; name: string; args: Record<string, unknown> };
118
-
119
- function getDisplayItems(messages: Message[]): DisplayItem[] {
120
- const items: DisplayItem[] = [];
121
- for (const msg of messages) {
122
- if (msg.role === "assistant") {
123
- for (const part of msg.content) {
124
- if (part.type === "text" && part.text.trim()) {
125
- items.push({ type: "text", text: part.text });
126
- } else if (part.type === "toolCall") {
127
- items.push({
128
- type: "toolCall",
129
- name: part.name,
130
- args: asRecord(part.arguments),
131
- });
132
- }
133
- }
134
- }
135
- }
136
- return items;
137
- }
138
14
 
139
15
  // ---------------------------------------------------------------------------
140
16
  // Theme types
@@ -247,7 +123,8 @@ export class ThreadViewer {
247
123
  else if (this.thread.mode === "chain-step") modeLabel = t.fg("muted", " [chain]");
248
124
 
249
125
  // Header
250
- let header = `${icon} ${t.fg("toolTitle", t.bold(this.thread.agentName))}${modeLabel}`;
126
+ const agentColor = this.thread.color ?? "accent";
127
+ let header = `${icon} ${t.fg(agentColor, t.bold(this.thread.agentName))}${modeLabel}`;
251
128
  if (status === "running") header += ` ${t.fg("warning", "(running...)")}`;
252
129
  else if (status === "aborted") header += ` ${t.fg("error", "[aborted]")}`;
253
130
  if (result && isErr && result.stopReason && result.stopReason !== "error" && result.stopReason !== "aborted") {
@@ -23,6 +23,7 @@ export interface SubagentThread {
23
23
  status: ThreadStatus;
24
24
  result?: SubAgentResult;
25
25
  toolCallId?: string;
26
+ color?: string;
26
27
  createdAt: number;
27
28
  updatedAt: number;
28
29
  }
@@ -53,6 +54,7 @@ export class ThreadStore {
53
54
  task: string;
54
55
  mode: ThreadMode;
55
56
  toolCallId?: string;
57
+ color?: string;
56
58
  }): SubagentThread {
57
59
  const id = cryptoGenId();
58
60
  const now = Date.now();
@@ -63,6 +65,7 @@ export class ThreadStore {
63
65
  mode: params.mode,
64
66
  status: "running",
65
67
  toolCallId: params.toolCallId,
68
+ color: params.color,
66
69
  createdAt: now,
67
70
  updatedAt: now,
68
71
  };
@@ -103,14 +106,4 @@ export const threadStore = new ThreadStore();
103
106
  // ID generation (UUID v4 style, good enough for in-memory use)
104
107
  // ---------------------------------------------------------------------------
105
108
 
106
- function cryptoGenId(): string {
107
- if (typeof crypto !== "undefined" && crypto.randomUUID) {
108
- return crypto.randomUUID();
109
- }
110
- // Fallback for environments without crypto (very unlikely in Node)
111
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
112
- const r = (Math.random() * 16) | 0;
113
- const v = c === "x" ? r : (r & 0x3) | 0x8;
114
- return v.toString(16);
115
- });
116
- }
109
+ const cryptoGenId = () => crypto.randomUUID();
package/package.json CHANGED
@@ -1,21 +1,31 @@
1
1
  {
2
2
  "name": "@bacnh85/pi-subagent",
3
- "version": "0.5.0",
4
- "description": "Minimal-overhead sub-agents for Pi with isolated SDK sessions, parallel and chained delegation, and inspectable threads.",
3
+ "version": "0.7.0",
4
+ "description": "In-process subagents for Pi with isolated SDK sessions, parallel and chained delegation, and inspectable threads.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "publishConfig": { "access": "public" },
8
- "homepage": "https://github.com/bacnh85/pi-extensions#readme",
8
+ "homepage": "https://github.com/bacnh85/pi-extensions/tree/main/pi-subagent",
9
9
  "repository": {
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/bacnh85/pi-extensions.git",
12
12
  "directory": "pi-subagent"
13
13
  },
14
14
  "bugs": { "url": "https://github.com/bacnh85/pi-extensions/issues" },
15
- "keywords": ["pi-package", "pi-extension", "subagent", "sub-agent", "delegation", "parallel"],
15
+ "keywords": [
16
+ "pi-package",
17
+ "pi-extension",
18
+ "subagent",
19
+ "sub-agent",
20
+ "delegation",
21
+ "parallel",
22
+ "chain",
23
+ "security"
24
+ ],
16
25
  "files": [
17
26
  "README.md",
18
27
  "agent-format.md",
28
+ "CHANGELOG.md",
19
29
  "agents/",
20
30
  "extensions/index.ts",
21
31
  "extensions/agents.ts",
@@ -25,19 +35,24 @@
25
35
  "extensions/render.ts",
26
36
  "extensions/threads.ts",
27
37
  "extensions/thread-viewer.ts",
38
+ "extensions/security.ts",
28
39
  "extensions/package.json"
29
40
  ],
30
41
  "pi": { "extensions": ["./extensions/index.ts"] },
42
+ "engines": {
43
+ "node": ">=20.18"
44
+ },
31
45
  "scripts": {
32
- "test": "cd extensions && npx mocha",
33
- "typecheck": "tsc --noEmit"
46
+ "test": "cd extensions && mocha",
47
+ "typecheck": "tsc --noEmit",
48
+ "check": "npm run typecheck && npm test"
34
49
  },
35
50
  "peerDependencies": {
36
- "@earendil-works/pi-coding-agent": "*",
37
- "@earendil-works/pi-ai": "*",
38
- "@earendil-works/pi-agent-core": "*",
39
- "@earendil-works/pi-tui": "*",
40
- "typebox": "*"
51
+ "@earendil-works/pi-coding-agent": ">=0.80.0 <0.81.0",
52
+ "@earendil-works/pi-ai": ">=0.80.0 <0.81.0",
53
+ "@earendil-works/pi-agent-core": ">=0.80.0 <0.81.0",
54
+ "@earendil-works/pi-tui": ">=0.80.0 <0.81.0",
55
+ "typebox": ">=1.3.0 <2.0.0"
41
56
  },
42
57
  "devDependencies": {
43
58
  "@earendil-works/pi-agent-core": "^0.80.2",