@bastani/atomic 0.9.19-alpha.8 → 0.9.19-alpha.9
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 +14 -0
- package/dist/builtin/intercom/CHANGELOG.md +14 -0
- package/dist/builtin/intercom/README.md +7 -7
- package/dist/builtin/intercom/broker/client.ts +4 -0
- package/dist/builtin/intercom/broker/send-handler.ts +18 -1
- package/dist/builtin/intercom/index.bundle.mjs +20 -17
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/intercom/skills/intercom/SKILL.md +3 -3
- package/dist/builtin/intercom/types.ts +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/CHANGELOG.md +6 -0
- package/dist/builtin/web-access/index.bundle.mjs +19 -17
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +7 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +121 -5
- package/docs/intercom/reference.md +4 -0
- package/docs/intercom.md +4 -4
- package/docs/web-access.md +10 -0
- package/docs/workflows/operations.md +4 -0
- package/npm-shrinkwrap.json +32 -32
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.19-alpha.9] - 2026-09-12
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- Explicit Intercom `action: "reply"` selectors reject stale, unknown, empty, or sender-mismatched threads instead of falling back. Use `action: "pending"` and `replyTo: "<pending-ask-id>"` for unresolved questions.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Intercom agent lists put exact copyable session IDs and workflow paths first, with useful metadata and meaningful names secondary rather than redundant generated aliases.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Intercom targeted replies now complete the requested ask when an unrelated message is active, rather than delivering a response with the wrong thread ID or refusing it.
|
|
18
|
+
|
|
5
19
|
## [0.9.19-alpha.7] - 2026-09-12
|
|
6
20
|
|
|
7
21
|
### Fixed
|
|
@@ -4,6 +4,20 @@ All notable changes to the `pi-intercom` extension will be documented in this fi
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.9.19-alpha.9] - 2026-09-12
|
|
8
|
+
|
|
9
|
+
### Breaking Changes
|
|
10
|
+
|
|
11
|
+
- Explicit `reply` selectors now fail closed: stale, unknown, empty, or sender-mismatched `replyTo` values no longer fall back to another active or pending thread. Use `pending` to select the exact unresolved question.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Agent-visible Intercom lists lead with copyable full session IDs and canonical workflow paths, keeping status, working directory, meaningful names, and future queued counts without redundant generated aliases.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Targeted replies select the requested sender's pending ask even when an unrelated message triggered the current turn, so a delivered reply no longer leaves that ask waiting on the wrong thread.
|
|
20
|
+
|
|
7
21
|
## [0.9.19-alpha.6] - 2026-09-11
|
|
8
22
|
|
|
9
23
|
### Fixed
|
|
@@ -57,7 +57,7 @@ A session becomes intercom-connected when all of these are true:
|
|
|
57
57
|
|
|
58
58
|
The session list, ALT+M picker, and group counts include connected agent sessions only. Internal workflow routing/control connections, model-less `ctx.ui` prompts, and `ctx.tool` nodes are hidden and cannot receive ordinary messages, even by a known ID or through a supervisor route. This includes run-level prompts and retained completed synthetic prompt stages. An agent executing a tool, including `tool:workflow`, or awaiting human input remains visible and messageable.
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
Unnamed sessions retain a runtime-only alias such as `subagent-chat-1a2b3c4d-1111-4222-8333-123456789abc` for name lookup. Agent lists omit this redundant alias and lead with the copyable full session ID. Meaningful names remain secondary metadata; resume titles are unchanged.
|
|
61
61
|
|
|
62
62
|
## Quick Start
|
|
63
63
|
|
|
@@ -76,10 +76,10 @@ The agent can list sessions and send messages using the `intercom` tool. Tool ca
|
|
|
76
76
|
```typescript
|
|
77
77
|
// List active sessions
|
|
78
78
|
intercom({ action: "list" })
|
|
79
|
-
// → **Current session
|
|
80
|
-
// →
|
|
81
|
-
// → **Other sessions:**
|
|
82
|
-
// →
|
|
79
|
+
// → **Current session** (groups: default):
|
|
80
|
+
// → - `20d43841-1111-4222-8333-123456789abc` [self, idle] ~/projects/api (claude-sonnet-4) name: executor
|
|
81
|
+
// → **Other visible sessions and workflow stages:**
|
|
82
|
+
// → - `6332faab-1111-4222-8333-123456789abc` [same cwd, thinking] ~/projects/api (claude-sonnet-4) name: research
|
|
83
83
|
|
|
84
84
|
// Add a named membership (it is created if no session is there yet)
|
|
85
85
|
intercom({ action: "join", group: "api-review" })
|
|
@@ -156,7 +156,7 @@ Verify they see each other from either session:
|
|
|
156
156
|
|
|
157
157
|
```typescript
|
|
158
158
|
intercom({ action: "list" })
|
|
159
|
-
// →
|
|
159
|
+
// → - `6332faab-1111-4222-8333-123456789abc` [same cwd, idle] ~/projects/api (claude-sonnet-4) name: worker
|
|
160
160
|
```
|
|
161
161
|
|
|
162
162
|
### The Conversation
|
|
@@ -383,7 +383,7 @@ When multiple live agent stages share a name, `ask` refuses the ambiguous target
|
|
|
383
383
|
|
|
384
384
|
**`ask`** — Sends a message and waits for a live recipient to reply (10-minute timeout). Invocation control supports a live ask into an owned isolated subgroup, and the exact broker-recorded reply resolves the waiting tool call at the asker without opening reverse or lateral group access. Ask to an uninitialized stage remains refused with `pending_stage_ask_unsupported`; use queued `send` instead, because holding a reply waiter until a stage eventually starts would be unbounded. A recipient disconnect after live delivery fails only that peer's exact wait promptly; the timeout remains the backstop while the recipient stays connected. Up to `maxPendingAsks` blocking asks (default: 6) may run concurrently, including same-target and mixed-target fan-out. Replies resolve by exact sender and message ID, so out-of-order replies cannot cross-settle another call. When capacity is full, new asks receive a structured refusal.
|
|
385
385
|
|
|
386
|
-
**`reply`**
|
|
386
|
+
**`reply`** without selectors replies to the current intercom-triggered message, or otherwise the single unresolved inbound ask. Explicit `to` selects a pending ask from that exact name/full session ID, even during another sender's turn. If that sender has multiple asks, use `pending` and pass the exact message ID as `replyTo`. Explicit `replyTo` selects only that pending ask or the exact active ordinary message; stale, unknown, or empty IDs fail instead of falling back. When both selectors are supplied, the sender must match the selected thread. A successful reply keeps that sender/thread pair across internal retries.
|
|
387
387
|
|
|
388
388
|
**`pending`** — Lists unresolved inbound asks with sender, message ID, elapsed time, and a short preview. Useful when replying after the original triggered turn.
|
|
389
389
|
|
|
@@ -38,6 +38,8 @@ export interface SendOptions {
|
|
|
38
38
|
logicalTarget?: string;
|
|
39
39
|
/** Public reply must use the broker's exact pending reverse question route. */
|
|
40
40
|
requirePendingReply?: true;
|
|
41
|
+
/** An explicit reply selector must resolve to this frozen recipient, never a namesake. */
|
|
42
|
+
expectedRecipientId?: string;
|
|
41
43
|
/** Broker-authorized identity for a canonical-path ask, delivered before its recipient can reply. */
|
|
42
44
|
onReplyTarget?: (sessionId: string) => void;
|
|
43
45
|
}
|
|
@@ -1004,6 +1006,7 @@ export class IntercomClient extends EventEmitter {
|
|
|
1004
1006
|
transportTarget: to,
|
|
1005
1007
|
logicalSignature: buildSendSignature(options.logicalTarget ?? to, options),
|
|
1006
1008
|
requirePendingReply: options.requirePendingReply ?? false,
|
|
1009
|
+
...(options.expectedRecipientId === undefined ? {} : { expectedRecipientId: options.expectedRecipientId }),
|
|
1007
1010
|
});
|
|
1008
1011
|
acquired = this.pendingSends.acquire(messageId, pendingSignature, 10000);
|
|
1009
1012
|
} catch (error) {
|
|
@@ -1028,6 +1031,7 @@ export class IntercomClient extends EventEmitter {
|
|
|
1028
1031
|
to,
|
|
1029
1032
|
...(options.logicalTarget === undefined ? {} : { logicalTarget: options.logicalTarget }),
|
|
1030
1033
|
...(options.requirePendingReply === undefined ? {} : { requirePendingReply: options.requirePendingReply }),
|
|
1034
|
+
...(options.expectedRecipientId === undefined ? {} : { expectedRecipientId: options.expectedRecipientId }),
|
|
1031
1035
|
...(options.onReplyTarget === undefined ? {} : { resolveReplyTarget: true as const }),
|
|
1032
1036
|
message,
|
|
1033
1037
|
attemptId: acquired.attempt.attemptId,
|
|
@@ -143,6 +143,12 @@ export function handleBrokerSend(
|
|
|
143
143
|
return;
|
|
144
144
|
}
|
|
145
145
|
const requirePendingReply = clientMessage.requirePendingReply === true;
|
|
146
|
+
const hasExpectedRecipientId = Object.prototype.hasOwnProperty.call(clientMessage, "expectedRecipientId");
|
|
147
|
+
if (hasExpectedRecipientId && (typeof clientMessage.expectedRecipientId !== "string" || !clientMessage.expectedRecipientId.trim())) {
|
|
148
|
+
write(socket, { type: "delivery_failed", messageId, attemptId, reason: "Invalid expectedRecipientId format" });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const expectedRecipientId = hasExpectedRecipientId ? clientMessage.expectedRecipientId as string : undefined;
|
|
146
152
|
if (Object.prototype.hasOwnProperty.call(clientMessage, "channel")) {
|
|
147
153
|
write(socket, { type: "delivery_failed", messageId: message.id, attemptId, reason: "Invalid channel" });
|
|
148
154
|
return;
|
|
@@ -152,6 +158,10 @@ export function handleBrokerSend(
|
|
|
152
158
|
write(socket, { type: "delivery_failed", messageId, attemptId, reason: "Invalid requirePendingReply message" });
|
|
153
159
|
return;
|
|
154
160
|
}
|
|
161
|
+
if (expectedRecipientId !== undefined && (supervisorSend || message.replyTo === undefined || message.expectsReply === true)) {
|
|
162
|
+
write(socket, { type: "delivery_failed", messageId, attemptId, reason: "Invalid expectedRecipientId message" });
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
155
165
|
|
|
156
166
|
|
|
157
167
|
const fromSession = currentId ? sessions.get(currentId) : undefined;
|
|
@@ -161,7 +171,8 @@ export function handleBrokerSend(
|
|
|
161
171
|
}
|
|
162
172
|
const senderIdentity = fromSession.registrationReturnAddress ?? fromSession.info.id;
|
|
163
173
|
const logicalTarget = typeof clientMessage.logicalTarget === "string" ? clientMessage.logicalTarget : clientMessage.to;
|
|
164
|
-
const
|
|
174
|
+
const messageSignature = buildMessageSendSignature(logicalTarget, message, senderIdentity);
|
|
175
|
+
const baseSignature = expectedRecipientId === undefined ? messageSignature : JSON.stringify({ expectedRecipientId, messageSignature });
|
|
165
176
|
const signature = requirePendingReply
|
|
166
177
|
? JSON.stringify({
|
|
167
178
|
requirePendingReply: true,
|
|
@@ -221,6 +232,12 @@ export function handleBrokerSend(
|
|
|
221
232
|
const resolution = exactIdTarget
|
|
222
233
|
? ({ kind: "resolved", session: exactIdTarget.info } as const)
|
|
223
234
|
: resolveSessionTarget(candidates, trimmedTo);
|
|
235
|
+
if (expectedRecipientId !== undefined && (resolution.kind !== "resolved" || resolution.session.id !== expectedRecipientId)) {
|
|
236
|
+
write(socket, { type: "delivery_failed", messageId, attemptId,
|
|
237
|
+
reason: resolution.kind === "resolved" ? "Reply target does not match the expected recipient" : sessionTargetFailureReason(clientMessage.to, resolution),
|
|
238
|
+
});
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
224
241
|
if (resolution.kind === "resolved") {
|
|
225
242
|
const target = sessions.get(resolution.session.id);
|
|
226
243
|
if (!target) {
|
|
@@ -1247,7 +1247,8 @@ var init_client = __esm(() => {
|
|
|
1247
1247
|
const pendingSignature = JSON.stringify({
|
|
1248
1248
|
transportTarget: to,
|
|
1249
1249
|
logicalSignature: buildSendSignature(options.logicalTarget ?? to, options),
|
|
1250
|
-
requirePendingReply: options.requirePendingReply ?? false
|
|
1250
|
+
requirePendingReply: options.requirePendingReply ?? false,
|
|
1251
|
+
...options.expectedRecipientId === undefined ? {} : { expectedRecipientId: options.expectedRecipientId }
|
|
1251
1252
|
});
|
|
1252
1253
|
acquired = this.pendingSends.acquire(messageId, pendingSignature, 1e4);
|
|
1253
1254
|
} catch (error) {
|
|
@@ -1273,6 +1274,7 @@ var init_client = __esm(() => {
|
|
|
1273
1274
|
to,
|
|
1274
1275
|
...options.logicalTarget === undefined ? {} : { logicalTarget: options.logicalTarget },
|
|
1275
1276
|
...options.requirePendingReply === undefined ? {} : { requirePendingReply: options.requirePendingReply },
|
|
1277
|
+
...options.expectedRecipientId === undefined ? {} : { expectedRecipientId: options.expectedRecipientId },
|
|
1276
1278
|
...options.onReplyTarget === undefined ? {} : { resolveReplyTarget: true },
|
|
1277
1279
|
message,
|
|
1278
1280
|
attemptId: acquired.attempt.attemptId,
|
|
@@ -2008,22 +2010,22 @@ class ReplyTracker {
|
|
|
2008
2010
|
}
|
|
2009
2011
|
resolveReplyTarget(options, now = Date.now()) {
|
|
2010
2012
|
this.pruneExpired(now);
|
|
2011
|
-
if (options.replyTo) {
|
|
2012
|
-
const exact = this.pendingAsks.get(options.replyTo);
|
|
2013
|
-
if (exact)
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
return exact;
|
|
2013
|
+
if (options.replyTo !== undefined) {
|
|
2014
|
+
const exact = this.pendingAsks.get(options.replyTo) ?? (this.currentTurnContext?.message.id === options.replyTo && !this.currentTurnContext.message.expectsReply ? this.currentTurnContext : undefined);
|
|
2015
|
+
if (!exact)
|
|
2016
|
+
throw new Error(`No reply context for "${options.replyTo}"`);
|
|
2017
|
+
if (options.to !== undefined) {
|
|
2018
|
+
const resolution = resolveSessionTarget([exact.from], options.to);
|
|
2019
|
+
if (resolution.kind !== "resolved")
|
|
2020
|
+
throw new Error(`Reply context "${options.replyTo}" is not from "${options.to}"`);
|
|
2020
2021
|
}
|
|
2022
|
+
return exact;
|
|
2021
2023
|
}
|
|
2022
|
-
if (this.currentTurnContext) {
|
|
2024
|
+
if (options.to === undefined && this.currentTurnContext) {
|
|
2023
2025
|
return this.currentTurnContext;
|
|
2024
2026
|
}
|
|
2025
2027
|
const pending = Array.from(this.pendingAsks.values());
|
|
2026
|
-
if (options.to) {
|
|
2028
|
+
if (options.to !== undefined) {
|
|
2027
2029
|
const senders = [...new Map(pending.map((context) => [context.from.id, context.from])).values()];
|
|
2028
2030
|
const resolution = resolveSessionTarget(senders, options.to);
|
|
2029
2031
|
if (resolution.kind !== "resolved") {
|
|
@@ -2446,13 +2448,13 @@ function formatSessionLabel(session, duplicates) {
|
|
|
2446
2448
|
return duplicates.has(session.name.toLowerCase()) ? `${session.name} (${session.id})` : session.name;
|
|
2447
2449
|
}
|
|
2448
2450
|
function formatSessionListRow(session, currentCwd, isSelf) {
|
|
2449
|
-
const name = session.name
|
|
2451
|
+
const name = session.name && session.name !== session.id && session.name !== resolveIntercomPresenceName(undefined, session.id) ? ` name: ${session.name}` : "";
|
|
2450
2452
|
const normalizedGroup = normalizeGroup(session.group);
|
|
2451
2453
|
const groupTag = normalizedGroup !== DEFAULT_GROUP ? `group: ${normalizedGroup}` : undefined;
|
|
2452
2454
|
const capabilityTag = session.replyCapability === undefined ? undefined : `replyCapability: ${session.replyCapability}`;
|
|
2453
2455
|
const tags = [isSelf ? "self" : session.cwd === currentCwd ? "same cwd" : undefined, session.status, capabilityTag, groupTag].filter((tag) => Boolean(tag));
|
|
2454
2456
|
const suffix = tags.length ? ` [${tags.join(", ")}]` : "";
|
|
2455
|
-
return
|
|
2457
|
+
return `- \`${session.id}\`${suffix} ${session.cwd} (${session.model})${name}`;
|
|
2456
2458
|
}
|
|
2457
2459
|
function previewText(value, maxLength = 72) {
|
|
2458
2460
|
if (typeof value !== "string") {
|
|
@@ -3192,10 +3194,10 @@ async function resolveReplySender(client, logicalTarget, sendTarget) {
|
|
|
3192
3194
|
return { kind: "unresolved" };
|
|
3193
3195
|
}
|
|
3194
3196
|
function formatWorkflowStageRow(stage) {
|
|
3195
|
-
return `-
|
|
3197
|
+
return `- \`${stage.target}\` [${stage.lifecycle.toUpperCase()}] workflow stage: ${stage.stageName}${stage.sessionId === undefined ? "" : ` session: \`${stage.sessionId}\``}`;
|
|
3196
3198
|
}
|
|
3197
3199
|
function formatWorkflowFutureStageRow(stage) {
|
|
3198
|
-
return `-
|
|
3200
|
+
return `- \`${stage.target}\` [future] ${stage.queuedCount} queued message${stage.queuedCount === 1 ? "" : "s"}`;
|
|
3199
3201
|
}
|
|
3200
3202
|
function registerIntercomTool(pi, deps) {
|
|
3201
3203
|
const retryIdentities = new RetryIdentityReservations({ maxEntries: deps.retryIdentityMaxEntries });
|
|
@@ -3949,12 +3951,13 @@ ${replyText}${replyAttachments}` }],
|
|
|
3949
3951
|
messageId: replyMessageId,
|
|
3950
3952
|
logicalTarget: replyLogicalTarget,
|
|
3951
3953
|
...route.expectsReply ? { requirePendingReply: true } : {},
|
|
3954
|
+
...to === undefined && replyTo === undefined ? {} : { expectedRecipientId: route.senderId },
|
|
3952
3955
|
text: message,
|
|
3953
3956
|
attachments,
|
|
3954
3957
|
replyTo: route.messageId
|
|
3955
3958
|
});
|
|
3956
3959
|
let result = await sendReply(replySendTo);
|
|
3957
|
-
if (to === undefined && result.delivered === false && result.reasonCode === "session_not_found" && route.senderName !== undefined) {
|
|
3960
|
+
if (to === undefined && replyTo === undefined && result.delivered === false && result.reasonCode === "session_not_found" && route.senderName !== undefined) {
|
|
3958
3961
|
result = await sendReply(route.senderName);
|
|
3959
3962
|
}
|
|
3960
3963
|
if (result.queued === true) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/intercom",
|
|
3
|
-
"version": "0.9.19-alpha.
|
|
3
|
+
"version": "0.9.19-alpha.9",
|
|
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": [
|
|
@@ -67,11 +67,11 @@ intercom({
|
|
|
67
67
|
|
|
68
68
|
### Pattern 2: Quick Status Check
|
|
69
69
|
|
|
70
|
-
Before sending, verify who's connected.
|
|
70
|
+
Before sending, verify who's connected. Each list row leads with a copyable full session ID or canonical workflow path. Names are secondary; redundant generated aliases are omitted from the list but remain valid targets.
|
|
71
71
|
|
|
72
72
|
```typescript
|
|
73
73
|
intercom({ action: "list" })
|
|
74
|
-
// →
|
|
74
|
+
// → - `6332faab-1111-4222-8333-123456789abc` [idle] /workspace (model) name: planner
|
|
75
75
|
intercom({ action: "ask", to: "6332faab-1111-4222-8333-123456789abc", message: "Which option should I use?" })
|
|
76
76
|
```
|
|
77
77
|
|
|
@@ -128,7 +128,7 @@ intercom({ action: "pending" })
|
|
|
128
128
|
intercom({ action: "reply", to: "planner", message: "Use exponential backoff starting at 100ms." })
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
`
|
|
131
|
+
Explicit `to` selects that sender's pending ask even if another message triggered the current turn. Use `pending` and an exact `replyTo` when the sender has several asks. Stale, unknown, empty, or sender-mismatched explicit selectors fail without replying to another thread. Omit both selectors only when you intend to reply to the active message, or otherwise the single pending ask.
|
|
132
132
|
|
|
133
133
|
### Pattern 4: Broadcast to Multiple Workers
|
|
134
134
|
|
|
@@ -124,7 +124,7 @@ export type ClientMessage =
|
|
|
124
124
|
| { type: "join_group"; requestId: string; group: string }
|
|
125
125
|
| { type: "leave_group"; requestId: string; group?: string }
|
|
126
126
|
| { type: "authorize_supervisor"; requestId: string; childName: string; capability?: string }
|
|
127
|
-
| { type: "send" | "supervisor_send"; to: string; logicalTarget?: string; requirePendingReply?: true; resolveReplyTarget?: true; message: Message; attemptId?: string }
|
|
127
|
+
| { type: "send" | "supervisor_send"; to: string; logicalTarget?: string; requirePendingReply?: true; expectedRecipientId?: string; resolveReplyTarget?: true; message: Message; attemptId?: string }
|
|
128
128
|
| {
|
|
129
129
|
type: "send_pending_stage_notification";
|
|
130
130
|
runId: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/mcp",
|
|
3
|
-
"version": "0.9.19-alpha.
|
|
3
|
+
"version": "0.9.19-alpha.9",
|
|
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.19-alpha.
|
|
3
|
+
"version": "0.9.19-alpha.9",
|
|
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": [
|
|
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.9.19-alpha.9] - 2026-09-12
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- `fetch_content` ignores video frame options for non-video inputs, so webpages and mixed batches no longer fail with video-only errors. Tool and parameter descriptions now distinguish ordinary page fetching, video analysis, and frame extraction.
|
|
12
|
+
|
|
7
13
|
## [0.9.19-alpha.7] - 2026-09-12
|
|
8
14
|
|
|
9
15
|
### Fixed
|
|
@@ -74476,13 +74476,15 @@ async function extractContent(url, signal, options) {
|
|
|
74476
74476
|
if (signal?.aborted) {
|
|
74477
74477
|
return { url, title: "", content: "", error: "Aborted" };
|
|
74478
74478
|
}
|
|
74479
|
-
const frameResult = await extractRequestedFrames(url, options, signal);
|
|
74480
|
-
if (frameResult)
|
|
74481
|
-
return frameResult;
|
|
74482
74479
|
const localVideo = safeVideoInfo2(url);
|
|
74483
74480
|
if (localVideo.error) {
|
|
74484
74481
|
return { url, title: "", content: "", error: localVideo.error };
|
|
74485
74482
|
}
|
|
74483
|
+
if (localVideo.info || isYouTubeURL(url).isYouTube) {
|
|
74484
|
+
const frameResult = await extractRequestedFrames(url, options, signal);
|
|
74485
|
+
if (frameResult)
|
|
74486
|
+
return frameResult;
|
|
74487
|
+
}
|
|
74486
74488
|
if (localVideo.info) {
|
|
74487
74489
|
try {
|
|
74488
74490
|
const result = await extractVideo(localVideo.info, signal, options);
|
|
@@ -90949,29 +90951,29 @@ function registerContentTools(pi, deps) {
|
|
|
90949
90951
|
pi.registerTool({
|
|
90950
90952
|
name: "fetch_content",
|
|
90951
90953
|
label: "Fetch Content",
|
|
90952
|
-
description:
|
|
90953
|
-
promptSnippet: "
|
|
90954
|
+
description: `Fetch webpages, PDFs, GitHub repositories, YouTube videos, or local video files. For ordinary pages, pass only {"urls":["https://example.com"]}; readable content is returned as markdown. For video analysis, include the user's question in prompt. Optional frames and timestamp extract images only from YouTube or local videos and are ignored for other inputs, including in mixed batches. Content is stored for get_search_content. Blocked or unreadable pages use extraction fallbacks.`,
|
|
90955
|
+
promptSnippet: "Fetch webpages with urls only. For YouTube or local video analysis, include the user's question in prompt; omit frames and timestamp unless images are wanted.",
|
|
90954
90956
|
parameters: Type2.Object({
|
|
90955
90957
|
urls: Type2.Array(Type2.String({ minLength: 1 }), {
|
|
90956
90958
|
minItems: 1,
|
|
90957
90959
|
description: 'URLs or local video paths to fetch. Always use an array, even for one URL: {"urls":["https://example.com"]}. Multiple URLs are fetched in parallel.'
|
|
90958
90960
|
}),
|
|
90959
90961
|
forceClone: Type2.Optional(Type2.Boolean({
|
|
90960
|
-
description: "
|
|
90962
|
+
description: "GitHub repositories only: allow cloning above the configured size threshold. Omit for webpages and videos."
|
|
90961
90963
|
})),
|
|
90962
90964
|
prompt: Type2.Optional(Type2.String({
|
|
90963
|
-
description: "
|
|
90965
|
+
description: "YouTube/local video analysis only: the user's specific question. Omit for webpages; this is not a webpage search or extraction filter."
|
|
90964
90966
|
})),
|
|
90965
90967
|
timestamp: Type2.Optional(Type2.String({
|
|
90966
|
-
description: "
|
|
90968
|
+
description: "YouTube/local video images only: seconds ('85'), time ('1:25'), or range ('1:25-2:00'). Omit for text/transcripts. Ignored for non-video inputs. Ranges default to 6 frames; use frames to adjust."
|
|
90967
90969
|
})),
|
|
90968
90970
|
frames: Type2.Optional(Type2.Integer({
|
|
90969
90971
|
minimum: 1,
|
|
90970
90972
|
maximum: 12,
|
|
90971
|
-
description: "
|
|
90973
|
+
description: "YouTube/local video images only: 1-12 frames. Alone, samples the whole video; with a range, samples that span; with a single timestamp, uses 5s intervals. Ignored for non-video inputs. Omit for text/transcripts. Requires ffmpeg, plus yt-dlp for YouTube."
|
|
90972
90974
|
})),
|
|
90973
90975
|
model: Type2.Optional(Type2.String({
|
|
90974
|
-
description: "
|
|
90976
|
+
description: "Gemini model override for YouTube/local video analysis only. Omit to use the configured default; does not select the webpage extraction model."
|
|
90975
90977
|
}))
|
|
90976
90978
|
}, { additionalProperties: false }),
|
|
90977
90979
|
async execute(_toolCallId, params, signal, onUpdate) {
|
|
@@ -97598,18 +97600,18 @@ function webAccess(pi) {
|
|
|
97598
97600
|
pi.registerTool({
|
|
97599
97601
|
name: "fetch_content",
|
|
97600
97602
|
label: "Fetch Content",
|
|
97601
|
-
description:
|
|
97602
|
-
promptSnippet: "
|
|
97603
|
+
description: `Fetch webpages, PDFs, GitHub repositories, YouTube videos, or local video files. For ordinary pages, pass only {"urls":["https://example.com"]}; readable content is returned as markdown. For video analysis, include the user's question in prompt. Optional frames and timestamp extract images only from YouTube or local videos and are ignored for other inputs, including in mixed batches. Content is stored for get_search_content. Blocked or unreadable pages use extraction fallbacks.`,
|
|
97604
|
+
promptSnippet: "Fetch webpages with urls only. For YouTube or local video analysis, include the user's question in prompt; omit frames and timestamp unless images are wanted.",
|
|
97603
97605
|
parameters: Type4.Object({
|
|
97604
97606
|
urls: Type4.Array(Type4.String({ minLength: 1 }), {
|
|
97605
97607
|
minItems: 1,
|
|
97606
97608
|
description: 'URLs or local video paths to fetch. Always use an array, even for one URL: {"urls":["https://example.com"]}. Multiple URLs are fetched in parallel.'
|
|
97607
97609
|
}),
|
|
97608
|
-
forceClone: Type4.Optional(Type4.Boolean({ description: "
|
|
97609
|
-
prompt: Type4.Optional(Type4.String({ description: "
|
|
97610
|
-
timestamp: Type4.Optional(Type4.String({ description: "
|
|
97611
|
-
frames: Type4.Optional(Type4.Integer({ minimum: 1, maximum: 12, description: "
|
|
97612
|
-
model: Type4.Optional(Type4.String({ description: "
|
|
97610
|
+
forceClone: Type4.Optional(Type4.Boolean({ description: "GitHub repositories only: allow cloning above the configured size threshold. Omit for webpages and videos." })),
|
|
97611
|
+
prompt: Type4.Optional(Type4.String({ description: "YouTube/local video analysis only: the user's specific question. Omit for webpages; this is not a webpage search or extraction filter." })),
|
|
97612
|
+
timestamp: Type4.Optional(Type4.String({ description: "YouTube/local video images only: seconds ('85'), time ('1:25'), or range ('1:25-2:00'). Omit for text/transcripts. Ignored for non-video inputs. Ranges default to 6 frames; use frames to adjust." })),
|
|
97613
|
+
frames: Type4.Optional(Type4.Integer({ minimum: 1, maximum: 12, description: "YouTube/local video images only: 1-12 frames. Alone, samples the whole video; with a range, samples that span; with a single timestamp, uses 5s intervals. Ignored for non-video inputs. Omit for text/transcripts. Requires ffmpeg, plus yt-dlp for YouTube." })),
|
|
97614
|
+
model: Type4.Optional(Type4.String({ description: "Gemini model override for YouTube/local video analysis only. Omit to use the configured default; does not select the webpage extraction model." }))
|
|
97613
97615
|
}, { additionalProperties: false }),
|
|
97614
97616
|
execute: (...args) => executeHeavyTool(loadHeavy, "fetch_content", args),
|
|
97615
97617
|
renderResult: (...args) => renderHeavyToolResult(loadedHeavy?.heavy ?? null, "fetch_content", args)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/web-access",
|
|
3
|
-
"version": "0.9.19-alpha.
|
|
3
|
+
"version": "0.9.19-alpha.9",
|
|
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,13 @@ 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.19-alpha.9] - 2026-09-12
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Kept the background workflow summary within narrow terminal widths, including 27 columns. The shared workflow widget now stays within ten rows, shrinks on short terminals, and scrolls one row at a time with Alt+PageUp / Alt+PageDown without taking focus from the editor. All workflow rows remain reachable when a multiline draft reduces the visible widget area ([#3015](https://github.com/bastani-inc/atomic/issues/3015)).
|
|
14
|
+
- Kept the workflow being read in view when live runs are inserted or removed above it, including workflows with identical names. Resizing to a collapsed summary and back preserves the reading position; removing the viewed run selects its next surviving neighbour, or the previous one when no later run remains ([#3017](https://github.com/bastani-inc/atomic/pull/3017)).
|
|
15
|
+
|
|
9
16
|
## [0.9.19-alpha.8] - 2026-09-12
|
|
10
17
|
|
|
11
18
|
### Fixed
|
|
@@ -79093,7 +79093,9 @@ function plainCollapsed(counts, activeTools) {
|
|
|
79093
79093
|
const tools = activeTools > 0 ? ` · ${activeTools} tool${activeTools === 1 ? "" : "s"}` : "";
|
|
79094
79094
|
return ` ▾ ${total} background · ${counts.active} ●${paused}${quit}${blocked}${tools}`;
|
|
79095
79095
|
}
|
|
79096
|
-
function buildThemedWidgetLines(snap, piTheme, width = 120, now = Date.now()) {
|
|
79096
|
+
function buildThemedWidgetLines(snap, piTheme, width = 120, now = Date.now(), layout) {
|
|
79097
|
+
if (layout)
|
|
79098
|
+
layout.runs = [];
|
|
79097
79099
|
const display = selectDisplayRuns(snap, now);
|
|
79098
79100
|
if (display.length === 0)
|
|
79099
79101
|
return [];
|
|
@@ -79112,7 +79114,7 @@ function buildThemedWidgetLines(snap, piTheme, width = 120, now = Date.now()) {
|
|
|
79112
79114
|
const activeTools = display.filter(isActive).reduce((total, run) => total + activeToolNodes(run).length, 0);
|
|
79113
79115
|
if (width < COLLAPSED_BREAKPOINT_COLS) {
|
|
79114
79116
|
return [
|
|
79115
|
-
themed ? themedCollapsed(visibleCounts, activeTools, graphTheme) : plainCollapsed(visibleCounts, activeTools)
|
|
79117
|
+
truncateToWidth2(themed ? themedCollapsed(visibleCounts, activeTools, graphTheme) : plainCollapsed(visibleCounts, activeTools), width, "…")
|
|
79116
79118
|
];
|
|
79117
79119
|
}
|
|
79118
79120
|
const total = display.length;
|
|
@@ -79126,6 +79128,11 @@ function buildThemedWidgetLines(snap, piTheme, width = 120, now = Date.now()) {
|
|
|
79126
79128
|
const run = display[i];
|
|
79127
79129
|
const runLines = themed ? themedRunLines(run, now, graphTheme, snap.runs, width, expandGraph) : plainRunLines(run, now, snap.runs, width, expandGraph);
|
|
79128
79130
|
body.push(...runLines);
|
|
79131
|
+
layout?.runs.push({
|
|
79132
|
+
id: run.id,
|
|
79133
|
+
start: body.length - runLines.length + 1,
|
|
79134
|
+
end: body.length + (i === display.length - 1 ? 2 : 1)
|
|
79135
|
+
});
|
|
79129
79136
|
if (i < display.length - 1)
|
|
79130
79137
|
body.push("");
|
|
79131
79138
|
}
|
|
@@ -79138,7 +79145,95 @@ function buildThemedWidgetLines(snap, piTheme, width = 120, now = Date.now()) {
|
|
|
79138
79145
|
});
|
|
79139
79146
|
}
|
|
79140
79147
|
|
|
79148
|
+
// dist/builtin/workflows/src/tui/widget-viewport.ts
|
|
79149
|
+
import { ScrollableComponentViewport } from "@bastani/atomic";
|
|
79150
|
+
var WORKFLOW_WIDGET_MAX_ROWS = 10;
|
|
79151
|
+
|
|
79152
|
+
class WorkflowWidgetViewport {
|
|
79153
|
+
content;
|
|
79154
|
+
terminalRows;
|
|
79155
|
+
requestRender;
|
|
79156
|
+
getRunRows;
|
|
79157
|
+
viewport = new ScrollableComponentViewport;
|
|
79158
|
+
lines = [];
|
|
79159
|
+
scrollable = false;
|
|
79160
|
+
runRows = [];
|
|
79161
|
+
constructor(content, terminalRows, requestRender, getRunRows) {
|
|
79162
|
+
this.content = content;
|
|
79163
|
+
this.terminalRows = terminalRows;
|
|
79164
|
+
this.requestRender = requestRender;
|
|
79165
|
+
this.getRunRows = getRunRows;
|
|
79166
|
+
this.viewport.setComponents([{ render: () => this.lines, invalidate() {} }]);
|
|
79167
|
+
this.viewport.scrollTo(0);
|
|
79168
|
+
}
|
|
79169
|
+
scroll(direction) {
|
|
79170
|
+
if (!this.scrollable)
|
|
79171
|
+
return;
|
|
79172
|
+
this.viewport.scrollBy(direction);
|
|
79173
|
+
this.requestRender();
|
|
79174
|
+
}
|
|
79175
|
+
render(width) {
|
|
79176
|
+
const cap = Math.max(1, Math.min(WORKFLOW_WIDGET_MAX_ROWS, Math.floor(this.terminalRows() / 3)));
|
|
79177
|
+
const nextLines = this.content.render(width);
|
|
79178
|
+
const nextRunRows = this.getRunRows?.() ?? [];
|
|
79179
|
+
if (this.getRunRows && nextLines.length === 1 && nextRunRows.length === 0) {
|
|
79180
|
+
this.scrollable = false;
|
|
79181
|
+
return nextLines;
|
|
79182
|
+
}
|
|
79183
|
+
const previousLineCount = this.lines.length;
|
|
79184
|
+
const offset = this.viewport.getMaxScroll() - this.viewport.getScrollFromBottom();
|
|
79185
|
+
const anchoredOffset = this.getRunRows ? this.resolveAnchor(offset, nextRunRows) : undefined;
|
|
79186
|
+
this.lines = nextLines;
|
|
79187
|
+
this.runRows = nextRunRows;
|
|
79188
|
+
if (anchoredOffset !== undefined) {
|
|
79189
|
+
this.viewport.scrollTo(anchoredOffset);
|
|
79190
|
+
} else if (previousLineCount <= 1 || this.lines.length < previousLineCount && this.lines.length <= cap) {
|
|
79191
|
+
this.viewport.scrollTo(0);
|
|
79192
|
+
}
|
|
79193
|
+
this.scrollable = this.lines.length > 1;
|
|
79194
|
+
this.viewport.setVisibleRows(1);
|
|
79195
|
+
this.viewport.render(width);
|
|
79196
|
+
if (!this.scrollable)
|
|
79197
|
+
return this.lines;
|
|
79198
|
+
const first = this.viewport.getMaxScroll() - this.viewport.getScrollFromBottom() + 1;
|
|
79199
|
+
const visible = this.lines.slice(first - 1, first - 1 + Math.max(1, cap - 1));
|
|
79200
|
+
if (cap === 1)
|
|
79201
|
+
return visible;
|
|
79202
|
+
const hint = ` ${first}–${first + visible.length - 1}/${this.lines.length} · Alt+PgUp/PgDn scroll workflows`;
|
|
79203
|
+
return [...visible, truncateToWidth2(hint, width, "…")];
|
|
79204
|
+
}
|
|
79205
|
+
resolveAnchor(offset, next) {
|
|
79206
|
+
if (offset === 0 || next.length === 0)
|
|
79207
|
+
return 0;
|
|
79208
|
+
const index = this.runRows.findIndex((run) => offset >= run.start - 1 && offset < run.end);
|
|
79209
|
+
const anchor = this.runRows[index];
|
|
79210
|
+
if (!anchor)
|
|
79211
|
+
return 0;
|
|
79212
|
+
const byId = new Map(next.map((run) => [run.id, run]));
|
|
79213
|
+
const retained = byId.get(anchor.id);
|
|
79214
|
+
if (retained)
|
|
79215
|
+
return Math.min(retained.end - 1, retained.start + offset - anchor.start);
|
|
79216
|
+
const neighbours = [...this.runRows.slice(index + 1), ...this.runRows.slice(0, index).reverse()];
|
|
79217
|
+
for (const neighbour of neighbours) {
|
|
79218
|
+
const survivor = byId.get(neighbour.id);
|
|
79219
|
+
if (survivor)
|
|
79220
|
+
return survivor.start;
|
|
79221
|
+
}
|
|
79222
|
+
return 0;
|
|
79223
|
+
}
|
|
79224
|
+
invalidate() {
|
|
79225
|
+
this.content.invalidate?.();
|
|
79226
|
+
}
|
|
79227
|
+
dispose() {
|
|
79228
|
+
this.content.dispose?.();
|
|
79229
|
+
}
|
|
79230
|
+
}
|
|
79231
|
+
|
|
79141
79232
|
// dist/builtin/workflows/src/tui/store-widget-installer.ts
|
|
79233
|
+
var widgetViewports = new WeakMap;
|
|
79234
|
+
function scrollStoreWidget(storeInstance, direction) {
|
|
79235
|
+
widgetViewports.get(storeInstance)?.scroll(direction);
|
|
79236
|
+
}
|
|
79142
79237
|
var defaultTimerApi = {
|
|
79143
79238
|
setTimeout: (handler, delayMs) => setTimeout(handler, delayMs),
|
|
79144
79239
|
clearTimeout: (handle) => clearTimeout(handle)
|
|
@@ -79169,9 +79264,19 @@ function installStoreWidget(pi, storeInstance, timers = defaultTimerApi) {
|
|
|
79169
79264
|
const setWidget = ui.setWidget;
|
|
79170
79265
|
const requestRender = ui.requestRender;
|
|
79171
79266
|
const onWidgetRelease = ui.onWidgetRelease;
|
|
79267
|
+
const layout = { runs: [] };
|
|
79172
79268
|
const controller = installReactiveWidget({
|
|
79173
79269
|
ui: {
|
|
79174
|
-
setWidget: (key, factory, opts) =>
|
|
79270
|
+
setWidget: (key, factory, opts) => {
|
|
79271
|
+
if (!factory)
|
|
79272
|
+
widgetViewports.delete(storeInstance);
|
|
79273
|
+
setWidget.call(ui, key, factory ? (tui, theme) => {
|
|
79274
|
+
const host = tui;
|
|
79275
|
+
const viewport = new WorkflowWidgetViewport(factory(tui, theme), () => host?.terminal?.rows ?? 30, () => requestRender ? requestRender.call(ui) : host?.requestRender?.(), () => layout.runs);
|
|
79276
|
+
widgetViewports.set(storeInstance, viewport);
|
|
79277
|
+
return viewport;
|
|
79278
|
+
} : undefined, opts);
|
|
79279
|
+
},
|
|
79175
79280
|
...requestRender ? { requestRender: () => requestRender.call(ui) } : {},
|
|
79176
79281
|
...onWidgetRelease ? { onWidgetRelease: (key, listener) => onWidgetRelease.call(ui, key, listener) } : {}
|
|
79177
79282
|
},
|
|
@@ -79181,7 +79286,7 @@ function installStoreWidget(pi, storeInstance, timers = defaultTimerApi) {
|
|
|
79181
79286
|
getSnapshot: () => liveWidgetSnapshot(storeInstance),
|
|
79182
79287
|
subscribe: (listener) => subscribeStoreInvalidation(storeInstance, listener),
|
|
79183
79288
|
getPreviewLines: (snap, now) => buildThemedWidgetLines(snap, undefined, 120, now),
|
|
79184
|
-
render: (snap, { theme, width, now }) => buildThemedWidgetLines(snap, theme, width, now),
|
|
79289
|
+
render: (snap, { theme, width, now }) => buildThemedWidgetLines(snap, theme, width, now, layout),
|
|
79185
79290
|
getNextRefreshDelayMs: (snap, now) => nextWidgetRefreshDelayMs(snap, now),
|
|
79186
79291
|
requestRenderOnStateNoop: false,
|
|
79187
79292
|
isStaleError: isStaleExtensionContextError4,
|
|
@@ -79189,7 +79294,10 @@ function installStoreWidget(pi, storeInstance, timers = defaultTimerApi) {
|
|
|
79189
79294
|
reportWidgetFailure(ui, `Workflow progress widget could not mount: ${error.message}`);
|
|
79190
79295
|
}
|
|
79191
79296
|
});
|
|
79192
|
-
return () =>
|
|
79297
|
+
return () => {
|
|
79298
|
+
widgetViewports.delete(storeInstance);
|
|
79299
|
+
controller.dispose();
|
|
79300
|
+
};
|
|
79193
79301
|
}
|
|
79194
79302
|
function installToolExecutionHooks(pi, storeInstance) {
|
|
79195
79303
|
const eventBusOn = pi.events?.on;
|
|
@@ -117735,6 +117843,14 @@ function registerWorkflowShortcut(pi, overlay) {
|
|
|
117735
117843
|
description: "Open workflow orchestrator pane",
|
|
117736
117844
|
handler: openPane
|
|
117737
117845
|
});
|
|
117846
|
+
pi.registerShortcut("alt+pageUp", {
|
|
117847
|
+
description: "Scroll background workflows up",
|
|
117848
|
+
handler: () => scrollStoreWidget(store, -1)
|
|
117849
|
+
});
|
|
117850
|
+
pi.registerShortcut("alt+pageDown", {
|
|
117851
|
+
description: "Scroll background workflows down",
|
|
117852
|
+
handler: () => scrollStoreWidget(store, 1)
|
|
117853
|
+
});
|
|
117738
117854
|
}
|
|
117739
117855
|
function registerIntercomControl(pi, intercomControlRef) {
|
|
117740
117856
|
intercomControlRef.current = subscribeIntercomControl(pi, buildIntercomCallbacks({
|
|
@@ -53,6 +53,8 @@ Sent and received messages are recorded in session history as `intercom_sent` /
|
|
|
53
53
|
|
|
54
54
|
Live-session lookup accepts only an exact full Intercom session ID or an exact case-insensitive session name. Workflow stages use the canonical `workflow:<rootRunId>/<segment>[/<segment>...]` path printed by `intercom list` and workflow status surfaces; an exact target works while the row is `PENDING` and after it becomes `RUNNING`. Each segment may be a stage name, run id, or glob: `*` matches one segment and may be embedded, while `**` matches any depth. Status surfaces label pending stages whose pre-start delivery capability is unavailable without presenting a usable target and never advertise a retained pending stage after its run terminates. The `sessionId` shown by `workflow status` belongs to the workflow SDK and is **not** an Intercom target.
|
|
55
55
|
|
|
56
|
+
Agent list rows put the copyable exact ID or canonical workflow path first, followed by status and working directory. Meaningful names remain secondary metadata; redundant generated aliases are hidden only in the list, not removed from lookup. Pending and future workflow rows retain lifecycle and queued counts. Copy the target rather than reconstructing it from a name.
|
|
57
|
+
|
|
56
58
|
Known non-agent IDs, names, and workflow paths are refused rather than delivered or queued for a future agent. This includes run-level `ctx.ui` prompts, synthetic prompt stages (including retained completed prompts), and `ctx.tool` nodes. Knowing an internal connection's ID does not bypass this broker policy, and supervisor delivery cannot bypass it either. Workflow patterns and `workflow:<rootRunId>/**` still queue for future agent stages, but never deliver to prompt/tool nodes or routing connections.
|
|
57
59
|
|
|
58
60
|
This refusal also covers nested paths using boundary-stage names or IDs, mixed with materialized run-ID segments. The same spellings still resolve genuine agent stages.
|
|
@@ -100,6 +102,8 @@ An `idle` registration does not guarantee reply capability. Completed, failed, i
|
|
|
100
102
|
|
|
101
103
|
**`reply`** is receiver-side sugar for replying to an inbound ask. In the turn triggered by an incoming intercom message, `intercom({ action: "reply", message: "..." })` targets that exact sender and message automatically. If you reply later, it falls back to the single unresolved inbound ask; with multiple pending asks, use `pending` and pass `to`, or pass the listed message ID as `replyTo` to disambiguate multiple asks from the same sender. Under the hood this is still a normal `send` with the exact `replyTo` value.
|
|
102
104
|
|
|
105
|
+
Explicit selectors override the active turn: `to` selects a pending ask from that exact name/full session ID, and `replyTo` selects only that pending ask or the exact active ordinary message. Stale, unknown, or empty thread IDs fail without falling back. If both selectors are supplied, the sender must match the thread. If an asker is still waiting, inspect `pending` and reply using its exact message ID rather than replying implicitly to an unrelated notification. Multiple asks from the same sender require `replyTo`.
|
|
106
|
+
|
|
103
107
|
### Attachments
|
|
104
108
|
|
|
105
109
|
`send`, `ask`, and `reply` accept an `attachments` array of `{ type, name, content, language? }` objects where `type` is `"file"`, `"snippet"`, or `"context"`. Attachment content is included in the recipient's agent-visible message body. When a parent-targeted foreground `ask` is terminally handed off at the source, the same ordered attachment array is retained and rendered with the question for the launching parent; duplicate names and content are not rewritten. Attachments are supported in the protocol but not in the ALT+M compose overlay.
|
package/docs/intercom.md
CHANGED
|
@@ -81,10 +81,10 @@ The agent can list sessions and send messages using the `intercom` tool. Tool ca
|
|
|
81
81
|
```typescript
|
|
82
82
|
// List active sessions
|
|
83
83
|
intercom({ action: "list" })
|
|
84
|
-
// → **Current session
|
|
85
|
-
// →
|
|
86
|
-
// → **Other sessions:**
|
|
87
|
-
// →
|
|
84
|
+
// → **Current session** (groups: default):
|
|
85
|
+
// → - `20d43841-1111-4222-8333-123456789abc` [self, idle] ~/projects/api (claude-sonnet-4) name: executor
|
|
86
|
+
// → **Other visible sessions and workflow stages:**
|
|
87
|
+
// → - `6332faab-1111-4222-8333-123456789abc` [same cwd, thinking] ~/projects/api (claude-sonnet-4) name: research
|
|
88
88
|
|
|
89
89
|
// Send a message
|
|
90
90
|
intercom({ action: "send", to: "research", message: "Check if UserService.validate() handles null" })
|
package/docs/web-access.md
CHANGED
|
@@ -22,6 +22,16 @@ For a video, include the question in `prompt`:
|
|
|
22
22
|
{"urls": ["/path/to/recording.mp4"], "prompt": "What error appears on screen?"}
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
For webpages, `urls` is enough. `prompt` and `model` apply to video analysis, not webpage filtering or extraction. `forceClone` applies only to GitHub repositories.
|
|
26
|
+
|
|
27
|
+
Use `frames` and `timestamp` only when you want images from YouTube or local video files. Omit both for readable text or transcripts. `frames` alone samples the whole video; `timestamp` accepts seconds, a time such as `1:25`, or a range such as `1:25-2:00`.
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{"urls": ["/path/to/recording.mp4"], "timestamp": "1:25-2:00", "frames": 3}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Frame extraction requires ffmpeg, plus yt-dlp for YouTube. Non-video inputs ignore `frames` and `timestamp` and are fetched normally, even in a batch containing videos. Video inputs still validate timestamps and report extraction errors.
|
|
34
|
+
|
|
25
35
|
Existing prompts or integrations using `{"url": "..."}` must change to `{"urls": ["..."]}`. The singular `url` field is no longer accepted by `fetch_content`. This does not change the `url` selector on `get_search_content`.
|
|
26
36
|
|
|
27
37
|
Atomic can normalize a scalar `urls` string into a one-item array. Always use the documented array form in prompts and integrations; the legacy `url` field is not normalized.
|
|
@@ -27,6 +27,10 @@ Send material updates through Intercom to every affected workflow stage, includi
|
|
|
27
27
|
|
|
28
28
|
At 80 columns and wider, each `BACKGROUND` card keeps the full run identity and preserves its mode, progress, live-tool details, and elapsed/status metadata. When the remaining single-row budget permits, it adds bounded pending-stage details: a target is either shown exactly or replaced by a `stage`-labeled canonical ID, and `… N more` reports omitted pending stages. If no bounded pending-stage form fits, the pending label is omitted entirely rather than displacing the existing metadata. Tool nodes are read-only durable graph nodes, not attachable stage chats. Below 80 columns, the panel keeps its aggregate collapsed form and omits run IDs, stage identities, targets, and tool names.
|
|
29
29
|
|
|
30
|
+
The shared background widget occupies at most **10 rows**, including its scroll hint, and at most one third of the terminal height, rounded down with a one-row minimum. From the main editor, use **Alt+PageUp** and **Alt+PageDown** to scroll one row at a time. Every row remains reachable even when a multiline draft leaves room for only one widget row. The row range at the bottom shows your position when there is room for the hint. Ordinary typing, arrows, PageUp/PageDown, and mouse scrolling keep their existing editor and transcript behavior.
|
|
31
|
+
|
|
32
|
+
Below 80 columns the widget remains a single count line, truncated to fit when necessary; widen the terminal to scroll the cards or use `/workflow connect` to inspect runs at narrow widths.
|
|
33
|
+
|
|
30
34
|
The panel's stage progress includes recursively nested child workflows and updates as new stages appear. The numerator counts completed, failed, and skipped stages; the denominator counts all currently materialized stages, not future work. Expanded children replace their workflow boundary rather than counting both, and durable tool nodes are not stages. A failed, skipped, missing, or invalid child expansion keeps its boundary summary. `single`/`chain` follows this same stage count.
|
|
31
35
|
|
|
32
36
|
For chat surfaces such as workflow status, run detail, dispatch confirmation, and the run picker, a full id wraps onto continuation rows when the card is narrower than the id. Pending-stage targets in run detail use the same rule: the exact address wraps instead of being ellipsized, and narrow status cards wrap the canonical stage ID or drop its display-name decoration rather than rendering a partial ID. The renderer keeps the card border closed at its minimum layout width, while terminals below that floor — including sub-30-column terminals — can hard-clip the box. An awaiting-input attribution banner is titled `AWAITING INPUT` and contains the same two identity rows — `?` plus the full run id, then the workflow name and optional metadata — while the existing prompt question and options remain below it in the normal prompt UI.
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/atomic",
|
|
3
|
-
"version": "0.9.19-alpha.
|
|
3
|
+
"version": "0.9.19-alpha.9",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bastani/atomic",
|
|
9
|
-
"version": "0.9.19-alpha.
|
|
9
|
+
"version": "0.9.19-alpha.9",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@bastani/atomic-natives": "0.9.19-alpha.
|
|
13
|
-
"@bastani/pi-ai": "0.9.19-alpha.
|
|
12
|
+
"@bastani/atomic-natives": "0.9.19-alpha.9",
|
|
13
|
+
"@bastani/pi-ai": "0.9.19-alpha.9",
|
|
14
14
|
"@dbos-inc/dbos-sdk": "4.25.14",
|
|
15
15
|
"@earendil-works/pi-agent-core": "0.85.1",
|
|
16
16
|
"@earendil-works/pi-client": "0.85.1",
|
|
@@ -518,18 +518,18 @@
|
|
|
518
518
|
}
|
|
519
519
|
},
|
|
520
520
|
"node_modules/@bastani/atomic-natives": {
|
|
521
|
-
"version": "0.9.19-alpha.
|
|
522
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives/-/atomic-natives-0.9.19-alpha.
|
|
521
|
+
"version": "0.9.19-alpha.9",
|
|
522
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives/-/atomic-natives-0.9.19-alpha.9.tgz",
|
|
523
523
|
"license": "MIT",
|
|
524
524
|
"optionalDependencies": {
|
|
525
|
-
"@bastani/atomic-natives-darwin-arm64": "0.9.19-alpha.
|
|
526
|
-
"@bastani/atomic-natives-darwin-x64": "0.9.19-alpha.
|
|
527
|
-
"@bastani/atomic-natives-linux-arm64-gnu": "0.9.19-alpha.
|
|
528
|
-
"@bastani/atomic-natives-linux-arm64-musl": "0.9.19-alpha.
|
|
529
|
-
"@bastani/atomic-natives-linux-x64-gnu": "0.9.19-alpha.
|
|
530
|
-
"@bastani/atomic-natives-linux-x64-musl": "0.9.19-alpha.
|
|
531
|
-
"@bastani/atomic-natives-win32-arm64-msvc": "0.9.19-alpha.
|
|
532
|
-
"@bastani/atomic-natives-win32-x64-msvc": "0.9.19-alpha.
|
|
525
|
+
"@bastani/atomic-natives-darwin-arm64": "0.9.19-alpha.9",
|
|
526
|
+
"@bastani/atomic-natives-darwin-x64": "0.9.19-alpha.9",
|
|
527
|
+
"@bastani/atomic-natives-linux-arm64-gnu": "0.9.19-alpha.9",
|
|
528
|
+
"@bastani/atomic-natives-linux-arm64-musl": "0.9.19-alpha.9",
|
|
529
|
+
"@bastani/atomic-natives-linux-x64-gnu": "0.9.19-alpha.9",
|
|
530
|
+
"@bastani/atomic-natives-linux-x64-musl": "0.9.19-alpha.9",
|
|
531
|
+
"@bastani/atomic-natives-win32-arm64-msvc": "0.9.19-alpha.9",
|
|
532
|
+
"@bastani/atomic-natives-win32-x64-msvc": "0.9.19-alpha.9"
|
|
533
533
|
},
|
|
534
534
|
"engines": {
|
|
535
535
|
"bun": ">=1.4.2",
|
|
@@ -537,8 +537,8 @@
|
|
|
537
537
|
}
|
|
538
538
|
},
|
|
539
539
|
"node_modules/@bastani/atomic-natives-darwin-arm64": {
|
|
540
|
-
"version": "0.9.19-alpha.
|
|
541
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-arm64/-/atomic-natives-darwin-arm64-0.9.19-alpha.
|
|
540
|
+
"version": "0.9.19-alpha.9",
|
|
541
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-arm64/-/atomic-natives-darwin-arm64-0.9.19-alpha.9.tgz",
|
|
542
542
|
"license": "MIT",
|
|
543
543
|
"os": [
|
|
544
544
|
"darwin"
|
|
@@ -549,8 +549,8 @@
|
|
|
549
549
|
"optional": true
|
|
550
550
|
},
|
|
551
551
|
"node_modules/@bastani/atomic-natives-darwin-x64": {
|
|
552
|
-
"version": "0.9.19-alpha.
|
|
553
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-x64/-/atomic-natives-darwin-x64-0.9.19-alpha.
|
|
552
|
+
"version": "0.9.19-alpha.9",
|
|
553
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-x64/-/atomic-natives-darwin-x64-0.9.19-alpha.9.tgz",
|
|
554
554
|
"license": "MIT",
|
|
555
555
|
"os": [
|
|
556
556
|
"darwin"
|
|
@@ -561,8 +561,8 @@
|
|
|
561
561
|
"optional": true
|
|
562
562
|
},
|
|
563
563
|
"node_modules/@bastani/atomic-natives-linux-arm64-gnu": {
|
|
564
|
-
"version": "0.9.19-alpha.
|
|
565
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-gnu/-/atomic-natives-linux-arm64-gnu-0.9.19-alpha.
|
|
564
|
+
"version": "0.9.19-alpha.9",
|
|
565
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-gnu/-/atomic-natives-linux-arm64-gnu-0.9.19-alpha.9.tgz",
|
|
566
566
|
"license": "MIT",
|
|
567
567
|
"os": [
|
|
568
568
|
"linux"
|
|
@@ -576,8 +576,8 @@
|
|
|
576
576
|
"optional": true
|
|
577
577
|
},
|
|
578
578
|
"node_modules/@bastani/atomic-natives-linux-arm64-musl": {
|
|
579
|
-
"version": "0.9.19-alpha.
|
|
580
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-musl/-/atomic-natives-linux-arm64-musl-0.9.19-alpha.
|
|
579
|
+
"version": "0.9.19-alpha.9",
|
|
580
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-musl/-/atomic-natives-linux-arm64-musl-0.9.19-alpha.9.tgz",
|
|
581
581
|
"license": "MIT",
|
|
582
582
|
"os": [
|
|
583
583
|
"linux"
|
|
@@ -591,8 +591,8 @@
|
|
|
591
591
|
"optional": true
|
|
592
592
|
},
|
|
593
593
|
"node_modules/@bastani/atomic-natives-linux-x64-gnu": {
|
|
594
|
-
"version": "0.9.19-alpha.
|
|
595
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-gnu/-/atomic-natives-linux-x64-gnu-0.9.19-alpha.
|
|
594
|
+
"version": "0.9.19-alpha.9",
|
|
595
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-gnu/-/atomic-natives-linux-x64-gnu-0.9.19-alpha.9.tgz",
|
|
596
596
|
"license": "MIT",
|
|
597
597
|
"os": [
|
|
598
598
|
"linux"
|
|
@@ -606,8 +606,8 @@
|
|
|
606
606
|
"optional": true
|
|
607
607
|
},
|
|
608
608
|
"node_modules/@bastani/atomic-natives-linux-x64-musl": {
|
|
609
|
-
"version": "0.9.19-alpha.
|
|
610
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-musl/-/atomic-natives-linux-x64-musl-0.9.19-alpha.
|
|
609
|
+
"version": "0.9.19-alpha.9",
|
|
610
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-musl/-/atomic-natives-linux-x64-musl-0.9.19-alpha.9.tgz",
|
|
611
611
|
"license": "MIT",
|
|
612
612
|
"os": [
|
|
613
613
|
"linux"
|
|
@@ -621,8 +621,8 @@
|
|
|
621
621
|
"optional": true
|
|
622
622
|
},
|
|
623
623
|
"node_modules/@bastani/atomic-natives-win32-arm64-msvc": {
|
|
624
|
-
"version": "0.9.19-alpha.
|
|
625
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-arm64-msvc/-/atomic-natives-win32-arm64-msvc-0.9.19-alpha.
|
|
624
|
+
"version": "0.9.19-alpha.9",
|
|
625
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-arm64-msvc/-/atomic-natives-win32-arm64-msvc-0.9.19-alpha.9.tgz",
|
|
626
626
|
"license": "MIT",
|
|
627
627
|
"os": [
|
|
628
628
|
"win32"
|
|
@@ -633,8 +633,8 @@
|
|
|
633
633
|
"optional": true
|
|
634
634
|
},
|
|
635
635
|
"node_modules/@bastani/atomic-natives-win32-x64-msvc": {
|
|
636
|
-
"version": "0.9.19-alpha.
|
|
637
|
-
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-x64-msvc/-/atomic-natives-win32-x64-msvc-0.9.19-alpha.
|
|
636
|
+
"version": "0.9.19-alpha.9",
|
|
637
|
+
"resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-x64-msvc/-/atomic-natives-win32-x64-msvc-0.9.19-alpha.9.tgz",
|
|
638
638
|
"license": "MIT",
|
|
639
639
|
"os": [
|
|
640
640
|
"win32"
|
|
@@ -645,8 +645,8 @@
|
|
|
645
645
|
"optional": true
|
|
646
646
|
},
|
|
647
647
|
"node_modules/@bastani/pi-ai": {
|
|
648
|
-
"version": "0.9.19-alpha.
|
|
649
|
-
"resolved": "https://registry.npmjs.org/@bastani/pi-ai/-/pi-ai-0.9.19-alpha.
|
|
648
|
+
"version": "0.9.19-alpha.9",
|
|
649
|
+
"resolved": "https://registry.npmjs.org/@bastani/pi-ai/-/pi-ai-0.9.19-alpha.9.tgz",
|
|
650
650
|
"license": "MIT",
|
|
651
651
|
"dependencies": {
|
|
652
652
|
"@anthropic-ai/sdk": "0.124.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/atomic",
|
|
3
|
-
"version": "0.9.19-alpha.
|
|
3
|
+
"version": "0.9.19-alpha.9",
|
|
4
4
|
"description": "Atomic coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"atomicConfig": {
|
|
@@ -79,8 +79,8 @@
|
|
|
79
79
|
"prepublishOnly": "bun run clean && bun run build && bun run shrinkwrap"
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
|
-
"@bastani/atomic-natives": "0.9.19-alpha.
|
|
83
|
-
"@bastani/pi-ai": "0.9.19-alpha.
|
|
82
|
+
"@bastani/atomic-natives": "0.9.19-alpha.9",
|
|
83
|
+
"@bastani/pi-ai": "0.9.19-alpha.9",
|
|
84
84
|
"@dbos-inc/dbos-sdk": "4.25.14",
|
|
85
85
|
"@earendil-works/pi-agent-core": "0.85.1",
|
|
86
86
|
"@earendil-works/pi-client": "0.85.1",
|