@bastani/atomic 0.9.16-alpha.1 → 0.9.16-alpha.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/CHANGELOG.md +12 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +13 -0
- package/dist/builtin/subagents/README.md +17 -105
- package/dist/builtin/subagents/package.json +1 -5
- package/dist/builtin/subagents/skills/subagent/SKILL.md +18 -88
- package/dist/builtin/subagents/src/extension/tool-description.ts +1 -3
- package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +0 -53
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +3 -40
- package/dist/builtin/subagents/src/shared/types-runtime.ts +1 -1
- package/dist/builtin/subagents/src/slash/slash-commands.ts +4 -468
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +6 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/bro/LICENSE.txt +21 -0
- package/dist/builtin/workflows/skills/bro/SKILL.md +14 -0
- package/dist/builtin/workflows/skills/how/LICENSE.txt +21 -0
- package/dist/builtin/workflows/skills/how/SKILL.md +162 -0
- package/dist/builtin/workflows/skills/how/references/critic-prompt.md +59 -0
- package/dist/builtin/workflows/skills/how/references/critique-rubric.md +58 -0
- package/dist/builtin/workflows/skills/how/references/explainer-prompt.md +55 -0
- package/dist/builtin/workflows/skills/how/references/explorer-prompt.md +52 -0
- package/dist/builtin/workflows/skills/teach/LICENSE.txt +21 -0
- package/dist/builtin/workflows/skills/teach/SKILL.md +28 -0
- package/dist/builtin/workflows/skills/unslop/LICENSE.txt +21 -0
- package/dist/builtin/workflows/skills/unslop/SKILL.md +87 -0
- package/dist/builtin/workflows/skills/why/LICENSE.txt +21 -0
- package/dist/builtin/workflows/skills/why/SKILL.md +267 -0
- package/dist/builtin/workflows/skills/why/references/epistemics.md +144 -0
- package/dist/builtin/workflows/skills/why/references/investigator-prompt.md +103 -0
- package/dist/builtin/workflows/skills/why/references/source-playbook.md +17 -0
- package/dist/builtin/workflows/skills/why/references/sources/code-archaeology.md +88 -0
- package/dist/builtin/workflows/skills/why/references/sources/databricks.md +70 -0
- package/dist/builtin/workflows/skills/why/references/sources/datadog.md +99 -0
- package/dist/builtin/workflows/skills/why/references/sources/incident-postmortem.md +15 -0
- package/dist/builtin/workflows/skills/why/references/sources/linear.md +48 -0
- package/dist/builtin/workflows/skills/why/references/sources/notion.md +55 -0
- package/dist/builtin/workflows/skills/why/references/sources/sentry.md +100 -0
- package/dist/builtin/workflows/skills/why/references/sources/slack.md +54 -0
- package/dist/builtin/workflows/skills/why/references/synthesizer-prompt.md +135 -0
- package/dist/builtin/workflows/src/extension/companions.ts +1 -1
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +1 -1
- package/dist/core/agent-session-auto-compaction.js +10 -10
- package/dist/core/agent-session-auto-compaction.js.map +1 -1
- package/dist/core/agent-session-events.d.ts.map +1 -1
- package/dist/core/agent-session-events.js +11 -13
- package/dist/core/agent-session-events.js.map +1 -1
- package/dist/core/agent-session-methods.d.ts +1 -1
- package/dist/core/agent-session-methods.d.ts.map +1 -1
- package/dist/core/agent-session-methods.js.map +1 -1
- package/dist/core/agent-session-models.d.ts.map +1 -1
- package/dist/core/agent-session-models.js +5 -7
- package/dist/core/agent-session-models.js.map +1 -1
- package/dist/core/agent-session-prompt.js +4 -4
- package/dist/core/agent-session-prompt.js.map +1 -1
- package/dist/core/agent-session-retry.d.ts +4 -4
- package/dist/core/agent-session-retry.d.ts.map +1 -1
- package/dist/core/agent-session-retry.js +8 -34
- package/dist/core/agent-session-retry.js.map +1 -1
- package/dist/core/agent-session-tool-hooks.js +4 -4
- package/dist/core/agent-session-tool-hooks.js.map +1 -1
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +3 -10
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +0 -3
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +0 -12
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/modes/interactive-engine/remote-command-catalog.d.ts +1 -1
- package/dist/modes/interactive-engine/remote-command-catalog.js +1 -1
- package/dist/modes/interactive-engine/remote-command-catalog.js.map +1 -1
- package/docs/intercom.md +1 -1
- package/docs/settings.md +2 -2
- package/docs/subagents.md +6 -7
- package/docs/usage.md +0 -9
- package/npm-shrinkwrap.json +32 -32
- package/package.json +3 -3
- package/dist/builtin/subagents/prompts/gather-context-and-clarify.md +0 -27
- package/dist/builtin/subagents/prompts/parallel-cleanup.md +0 -61
- package/dist/builtin/subagents/prompts/parallel-context-build.md +0 -43
- package/dist/builtin/subagents/prompts/parallel-handoff-plan.md +0 -47
- package/dist/builtin/subagents/prompts/parallel-research.md +0 -34
- package/dist/builtin/subagents/prompts/parallel-review.md +0 -47
- package/dist/builtin/subagents/prompts/review-loop.md +0 -39
- package/dist/builtin/subagents/src/extension/doctor.ts +0 -188
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@bastani/atomic";
|
|
2
2
|
import { handleManagementAction } from "../../agents/agent-management.js";
|
|
3
3
|
import { clearPendingForegroundControlNotices } from "../../extension/control-notices.js";
|
|
4
|
-
import { buildDoctorReport } from "../../extension/doctor.js";
|
|
5
|
-
import { resolveIntercomSessionTarget } from "../../intercom/intercom-bridge.js";
|
|
6
4
|
import { SUBAGENT_ACTIONS, type SubagentToolResult } from "../../shared/types.js";
|
|
7
5
|
import { inspectInProcessChildStatus, interruptInProcessChild } from "../inprocess/control-status.js";
|
|
8
6
|
import { createExecutionBurstDispatcher } from "./subagent-executor-burst.js";
|
|
@@ -22,7 +20,7 @@ import {
|
|
|
22
20
|
|
|
23
21
|
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete"]);
|
|
24
22
|
/** Observing management actions do not start or mutate child execution. */
|
|
25
|
-
const READ_ONLY_MANAGEMENT_ACTIONS = new Set(["list", "get", "status"
|
|
23
|
+
const READ_ONLY_MANAGEMENT_ACTIONS = new Set(["list", "get", "status"]);
|
|
26
24
|
const FANOUT_REFUSAL_MESSAGE = "Subagent fanout is not authorized for this child.";
|
|
27
25
|
|
|
28
26
|
export type { SubagentExecutorRuntimeDeps, SubagentParamsLike } from "./subagent-executor-types.js";
|
|
@@ -71,41 +69,6 @@ async function handleManagementRequest(input: {
|
|
|
71
69
|
const childRefusal = refuseSubagentChildDelegation(ctx, "management");
|
|
72
70
|
if (childRefusal) return childRefusal;
|
|
73
71
|
}
|
|
74
|
-
if (action === "doctor") {
|
|
75
|
-
let currentSessionFile: string | null = null;
|
|
76
|
-
let currentSessionId = deps.state.currentSessionId;
|
|
77
|
-
let sessionError: string | undefined;
|
|
78
|
-
try {
|
|
79
|
-
currentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
|
|
80
|
-
currentSessionId = ctx.sessionManager.getSessionId();
|
|
81
|
-
} catch (error) {
|
|
82
|
-
sessionError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
83
|
-
}
|
|
84
|
-
let orchestratorTarget: string | undefined;
|
|
85
|
-
try {
|
|
86
|
-
orchestratorTarget = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
|
|
87
|
-
} catch {}
|
|
88
|
-
return {
|
|
89
|
-
content: [
|
|
90
|
-
{
|
|
91
|
-
type: "text",
|
|
92
|
-
text: buildDoctorReport({
|
|
93
|
-
cwd: requestCwd,
|
|
94
|
-
config: deps.config,
|
|
95
|
-
state: deps.state,
|
|
96
|
-
context: paramsWithResolvedCwd.context,
|
|
97
|
-
requestedSessionDir: paramsWithResolvedCwd.sessionDir,
|
|
98
|
-
currentSessionFile,
|
|
99
|
-
currentSessionId,
|
|
100
|
-
orchestratorTarget,
|
|
101
|
-
sessionError,
|
|
102
|
-
expandTilde: deps.expandTilde,
|
|
103
|
-
}),
|
|
104
|
-
},
|
|
105
|
-
],
|
|
106
|
-
details: { mode: "management", results: [] },
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
72
|
if (action === "status") {
|
|
110
73
|
const targetRunId = paramsWithResolvedCwd.id ?? paramsWithResolvedCwd.runId;
|
|
111
74
|
const inProcess = inspectInProcessChildStatus(targetRunId);
|
|
@@ -189,8 +152,8 @@ export function createSubagentExecutor(rawDeps: ExecutorDeps): {
|
|
|
189
152
|
return handleManagementRequest({ params, paramsWithResolvedCwd, requestCwd, ctx, deps });
|
|
190
153
|
}
|
|
191
154
|
// Fanout authorization gates delegation and privileged control. Only `list`,
|
|
192
|
-
// `get`,
|
|
193
|
-
//
|
|
155
|
+
// `get`, and `status` stay available to an unauthorized child; `interrupt`
|
|
156
|
+
// and mutating management are refused by handleManagementRequest.
|
|
194
157
|
if (deps.childPolicy && !deps.childPolicy.fanoutAuthorized) {
|
|
195
158
|
return {
|
|
196
159
|
content: [{ type: "text", text: FANOUT_REFUSAL_MESSAGE }],
|
|
@@ -81,7 +81,7 @@ export const SLASH_SUBAGENT_STARTED_EVENT = "subagent:slash:started";
|
|
|
81
81
|
export const SLASH_SUBAGENT_RESPONSE_EVENT = "subagent:slash:response";
|
|
82
82
|
export const SLASH_SUBAGENT_UPDATE_EVENT = "subagent:slash:update";
|
|
83
83
|
export const SLASH_SUBAGENT_CANCEL_EVENT = "subagent:slash:cancel";
|
|
84
|
-
export const SUBAGENT_ACTIONS = ["list", "get", "create", "update", "delete", "status", "interrupt"
|
|
84
|
+
export const SUBAGENT_ACTIONS = ["list", "get", "create", "update", "delete", "status", "interrupt"] as const;
|
|
85
85
|
|
|
86
86
|
export const DEFAULT_FORK_PREAMBLE =
|
|
87
87
|
"You are a delegated subagent running from a fork of the parent session. " +
|
|
@@ -1,469 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
type ExtensionAPI,
|
|
6
|
-
type ExtensionContext,
|
|
7
|
-
isStaleExtensionContextError,
|
|
8
|
-
keyHintIfBound,
|
|
9
|
-
} from "@bastani/atomic";
|
|
10
|
-
import { Key, matchesKey } from "@earendil-works/pi-tui";
|
|
11
|
-
import { discoverAgents } from "../agents/agents.js";
|
|
12
|
-
import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.js";
|
|
13
|
-
import {
|
|
14
|
-
type SingleResult,
|
|
15
|
-
SLASH_RESULT_TYPE,
|
|
16
|
-
SLASH_SUBAGENT_CANCEL_EVENT,
|
|
17
|
-
SLASH_SUBAGENT_REQUEST_EVENT,
|
|
18
|
-
SLASH_SUBAGENT_RESPONSE_EVENT,
|
|
19
|
-
SLASH_SUBAGENT_STARTED_EVENT,
|
|
20
|
-
SLASH_SUBAGENT_UPDATE_EVENT,
|
|
21
|
-
type SubagentState,
|
|
22
|
-
} from "../shared/types.js";
|
|
23
|
-
import { registerBridgeRequestSettlement } from "./bridge-settlement.js";
|
|
24
|
-
import type { SlashSubagentResponse, SlashSubagentUpdate } from "./slash-bridge.js";
|
|
25
|
-
import { applySlashUpdate, buildSlashInitialResult, failSlashResult, finalizeSlashResult } from "./slash-live-state.js";
|
|
1
|
+
import type { ExtensionAPI } from "@bastani/atomic";
|
|
2
|
+
import type { SubagentState } from "../shared/types.js";
|
|
26
3
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
outputMode?: "inline" | "file-only";
|
|
30
|
-
reads?: string[] | false;
|
|
31
|
-
model?: string;
|
|
32
|
-
skill?: string[] | false;
|
|
33
|
-
progress?: boolean;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const parseInlineConfig = (raw: string): InlineConfig => {
|
|
37
|
-
const config: InlineConfig = {};
|
|
38
|
-
for (const part of raw.split(",")) {
|
|
39
|
-
const trimmed = part.trim();
|
|
40
|
-
if (!trimmed) continue;
|
|
41
|
-
const eq = trimmed.indexOf("=");
|
|
42
|
-
if (eq === -1) {
|
|
43
|
-
if (trimmed === "progress") config.progress = true;
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
const key = trimmed.slice(0, eq).trim();
|
|
47
|
-
const val = trimmed.slice(eq + 1).trim();
|
|
48
|
-
switch (key) {
|
|
49
|
-
case "output":
|
|
50
|
-
config.output = val === "false" ? false : val;
|
|
51
|
-
break;
|
|
52
|
-
case "outputMode":
|
|
53
|
-
if (val === "inline" || val === "file-only") config.outputMode = val;
|
|
54
|
-
break;
|
|
55
|
-
case "reads":
|
|
56
|
-
config.reads = val === "false" ? false : val.split("+").filter(Boolean);
|
|
57
|
-
break;
|
|
58
|
-
case "model":
|
|
59
|
-
config.model = val || undefined;
|
|
60
|
-
break;
|
|
61
|
-
case "skill":
|
|
62
|
-
case "skills":
|
|
63
|
-
config.skill = val === "false" ? false : val.split("+").filter(Boolean);
|
|
64
|
-
break;
|
|
65
|
-
case "progress":
|
|
66
|
-
config.progress = val !== "false";
|
|
67
|
-
break;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return config;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const parseAgentToken = (token: string): { name: string; config: InlineConfig } => {
|
|
74
|
-
const bracket = token.indexOf("[");
|
|
75
|
-
if (bracket === -1) return { name: token, config: {} };
|
|
76
|
-
const end = token.lastIndexOf("]");
|
|
77
|
-
return {
|
|
78
|
-
name: token.slice(0, bracket),
|
|
79
|
-
config: parseInlineConfig(token.slice(bracket + 1, end !== -1 ? end : undefined)),
|
|
80
|
-
};
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const extractExecutionFlags = (rawArgs: string): { args: string; fork: boolean } => {
|
|
84
|
-
let args = rawArgs.trim();
|
|
85
|
-
let fork = false;
|
|
86
|
-
while (true) {
|
|
87
|
-
if (args.endsWith(" --fork") || args === "--fork") {
|
|
88
|
-
fork = true;
|
|
89
|
-
args = args === "--fork" ? "" : args.slice(0, -7).trim();
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
break;
|
|
93
|
-
}
|
|
94
|
-
return { args, fork };
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const makeAgentCompletions = (state: SubagentState, multiAgent: boolean) => (prefix: string) => {
|
|
98
|
-
if (!state.baseCwd) return null;
|
|
99
|
-
const agents = discoverAgents(state.baseCwd, "both").agents;
|
|
100
|
-
if (!multiAgent) {
|
|
101
|
-
if (prefix.includes(" ")) return null;
|
|
102
|
-
return agents.filter((a) => a.name.startsWith(prefix)).map((a) => ({ value: a.name, label: a.name }));
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const lastArrow = prefix.lastIndexOf(" -> ");
|
|
106
|
-
const segment = lastArrow !== -1 ? prefix.slice(lastArrow + 4) : prefix;
|
|
107
|
-
if (segment.includes(" -- ") || segment.includes('"') || segment.includes("'")) return null;
|
|
108
|
-
|
|
109
|
-
const lastWord = (prefix.match(/(\S*)$/) || ["", ""])[1];
|
|
110
|
-
const beforeLastWord = prefix.slice(0, prefix.length - lastWord.length);
|
|
111
|
-
|
|
112
|
-
if (lastWord === "->") {
|
|
113
|
-
return agents.map((a) => ({ value: `${prefix} ${a.name}`, label: a.name }));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return agents
|
|
117
|
-
.filter((a) => a.name.startsWith(lastWord))
|
|
118
|
-
.map((a) => ({ value: `${beforeLastWord}${a.name}`, label: a.name }));
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
async function requestSlashRun(
|
|
122
|
-
pi: ExtensionAPI,
|
|
123
|
-
ctx: ExtensionContext,
|
|
124
|
-
requestId: string,
|
|
125
|
-
params: SubagentParamsLike,
|
|
126
|
-
): Promise<SlashSubagentResponse> {
|
|
127
|
-
return new Promise((resolve, reject) => {
|
|
128
|
-
let done = false;
|
|
129
|
-
let started = false;
|
|
130
|
-
|
|
131
|
-
const startTimeoutMs = 15_000;
|
|
132
|
-
const startTimeout = setTimeout(() => {
|
|
133
|
-
finish(() =>
|
|
134
|
-
reject(
|
|
135
|
-
new Error("Slash subagent bridge did not start within 15s. Ensure the extension is loaded correctly."),
|
|
136
|
-
),
|
|
137
|
-
);
|
|
138
|
-
}, startTimeoutMs);
|
|
139
|
-
|
|
140
|
-
const onStarted = (data: unknown) => {
|
|
141
|
-
if (done || !data || typeof data !== "object") return;
|
|
142
|
-
if ((data as { requestId?: unknown }).requestId !== requestId) return;
|
|
143
|
-
started = true;
|
|
144
|
-
clearTimeout(startTimeout);
|
|
145
|
-
if (ctx.hasUI) ctx.ui.setStatus("subagent-slash", "running...");
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
const onResponse = (data: unknown) => {
|
|
149
|
-
if (done || !data || typeof data !== "object") return;
|
|
150
|
-
const response = data as Partial<SlashSubagentResponse>;
|
|
151
|
-
if (response.requestId !== requestId) return;
|
|
152
|
-
clearTimeout(startTimeout);
|
|
153
|
-
finish(() => resolve(response as SlashSubagentResponse));
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
const onUpdate = (data: unknown) => {
|
|
157
|
-
if (done || !data || typeof data !== "object") return;
|
|
158
|
-
const update = data as SlashSubagentUpdate;
|
|
159
|
-
if (update.requestId !== requestId) return;
|
|
160
|
-
applySlashUpdate(requestId, update, pi);
|
|
161
|
-
if (!ctx.hasUI) return;
|
|
162
|
-
const tool = update.currentTool ? ` ${update.currentTool}` : "";
|
|
163
|
-
const count = update.toolCount ?? 0;
|
|
164
|
-
const expandHint = keyHintIfBound("app.tools.expand", "live detail");
|
|
165
|
-
ctx.ui.setStatus("subagent-slash", `${count} tools${tool}${expandHint ? ` | ${expandHint}` : ""}`);
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const onTerminalInput = ctx.hasUI
|
|
169
|
-
? ctx.ui.onTerminalInput((input) => {
|
|
170
|
-
if (!matchesKey(input, Key.escape)) return undefined;
|
|
171
|
-
try {
|
|
172
|
-
pi.events.emit(SLASH_SUBAGENT_CANCEL_EVENT, { requestId });
|
|
173
|
-
finish(() => reject(new Error("Cancelled")));
|
|
174
|
-
} catch (error) {
|
|
175
|
-
finish(() => reject(error));
|
|
176
|
-
}
|
|
177
|
-
return { consume: true };
|
|
178
|
-
})
|
|
179
|
-
: undefined;
|
|
180
|
-
|
|
181
|
-
const unsubStarted = pi.events.on(SLASH_SUBAGENT_STARTED_EVENT, onStarted);
|
|
182
|
-
const unsubResponse = pi.events.on(SLASH_SUBAGENT_RESPONSE_EVENT, onResponse);
|
|
183
|
-
const unsubUpdate = pi.events.on(SLASH_SUBAGENT_UPDATE_EVENT, onUpdate);
|
|
184
|
-
|
|
185
|
-
let unregisterSettlement: (() => void) | undefined;
|
|
186
|
-
let reportedStop = false;
|
|
187
|
-
const finish = (next: () => void) => {
|
|
188
|
-
if (done) return;
|
|
189
|
-
done = true;
|
|
190
|
-
clearTimeout(startTimeout);
|
|
191
|
-
unsubStarted();
|
|
192
|
-
unsubResponse();
|
|
193
|
-
unsubUpdate();
|
|
194
|
-
onTerminalInput?.();
|
|
195
|
-
unregisterSettlement?.();
|
|
196
|
-
next();
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
unregisterSettlement = registerBridgeRequestSettlement("slash", requestId, {
|
|
200
|
-
reject: (error: unknown) => {
|
|
201
|
-
if (!reportedStop) {
|
|
202
|
-
reportedStop = true;
|
|
203
|
-
console.error(
|
|
204
|
-
isStaleExtensionContextError(error)
|
|
205
|
-
? "Subagent slash command stopped because its response runtime was replaced or reloaded."
|
|
206
|
-
: "Subagent slash command stopped before its response was delivered.",
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
finish(() => reject(error));
|
|
210
|
-
},
|
|
211
|
-
});
|
|
212
|
-
try {
|
|
213
|
-
pi.events.emit(SLASH_SUBAGENT_REQUEST_EVENT, { requestId, params });
|
|
214
|
-
} catch (error) {
|
|
215
|
-
finish(() => reject(error));
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Bridge emits STARTED synchronously during REQUEST emit.
|
|
220
|
-
// If not started, no bridge received the request.
|
|
221
|
-
if (!started && done) return;
|
|
222
|
-
if (!started) {
|
|
223
|
-
finish(() =>
|
|
224
|
-
reject(new Error("No slash subagent bridge responded. Ensure the subagent extension is loaded correctly.")),
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function extractSlashMessageText(content: string | Array<{ type?: string; text?: string }>): string {
|
|
231
|
-
if (typeof content === "string") return content;
|
|
232
|
-
if (!Array.isArray(content)) return "";
|
|
233
|
-
return content
|
|
234
|
-
.filter((part): part is { type: "text"; text: string } => part?.type === "text" && typeof part.text === "string")
|
|
235
|
-
.map((part) => part.text)
|
|
236
|
-
.join("\n");
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function formatExportPathList(paths: string[]): string {
|
|
240
|
-
return paths.map((file) => `- \`${file}\``).join("\n");
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function collectResultPaths(results: SingleResult[], getPath: (result: SingleResult) => string | undefined): string[] {
|
|
244
|
-
return results.map(getPath).filter((file): file is string => typeof file === "string" && file.length > 0);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function buildSlashExportText(response: SlashSubagentResponse): string {
|
|
248
|
-
const output = extractSlashMessageText(response.result.content) || response.errorText || "(no output)";
|
|
249
|
-
const results = response.result.details?.results ?? [];
|
|
250
|
-
const sessionFiles = collectResultPaths(results, (result) => result.sessionFile);
|
|
251
|
-
const savedOutputs = collectResultPaths(results, (result) => result.savedOutputPath);
|
|
252
|
-
const artifactOutputs = collectResultPaths(results, (result) => result.artifactPaths?.outputPath);
|
|
253
|
-
const sections = ["## Subagent result", output];
|
|
254
|
-
if (sessionFiles.length > 0) sections.push("## Child session exports", formatExportPathList(sessionFiles));
|
|
255
|
-
if (savedOutputs.length > 0) sections.push("## Saved outputs", formatExportPathList(savedOutputs));
|
|
256
|
-
if (artifactOutputs.length > 0) sections.push("## Artifact outputs", formatExportPathList(artifactOutputs));
|
|
257
|
-
return sections.join("\n\n");
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function persistSlashSessionSnapshot(ctx: ExtensionContext): void {
|
|
261
|
-
try {
|
|
262
|
-
if (!ctx.sessionManager) return;
|
|
263
|
-
const sessionManager = ctx.sessionManager as typeof ctx.sessionManager & {
|
|
264
|
-
_rewriteFile?: () => void;
|
|
265
|
-
flushed?: boolean;
|
|
266
|
-
};
|
|
267
|
-
const sessionFile = sessionManager.getSessionFile();
|
|
268
|
-
if (!sessionFile || typeof sessionManager._rewriteFile !== "function") return;
|
|
269
|
-
fs.mkdirSync(path.dirname(sessionFile), { recursive: true });
|
|
270
|
-
sessionManager._rewriteFile();
|
|
271
|
-
sessionManager.flushed = true;
|
|
272
|
-
} catch (error) {
|
|
273
|
-
console.error("Failed to persist slash session snapshot for export:", error);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
async function runSlashSubagent(pi: ExtensionAPI, ctx: ExtensionContext, params: SubagentParamsLike): Promise<void> {
|
|
278
|
-
if (ctx.hasUI) ctx.ui.setToolsExpanded(false);
|
|
279
|
-
const requestId = randomUUID();
|
|
280
|
-
const initialDetails = buildSlashInitialResult(requestId, params, pi);
|
|
281
|
-
const initialText = extractSlashMessageText(initialDetails.result.content) || "Running subagent...";
|
|
282
|
-
pi.sendMessage({
|
|
283
|
-
customType: SLASH_RESULT_TYPE,
|
|
284
|
-
content: initialText,
|
|
285
|
-
display: true,
|
|
286
|
-
details: initialDetails,
|
|
287
|
-
});
|
|
288
|
-
persistSlashSessionSnapshot(ctx);
|
|
289
|
-
|
|
290
|
-
try {
|
|
291
|
-
const response = await requestSlashRun(pi, ctx, requestId, params);
|
|
292
|
-
const finalDetails = finalizeSlashResult(response, pi);
|
|
293
|
-
pi.sendMessage({
|
|
294
|
-
customType: SLASH_RESULT_TYPE,
|
|
295
|
-
content: buildSlashExportText(response),
|
|
296
|
-
display: true,
|
|
297
|
-
details: finalDetails,
|
|
298
|
-
});
|
|
299
|
-
persistSlashSessionSnapshot(ctx);
|
|
300
|
-
if (ctx.hasUI) {
|
|
301
|
-
ctx.ui.setStatus("subagent-slash", undefined);
|
|
302
|
-
}
|
|
303
|
-
if (response.isError && ctx.hasUI) {
|
|
304
|
-
ctx.ui.notify(response.errorText || "Subagent failed", "error");
|
|
305
|
-
}
|
|
306
|
-
} catch (error) {
|
|
307
|
-
if (isStaleExtensionContextError(error)) throw error;
|
|
308
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
309
|
-
const failedDetails = failSlashResult(requestId, params, message, pi);
|
|
310
|
-
pi.sendMessage({
|
|
311
|
-
customType: SLASH_RESULT_TYPE,
|
|
312
|
-
content: `## Subagent result\n\n${message}`,
|
|
313
|
-
display: true,
|
|
314
|
-
details: failedDetails,
|
|
315
|
-
});
|
|
316
|
-
persistSlashSessionSnapshot(ctx);
|
|
317
|
-
if (ctx.hasUI) {
|
|
318
|
-
ctx.ui.setStatus("subagent-slash", undefined);
|
|
319
|
-
}
|
|
320
|
-
if (message === "Cancelled") {
|
|
321
|
-
if (ctx.hasUI) ctx.ui.notify("Cancelled", "warning");
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
if (ctx.hasUI) ctx.ui.notify(message, "error");
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
interface ParsedStep {
|
|
329
|
-
name: string;
|
|
330
|
-
config: InlineConfig;
|
|
331
|
-
task?: string;
|
|
332
|
-
}
|
|
333
|
-
const parseAgentArgs = (
|
|
334
|
-
state: SubagentState,
|
|
335
|
-
args: string,
|
|
336
|
-
command: string,
|
|
337
|
-
ctx: ExtensionContext,
|
|
338
|
-
): { steps: ParsedStep[]; task: string } | null => {
|
|
339
|
-
const input = args.trim();
|
|
340
|
-
const usage = `Usage: /${command} agent1 "task1" -> agent2 "task2"`;
|
|
341
|
-
let steps: ParsedStep[];
|
|
342
|
-
let sharedTask: string;
|
|
343
|
-
if (input.includes(" -> ")) {
|
|
344
|
-
const segments = input.split(" -> ");
|
|
345
|
-
steps = [];
|
|
346
|
-
for (const seg of segments) {
|
|
347
|
-
const trimmed = seg.trim();
|
|
348
|
-
if (!trimmed) continue;
|
|
349
|
-
let agentPart: string;
|
|
350
|
-
let task: string | undefined;
|
|
351
|
-
const qMatch = trimmed.match(/^(\S+(?:\[[^\]]*\])?)\s+(?:"([^"]*)"|'([^']*)')$/);
|
|
352
|
-
if (qMatch) {
|
|
353
|
-
agentPart = qMatch[1]!;
|
|
354
|
-
task = (qMatch[2] ?? qMatch[3]) || undefined;
|
|
355
|
-
} else {
|
|
356
|
-
const dashIdx = trimmed.indexOf(" -- ");
|
|
357
|
-
if (dashIdx !== -1) {
|
|
358
|
-
agentPart = trimmed.slice(0, dashIdx).trim();
|
|
359
|
-
task = trimmed.slice(dashIdx + 4).trim() || undefined;
|
|
360
|
-
} else {
|
|
361
|
-
agentPart = trimmed;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
const parsed = parseAgentToken(agentPart);
|
|
365
|
-
steps.push({ ...parsed, task });
|
|
366
|
-
}
|
|
367
|
-
sharedTask = steps.find((s) => s.task)?.task ?? "";
|
|
368
|
-
} else {
|
|
369
|
-
const delimiterIndex = input.indexOf(" -- ");
|
|
370
|
-
if (delimiterIndex === -1) {
|
|
371
|
-
ctx.ui.notify(usage, "error");
|
|
372
|
-
return null;
|
|
373
|
-
}
|
|
374
|
-
const agentsPart = input.slice(0, delimiterIndex).trim();
|
|
375
|
-
sharedTask = input.slice(delimiterIndex + 4).trim();
|
|
376
|
-
if (!agentsPart || !sharedTask) {
|
|
377
|
-
ctx.ui.notify(usage, "error");
|
|
378
|
-
return null;
|
|
379
|
-
}
|
|
380
|
-
steps = agentsPart
|
|
381
|
-
.split(/\s+/)
|
|
382
|
-
.filter(Boolean)
|
|
383
|
-
.map((t) => parseAgentToken(t));
|
|
384
|
-
}
|
|
385
|
-
if (steps.length === 0) {
|
|
386
|
-
ctx.ui.notify(usage, "error");
|
|
387
|
-
return null;
|
|
388
|
-
}
|
|
389
|
-
if (!state.baseCwd) {
|
|
390
|
-
ctx.ui.notify("Subagent session cwd is not initialized yet", "error");
|
|
391
|
-
return null;
|
|
392
|
-
}
|
|
393
|
-
const agents = discoverAgents(state.baseCwd, "both").agents;
|
|
394
|
-
for (const step of steps) {
|
|
395
|
-
if (!agents.find((a) => a.name === step.name)) {
|
|
396
|
-
ctx.ui.notify(`Unknown agent: ${step.name}`, "error");
|
|
397
|
-
return null;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
if (command === "parallel" && !steps.some((s) => s.task) && !sharedTask) {
|
|
401
|
-
ctx.ui.notify("At least one step must have a task", "error");
|
|
402
|
-
return null;
|
|
403
|
-
}
|
|
404
|
-
return { steps, task: sharedTask };
|
|
405
|
-
};
|
|
406
|
-
export function registerSlashCommands(pi: ExtensionAPI, state: SubagentState): void {
|
|
407
|
-
pi.registerCommand("run", {
|
|
408
|
-
description: "Run a subagent directly: /run agent[output=file] [task] [--fork]",
|
|
409
|
-
getArgumentCompletions: makeAgentCompletions(state, false),
|
|
410
|
-
handler: async (args, ctx) => {
|
|
411
|
-
const { args: cleanedArgs, fork } = extractExecutionFlags(args);
|
|
412
|
-
const input = cleanedArgs.trim();
|
|
413
|
-
const firstSpace = input.indexOf(" ");
|
|
414
|
-
if (!input) {
|
|
415
|
-
ctx.ui.notify("Usage: /run <agent> [task] [--fork]", "error");
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
const { name: agentName, config: inline } = parseAgentToken(
|
|
419
|
-
firstSpace === -1 ? input : input.slice(0, firstSpace),
|
|
420
|
-
);
|
|
421
|
-
const task = firstSpace === -1 ? "" : input.slice(firstSpace + 1).trim();
|
|
422
|
-
if (!state.baseCwd) {
|
|
423
|
-
ctx.ui.notify("Subagent session cwd is not initialized yet", "error");
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
const agents = discoverAgents(state.baseCwd, "both").agents;
|
|
427
|
-
if (!agents.find((a) => a.name === agentName)) {
|
|
428
|
-
ctx.ui.notify(`Unknown agent: ${agentName}`, "error");
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
const params: SubagentParamsLike = { agent: agentName, task, agentScope: "both" };
|
|
432
|
-
if (inline.reads !== undefined) params.reads = inline.reads;
|
|
433
|
-
if (inline.output !== undefined) params.output = inline.output;
|
|
434
|
-
if (inline.outputMode !== undefined) params.outputMode = inline.outputMode;
|
|
435
|
-
if (inline.skill !== undefined) params.skill = inline.skill;
|
|
436
|
-
if (inline.model) params.model = inline.model;
|
|
437
|
-
if (fork) params.context = "fork";
|
|
438
|
-
await runSlashSubagent(pi, ctx, params);
|
|
439
|
-
},
|
|
440
|
-
});
|
|
441
|
-
pi.registerCommand("parallel", {
|
|
442
|
-
description: 'Run agents in parallel: /parallel scout "task1" -> reviewer "task2" [--fork]',
|
|
443
|
-
getArgumentCompletions: makeAgentCompletions(state, true),
|
|
444
|
-
handler: async (args, ctx) => {
|
|
445
|
-
const { args: cleanedArgs, fork } = extractExecutionFlags(args);
|
|
446
|
-
const parsed = parseAgentArgs(state, cleanedArgs, "parallel", ctx);
|
|
447
|
-
if (!parsed) return;
|
|
448
|
-
const tasks = parsed.steps.map(({ name, config, task: stepTask }) => ({
|
|
449
|
-
agent: name,
|
|
450
|
-
task: stepTask ?? parsed.task,
|
|
451
|
-
...(config.output !== undefined ? { output: config.output } : {}),
|
|
452
|
-
...(config.outputMode !== undefined ? { outputMode: config.outputMode } : {}),
|
|
453
|
-
...(config.reads !== undefined ? { reads: config.reads } : {}),
|
|
454
|
-
...(config.model ? { model: config.model } : {}),
|
|
455
|
-
...(config.skill !== undefined ? { skill: config.skill } : {}),
|
|
456
|
-
...(config.progress !== undefined ? { progress: config.progress } : {}),
|
|
457
|
-
}));
|
|
458
|
-
const params: SubagentParamsLike = { tasks, agentScope: "both" };
|
|
459
|
-
if (fork) params.context = "fork";
|
|
460
|
-
await runSlashSubagent(pi, ctx, params);
|
|
461
|
-
},
|
|
462
|
-
});
|
|
463
|
-
pi.registerCommand("subagents-doctor", {
|
|
464
|
-
description: "Show subagent diagnostics",
|
|
465
|
-
handler: async (_args, ctx) => {
|
|
466
|
-
await runSlashSubagent(pi, ctx, { action: "doctor" });
|
|
467
|
-
},
|
|
468
|
-
});
|
|
469
|
-
}
|
|
4
|
+
/** Human `/run` and `/parallel` commands are gone. Launch children with `subagent`. */
|
|
5
|
+
export function registerSlashCommands(_pi: ExtensionAPI, _state: SubagentState): void {}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/web-access",
|
|
3
|
-
"version": "0.9.16-alpha.
|
|
3
|
+
"version": "0.9.16-alpha.2",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for web search, URL fetching, GitHub repo cloning, PDF/video extraction. Fork of: https://github.com/nicobailon/pi-web-access",
|
|
6
6
|
"contributors": [
|
|
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.16-alpha.2] - 2026-08-23
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Added Lauren Tan's bundled `bro`, `how`, `teach`, `unslop`, and `why` pstack skills for plain-language restatement, codebase explanation, teaching, writing cleanup, and evidence-backed design rationale. Sourced from https://github.com/cursor/plugins and distributed under the MIT License.
|
|
14
|
+
|
|
9
15
|
## [0.9.16-alpha.1] - 2026-08-23
|
|
10
16
|
|
|
11
17
|
### Fixed
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lauren Tan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bro
|
|
3
|
+
description: Restate the last message in plain human language, with no jargon.
|
|
4
|
+
license: MIT. LICENSE.txt has complete terms
|
|
5
|
+
metadata:
|
|
6
|
+
author: Lauren Tan
|
|
7
|
+
github-repo: https://github.com/cursor/plugins
|
|
8
|
+
github-path: pstack/skills/bro
|
|
9
|
+
github-ref: refs/heads/main
|
|
10
|
+
github-tree-sha: 46125561306434d8a1d7745d540d8932ab0cd2a2
|
|
11
|
+
disable-model-invocation: true
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Restate your last message. Stop using jargon and speak coherently. State it more simply and concisely, like one human talking to another.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lauren Tan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|