@bastani/atomic 0.9.19-alpha.7 → 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 +17 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +403 -232
- 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 +8 -3
- 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,23 @@ 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
|
+
|
|
16
|
+
## [0.9.19-alpha.8] - 2026-09-12
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Workflow status detail now shows started and ended times in the system local timezone instead of UTC, preserving the compact `HH:mm:ss` display and elapsed durations ([#3008](https://github.com/bastani-inc/atomic/issues/3008)).
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Removed generated `root`, `dep`, and `deps` labels from workflow graph nodes and left model text blank when no model is set. Cards omit the extra padding row. Queued-message counts appear on a separate `✉ N queued` row, reusing empty space or expanding occupied cards without hiding status or model details. Dependency edges are unchanged.
|
|
25
|
+
|
|
9
26
|
## [0.9.19-alpha.7] - 2026-09-12
|
|
10
27
|
|
|
11
28
|
### Fixed
|