@ryan_nookpi/pi-extension-subagent 0.5.0 → 0.5.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/activity.ts CHANGED
@@ -7,6 +7,7 @@
7
7
 
8
8
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
9
9
  import type { CommandRunState } from "./types.js";
10
+ import { getUsedContextPercent } from "./utils/format-utils.js";
10
11
 
11
12
  export const SUBAGENT_ACTIVITY_CUSTOM_TYPE = "subagent-activity";
12
13
  export const SUBAGENT_ACTIVITY_SCHEMA_VERSION = 1;
@@ -24,6 +25,7 @@ export interface ActivitySnapshot {
24
25
  toolCallCount: number;
25
26
  lastLine?: string;
26
27
  contextTokens?: number;
28
+ contextWindow?: number;
27
29
  }
28
30
 
29
31
  export interface ActivityPayload {
@@ -36,12 +38,15 @@ export interface ActivityPayload {
36
38
  toolCallCount: number;
37
39
  lastLine?: string;
38
40
  contextTokens?: number;
41
+ contextWindow?: number;
42
+ contextPercent?: number;
39
43
  }
40
44
 
41
45
  export interface ActivityThrottleState {
42
46
  lastEmittedAt: number;
43
47
  lastEmittedToolName?: string;
44
48
  lastEmittedToolCallCount?: number;
49
+ lastEmittedContextPercent?: number;
45
50
  }
46
51
 
47
52
  export function createActivityThrottleState(): ActivityThrottleState {
@@ -69,8 +74,8 @@ export function normalizeActivityLine(text: string | undefined): string | undefi
69
74
  /**
70
75
  * Change-driven throttle policy. Returns a payload to emit, or null.
71
76
  *
72
- * Emits only while the run is `running`, only when `lastToolName` or
73
- * `toolCallCount` changed since the last emission, and at most once per
77
+ * Emits only while the run is `running`, when tool metadata or the displayed
78
+ * context percentage changes, and at most once per
74
79
  * `ACTIVITY_MIN_INTERVAL_MS` per run. A change suppressed by the interval
75
80
  * stays pending and emits on a later evaluation.
76
81
  */
@@ -84,14 +89,18 @@ export function evaluateActivityEmission(
84
89
  // on the run state, so a positive count is required regardless of the name.
85
90
  if (snapshot.toolCallCount <= 0) return null;
86
91
 
92
+ const contextPercent = getUsedContextPercent(snapshot.contextTokens, snapshot.contextWindow);
87
93
  const changed =
88
- snapshot.lastToolName !== state.lastEmittedToolName || snapshot.toolCallCount !== state.lastEmittedToolCallCount;
94
+ snapshot.lastToolName !== state.lastEmittedToolName ||
95
+ snapshot.toolCallCount !== state.lastEmittedToolCallCount ||
96
+ contextPercent !== state.lastEmittedContextPercent;
89
97
  if (!changed) return null;
90
98
  if (state.lastEmittedAt !== 0 && now - state.lastEmittedAt < ACTIVITY_MIN_INTERVAL_MS) return null;
91
99
 
92
100
  state.lastEmittedAt = now;
93
101
  state.lastEmittedToolName = snapshot.lastToolName;
94
102
  state.lastEmittedToolCallCount = snapshot.toolCallCount;
103
+ state.lastEmittedContextPercent = contextPercent;
95
104
 
96
105
  const payload: ActivityPayload = {
97
106
  runId: snapshot.runId,
@@ -105,10 +114,12 @@ export function evaluateActivityEmission(
105
114
  const lastLine = normalizeActivityLine(snapshot.lastLine);
106
115
  if (lastLine) payload.lastLine = lastLine;
107
116
  if (snapshot.contextTokens != null && snapshot.contextTokens > 0) payload.contextTokens = snapshot.contextTokens;
117
+ if (snapshot.contextWindow != null && snapshot.contextWindow > 0) payload.contextWindow = snapshot.contextWindow;
118
+ if (contextPercent !== undefined) payload.contextPercent = contextPercent;
108
119
  return payload;
109
120
  }
110
121
 
111
- export function snapshotFromRunState(runState: CommandRunState): ActivitySnapshot {
122
+ export function snapshotFromRunState(runState: CommandRunState, contextWindow?: number): ActivitySnapshot {
112
123
  return {
113
124
  status: runState.status,
114
125
  runId: runState.id,
@@ -120,6 +131,7 @@ export function snapshotFromRunState(runState: CommandRunState): ActivitySnapsho
120
131
  toolCallCount: runState.toolCalls,
121
132
  lastLine: runState.lastLine,
122
133
  contextTokens: runState.usage?.contextTokens,
134
+ contextWindow,
123
135
  };
124
136
  }
125
137
 
@@ -130,11 +142,16 @@ export function snapshotFromRunState(runState: CommandRunState): ActivitySnapsho
130
142
  export function createRunActivityRecorder(
131
143
  pi: Pick<ExtensionAPI, "appendEntry">,
132
144
  runState: CommandRunState,
145
+ resolveContextWindow?: (model?: string) => number | undefined,
133
146
  ): () => void {
134
147
  const throttleState = createActivityThrottleState();
135
148
  return () => {
136
149
  try {
137
- const payload = evaluateActivityEmission(throttleState, snapshotFromRunState(runState), Date.now());
150
+ const payload = evaluateActivityEmission(
151
+ throttleState,
152
+ snapshotFromRunState(runState, resolveContextWindow?.(runState.model)),
153
+ Date.now(),
154
+ );
138
155
  if (!payload) return;
139
156
  pi.appendEntry(SUBAGENT_ACTIVITY_CUSTOM_TYPE, {
140
157
  schemaVersion: SUBAGENT_ACTIVITY_SCHEMA_VERSION,
package/agents.ts CHANGED
@@ -140,25 +140,30 @@ function loadAgentsFromDir(dir: string, source: "user" | "project", options: Loa
140
140
  continue;
141
141
  }
142
142
 
143
- const { frontmatter, body } = parseFrontmatter<Record<string, string>>(content);
144
- if (!frontmatter.name || !frontmatter.description) continue;
145
-
146
- const tools = normalizeTools(frontmatter.tools, format);
147
- const model = normalizeModel(frontmatter.model, format);
148
- const thinking = normalizeThinkingLevel(frontmatter.thinking);
149
- const runtime: AgentRuntime = frontmatter.runtime === "claude" ? "claude" : "pi";
150
-
151
- agents.push({
152
- name: frontmatter.name,
153
- description: frontmatter.description,
154
- tools,
155
- model,
156
- thinking,
157
- systemPrompt: attachCommonSubagentRule(body, runtime),
158
- source,
159
- filePath,
160
- runtime,
161
- });
143
+ try {
144
+ const { frontmatter, body } = parseFrontmatter<Record<string, unknown>>(content);
145
+ if (typeof frontmatter.name !== "string" || typeof frontmatter.description !== "string") continue;
146
+ if (!frontmatter.name || !frontmatter.description) continue;
147
+
148
+ const tools = normalizeTools(frontmatter.tools, format);
149
+ const model = normalizeModel(typeof frontmatter.model === "string" ? frontmatter.model : undefined, format);
150
+ const thinking = normalizeThinkingLevel(
151
+ typeof frontmatter.thinking === "string" ? frontmatter.thinking : undefined,
152
+ );
153
+ const runtime: AgentRuntime = frontmatter.runtime === "claude" ? "claude" : "pi";
154
+
155
+ agents.push({
156
+ name: frontmatter.name,
157
+ description: frontmatter.description,
158
+ tools,
159
+ model,
160
+ thinking,
161
+ systemPrompt: attachCommonSubagentRule(body, runtime),
162
+ source,
163
+ filePath,
164
+ runtime,
165
+ });
166
+ } catch {}
162
167
  }
163
168
 
164
169
  return agents;
package/commands.ts CHANGED
@@ -35,7 +35,14 @@ import {
35
35
  shouldSummarizeSubagentTask,
36
36
  summarizeSubagentDisplayTask,
37
37
  } from "./display-task.js";
38
- import { AGENT_NAME_PALETTE, agentBgIndex, formatUsageStats, truncateLines, truncatePlainToWidth } from "./format.js";
38
+ import {
39
+ AGENT_NAME_PALETTE,
40
+ agentBgIndex,
41
+ formatUsageStats,
42
+ resolveContextWindow,
43
+ truncateLines,
44
+ truncatePlainToWidth,
45
+ } from "./format.js";
39
46
  import {
40
47
  clearPendingGroupCompletion,
41
48
  consumePendingGroupCompletionsForSession,
@@ -1250,6 +1257,8 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): SubagentReg
1250
1257
  runState.model = undefined;
1251
1258
  runState.retryCount = 0;
1252
1259
  runState.lastRetryReason = undefined;
1260
+ runState.errorClass = undefined;
1261
+ runState.autoAbortReason = undefined;
1253
1262
  runState.removed = false;
1254
1263
  runState.deliveryMode = deliveryMode;
1255
1264
  runState.turnCount = Math.max(DEFAULT_TURN_COUNT, runState.turnCount || DEFAULT_TURN_COUNT) + 1;
@@ -1400,7 +1409,9 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): SubagentReg
1400
1409
  }, RUN_TICK_INTERVAL_MS);
1401
1410
 
1402
1411
  let claudeCheckpointSent = !!runState.claudeSessionId;
1403
- const recordActivity = hiddenFromMain ? () => {} : createRunActivityRecorder(pi, runState);
1412
+ const recordActivity = hiddenFromMain
1413
+ ? () => {}
1414
+ : createRunActivityRecorder(pi, runState, (model) => resolveContextWindow(ctx, model));
1404
1415
  void (async () => {
1405
1416
  try {
1406
1417
  const { result, retryCount } = await invokeWithAutoRetry({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryan_nookpi/pi-extension-subagent",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Asynchronous subagent delegation for pi with run, batch, chain, and continuation workflows.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/tool-execute.ts CHANGED
@@ -1254,6 +1254,10 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
1254
1254
  runState.lastOutput = "";
1255
1255
  runState.usage = undefined;
1256
1256
  runState.model = undefined;
1257
+ runState.retryCount = 0;
1258
+ runState.lastRetryReason = undefined;
1259
+ runState.errorClass = undefined;
1260
+ runState.autoAbortReason = undefined;
1257
1261
  runState.removed = false;
1258
1262
  runState.turnCount = Math.max(DEFAULT_TURN_COUNT, runState.turnCount || DEFAULT_TURN_COUNT) + 1;
1259
1263
  runState.contextMode = runState.contextMode ?? (config.inheritMainContext ? "main" : "sub");
@@ -1315,7 +1319,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
1315
1319
 
1316
1320
  function launchRunInBackground(runState: CommandRunState, taskForAgent: string): Promise<FinalizedRun> {
1317
1321
  let claudeCheckpointSent = !!runState.claudeSessionId;
1318
- const recordActivity = createRunActivityRecorder(pi, runState);
1322
+ const recordActivity = createRunActivityRecorder(pi, runState, (model) => resolveContextWindow(ctx, model));
1319
1323
  return enqueueSubagentInvocation(() =>
1320
1324
  runSingleAgent(
1321
1325
  ctx.cwd,
@@ -1979,7 +1983,14 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
1979
1983
  pipeline.stepRunIds.push(runState.id);
1980
1984
  chainLaunches.push(toLaunchSummary(runState, "chain"));
1981
1985
 
1982
- const finalized = await launchRunInBackground(runState, taskForAgent);
1986
+ let finalized: FinalizedRun;
1987
+ try {
1988
+ finalized = await launchRunInBackground(runState, taskForAgent);
1989
+ } catch (error: unknown) {
1990
+ finalized = finalizeRunError(runState, error);
1991
+ } finally {
1992
+ clearRunAbortState(runState);
1993
+ }
1983
1994
  if (runState.removed) {
1984
1995
  terminalStatus = finalized.isError ? "error" : "stopped";
1985
1996
  pipeline.stepResults.push({
@@ -88,12 +88,10 @@ export const CLAUDE_MODEL_ALIAS_MAP: Record<string, string> = {
88
88
  * Normalize a comma-separated tool list according to the given format.
89
89
  * For "claude" format, maps Claude tool names to pi equivalents.
90
90
  */
91
- export function normalizeTools(rawTools: string | undefined, format: "pi" | "claude"): string[] | undefined {
92
- if (!rawTools) return undefined;
93
-
94
- const parsed = rawTools
95
- .split(",")
96
- .map((t) => t.trim())
91
+ export function normalizeTools(rawTools: unknown, format: "pi" | "claude"): string[] | undefined {
92
+ const parsed = (Array.isArray(rawTools) ? rawTools : typeof rawTools === "string" ? rawTools.split(",") : [])
93
+ .filter((tool): tool is string => typeof tool === "string")
94
+ .map((tool) => tool.trim())
97
95
  .filter(Boolean);
98
96
 
99
97
  if (parsed.length === 0) return undefined;