@bastani/atomic 0.9.15 → 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 +19 -0
- package/dist/builtin/intercom/CHANGELOG.md +10 -0
- package/dist/builtin/intercom/README.md +8 -8
- package/dist/builtin/intercom/contact-supervisor-tool.ts +68 -32
- package/dist/builtin/intercom/index-heavy.ts +1 -0
- package/dist/builtin/intercom/index.ts +21 -15
- package/dist/builtin/intercom/intercom-tool.ts +66 -10
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/intercom/parent-ask-handoff.ts +72 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +26 -0
- package/dist/builtin/subagents/README.md +37 -114
- package/dist/builtin/subagents/agents/worker.md +2 -2
- package/dist/builtin/subagents/package.json +1 -5
- package/dist/builtin/subagents/skills/subagent/SKILL.md +26 -103
- package/dist/builtin/subagents/src/extension/index.ts +5 -30
- package/dist/builtin/subagents/src/extension/notification-content.ts +1 -1
- package/dist/builtin/subagents/src/extension/schemas.ts +2 -14
- package/dist/builtin/subagents/src/extension/tool-description.ts +1 -4
- package/dist/builtin/subagents/src/extension/tool-rendering.ts +53 -0
- package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +1 -54
- package/dist/builtin/subagents/src/intercom/result-intercom.ts +4 -4
- package/dist/builtin/subagents/src/runs/foreground/execution-parent-ask-handoff.ts +78 -0
- package/dist/builtin/subagents/src/runs/foreground/inprocess-run-sync.ts +7 -5
- package/dist/builtin/subagents/src/runs/foreground/notify.ts +5 -6
- package/dist/builtin/subagents/src/runs/foreground/parent-ask-output.ts +48 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-burst-display.ts +46 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-burst.ts +403 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-context.ts +48 -9
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-input.ts +3 -1
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-live-update.ts +11 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +164 -93
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel.ts +36 -29
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parent-ask-projection.ts +11 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +42 -17
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-status.ts +3 -121
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-types.ts +7 -2
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +30 -257
- package/dist/builtin/subagents/src/runs/inprocess/control-status.ts +0 -34
- package/dist/builtin/subagents/src/runs/inprocess/index.ts +0 -12
- package/dist/builtin/subagents/src/runs/inprocess/prompt-behavior.ts +1 -1
- package/dist/builtin/subagents/src/runs/inprocess/runner.ts +2 -141
- package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +8 -0
- package/dist/builtin/subagents/src/shared/status-format.ts +3 -3
- package/dist/builtin/subagents/src/shared/types-config.ts +41 -0
- package/dist/builtin/subagents/src/shared/types-foreground-state.ts +7 -15
- package/dist/builtin/subagents/src/shared/types-results.ts +3 -1
- package/dist/builtin/subagents/src/shared/types-runtime.ts +1 -11
- package/dist/builtin/subagents/src/slash/slash-commands.ts +4 -468
- package/dist/builtin/subagents/src/tui/render-progress.ts +2 -2
- package/dist/builtin/subagents/src/tui/render-result-compact.ts +2 -2
- package/dist/builtin/subagents/src/tui/render-result.ts +38 -14
- package/dist/builtin/subagents/src/tui/render-status-progress.ts +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +16 -0
- package/dist/builtin/workflows/README.md +1 -1
- 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/durable/dbos-embedded-postgres-root.ts +548 -39
- package/dist/builtin/workflows/src/durable/dbos-embedded-postgres.ts +529 -65
- package/dist/builtin/workflows/src/durable/dbos-lifecycle.ts +30 -8
- package/dist/builtin/workflows/src/durable/dbos-local-postgres.ts +36 -3
- package/dist/builtin/workflows/src/durable/local-command.ts +68 -10
- package/dist/builtin/workflows/src/extension/companions.ts +1 -1
- package/dist/builtin/workflows/src/extension/dispatcher.ts +7 -1
- package/dist/builtin/workflows/src/extension/extension-factory.ts +2 -2
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +1094 -298
- package/dist/builtin/workflows/src/extension/public-types.ts +2 -2
- package/dist/builtin/workflows/src/extension/render-result.ts +16 -2
- package/dist/builtin/workflows/src/extension/runtime.ts +8 -1
- package/dist/builtin/workflows/src/extension/workflow-request-abort.ts +15 -0
- package/dist/builtin/workflows/src/extension/workflow-tool-content.ts +4 -3
- package/dist/builtin/workflows/src/extension/workflow-tool-registration.ts +107 -13
- package/dist/builtin/workflows/src/extension/workflow-tool.ts +22 -12
- package/dist/builtin/workflows/src/tui/widget.ts +25 -6
- 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/event-bus.d.ts.map +1 -1
- package/dist/core/event-bus.js +19 -3
- package/dist/core/event-bus.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 +29 -27
- package/docs/settings.md +2 -2
- package/docs/subagents.md +18 -15
- package/docs/usage.md +0 -9
- package/docs/workflows.md +27 -10
- 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
- package/dist/builtin/subagents/src/runs/inprocess/attempt-handles.ts +0 -104
- /package/dist/builtin/subagents/src/runs/foreground/{subagent-executor-resume.ts → subagent-executor-cwd.ts} +0 -0
|
@@ -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 {}
|
|
@@ -27,7 +27,7 @@ export function buildMultiProgressLabel(
|
|
|
27
27
|
|
|
28
28
|
if (details.mode === "parallel") {
|
|
29
29
|
const statuses = new Array(totalCount).fill("pending") as Array<
|
|
30
|
-
"pending" | "running" | "completed" | "failed" | "
|
|
30
|
+
"pending" | "running" | "completed" | "failed" | "interrupted" | "detached"
|
|
31
31
|
>;
|
|
32
32
|
for (const progress of details.progress ?? []) {
|
|
33
33
|
if (progress.index >= 0 && progress.index < totalCount) statuses[progress.index] = progress.status;
|
|
@@ -42,7 +42,7 @@ export function buildMultiProgressLabel(
|
|
|
42
42
|
const status =
|
|
43
43
|
result.progress?.status ??
|
|
44
44
|
(result.interrupted || result.status === "interrupted"
|
|
45
|
-
? "
|
|
45
|
+
? "interrupted"
|
|
46
46
|
: result.detached || result.status === "continued"
|
|
47
47
|
? "detached"
|
|
48
48
|
: result.status === "ok"
|
|
@@ -87,7 +87,7 @@ export function renderMultiCompact(d: Details, theme: Theme, now?: number, pulse
|
|
|
87
87
|
const hasRunning =
|
|
88
88
|
d.progress?.some((p) => p.status === "running") || d.results.some((r) => r.progress?.status === "running");
|
|
89
89
|
const failed = d.results.some((r) => r.status === "error" && r.progress?.status !== "running");
|
|
90
|
-
const
|
|
90
|
+
const interruptedOrDetached = d.results.some(
|
|
91
91
|
(r) =>
|
|
92
92
|
(r.interrupted ||
|
|
93
93
|
r.detached ||
|
|
@@ -117,7 +117,7 @@ export function renderMultiCompact(d: Details, theme: Theme, now?: number, pulse
|
|
|
117
117
|
? theme.fg("accent", pulseGlyph(pulseFrame))
|
|
118
118
|
: failed
|
|
119
119
|
? theme.fg("error", "✗")
|
|
120
|
-
:
|
|
120
|
+
: interruptedOrDetached
|
|
121
121
|
? theme.fg("warning", "■")
|
|
122
122
|
: theme.fg("success", "✓");
|
|
123
123
|
const contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "";
|
|
@@ -24,6 +24,19 @@ import {
|
|
|
24
24
|
snapshotNowForProgress,
|
|
25
25
|
} from "./render-status-progress.js";
|
|
26
26
|
|
|
27
|
+
function parentAskOutput(result: AgentToolResult<Details>): string | undefined {
|
|
28
|
+
if (!result.details?.parentAskYielded) return undefined;
|
|
29
|
+
return result.content.find((part) => part.type === "text")?.text;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function appendParentAskOutput(component: Component, output: string | undefined, theme: Theme): Component {
|
|
33
|
+
if (!output) return component;
|
|
34
|
+
const container = new Container();
|
|
35
|
+
container.addChild(component);
|
|
36
|
+
container.addChild(new Text(theme.fg("accent", output), 0, 0));
|
|
37
|
+
return container;
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
export function renderLiveSubagentResult(
|
|
28
41
|
result: AgentToolResult<Details>,
|
|
29
42
|
options: { expanded: boolean; isPartial: boolean },
|
|
@@ -79,18 +92,26 @@ export function renderSubagentResult(
|
|
|
79
92
|
|
|
80
93
|
const expanded = options.expanded;
|
|
81
94
|
const mdTheme = getMarkdownTheme();
|
|
95
|
+
const handoffOutput = parentAskOutput(result);
|
|
82
96
|
|
|
83
97
|
if (d.mode === "single" && d.results.length === 1) {
|
|
84
98
|
const r = d.results[0];
|
|
85
|
-
if (!expanded)
|
|
99
|
+
if (!expanded)
|
|
100
|
+
return appendParentAskOutput(
|
|
101
|
+
renderSingleCompact(d, r, theme, options.now, options.pulseFrame),
|
|
102
|
+
handoffOutput,
|
|
103
|
+
theme,
|
|
104
|
+
);
|
|
86
105
|
const isRunning = r.progress?.status === "running";
|
|
87
106
|
const icon = isRunning
|
|
88
107
|
? theme.fg("warning", "running")
|
|
89
|
-
:
|
|
90
|
-
? theme.fg("warning", "
|
|
91
|
-
: r.status === "
|
|
92
|
-
? theme.fg("
|
|
93
|
-
:
|
|
108
|
+
: d.parentAskYielded
|
|
109
|
+
? theme.fg("warning", "yielded")
|
|
110
|
+
: r.detached || r.status === "continued"
|
|
111
|
+
? theme.fg("warning", "detached")
|
|
112
|
+
: r.status === "ok"
|
|
113
|
+
? theme.fg("success", "ok")
|
|
114
|
+
: theme.fg("error", "failed");
|
|
94
115
|
const contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "";
|
|
95
116
|
const output = r.truncation?.text || getSingleResultOutput(r);
|
|
96
117
|
|
|
@@ -183,10 +204,11 @@ export function renderSubagentResult(
|
|
|
183
204
|
c.addChild(new Spacer(1));
|
|
184
205
|
c.addChild(new Text(fit(theme.fg("dim", `Artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));
|
|
185
206
|
}
|
|
186
|
-
return c;
|
|
207
|
+
return appendParentAskOutput(c, handoffOutput, theme);
|
|
187
208
|
}
|
|
188
209
|
|
|
189
|
-
if (!expanded)
|
|
210
|
+
if (!expanded)
|
|
211
|
+
return appendParentAskOutput(renderMultiCompact(d, theme, options.now, options.pulseFrame), handoffOutput, theme);
|
|
190
212
|
|
|
191
213
|
const hasRunning =
|
|
192
214
|
d.progress?.some((p) => p.status === "running") || d.results.some((r) => r.progress?.status === "running");
|
|
@@ -201,11 +223,13 @@ export function renderSubagentResult(
|
|
|
201
223
|
);
|
|
202
224
|
const icon = hasRunning
|
|
203
225
|
? theme.fg("warning", "running")
|
|
204
|
-
:
|
|
205
|
-
? theme.fg("warning", "
|
|
206
|
-
:
|
|
207
|
-
? theme.fg("
|
|
208
|
-
:
|
|
226
|
+
: d.parentAskYielded
|
|
227
|
+
? theme.fg("warning", "yielded")
|
|
228
|
+
: hasEmptyWithoutTarget
|
|
229
|
+
? theme.fg("warning", "warning")
|
|
230
|
+
: ok === d.results.length
|
|
231
|
+
? theme.fg("success", "ok")
|
|
232
|
+
: theme.fg("error", "failed");
|
|
209
233
|
|
|
210
234
|
const totalSummary =
|
|
211
235
|
d.progressSummary ||
|
|
@@ -410,5 +434,5 @@ export function renderSubagentResult(
|
|
|
410
434
|
c.addChild(new Spacer(1));
|
|
411
435
|
c.addChild(new Text(fit(theme.fg("dim", `Artifacts dir: ${shortenPath(d.artifacts.dir)}`)), 0, 0));
|
|
412
436
|
}
|
|
413
|
-
return c;
|
|
437
|
+
return appendParentAskOutput(c, handoffOutput, theme);
|
|
414
438
|
}
|
|
@@ -135,7 +135,7 @@ export function firstOutputLine(text: string): string {
|
|
|
135
135
|
export function resultStatusLine(result: Details["results"][number], output: string): string {
|
|
136
136
|
if (result.detached) return result.detachedReason ? `Detached: ${result.detachedReason}` : "Detached";
|
|
137
137
|
if (result.status === "continued") return "Continued";
|
|
138
|
-
if (result.interrupted || result.status === "interrupted") return "
|
|
138
|
+
if (result.interrupted || result.status === "interrupted") return "Interrupted";
|
|
139
139
|
if (result.status === "skipped") return result.error ? `Skipped: ${result.error}` : "Skipped";
|
|
140
140
|
if (result.status === "error") return `Error: ${result.error ?? (firstOutputLine(output) || "failed")}`;
|
|
141
141
|
if (hasEmptyTextOutputWithoutOutputTarget(result.task, output)) return "Done (no text output)";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/web-access",
|
|
3
|
-
"version": "0.9.
|
|
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,22 @@ 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
|
+
|
|
15
|
+
## [0.9.16-alpha.1] - 2026-08-23
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Fixed Windows embedded Postgres startup waiting forever when the daemon inherited `pg_ctl` pipes by directly spawning Postgres behind an opaque native process lease. Orderly shutdown now sends fast shutdown and bounded-waits on that exact retained child/HANDLE, preventing mutable-pidfile and PID-reuse races while leaving attached clusters untouched; failed or timed-out shutdown retains the lease for retry. Linux root runtime copies are now exact, root-owned, read/execute-only generations for the dropped Postgres account, with source and post-rename validation, atomically replaced monotonic setup heartbeats, final publication-lease checks, and bounded fail-closed corruption handling ([#2547](https://github.com/bastani-inc/atomic/issues/2547), [#2544](https://github.com/bastani-inc/atomic/pull/2544) by [@darionco](https://github.com/darionco)).
|
|
20
|
+
|
|
21
|
+
- Fixed active zero-stage workflows whose only work is durable `ctx.tool` calls being under-represented in the below-editor `BACKGROUND` panel. Normal-width cards now show a single pending or running tool node by name and status; when multiple nodes are live, their total appears before the width-clamped node details. The narrow collapsed form shows only the live-tool count, and no synthetic stage is created. The panel mounts for tool-only runs loaded after installation, keeps updating through workflow-resource reload, and remounts from the adopted run store after full `/reload`. Installed builds can evaluate the extension and host SDK as separate jiti module copies; their canonical session-bus map is now process-shared, preventing a replacement generation from adopting an empty store and dropping the live job, cancellation, and tool-control owners. Same-process extension reload preserves the live durable tool callback so its active node settles normally alongside cached completed siblings; a real process exit continues to require explicit durable resume for unfinished callbacks.
|
|
22
|
+
|
|
23
|
+
- Fixed a never-settling public workflow tool request blocking the interactive agent indefinitely. All 15 workflow actions now share one hard 30-second request deadline that aborts cancellable work, emits one structured timeout error, discards late settlement, and never retries. Timed-out mutations report an unknown outcome and direct callers to inspect workflow status before retrying; a timed-out run that already allocated detached execution returns its exact run id for that inspection, while background run/resume execution remains independent after its acknowledgement ([#2605](https://github.com/bastani-inc/atomic/issues/2605)).
|
|
24
|
+
|
|
9
25
|
## [0.9.15] - 2026-08-21
|
|
10
26
|
|
|
11
27
|
Cumulative release of the `0.9.15-alpha.1` prerelease. The summary below covers the user-visible outcome of that work; the per-change detail remains in the prerelease section below.
|
|
@@ -662,7 +662,7 @@ Named launches return only after startup admission, while the admitted workflow
|
|
|
662
662
|
|
|
663
663
|
Graceful quit is idempotent for already-paused runs and preserves unresolved `ctx.ui` prompts in DBOS. Pausing or interrupting a stage also holds every queued steering and follow-up item in place: no queued turn, late context-bearing delivery, or workflow continuation starts while the stage is paused, and the existing `resume` action releases the items once in their existing per-queue order without queue release itself starting a provider turn. Stable author-callsite-and-composed-nested-scope reservations are created before prompting and released by exact current-format token generation after answer checkpoint, rejection, or abort. Answering while quit/paused cannot advance workflow code until explicit resume.
|
|
664
664
|
|
|
665
|
-
Workflow durability
|
|
665
|
+
Workflow durability uses DBOS/Postgres as its only persistent backend. Atomic initializes it lazily on the first workflow action, reuses that process-wide instance, and awaits readiness before durable execution or control. `DBOS_SYSTEM_DATABASE_URL` selects an existing database when supplied. Otherwise Atomic first provisions its embedded Postgres (npm-distributed binaries, directly spawned under `~/.atomic/postgres` on port 5439, shared across sessions) and tries DBOS's reusable `dbos-db` Docker container if embedded provisioning fails without leaving retained-process cleanup pending. Linux root execution clears inherited supplementary groups before dropping Postgres to an unprivileged primary GID/UID; when that account cannot traverse the installed binaries, Atomic publishes one exact package-content runtime generation under a root-owned cache, where it stays readable/executable but not writable by Postgres. Reuse and publication re-snapshot the current source, validate the deterministic path after rename, and fail closed on source mutation, corruption, or setup-lease displacement without accumulating unbounded repair copies. If DBOS still cannot become ready, Atomic logs a loud warning and runs against one process-local in-memory backend: workflow execution continues, but its state does not survive the process and `/workflow resume` after exit has nothing to restore. Once DBOS is ready, query or write failures fail the workflow action and do not switch backends. Abrupt Atomic exit releases the retained native process lease without killing Postgres. Orderly shutdown sends fast shutdown and waits on that exact retained process; sessions attached to an already-running cluster never own or stop it. Concurrent Atomic sessions safely share one database: unique per-process executor ids, owner/heartbeat metadata on running workflows, and first-writer-wins claims on contended status transitions prevent double dispatch. Running workflows never appear as resume targets in any session; stale-heartbeat (crashed) ones surface as red `crashed` rows, paused rows render yellow, failed/blocked red, completed green, and the open picker live-updates on local changes plus a bounded cross-session poll.
|
|
666
666
|
|
|
667
667
|
DBOS is the only durable catalog for resume, completed inspection, deletion, and targeted lookup. Session JSONL files remain chat transcripts only. Atomic reads one current durable format and does not convert prior local state or pre-current DBOS records. A completed current-format child checkpoint created before boundary-start or invocation-fingerprint identity existed is shown only when its child checkpoints reciprocally prove the same root, parent run, boundary, child owner, and scope. Active checkpoints without a provable invocation fingerprint, and malformed, duplicate, stale, nonreciprocal, mixed, aliased, cyclic, orphaned, or unsupported topology, fail closed before cache exposure or child/control dispatch: Atomic does not invent a child link or execute child code to repair it.
|
|
668
668
|
|