@ryan_nookpi/pi-extension-subagent 0.4.9 → 0.5.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.
- package/activity.ts +148 -0
- package/commands.ts +3 -0
- package/package.json +102 -101
- package/tool-execute.ts +3 -0
package/activity.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throttled `subagent-activity` custom entries.
|
|
3
|
+
*
|
|
4
|
+
* Lets host UIs (e.g. Picky) observe live activity of headless subagent runs
|
|
5
|
+
* by appending change-driven, throttled entries to the parent session JSONL.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import type { CommandRunState } from "./types.js";
|
|
10
|
+
|
|
11
|
+
export const SUBAGENT_ACTIVITY_CUSTOM_TYPE = "subagent-activity";
|
|
12
|
+
export const SUBAGENT_ACTIVITY_SCHEMA_VERSION = 1;
|
|
13
|
+
export const ACTIVITY_MIN_INTERVAL_MS = 2500;
|
|
14
|
+
export const ACTIVITY_LAST_LINE_MAX_CHARS = 160;
|
|
15
|
+
|
|
16
|
+
export interface ActivitySnapshot {
|
|
17
|
+
status: CommandRunState["status"];
|
|
18
|
+
runId: number;
|
|
19
|
+
agent: string;
|
|
20
|
+
batchId?: string;
|
|
21
|
+
pipelineId?: string;
|
|
22
|
+
pipelineStepIndex?: number;
|
|
23
|
+
lastToolName?: string;
|
|
24
|
+
toolCallCount: number;
|
|
25
|
+
lastLine?: string;
|
|
26
|
+
contextTokens?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ActivityPayload {
|
|
30
|
+
runId: number;
|
|
31
|
+
agent: string;
|
|
32
|
+
batchId?: string;
|
|
33
|
+
pipelineId?: string;
|
|
34
|
+
pipelineStepIndex?: number;
|
|
35
|
+
lastToolName?: string;
|
|
36
|
+
toolCallCount: number;
|
|
37
|
+
lastLine?: string;
|
|
38
|
+
contextTokens?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ActivityThrottleState {
|
|
42
|
+
lastEmittedAt: number;
|
|
43
|
+
lastEmittedToolName?: string;
|
|
44
|
+
lastEmittedToolCallCount?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createActivityThrottleState(): ActivityThrottleState {
|
|
48
|
+
return { lastEmittedAt: 0 };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const ANSI_PATTERN =
|
|
52
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequences use control characters by definition.
|
|
53
|
+
/\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)?|[\u0000-\u0008\u000b-\u001f\u007f]/g;
|
|
54
|
+
|
|
55
|
+
/** Strip ANSI sequences, collapse to a single line, and cap length. */
|
|
56
|
+
export function normalizeActivityLine(text: string | undefined): string | undefined {
|
|
57
|
+
if (!text) return undefined;
|
|
58
|
+
const line = text
|
|
59
|
+
.replace(/\r\n?/g, "\n")
|
|
60
|
+
.replace(ANSI_PATTERN, "")
|
|
61
|
+
.split("\n")
|
|
62
|
+
.map((part) => part.trim())
|
|
63
|
+
.filter(Boolean)
|
|
64
|
+
.pop();
|
|
65
|
+
if (!line) return undefined;
|
|
66
|
+
return line.length > ACTIVITY_LAST_LINE_MAX_CHARS ? line.slice(0, ACTIVITY_LAST_LINE_MAX_CHARS) : line;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Change-driven throttle policy. Returns a payload to emit, or null.
|
|
71
|
+
*
|
|
72
|
+
* Emits only while the run is `running`, only when `lastToolName` or
|
|
73
|
+
* `toolCallCount` changed since the last emission, and at most once per
|
|
74
|
+
* `ACTIVITY_MIN_INTERVAL_MS` per run. A change suppressed by the interval
|
|
75
|
+
* stays pending and emits on a later evaluation.
|
|
76
|
+
*/
|
|
77
|
+
export function evaluateActivityEmission(
|
|
78
|
+
state: ActivityThrottleState,
|
|
79
|
+
snapshot: ActivitySnapshot,
|
|
80
|
+
now: number,
|
|
81
|
+
): ActivityPayload | null {
|
|
82
|
+
if (snapshot.status !== "running") return null;
|
|
83
|
+
// Continuations reset toolCalls to 0 while a stale lastToolName may linger
|
|
84
|
+
// on the run state, so a positive count is required regardless of the name.
|
|
85
|
+
if (snapshot.toolCallCount <= 0) return null;
|
|
86
|
+
|
|
87
|
+
const changed =
|
|
88
|
+
snapshot.lastToolName !== state.lastEmittedToolName || snapshot.toolCallCount !== state.lastEmittedToolCallCount;
|
|
89
|
+
if (!changed) return null;
|
|
90
|
+
if (state.lastEmittedAt !== 0 && now - state.lastEmittedAt < ACTIVITY_MIN_INTERVAL_MS) return null;
|
|
91
|
+
|
|
92
|
+
state.lastEmittedAt = now;
|
|
93
|
+
state.lastEmittedToolName = snapshot.lastToolName;
|
|
94
|
+
state.lastEmittedToolCallCount = snapshot.toolCallCount;
|
|
95
|
+
|
|
96
|
+
const payload: ActivityPayload = {
|
|
97
|
+
runId: snapshot.runId,
|
|
98
|
+
agent: snapshot.agent,
|
|
99
|
+
toolCallCount: snapshot.toolCallCount,
|
|
100
|
+
};
|
|
101
|
+
if (snapshot.batchId) payload.batchId = snapshot.batchId;
|
|
102
|
+
if (snapshot.pipelineId) payload.pipelineId = snapshot.pipelineId;
|
|
103
|
+
if (snapshot.pipelineStepIndex != null) payload.pipelineStepIndex = snapshot.pipelineStepIndex;
|
|
104
|
+
if (snapshot.lastToolName) payload.lastToolName = snapshot.lastToolName;
|
|
105
|
+
const lastLine = normalizeActivityLine(snapshot.lastLine);
|
|
106
|
+
if (lastLine) payload.lastLine = lastLine;
|
|
107
|
+
if (snapshot.contextTokens != null && snapshot.contextTokens > 0) payload.contextTokens = snapshot.contextTokens;
|
|
108
|
+
return payload;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function snapshotFromRunState(runState: CommandRunState): ActivitySnapshot {
|
|
112
|
+
return {
|
|
113
|
+
status: runState.status,
|
|
114
|
+
runId: runState.id,
|
|
115
|
+
agent: runState.agent,
|
|
116
|
+
batchId: runState.batchId,
|
|
117
|
+
pipelineId: runState.pipelineId,
|
|
118
|
+
pipelineStepIndex: runState.pipelineStepIndex,
|
|
119
|
+
lastToolName: runState.lastToolName,
|
|
120
|
+
toolCallCount: runState.toolCalls,
|
|
121
|
+
lastLine: runState.lastLine,
|
|
122
|
+
contextTokens: runState.usage?.contextTokens,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Best-effort recorder called from run update handlers. Never throws;
|
|
128
|
+
* activity logging must not affect run execution or completion delivery.
|
|
129
|
+
*/
|
|
130
|
+
export function createRunActivityRecorder(
|
|
131
|
+
pi: Pick<ExtensionAPI, "appendEntry">,
|
|
132
|
+
runState: CommandRunState,
|
|
133
|
+
): () => void {
|
|
134
|
+
const throttleState = createActivityThrottleState();
|
|
135
|
+
return () => {
|
|
136
|
+
try {
|
|
137
|
+
const payload = evaluateActivityEmission(throttleState, snapshotFromRunState(runState), Date.now());
|
|
138
|
+
if (!payload) return;
|
|
139
|
+
pi.appendEntry(SUBAGENT_ACTIVITY_CUSTOM_TYPE, {
|
|
140
|
+
schemaVersion: SUBAGENT_ACTIVITY_SCHEMA_VERSION,
|
|
141
|
+
recordedAt: new Date().toISOString(),
|
|
142
|
+
...payload,
|
|
143
|
+
});
|
|
144
|
+
} catch {
|
|
145
|
+
// Session teardown may invalidate the old extension runtime.
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
package/commands.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
10
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
11
11
|
import { Container, Key, matchesKey, Spacer, Text, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
12
|
+
import { createRunActivityRecorder } from "./activity.js";
|
|
12
13
|
import { discoverAgents } from "./agents.js";
|
|
13
14
|
import { loadSubagentConfig } from "./config.js";
|
|
14
15
|
import {
|
|
@@ -1399,6 +1400,7 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): SubagentReg
|
|
|
1399
1400
|
}, RUN_TICK_INTERVAL_MS);
|
|
1400
1401
|
|
|
1401
1402
|
let claudeCheckpointSent = !!runState.claudeSessionId;
|
|
1403
|
+
const recordActivity = hiddenFromMain ? () => {} : createRunActivityRecorder(pi, runState);
|
|
1402
1404
|
void (async () => {
|
|
1403
1405
|
try {
|
|
1404
1406
|
const { result, retryCount } = await invokeWithAutoRetry({
|
|
@@ -1428,6 +1430,7 @@ export function registerAll(pi: ExtensionAPI, store: SubagentStore): SubagentReg
|
|
|
1428
1430
|
const current = partial.details?.results?.[0];
|
|
1429
1431
|
if (!current) return;
|
|
1430
1432
|
updateRunFromResult(runState, current);
|
|
1433
|
+
recordActivity();
|
|
1431
1434
|
if (!claudeCheckpointSent && runState.claudeSessionId) {
|
|
1432
1435
|
claudeCheckpointSent = true;
|
|
1433
1436
|
if (!hiddenFromMain) {
|
package/package.json
CHANGED
|
@@ -1,102 +1,103 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
2
|
+
"name": "@ryan_nookpi/pi-extension-subagent",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Asynchronous subagent delegation for pi with run, batch, chain, and continuation workflows.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/Jonghakseo/pi-extension.git",
|
|
9
|
+
"directory": "packages/subagent"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/Jonghakseo/pi-extension/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/Jonghakseo/pi-extension/tree/main/packages/subagent#readme",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"pi-package",
|
|
18
|
+
"subagent",
|
|
19
|
+
"delegation"
|
|
20
|
+
],
|
|
21
|
+
"files": [
|
|
22
|
+
"above-widget.ts",
|
|
23
|
+
"activity.ts",
|
|
24
|
+
"agents.ts",
|
|
25
|
+
"claude-args.ts",
|
|
26
|
+
"claude-sdk-runner.ts",
|
|
27
|
+
"claude-sdk-stream-adapter.ts",
|
|
28
|
+
"claude-sidecar-writer.ts",
|
|
29
|
+
"claude-stream-parser.ts",
|
|
30
|
+
"cli.ts",
|
|
31
|
+
"commands.ts",
|
|
32
|
+
"config.ts",
|
|
33
|
+
"constants.ts",
|
|
34
|
+
"context-limits.ts",
|
|
35
|
+
"diagnostics.ts",
|
|
36
|
+
"display-task.ts",
|
|
37
|
+
"escalation.ts",
|
|
38
|
+
"failure-telemetry.ts",
|
|
39
|
+
"format.ts",
|
|
40
|
+
"group-pending.ts",
|
|
41
|
+
"index.ts",
|
|
42
|
+
"lifecycle.ts",
|
|
43
|
+
"invocation-queue.ts",
|
|
44
|
+
"live-preview.ts",
|
|
45
|
+
"mentions.ts",
|
|
46
|
+
"persisted-session.ts",
|
|
47
|
+
"replay.ts",
|
|
48
|
+
"registration-manifest.ts",
|
|
49
|
+
"retry.ts",
|
|
50
|
+
"run-utils.ts",
|
|
51
|
+
"runner.ts",
|
|
52
|
+
"session.ts",
|
|
53
|
+
"starter-pack.ts",
|
|
54
|
+
"store.ts",
|
|
55
|
+
"tool-execute.ts",
|
|
56
|
+
"tool-render.ts",
|
|
57
|
+
"types.ts",
|
|
58
|
+
"widget.ts",
|
|
59
|
+
"utils/agent-utils.ts",
|
|
60
|
+
"utils/short-label.ts",
|
|
61
|
+
"utils/string-utils.ts",
|
|
62
|
+
"utils/format-utils.ts",
|
|
63
|
+
"utils/time-utils.ts",
|
|
64
|
+
"seeds",
|
|
65
|
+
"README.md"
|
|
66
|
+
],
|
|
67
|
+
"pi": {
|
|
68
|
+
"extensions": [
|
|
69
|
+
"./index.ts"
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.98",
|
|
74
|
+
"yaml": "^2.9.0"
|
|
75
|
+
},
|
|
76
|
+
"peerDependencies": {
|
|
77
|
+
"@earendil-works/pi-ai": "*",
|
|
78
|
+
"@earendil-works/pi-agent-core": "*",
|
|
79
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
80
|
+
"@earendil-works/pi-tui": "*",
|
|
81
|
+
"typebox": "*"
|
|
82
|
+
},
|
|
83
|
+
"peerDependenciesMeta": {
|
|
84
|
+
"@earendil-works/pi-ai": {
|
|
85
|
+
"optional": true
|
|
86
|
+
},
|
|
87
|
+
"@earendil-works/pi-agent-core": {
|
|
88
|
+
"optional": true
|
|
89
|
+
},
|
|
90
|
+
"@earendil-works/pi-coding-agent": {
|
|
91
|
+
"optional": true
|
|
92
|
+
},
|
|
93
|
+
"@earendil-works/pi-tui": {
|
|
94
|
+
"optional": true
|
|
95
|
+
},
|
|
96
|
+
"typebox": {
|
|
97
|
+
"optional": true
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"publishConfig": {
|
|
101
|
+
"access": "public"
|
|
102
|
+
}
|
|
103
|
+
}
|
package/tool-execute.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
10
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
11
|
+
import { createRunActivityRecorder } from "./activity.js";
|
|
11
12
|
import { discoverAgents } from "./agents.js";
|
|
12
13
|
import { parseSubagentToolCommand, SUBAGENT_CLI_HELP_TEXT } from "./cli.js";
|
|
13
14
|
import {
|
|
@@ -1314,6 +1315,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1314
1315
|
|
|
1315
1316
|
function launchRunInBackground(runState: CommandRunState, taskForAgent: string): Promise<FinalizedRun> {
|
|
1316
1317
|
let claudeCheckpointSent = !!runState.claudeSessionId;
|
|
1318
|
+
const recordActivity = createRunActivityRecorder(pi, runState);
|
|
1317
1319
|
return enqueueSubagentInvocation(() =>
|
|
1318
1320
|
runSingleAgent(
|
|
1319
1321
|
ctx.cwd,
|
|
@@ -1327,6 +1329,7 @@ export function createSubagentToolExecute(pi: ExtensionAPI, store: SubagentStore
|
|
|
1327
1329
|
const current = partial.details?.results?.[0];
|
|
1328
1330
|
if (!current) return;
|
|
1329
1331
|
updateRunFromResult(runState, current);
|
|
1332
|
+
recordActivity();
|
|
1330
1333
|
if (shouldRunAsync && !claudeCheckpointSent && runState.claudeSessionId) {
|
|
1331
1334
|
claudeCheckpointSent = true;
|
|
1332
1335
|
pi.sendMessage(buildRunStartMessage(runState, "started"), {
|