@bastani/atomic 0.9.18-alpha.1 → 0.9.18-alpha.3
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 +6 -0
- package/dist/builtin/intercom/CHANGELOG.md +8 -0
- package/dist/builtin/intercom/README.md +3 -3
- package/dist/builtin/intercom/broker/broker.ts +156 -12
- package/dist/builtin/intercom/broker/client.ts +89 -49
- package/dist/builtin/intercom/broker/pending-question-index.ts +10 -0
- package/dist/builtin/intercom/broker/send-handler.ts +32 -17
- package/dist/builtin/intercom/index.bundle.mjs +153 -50
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/intercom/skills/intercom/SKILL.md +1 -1
- package/dist/builtin/intercom/types.ts +30 -2
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +18 -0
- package/dist/builtin/workflows/README.md +11 -2
- package/dist/builtin/workflows/builtin/{chunk-ngffz3y8.js → chunk-fghhy2a5.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-6v0yv8tj.js → chunk-h3r2vkzc.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-brerg33r.js → chunk-n58a7v26.js} +0 -1
- package/dist/builtin/workflows/builtin/goal.js +2 -2
- package/dist/builtin/workflows/builtin/index.js +3 -3
- package/dist/builtin/workflows/builtin/ralph.js +2 -2
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +258 -49
- package/dist/builtin/workflows/src/index.js +35 -9
- package/docs/intercom.md +7 -5
- package/docs/windows.md +1 -1
- package/docs/workflows.md +18 -9
- package/npm-shrinkwrap.json +32 -32
- package/package.json +3 -3
|
@@ -54,7 +54,7 @@ var renderContactSupervisorResult = (result, { isPartial }, theme, context) => {
|
|
|
54
54
|
return new Text(theme.fg("warning", "Intercom working..."), 0, 0);
|
|
55
55
|
}
|
|
56
56
|
const details = result.details;
|
|
57
|
-
const failed = Boolean(context.isError || details?.error === true || details?.delivered === false);
|
|
57
|
+
const failed = Boolean(context.isError || details?.error === true || details?.delivered === false && details.queued !== true);
|
|
58
58
|
let text = failed ? theme.fg("error", "✗ ") : theme.fg("success", "✓ ");
|
|
59
59
|
text += theme.fg(failed ? "error" : "text", firstTextContent(result));
|
|
60
60
|
if (details?.messageId && !context.expanded) {
|
|
@@ -416,6 +416,9 @@ import { randomUUID as randomUUID2 } from "crypto";
|
|
|
416
416
|
function toError(error) {
|
|
417
417
|
return error instanceof Error ? error : new Error(String(error));
|
|
418
418
|
}
|
|
419
|
+
function isWorkflowStageRosterEntries(value) {
|
|
420
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "object" && entry !== null && entry.kind === "workflow-stage" && typeof entry.runId === "string" && typeof entry.stageId === "string" && typeof entry.stageName === "string" && typeof entry.target === "string" && (entry.lifecycle === "pending" || entry.lifecycle === "running") && typeof entry.group === "string" && (entry.sessionId === undefined || typeof entry.sessionId === "string"));
|
|
421
|
+
}
|
|
419
422
|
var BROKER_SOCKET, GROUP_REQUEST_TIMEOUT_MS = 5000, PRESENCE_ACK_TIMEOUT_MS = 5000, IntercomClient;
|
|
420
423
|
var init_client = __esm(() => {
|
|
421
424
|
init_paths();
|
|
@@ -636,16 +639,15 @@ var init_client = __esm(() => {
|
|
|
636
639
|
break;
|
|
637
640
|
}
|
|
638
641
|
case "sessions": {
|
|
639
|
-
const { requestId, sessions } = brokerMessage;
|
|
640
|
-
if (typeof requestId !== "string" || !Array.isArray(sessions) || !sessions.every(isSessionInfo)) {
|
|
642
|
+
const { requestId, sessions, workflowStages } = brokerMessage;
|
|
643
|
+
if (typeof requestId !== "string" || !Array.isArray(sessions) || !sessions.every(isSessionInfo) || workflowStages !== undefined && !isWorkflowStageRosterEntries(workflowStages)) {
|
|
641
644
|
throw new Error("Invalid sessions message");
|
|
642
645
|
}
|
|
643
646
|
const pending = this.pendingLists.get(requestId);
|
|
644
|
-
if (!pending)
|
|
647
|
+
if (!pending)
|
|
645
648
|
return;
|
|
646
|
-
}
|
|
647
649
|
this.pendingLists.delete(requestId);
|
|
648
|
-
pending.resolve(sessions);
|
|
650
|
+
pending.resolve({ sessions, workflowStages: workflowStages ?? [] });
|
|
649
651
|
break;
|
|
650
652
|
}
|
|
651
653
|
case "groups": {
|
|
@@ -885,7 +887,10 @@ var init_client = __esm(() => {
|
|
|
885
887
|
}
|
|
886
888
|
});
|
|
887
889
|
}
|
|
888
|
-
listSessions(group) {
|
|
890
|
+
async listSessions(group) {
|
|
891
|
+
return (await this.listDirectory(group)).sessions;
|
|
892
|
+
}
|
|
893
|
+
listDirectory(group) {
|
|
889
894
|
let socket;
|
|
890
895
|
try {
|
|
891
896
|
socket = this.requireActiveSocket();
|
|
@@ -894,21 +899,21 @@ var init_client = __esm(() => {
|
|
|
894
899
|
}
|
|
895
900
|
return new Promise((resolve, reject) => {
|
|
896
901
|
const requestId = randomUUID2();
|
|
897
|
-
const wrappedResolve = (sessions) => {
|
|
898
|
-
clearTimeout(timeout);
|
|
899
|
-
resolve(sessions);
|
|
900
|
-
};
|
|
901
|
-
const wrappedReject = (error) => {
|
|
902
|
-
clearTimeout(timeout);
|
|
903
|
-
reject(error);
|
|
904
|
-
};
|
|
905
902
|
const timeout = setTimeout(() => {
|
|
906
|
-
if (this.pendingLists.
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
}
|
|
903
|
+
if (!this.pendingLists.delete(requestId))
|
|
904
|
+
return;
|
|
905
|
+
reject(new Error("List sessions timeout"));
|
|
910
906
|
}, 5000);
|
|
911
|
-
this.pendingLists.set(requestId, {
|
|
907
|
+
this.pendingLists.set(requestId, {
|
|
908
|
+
resolve: (directory) => {
|
|
909
|
+
clearTimeout(timeout);
|
|
910
|
+
resolve(directory);
|
|
911
|
+
},
|
|
912
|
+
reject: (error) => {
|
|
913
|
+
clearTimeout(timeout);
|
|
914
|
+
reject(error);
|
|
915
|
+
}
|
|
916
|
+
});
|
|
912
917
|
try {
|
|
913
918
|
writeMessage(socket, group === undefined ? { type: "list", requestId } : { type: "list", requestId, group });
|
|
914
919
|
} catch (error) {
|
|
@@ -1026,8 +1031,8 @@ var init_client = __esm(() => {
|
|
|
1026
1031
|
}
|
|
1027
1032
|
});
|
|
1028
1033
|
}
|
|
1029
|
-
registerPendingStageRoute(runId, group, capability) {
|
|
1030
|
-
writeMessage(this.requireActiveSocket(), { type: "register_pending_stage_route", runId, group, capability });
|
|
1034
|
+
registerPendingStageRoute(runId, group, capability, stages) {
|
|
1035
|
+
writeMessage(this.requireActiveSocket(), { type: "register_pending_stage_route", runId, group, capability, stages });
|
|
1031
1036
|
}
|
|
1032
1037
|
registerLiveWorkflowStageRoute(runId, stageKeys, capability) {
|
|
1033
1038
|
let socket;
|
|
@@ -2574,10 +2579,58 @@ var init_contact_supervisor_tool = __esm(() => {
|
|
|
2574
2579
|
init_intercom_utils();
|
|
2575
2580
|
});
|
|
2576
2581
|
|
|
2582
|
+
// dist/builtin/intercom/broker/supervisor-channel.ts
|
|
2583
|
+
var DEFAULT_TTL_MS;
|
|
2584
|
+
var init_supervisor_channel = __esm(() => {
|
|
2585
|
+
DEFAULT_TTL_MS = 10 * 60 * 1000;
|
|
2586
|
+
});
|
|
2587
|
+
|
|
2588
|
+
// dist/builtin/intercom/broker/group-membership.ts
|
|
2589
|
+
var init_group_membership = __esm(() => {
|
|
2590
|
+
init_group();
|
|
2591
|
+
});
|
|
2592
|
+
|
|
2593
|
+
// dist/builtin/intercom/broker/group-isolation.ts
|
|
2594
|
+
var init_group_isolation = __esm(() => {
|
|
2595
|
+
init_group_membership();
|
|
2596
|
+
});
|
|
2597
|
+
// dist/builtin/intercom/broker/send-handler.ts
|
|
2598
|
+
function parsePendingStageTarget(target) {
|
|
2599
|
+
const separator = target.indexOf(":");
|
|
2600
|
+
if (separator < 0)
|
|
2601
|
+
return;
|
|
2602
|
+
const runId = target.slice(0, separator);
|
|
2603
|
+
const stageKey = target.slice(separator + 1);
|
|
2604
|
+
return WORKFLOW_RUN_ID_PATTERN.test(runId) && stageKey.length > 0 ? { runId, stageKey } : undefined;
|
|
2605
|
+
}
|
|
2606
|
+
var WORKFLOW_RUN_ID_PATTERN;
|
|
2607
|
+
var init_send_handler = __esm(() => {
|
|
2608
|
+
init_supervisor_channel();
|
|
2609
|
+
init_group_isolation();
|
|
2610
|
+
init_group_membership();
|
|
2611
|
+
WORKFLOW_RUN_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
2612
|
+
});
|
|
2613
|
+
|
|
2577
2614
|
// dist/builtin/intercom/intercom-tool.ts
|
|
2578
2615
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
2579
2616
|
import { Type as Type2 } from "typebox";
|
|
2580
2617
|
import { Text as Text3 } from "@earendil-works/pi-tui";
|
|
2618
|
+
async function listDirectory(client, group) {
|
|
2619
|
+
if (typeof client.listDirectory === "function")
|
|
2620
|
+
return client.listDirectory(group);
|
|
2621
|
+
return { sessions: await client.listSessions(group), workflowStages: [] };
|
|
2622
|
+
}
|
|
2623
|
+
async function resolveReplySender(client, logicalTarget, sendTarget) {
|
|
2624
|
+
if (logicalTarget !== sendTarget)
|
|
2625
|
+
return sendTarget;
|
|
2626
|
+
if (parsePendingStageTarget(logicalTarget) === undefined)
|
|
2627
|
+
return sendTarget;
|
|
2628
|
+
const stage = (await listDirectory(client)).workflowStages.find((candidate) => candidate.sessionId !== undefined && (candidate.target === logicalTarget || `${candidate.runId}:${candidate.stageName}` === logicalTarget));
|
|
2629
|
+
return stage?.sessionId ?? sendTarget;
|
|
2630
|
+
}
|
|
2631
|
+
function formatWorkflowStageRow(stage) {
|
|
2632
|
+
return `- **${stage.stageName}** — workflow stage [${stage.lifecycle.toUpperCase()}] — target: \`${stage.target}\`${stage.sessionId === undefined ? "" : ` — intercom session: ${stage.sessionId}`}`;
|
|
2633
|
+
}
|
|
2581
2634
|
function registerIntercomTool(pi, deps) {
|
|
2582
2635
|
const { childOrchestratorMetadata, ensureConnected, syncPresenceIdentity, beginReplyWait } = deps;
|
|
2583
2636
|
const resolveTarget = deps.resolveSessionTarget ?? resolveSessionTargetId;
|
|
@@ -2758,7 +2811,8 @@ ${lines.join(`
|
|
|
2758
2811
|
case "list": {
|
|
2759
2812
|
try {
|
|
2760
2813
|
const mySessionId = connectedClient.sessionId;
|
|
2761
|
-
const
|
|
2814
|
+
const ownDirectory = await listDirectory(connectedClient);
|
|
2815
|
+
const ownSessions = ownDirectory.sessions;
|
|
2762
2816
|
const currentSession = ownSessions.find((session) => session.id === mySessionId);
|
|
2763
2817
|
if (!currentSession) {
|
|
2764
2818
|
return {
|
|
@@ -2769,32 +2823,49 @@ ${lines.join(`
|
|
|
2769
2823
|
}
|
|
2770
2824
|
const ownGroups = resolveOwnGroups(ownSessions);
|
|
2771
2825
|
if (requestedGroup && !isOnlyOwnGroup(requestedGroup, ownGroups)) {
|
|
2772
|
-
const peeked = await connectedClient
|
|
2773
|
-
const
|
|
2774
|
-
|
|
2775
|
-
|
|
2826
|
+
const peeked = await listDirectory(connectedClient, requestedGroup);
|
|
2827
|
+
const rows = [
|
|
2828
|
+
...peeked.sessions.map((session) => formatSessionListRow(session, currentSession.cwd, session.id === mySessionId)),
|
|
2829
|
+
...peeked.workflowStages.map(formatWorkflowStageRow)
|
|
2830
|
+
];
|
|
2831
|
+
const section = rows.length === 0 ? `**Group [${requestedGroup}] (read-only peek):**
|
|
2832
|
+
No sessions or workflow stages in this group.` : `**Group [${requestedGroup}] (read-only peek):**
|
|
2833
|
+
${rows.join(`
|
|
2776
2834
|
`)}`;
|
|
2777
2835
|
return {
|
|
2778
2836
|
content: [{ type: "text", text: `Your groups: ${ownGroups.join(", ")}
|
|
2779
2837
|
|
|
2780
2838
|
${section}` }],
|
|
2781
2839
|
isError: false,
|
|
2782
|
-
details: {
|
|
2840
|
+
details: {
|
|
2841
|
+
group: ownGroups.at(-1),
|
|
2842
|
+
groups: ownGroups,
|
|
2843
|
+
peekGroup: requestedGroup,
|
|
2844
|
+
workflowStages: peeked.workflowStages
|
|
2845
|
+
}
|
|
2783
2846
|
};
|
|
2784
2847
|
}
|
|
2785
2848
|
const otherSessions = ownSessions.filter((session) => session.id !== mySessionId);
|
|
2786
2849
|
const currentSection = `**Current session** (groups: ${ownGroups.join(", ")}):
|
|
2787
2850
|
${formatSessionListRow(currentSession, currentSession.cwd, true)}`;
|
|
2788
|
-
const
|
|
2789
|
-
|
|
2790
|
-
|
|
2851
|
+
const visibleRows = [
|
|
2852
|
+
...otherSessions.map((session) => formatSessionListRow(session, currentSession.cwd, false)),
|
|
2853
|
+
...ownDirectory.workflowStages.map(formatWorkflowStageRow)
|
|
2854
|
+
];
|
|
2855
|
+
const otherSection = visibleRows.length === 0 ? `**Other sessions and workflow stages:**
|
|
2856
|
+
No other sessions or workflow stages share any of your groups.` : `**Other visible sessions and workflow stages:**
|
|
2857
|
+
${visibleRows.join(`
|
|
2791
2858
|
`)}`;
|
|
2792
2859
|
return {
|
|
2793
2860
|
content: [{ type: "text", text: `${currentSection}
|
|
2794
2861
|
|
|
2795
2862
|
${otherSection}` }],
|
|
2796
2863
|
isError: false,
|
|
2797
|
-
details: {
|
|
2864
|
+
details: {
|
|
2865
|
+
group: ownGroups.at(-1),
|
|
2866
|
+
groups: ownGroups,
|
|
2867
|
+
...ownDirectory.workflowStages.length === 0 ? {} : { workflowStages: ownDirectory.workflowStages }
|
|
2868
|
+
}
|
|
2798
2869
|
};
|
|
2799
2870
|
} catch (error) {
|
|
2800
2871
|
return {
|
|
@@ -2957,7 +3028,8 @@ ${message}${attachmentText}`);
|
|
|
2957
3028
|
};
|
|
2958
3029
|
}
|
|
2959
3030
|
const questionId = randomUUID4();
|
|
2960
|
-
const
|
|
3031
|
+
const replyFrom = await resolveReplySender(connectedClient, to, sendTo);
|
|
3032
|
+
const admission = beginReplyWait(replyFrom, questionId, _signal);
|
|
2961
3033
|
if (!admission.ok) {
|
|
2962
3034
|
const text = admission.reason === "busy" ? `Too many pending asks (${admission.limit}); reply-wait slots are full` : "Cancelled";
|
|
2963
3035
|
return {
|
|
@@ -3166,6 +3238,7 @@ var init_intercom_tool = __esm(() => {
|
|
|
3166
3238
|
init_result_renderers();
|
|
3167
3239
|
init_intercom_utils();
|
|
3168
3240
|
init_group();
|
|
3241
|
+
init_send_handler();
|
|
3169
3242
|
});
|
|
3170
3243
|
|
|
3171
3244
|
// dist/builtin/intercom/ui/compose.ts
|
|
@@ -3358,13 +3431,15 @@ class SessionListOverlay {
|
|
|
3358
3431
|
done;
|
|
3359
3432
|
sessions;
|
|
3360
3433
|
selectedIndex = 0;
|
|
3434
|
+
workflowStages;
|
|
3361
3435
|
maxVisible = 8;
|
|
3362
|
-
constructor(theme, keybindings, currentSession, sessions, done) {
|
|
3436
|
+
constructor(theme, keybindings, currentSession, sessions, done, workflowStages = []) {
|
|
3363
3437
|
this.theme = theme;
|
|
3364
3438
|
this.keybindings = keybindings;
|
|
3365
3439
|
this.currentSession = currentSession;
|
|
3366
3440
|
this.sessions = sessions;
|
|
3367
3441
|
this.done = done;
|
|
3442
|
+
this.workflowStages = workflowStages;
|
|
3368
3443
|
}
|
|
3369
3444
|
onSessionSelect(sessionId) {
|
|
3370
3445
|
const session = this.sessions.find((s) => s.id === sessionId);
|
|
@@ -3448,6 +3523,18 @@ class SessionListOverlay {
|
|
|
3448
3523
|
lines.push(row(this.theme.fg("dim", ` ${this.selectedIndex + 1}/${this.sessions.length}`)));
|
|
3449
3524
|
}
|
|
3450
3525
|
}
|
|
3526
|
+
if (this.workflowStages.length > 0) {
|
|
3527
|
+
const visibleStages = this.workflowStages.slice(0, this.maxVisible);
|
|
3528
|
+
lines.push(row());
|
|
3529
|
+
lines.push(row(this.theme.bold(" Workflow Stages")));
|
|
3530
|
+
for (const stage of visibleStages) {
|
|
3531
|
+
lines.push(row(` ${stage.stageName} [${stage.lifecycle.toUpperCase()}]`));
|
|
3532
|
+
lines.push(row(` ${this.theme.fg("dim", stage.target)}`));
|
|
3533
|
+
}
|
|
3534
|
+
if (visibleStages.length < this.workflowStages.length) {
|
|
3535
|
+
lines.push(row(this.theme.fg("dim", ` ${visibleStages.length}/${this.workflowStages.length}`)));
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3451
3538
|
lines.push(row());
|
|
3452
3539
|
lines.push(border(`├${"─".repeat(contentWidth)}┤`));
|
|
3453
3540
|
lines.push(row(this.theme.fg("dim", ` ${footer}`)));
|
|
@@ -3476,25 +3563,27 @@ function registerIntercomOverlay(pi, deps) {
|
|
|
3476
3563
|
deps.syncPresenceIdentity(ctx.sessionManager.getSessionId());
|
|
3477
3564
|
let currentSession;
|
|
3478
3565
|
let sessions;
|
|
3566
|
+
let workflowStages;
|
|
3479
3567
|
let duplicates;
|
|
3480
3568
|
try {
|
|
3481
3569
|
const mySessionId = overlayClient.sessionId;
|
|
3482
|
-
const
|
|
3570
|
+
const directory = await overlayClient.listDirectory();
|
|
3483
3571
|
if (!deps.getLiveContext(ctx, overlayGeneration))
|
|
3484
3572
|
return;
|
|
3485
|
-
const foundCurrentSession =
|
|
3573
|
+
const foundCurrentSession = directory.sessions.find((session) => session.id === mySessionId);
|
|
3486
3574
|
if (!foundCurrentSession) {
|
|
3487
3575
|
deps.notifyIfLive(ctx, "Current session is missing from intercom session list", "error", overlayGeneration);
|
|
3488
3576
|
return;
|
|
3489
3577
|
}
|
|
3490
3578
|
currentSession = foundCurrentSession;
|
|
3491
|
-
duplicates = duplicateSessionNames(
|
|
3492
|
-
sessions =
|
|
3579
|
+
duplicates = duplicateSessionNames(directory.sessions);
|
|
3580
|
+
sessions = directory.sessions.filter((session) => session.id !== mySessionId);
|
|
3581
|
+
workflowStages = directory.workflowStages;
|
|
3493
3582
|
} catch (error) {
|
|
3494
3583
|
deps.notifyIfLive(ctx, `Failed to list sessions: ${getErrorMessage(error)}`, "error", overlayGeneration);
|
|
3495
3584
|
return;
|
|
3496
3585
|
}
|
|
3497
|
-
const selectedSession = await ctx.ui.custom((_tui, theme, keybindings, done) => new SessionListOverlay(theme, keybindings, currentSession, sessions, done), { overlay: true }).catch(() => {
|
|
3586
|
+
const selectedSession = await ctx.ui.custom((_tui, theme, keybindings, done) => new SessionListOverlay(theme, keybindings, currentSession, sessions, done, workflowStages), { overlay: true }).catch(() => {
|
|
3498
3587
|
return;
|
|
3499
3588
|
});
|
|
3500
3589
|
if (!selectedSession || !deps.getLiveContext(ctx, overlayGeneration))
|
|
@@ -3662,7 +3751,7 @@ class DeliveredMessageCache {
|
|
|
3662
3751
|
ttlMs;
|
|
3663
3752
|
maxEntries;
|
|
3664
3753
|
delivered = new Map;
|
|
3665
|
-
constructor(ttlMs =
|
|
3754
|
+
constructor(ttlMs = DEFAULT_TTL_MS2, maxEntries = DEFAULT_MAX_ENTRIES) {
|
|
3666
3755
|
this.ttlMs = ttlMs;
|
|
3667
3756
|
this.maxEntries = maxEntries;
|
|
3668
3757
|
}
|
|
@@ -3692,9 +3781,9 @@ class DeliveredMessageCache {
|
|
|
3692
3781
|
}
|
|
3693
3782
|
}
|
|
3694
3783
|
}
|
|
3695
|
-
var
|
|
3784
|
+
var DEFAULT_TTL_MS2, DEFAULT_MAX_ENTRIES = 1e4;
|
|
3696
3785
|
var init_delivered_message_cache = __esm(() => {
|
|
3697
|
-
|
|
3786
|
+
DEFAULT_TTL_MS2 = 10 * 60 * 1000;
|
|
3698
3787
|
});
|
|
3699
3788
|
|
|
3700
3789
|
// dist/builtin/intercom/terminal-ordering-barrier.ts
|
|
@@ -5407,10 +5496,15 @@ function piIntercomExtension(pi, testOverrides = {}) {
|
|
|
5407
5496
|
};
|
|
5408
5497
|
if (!existing)
|
|
5409
5498
|
pendingStageRouteClients.set(runId, state);
|
|
5410
|
-
if (state.client?.isConnected())
|
|
5499
|
+
if (state.client?.isConnected()) {
|
|
5500
|
+
state.route = route;
|
|
5501
|
+
state.client.registerPendingStageRoute(runId, normalizeGroup(route.group), route.capability, route.stages);
|
|
5411
5502
|
return;
|
|
5412
|
-
|
|
5413
|
-
|
|
5503
|
+
}
|
|
5504
|
+
if (state.promise) {
|
|
5505
|
+
await state.promise;
|
|
5506
|
+
return ensurePendingStageRouteClient(runId, route);
|
|
5507
|
+
}
|
|
5414
5508
|
const promise = (async () => {
|
|
5415
5509
|
await spawnBrokerIfNeeded(config.brokerCommand, config.brokerArgs);
|
|
5416
5510
|
const nextClient = new IntercomClient(`${currentSessionId}:pending-stage-route:${runId}`);
|
|
@@ -5421,7 +5515,7 @@ function piIntercomExtension(pi, testOverrides = {}) {
|
|
|
5421
5515
|
await nextClient.disconnect();
|
|
5422
5516
|
throw new Error("Intercom runtime no longer active");
|
|
5423
5517
|
}
|
|
5424
|
-
nextClient.registerPendingStageRoute(runId, normalizeGroup(route.group), route.capability);
|
|
5518
|
+
nextClient.registerPendingStageRoute(runId, normalizeGroup(route.group), route.capability, route.stages);
|
|
5425
5519
|
await nextClient.listSessions();
|
|
5426
5520
|
if (!pendingStageRouteClientIsCurrent(runId, state, contextAtStart, generationAtStart)) {
|
|
5427
5521
|
await nextClient.disconnect();
|
|
@@ -5469,7 +5563,7 @@ function piIntercomExtension(pi, testOverrides = {}) {
|
|
|
5469
5563
|
async function registerPendingStageRoute(activeClient, runId, route) {
|
|
5470
5564
|
const routeGroup = normalizeGroup(route.group);
|
|
5471
5565
|
if (clientRegistrationGroup === routeGroup) {
|
|
5472
|
-
activeClient.registerPendingStageRoute(runId, routeGroup, route.capability);
|
|
5566
|
+
activeClient.registerPendingStageRoute(runId, routeGroup, route.capability, route.stages);
|
|
5473
5567
|
return;
|
|
5474
5568
|
}
|
|
5475
5569
|
await ensurePendingStageRouteClient(runId, { ...route, group: routeGroup });
|
|
@@ -5541,7 +5635,11 @@ function piIntercomExtension(pi, testOverrides = {}) {
|
|
|
5541
5635
|
pi.events.on(PENDING_STAGE_ROUTE_EVENT, (payload) => {
|
|
5542
5636
|
if (!isPendingStageRouteRegistrationEvent(payload))
|
|
5543
5637
|
return;
|
|
5544
|
-
pendingStageRoutes.set(payload.runId, {
|
|
5638
|
+
pendingStageRoutes.set(payload.runId, {
|
|
5639
|
+
group: payload.group,
|
|
5640
|
+
capability: payload.capability,
|
|
5641
|
+
...payload.stages === undefined ? {} : { stages: payload.stages }
|
|
5642
|
+
});
|
|
5545
5643
|
const completion = ensureConnected("background").then((activeClient) => registerPendingStageRoute(activeClient, payload.runId, payload));
|
|
5546
5644
|
payload.completion = completion;
|
|
5547
5645
|
completion.catch(() => {});
|
|
@@ -5943,6 +6041,9 @@ function renderHeavyToolResult(loadedHeavy, name, args) {
|
|
|
5943
6041
|
return renderer(...args);
|
|
5944
6042
|
return renderIntercomToolResult(name, args);
|
|
5945
6043
|
}
|
|
6044
|
+
function isRecoverableHeavyInitializationDisconnect(error) {
|
|
6045
|
+
return error instanceof Error && error.message === "Client disconnected";
|
|
6046
|
+
}
|
|
5946
6047
|
function intercom(pi, options = {}) {
|
|
5947
6048
|
const inheritedDelegatedSessionName = readSubagentEnv("INTERCOM_SESSION_NAME");
|
|
5948
6049
|
let heavyAttempt = null;
|
|
@@ -6096,8 +6197,10 @@ function intercom(pi, options = {}) {
|
|
|
6096
6197
|
}, (error) => {
|
|
6097
6198
|
if (heavyAttempt?.promise === promise)
|
|
6098
6199
|
heavyAttempt = null;
|
|
6099
|
-
|
|
6100
|
-
|
|
6200
|
+
if (!isRecoverableHeavyInitializationDisconnect(error)) {
|
|
6201
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6202
|
+
console.error(`Intercom heavy initialization failed; a later call will retry: ${message}`, error);
|
|
6203
|
+
}
|
|
6101
6204
|
});
|
|
6102
6205
|
return promise;
|
|
6103
6206
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/intercom",
|
|
3
|
-
"version": "0.9.18-alpha.
|
|
3
|
+
"version": "0.9.18-alpha.3",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension providing a private coordination channel between parent and child agent sessions. Fork of: https://github.com/nicobailon/pi-intercom",
|
|
6
6
|
"contributors": [
|
|
@@ -75,7 +75,7 @@ intercom({ action: "list" })
|
|
|
75
75
|
intercom({ action: "ask", to: "6332faab-1111-4222-8333-123456789abc", message: "Which option should I use?" })
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
Live sessions accept an exact full session ID or exact case-insensitive name.
|
|
78
|
+
Live sessions accept an exact full Intercom session ID or exact case-insensitive name. For workflow stages, first use `intercom({ action: "list" })`: materialized stages appear as `PENDING` or `RUNNING` with canonical `<runId>:<stageId>` targets and actual groups. The invocation context can control owned isolated subgroups by exact target, while sibling subgroups and other runs remain isolated. Use queued `send` for `PENDING`; `ask` is supported only for `RUNNING`, where an exact correlated reply returns to the invocation asker.
|
|
79
79
|
|
|
80
80
|
### Deliver to workflow stages that have not started
|
|
81
81
|
|
|
@@ -13,6 +13,34 @@ export interface SessionInfo {
|
|
|
13
13
|
group?: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export interface WorkflowStageRosterEntry {
|
|
17
|
+
readonly kind: "workflow-stage";
|
|
18
|
+
readonly runId: string;
|
|
19
|
+
readonly stageId: string;
|
|
20
|
+
readonly stageName: string;
|
|
21
|
+
readonly target: string;
|
|
22
|
+
readonly lifecycle: "pending" | "running";
|
|
23
|
+
readonly group: string;
|
|
24
|
+
/** Broker session identity, present only while the workflow stage is connected. */
|
|
25
|
+
readonly sessionId?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SessionDirectory {
|
|
29
|
+
readonly sessions: SessionInfo[];
|
|
30
|
+
readonly workflowStages: WorkflowStageRosterEntry[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface WorkflowStageRosterAnnouncement {
|
|
34
|
+
readonly stageId: string;
|
|
35
|
+
readonly stageName: string;
|
|
36
|
+
readonly target: string;
|
|
37
|
+
readonly lifecycle: "pending" | "running";
|
|
38
|
+
readonly routeEligible: boolean;
|
|
39
|
+
/** Actual stage group after workflow invocation ownership resolution. */
|
|
40
|
+
readonly group: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
16
44
|
export interface GroupSummary {
|
|
17
45
|
group: string;
|
|
18
46
|
sessionCount: number;
|
|
@@ -77,7 +105,7 @@ export type ClientMessage =
|
|
|
77
105
|
attemptId?: string;
|
|
78
106
|
}
|
|
79
107
|
| { type: "pending_stage_notification_result"; requestId: string; delivered: boolean }
|
|
80
|
-
| { type: "register_pending_stage_route"; runId: string; group: string; capability: string }
|
|
108
|
+
| { type: "register_pending_stage_route"; runId: string; group: string; capability: string; stages?: WorkflowStageRosterAnnouncement[] }
|
|
81
109
|
| {
|
|
82
110
|
type: "register_live_workflow_stage_route";
|
|
83
111
|
requestId: string;
|
|
@@ -107,7 +135,7 @@ export type ClientMessage =
|
|
|
107
135
|
export type BrokerMessage =
|
|
108
136
|
| { type: "registered"; sessionId: string; supervisorSessionId?: string }
|
|
109
137
|
| { type: "registration_failed"; reason: string }
|
|
110
|
-
| { type: "sessions"; requestId: string; sessions: SessionInfo[] }
|
|
138
|
+
| { type: "sessions"; requestId: string; sessions: SessionInfo[]; workflowStages?: WorkflowStageRosterEntry[] }
|
|
111
139
|
| { type: "groups"; requestId: string; groups: GroupSummary[] }
|
|
112
140
|
| { type: "membership_ack"; requestId: string; groups: string[] }
|
|
113
141
|
| { type: "supervisor_authorized"; requestId: string; capability: string; supervisorSessionId: string; childName: string }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/mcp",
|
|
3
|
-
"version": "0.9.18-alpha.
|
|
3
|
+
"version": "0.9.18-alpha.3",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension that adapts MCP (Model Context Protocol) servers into the coding agent. Fork of: https://github.com/nicobailon/pi-mcp-adapter",
|
|
6
6
|
"contributors": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/subagents",
|
|
3
|
-
"version": "0.9.18-alpha.
|
|
3
|
+
"version": "0.9.18-alpha.3",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for delegating tasks to subagents with parallel execution. Fork of: https://github.com/nicobailon/pi-subagents",
|
|
6
6
|
"contributors": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/web-access",
|
|
3
|
-
"version": "0.9.18-alpha.
|
|
3
|
+
"version": "0.9.18-alpha.3",
|
|
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,24 @@ 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.18-alpha.3] - 2026-09-01
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Workflow status text, interactive list/detail views, and the persistent `BACKGROUND` panel now identify materialized pending stages by display name and canonical stage ID, show exact Intercom targets only when pre-start delivery is available, and distinguish unavailable pending delivery truthfully.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Workflow-authoring guidance now distinguishes inspectable workflow orchestration from cross-cutting extension-hook policy, requires companion-extension dependencies to remain explicit, and directs generated workflows toward readable top-level graphs with cohesive source boundaries.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Fixed pending-stage targets in `/workflow status` cards and the persistent `BACKGROUND` panel being truncated into unusable addresses. Status cards now wrap exact targets, while widget metadata uses only bounded pending-stage forms that fit without displacing existing metadata and width-based omissions show an explicit remaining-stage count. ([#2784](https://github.com/bastani-inc/atomic/issues/2784))
|
|
22
|
+
- Fixed `/workflow status` losing pending-stage delivery capability in its bounded graph projection, run detail ellipsizing exact pending targets at narrow widths, terminal runs advertising undeliverable targets, and narrow status cards truncating canonical stage IDs. Exact targets and IDs now wrap where appropriate, while ended runs report pending delivery as unavailable. ([#2784](https://github.com/bastani-inc/atomic/issues/2784))
|
|
23
|
+
- Fixed projected pending stages from terminal nested child runs advertising unusable Intercom targets through their still-live root, pending widget labels exceeding their width budget and evicting tool/elapsed metadata, duplicated Intercom coaching in model-facing status hints, and singular overflow counts using a plural noun. ([#2784](https://github.com/bastani-inc/atomic/issues/2784))
|
|
24
|
+
|
|
25
|
+
- Fixed explicit and automatic stage groups so they become invocation-owned, collision-free subgroups that remain controllable from the workflow invocation while preserving sibling isolation and their exact persisted identity across active durable resume; restored symmetric isolated reviewer groups for Goal and Ralph ([#2784](https://github.com/bastani-inc/atomic/issues/2784)).
|
|
26
|
+
|
|
9
27
|
## [0.9.16] - 2026-08-29
|
|
10
28
|
|
|
11
29
|
Cumulative release of the `0.9.16-alpha.1` – `0.9.16-alpha.11` prereleases. The summary below covers the user-visible outcome of that work; the per-change detail remains in the prerelease sections below.
|
|
@@ -86,7 +86,7 @@ Execution, replay, and DBOS hydration are the authoritative topology-validation
|
|
|
86
86
|
|
|
87
87
|
### Workflow source layout
|
|
88
88
|
|
|
89
|
-
Keep a small, readable workflow in one entry file.
|
|
89
|
+
Keep a small, readable workflow in one entry file and write it for human maintainers. Keep the graph and control flow visible in the top-level workflow entry file, use stage names that state each stage's responsibility, and make its inputs, outputs, evidence, and success contract explicit. A developer reading the entry file from top to bottom should be able to identify the graph, branches, gates, artifacts, and stop conditions. Avoid both monolithic prompt blobs and gratuitous fragmentation: do not split short one-use prompts, create one file per stage, add wrapper-only modules, hide the graph across files, or use line counts alone as a module boundary.
|
|
90
90
|
|
|
91
91
|
At a meaningful source boundary that improves clarity, reuse, ownership, or testability, keep the graph and control flow in the top-level workflow entry file and extract cohesive concerns: long or reused prompt builders; shared TypeBox schemas and workflow-specific types; model-policy constants shared by several stages; deterministic helpers with their own testable behavior; or reusable child workflow definitions.
|
|
92
92
|
|
|
@@ -101,6 +101,12 @@ The existing `.atomic/workflows/release-docs.ts` keeps its graph in the entry fi
|
|
|
101
101
|
.atomic/workflows/code-review/model-policy.ts
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
### Workflow and extension responsibilities
|
|
105
|
+
|
|
106
|
+
Evaluate Atomic extension hooks when a workflow needs fine-grained, cross-cutting tool or session event control. Workflow TypeScript owns the inspectable DAG, stages, handoffs, durable `ctx.tool` side effects, and gates. Extension hooks own cross-cutting session and model-tool policy such as `tool_call` interception, input mutation, or blocking; `tool_result` transformation; context and provider hooks; lifecycle observation; or reusable custom tools. Use hooks only when cross-stage or cross-workflow event control is materially clearer than embedding the policy in each stage. Do not require a companion extension for ordinary workflow logic. See the authoritative [extension event documentation](../coding-agent/docs/extensions.md#events) for hook contracts and ordering.
|
|
107
|
+
|
|
108
|
+
When a workflow depends on a companion extension, make that dependency explicit and package and document the extension with the workflow. If stages use `tools` allowlists, include any custom tools provided by the extension. Document the hook-driven behavior and keep the graph, stage contracts, artifacts, gates, and stop conditions visible in the workflow entry file so readers can distinguish inspectable workflow orchestration from event policy.
|
|
109
|
+
|
|
104
110
|
### Workflow-owned side effects
|
|
105
111
|
|
|
106
112
|
Prefer `ctx.tool(name, args, fn)` for workflow-owned TypeScript operations with side effects, including filesystem writes, network mutations, external API actions, and similar deterministic operations orchestrated directly by the workflow definition. Each invocation creates a non-chat, non-attachable durable graph node before `fn` runs; it may appear before, between, after, or without model stages. Atomic durably caches a completed call's serializable result, so resume returns that result without rerunning `fn` or repeating the side effect. Tool-only workflows are valid tracked execution, while a normal return with no stage, child, tool, or explicit exit remains invalid. Keep pure computation and side-effect-free transformations as ordinary TypeScript. Do not wrap agent-stage internals or every function call indiscriminately. The executor closes tool admission before publishing any terminal outcome; calling a retained `ctx.tool` function afterward returns a rejected native promise and creates no callback, retry, graph node, or checkpoint.
|
|
@@ -445,7 +451,7 @@ When an item configures both `schema` and `output`, its successful `structured_o
|
|
|
445
451
|
|
|
446
452
|
`subagent` is available as a default workflow-stage tool on the same terms as main chat: a stage is a top-level session, so it can delegate once, and the children it launches cannot delegate further. Delegation is one level deep and nothing configures it. `tools` allowlists apply to bundled extension tools as well as built-ins; if a stage sets `tools`, list every non-mandatory tool it should see. Ordinary `intercom` remains registered and active in every workflow model stage even under `noTools: "all"`, restrictive allowlists, or exclusions. Restrictions on `subagent`, `web_search`, `fetch_content`, and every other tool are unchanged. Bundled `@bastani/subagents` agent definitions are available to the `subagent` tool in workflow stages, including workflows launched from a subagent child process.
|
|
447
453
|
|
|
448
|
-
Send material updates through Intercom to every affected workflow stage, including stages that have not started. Address a known pending stage with ordinary `intercom send` at `<runId>:<stageKey>`; Atomic queues the message with workflow state and delivers it through the existing inbound Intercom path when the stage session initializes, before its first model turn. Live stage delivery is immediate. Use `ask` once the stage session is live and can reply. Unknown stage identities retain the ordinary unknown-target failure. Workflow `answer` handles pending human-input prompts, and workflow `resume` handles paused run control.
|
|
454
|
+
Send material updates through Intercom to every affected workflow stage, including stages that have not started. Address a known pending stage on a nonterminal run with ordinary `intercom send` at `<runId>:<stageKey>`; Atomic queues the message with workflow state and delivers it through the existing inbound Intercom path when the stage session initializes, before its first model turn. Live stage delivery is immediate. Status surfaces never advertise a retained pending stage after its run terminates. Use `ask` once the stage session is live and can reply. Unknown stage identities retain the ordinary unknown-target failure. Workflow `answer` handles pending human-input prompts, and workflow `resume` handles paused run control.
|
|
449
455
|
|
|
450
456
|
### Model fallbacks
|
|
451
457
|
|
|
@@ -662,6 +668,8 @@ Named workflow launches always run as **background tasks** in interactive sessio
|
|
|
662
668
|
|
|
663
669
|
Typing into an attached stage chat and pressing Enter steers: the message is consumed after the current assistant response finishes its tool batch and before the next model request, matching normal session steering. Ctrl+F queues a follow-up, consumed only when the agent would otherwise stop. Queued entries belong to the stage session rather than the pane, so leaving the stage and reattaching restores the pending `Steering:` / `Follow-up:` rows, and a detached stage node carries a `✉ N queued` badge in the graph.
|
|
664
670
|
|
|
671
|
+
At normal widths, the persistent `BACKGROUND` panel and interactive `/workflow status` list/detail name materialized pending stages by display name and canonical ID. They show `<runId>:<stageId>` only when pre-start Intercom delivery is available on a nonterminal owning run and label unavailable delivery otherwise; retained pending stages on ended runs, including ended nested child runs projected into a live root, never advertise a target. Interactive status cards and run detail wrap a full target onto continuation rows when needed, and narrow status cards preserve the full canonical stage ID rather than ellipsizing it. The single-line panel uses only pending-stage forms that fit its remaining metadata budget; if none fit, it omits the pending label entirely so mode, progress, live-tool, and elapsed/status metadata remains intact. The narrow widget remains aggregate-only.
|
|
672
|
+
|
|
665
673
|
Named launches return only after startup admission, while the admitted workflow body and stages remain background work. Pre-body setup failures (including invalid input-bound reusable worktrees) are returned immediately from the original tool call as structured failed results with the concrete error and allocated run id; Atomic does not first claim that the workflow started, and it removes the unadmitted run so corrected inputs can be retried immediately. Failures after admission continue to use normal background status and lifecycle notices.
|
|
666
674
|
|
|
667
675
|
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.
|
|
@@ -710,6 +718,7 @@ Raw stage-chat prompt answer replay is live-memory only. `StageSnapshot.promptAn
|
|
|
710
718
|
- **`answer`** — answers one pending primitive or structured workflow prompt while the authoritative root is nonterminal. It accepts `promptId` plus `response`, `text`, or `message`, but never sends stage chat, steers work, resumes a stage, or starts a model turn.
|
|
711
719
|
|
|
712
720
|
Free-form workflow-stage communication uses ordinary Intercom, not the workflow tool. Send to `<runId>:<stageKey>`; Atomic delivers immediately to live stages and queues messages for known stages that have not started.
|
|
721
|
+
The concise model-facing status listing enumerates pending stages with their display name, canonical ID, literal `pending` lifecycle, `pendingStageDeliveryAvailable` value, and an exact Intercom target only when that target is usable on a nonterminal owning run. Retained pending stages on ended root or nested child runs report unavailable delivery and no target, including when a child stage is projected through a live root. Duplicate names remain distinct through canonical IDs. Live `send` delivery is immediate, a known pending-stage `send` queues before the first model turn, and `ask` requires a live reply-capable stage.
|
|
713
722
|
- **`reload`** — refreshes workflow resources directly in-process instead of queuing a literal `/workflow reload` chat follow-up.
|
|
714
723
|
- **`models`** — returns safe model-catalog metadata from the configured registry. Each entry contains `provider` (e.g. `openai`), `id` (e.g. `gpt-4`), `fullId` (e.g. `openai/gpt-4`), `isCurrent` (whether this is the active model), and `availableThinkingLevels`, canonically derived from the registry model's `reasoning` and `thinkingLevelMap` metadata. The result is a configured-auth snapshot: it shows which models are present in the registry with configured authentication, not proof of credentials, entitlements, OAuth freshness, or live provider access. No secrets, tokens, or authentication details are returned.
|
|
715
724
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ralph_default
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-h3r2vkzc.js";
|
|
4
4
|
import {
|
|
5
5
|
tournament_default
|
|
6
6
|
} from "./chunk-2dqb5s2q.js";
|
|
@@ -18,8 +18,8 @@ import {
|
|
|
18
18
|
} from "./chunk-hzzn6adg.js";
|
|
19
19
|
import {
|
|
20
20
|
goal_default
|
|
21
|
-
} from "./chunk-
|
|
22
|
-
import"./chunk-
|
|
21
|
+
} from "./chunk-fghhy2a5.js";
|
|
22
|
+
import"./chunk-n58a7v26.js";
|
|
23
23
|
import"./chunk-7at6dnkr.js";
|
|
24
24
|
import"./chunk-7430zyas.js";
|
|
25
25
|
import {
|