@llblab/pi-telegram 0.9.3 → 0.9.5

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/AGENTS.md CHANGED
@@ -7,6 +7,7 @@
7
7
  - `Boundary Clarity`: Separate Telegram transport concerns, π integration concerns, rendering behavior, and release/documentation state
8
8
  - `Progressive Enhancement + Graceful Degradation`: Prefer behavior that upgrades automatically when richer runtime context exists, but always preserves a useful fallback path when it does not
9
9
  - `Runtime Safety`: Prefer queue and rendering behavior that fails predictably over clever behavior that can desynchronize the Telegram bridge from π session state
10
+ - `Pi-Native Extensibility`: `pi-telegram` should inherit π's own extension philosophy. It is not only a Telegram adapter; it should become a small, convenient, composable Telegram shell for π extensions, where new capabilities plug into stable contracts instead of forking polling, transport, or menu ownership.
10
11
 
11
12
  ## 1. Concept
12
13
 
package/BACKLOG.md CHANGED
@@ -2,4 +2,9 @@
2
2
 
3
3
  ## Open Work
4
4
 
5
- No open work.
5
+ - Implement Telegram Extension Sections Platform for the 0.10.0 line.
6
+ - Exit: Runtime registry, main-menu integration, `section:` callback routing, safe section context ports, diagnostics, docs, and at least one small demo/fixture prove ordinary pi extensions can add Telegram menu sections without owning a second poller.
7
+ - Explore always-available outbound Telegram tools for queued artifacts and controls.
8
+ - Priority: Low.
9
+ - Idea: Provide tools such as `telegram_attach_file` and `telegram_attach_button` that can be called outside an active Telegram turn, using the paired chat/session as the delivery target when safe.
10
+ - Exit: Design note defines active-turn versus ambient delivery semantics, safety constraints, failure modes, and whether the current `telegram_attach` contract should stay turn-scoped or gain an ambient companion.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.5: Telegram Delivery Resilience Hotfix
4
+
5
+ - `[Preview Delivery]` Preview flush failures from Telegram transport errors such as `fetch failed` / `ECONNRESET` are now caught and recorded as runtime diagnostics instead of escaping from the preview pipeline. Impact: transient Telegram connectivity failures no longer crash the extension during streamed preview edits.
6
+ - `[Final Delivery]` Final Markdown preview replacement now catches Telegram transport failures and returns a normal fallback signal; the agent-end delivery path records final-text delivery failures and continues cleanup, attachment handling, and queue dispatch. Impact: a failed `editMessageText` at `agent_end` no longer breaks the bridge lifecycle or blocks the next queued Telegram turn.
7
+ - `[Diagnostics]` Preview and final delivery failures now flow through the runtime event recorder with compact phase metadata. Impact: `/telegram-status` can show recent transport failures without dumping noisy stack traces into the extension runner.
8
+ - `[Tests]` Added preview and queue regressions for non-fatal Telegram transport failures during preview flush and final delivery.
9
+ - `[Extension Sections Draft]` Added a draft design note for pi-native Telegram extension sections, reserved the future `section:` callback prefix, linked the draft from docs, and recorded the project philosophy that `pi-telegram` should inherit π's extensibility model as a shared Telegram shell for loaded extensions. Impact: the future 0.10.0 extension platform direction is documented without exposing a stable API yet.
10
+ - `[Docs Formatting]` Normalized project Markdown so prose paragraphs stay as single logical lines and Markdown tables remain narrow instead of using artificial hard wraps. Impact: editors and viewers can handle visual wrapping naturally while fixed-width structures stay readable.
11
+ - `[Settings Copy]` Tightened the proactive-push settings text by removing redundant persistence/default wording.
12
+
13
+ ## 0.9.4: Temp Dir And Command Template Hotfix
14
+
15
+ - `[Telegram Temp Dir]` Default Telegram API temp files now respect `PI_CODING_AGENT_DIR`, falling back to `~/.pi/agent` when the env var is unset. Impact: sandboxed or relocated agent dirs no longer force Telegram downloads through the default home-directory path.
16
+ - `[Command Templates]` Synced the local Command Template Standard with `pi-auto-tools@0.5.5`: command-template nodes now document `mode`, `label`, `delay`, `repeat`, parallel fanout semantics, zero-based repeat placeholders, padding, and limited arithmetic expressions such as `{_(index+1)}`. Impact: inbound/outbound Telegram handler docs and helpers share the current portable automation contract.
17
+ - `[Queue Menu]` Empty queue refresh clicks now rotate through compact alternate empty-state headings while preserving the default first-open `⌛ Queue is empty.` state, and the Refresh button now stays directly under Back for both empty and populated queue lists. Impact: manual queue polling feels alive and the primary refresh control stays in a stable location without changing queue semantics.
18
+ - `[Package]` Bumped package metadata to `0.9.4` and kept the lockfile in sync.
19
+
3
20
  ## 0.9.3: External Handlers Rename
4
21
 
5
22
  - `[External Handlers]` Renamed the external update handlers domain to `external-handlers` across source, tests, and docs. Impact: the interop domain now has a cleaner name aligned with inbound/outbound handler naming.
package/README.md CHANGED
@@ -87,8 +87,8 @@ Use these inside the Telegram DM with your bot:
87
87
  - **`/compact`**: Start session compaction (only works when the session is idle).
88
88
  - **`/next`**: Dispatch the next queued turn (aborts π first if busy).
89
89
  - **`/continue`**: Enqueue a priority `continue` prompt. It waits like normal Telegram work when π is busy and can trigger prompt/skill handling that listens for `continue`.
90
- - **`/stop`**: Abort the active run and clear all waiting Telegram queue items.
91
90
  - **`/abort`**: Abort the active run without touching the queue.
91
+ - **`/stop`**: Abort the active run and clear all waiting Telegram queue items.
92
92
 
93
93
  Prompt-template commands: π prompt templates are mapped to Telegram-safe aliases (`fix-tests.md` becomes `/fix_tests`) and shown as compact command-only rows between the built-in commands and status rows in `/start`. They are not registered in the Telegram bot command menu, keeping the bot menu focused on bridge controls. Sending `/template_name args` from Telegram expands the matching π prompt-template file and queues the expanded prompt like normal Telegram work.
94
94
 
package/docs/README.md CHANGED
@@ -11,3 +11,4 @@ Living index of project documentation in `/docs`.
11
11
  - [locks.md](./locks.md) — Shared `locks.json` standard for singleton extension ownership
12
12
  - [callback-namespaces.md](./callback-namespaces.md) — Shared Telegram `callback_data` namespace standard for layered extensions
13
13
  - [external-handlers.md](./external-handlers.md) — Runtime interceptor registry that lets layered extensions observe and consume Telegram updates without owning their own polling connection
14
+ - [extension-sections.md](./extension-sections.md) — Draft Telegram Extension Sections Standard for external menu sections and structured Telegram UI extension points
@@ -23,29 +23,28 @@ Naming rule: because the repository already scopes this codebase to Telegram, ex
23
23
 
24
24
  Current runtime areas use these ownership boundaries:
25
25
 
26
- | Domain | Owns |
27
- | --------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
28
- | `index.ts` | Single composition root for live π/Telegram ports, session state, API-bound transport adapters, and status updates |
29
- | `api` | Bot API transport shapes/helpers, retries, file download, temp-dir lifecycle, inbound limits, chat actions, lazy bot-token clients, runtime error recording |
30
- | `config` / `setup` | Persisted bot/session pairing state, authorization, first-user pairing, token prompting, env fallback, validation, config persistence |
31
- | `locks` / `polling` | Singleton `locks.json` ownership, takeover/restart semantics, long-poll controller state, update offset persistence, poll-loop runtime wiring |
32
- | `updates` / `routing` | Update classification/execution planning, paired authorization, reactions, edits, callbacks, and inbound route composition |
33
- | `media` / `text-groups` / `turns` / `inbound-handlers` | Text/media extraction, media-group debounce, long-text split coalescing, inbound downloads, inbound text/media handler execution, turn building/editing, image reads, legacy `attachmentHandlers` compatibility |
34
- | `queue` | Queue item contracts, lane admission/order, stores, mutations, dispatch readiness/runtime, prompt/control enqueueing, session and agent/tool lifecycle sequencing |
35
- | `runtime` | Session-local coordination primitives: counters, lifecycle flags, setup guard, abort handler, typing-loop timers, prompt-dispatch flags, agent-end reset binding |
36
- | `model` / `menu-model` / `menu-thinking` / `menu-status` / `menu` / `menu-queue` / `menu-settings` / `commands` | Model identity/thinking levels, scoped model resolution, in-flight switching, model-menu UI, thinking-menu UI, status-menu UI, inline application callback composition, queue-menu UI, hidden settings UI, slash commands, bot command registration |
37
- | `keyboard` | Shared Telegram inline-keyboard reply-markup structure; feature domains own callback semantics and button construction |
38
- | `preview` / `replies` / `rendering` | Preview lifecycle/transports, final reply delivery and reply parameters, Telegram HTML Markdown rendering, chunking, stable-preview snapshots |
39
- | `outbound-handlers` | Outbound text transformation, assistant-authored outbound comments, generated reply artifacts, inline-keyboard callbacks, and post-`agent_end` outbound action delivery |
40
- | `outbound-attachments` | `telegram_attach` registration, outbound attachment queueing, stat/limit checks, photo/document delivery classification |
41
- | `status` | Status-bar/status-message rendering, queue-lane status views, redacted runtime event ring, grouped π diagnostics |
42
- | `lifecycle` / `prompts` / `prompt-templates` / `pi` | π hook registration, Telegram-specific before-agent prompt injection, π prompt-template discovery/expansion, centralized direct pi SDK imports and context adapters |
43
- | `command-templates` | Portable shell-free command-template standard helpers, composition expansion, placeholder substitution, and executable resolution |
26
+ - `index.ts`: single composition root for live π/Telegram ports, session state, API-bound transport adapters, and status updates.
27
+ - `api`: Bot API transport shapes/helpers, retries, file download, temp-dir lifecycle, inbound limits, chat actions, lazy bot-token clients, and runtime error recording.
28
+ - `config` / `setup`: persisted bot/session pairing state, authorization, first-user pairing, token prompting, env fallback, validation, and config persistence.
29
+ - `locks` / `polling`: singleton `locks.json` ownership, takeover/restart semantics, long-poll controller state, update offset persistence, and poll-loop runtime wiring.
30
+ - `updates` / `routing`: update classification/execution planning, paired authorization, reactions, edits, callbacks, and inbound route composition.
31
+ - `media` / `text-groups` / `turns` / `inbound-handlers`: text/media extraction, media-group debounce, long-text split coalescing, inbound downloads, inbound text/media handler execution, turn building/editing, image reads, and legacy `attachmentHandlers` compatibility.
32
+ - `queue`: queue item contracts, lane admission/order, stores, mutations, dispatch readiness/runtime, prompt/control enqueueing, and session/agent/tool lifecycle sequencing.
33
+ - `runtime`: session-local coordination primitives: counters, lifecycle flags, setup guard, abort handler, typing-loop timers, prompt-dispatch flags, and agent-end reset binding.
34
+ - `model` / `menu-model` / `menu-thinking` / `menu-status` / `menu` / `menu-queue` / `menu-settings` / `commands`: model identity/thinking levels, scoped model resolution, in-flight switching, model/thinking/status/queue/settings menu UI, inline application callback composition, slash commands, and bot command registration.
35
+ - Future `extension-sections`: structured external Telegram menu sections registered by ordinary pi extensions; owns section registry, compact section callback tokens, section render/callback dispatch, safe section runtime ports, and diagnostics.
36
+ - `keyboard`: shared Telegram inline-keyboard reply-markup structure; feature domains own callback semantics and button construction.
37
+ - `preview` / `replies` / `rendering`: preview lifecycle/transports, final reply delivery and reply parameters, Telegram HTML Markdown rendering, chunking, and stable-preview snapshots.
38
+ - `outbound-handlers`: outbound text transformation, assistant-authored outbound comments, generated reply artifacts, inline-keyboard callbacks, and post-`agent_end` outbound action delivery.
39
+ - `outbound-attachments`: `telegram_attach` registration, outbound attachment queueing, stat/limit checks, and photo/document delivery classification.
40
+ - `status`: status-bar/status-message rendering, queue-lane status views, redacted runtime event ring, and grouped π diagnostics.
41
+ - `lifecycle` / `prompts` / `prompt-templates` / `pi`: π hook registration, Telegram-specific before-agent prompt injection, π prompt-template discovery/expansion, and centralized direct pi SDK imports/context adapters.
42
+ - `command-templates`: portable shell-free command-template standard helpers, composition expansion, placeholder substitution, and executable resolution.
44
43
 
45
44
  Boundary invariants:
46
45
 
47
46
  - Constants and state types live with their owning domains; do not reintroduce shared buckets such as `lib/constants.ts` or `lib/types.ts`
48
- - Shared Telegram inline-keyboard structure belongs to `keyboard`; application-control labels, callback data, and callback behavior stay in `menu`/`menu-model`/`menu-thinking`/`menu-status`/`menu-queue`; core queue mechanics stay in `queue`
47
+ - Shared Telegram inline-keyboard structure belongs to `keyboard`; application-control labels, callback data, and callback behavior stay in `menu`/`menu-model`/`menu-thinking`/`menu-status`/`menu-queue`; future external section labels, callbacks, and dispatch stay in `extension-sections`; core queue mechanics stay in `queue`
49
48
  - Domain helpers use narrow structural projections when that avoids importing concrete wire DTOs or broader runtime objects unnecessarily
50
49
  - Preview appearance stays in `rendering`; preview transport/lifecycle stays in `preview`
51
50
  - Direct `node:*` file-operation imports stay in owning domains, not in `index.ts`
@@ -95,13 +94,11 @@ Queued items now use two explicit dimensions:
95
94
 
96
95
  Admission contract:
97
96
 
98
- | Admission | Examples | Queue shape | Dispatch rank |
99
- | --------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
100
- | Immediate execution | `/compact`, `/queue`, `/stop`, `/help`, `/start` | Does not enter the Telegram queue; `/help` opens the same menu as `/start`; `/stop` also clears queued items | N/A |
101
- | Queued prompt command | `/continue`, `/template_name args` | `/continue` enqueues a Telegram-owned `continue` prompt; prompt-template commands expand the matching π template before entering the normal prompt queue | priority for `/continue`, otherwise default |
102
- | Control queue | Model-switch continuation turns and future deferred controls | `queueLane: control`; accepts control items and continuation prompts | 0 |
103
- | Priority prompt queue | A waiting prompt promoted by `👍`, `⚡️`, `❤️`, `🕊`, or `🔥` | `kind: prompt`, `queueLane: priority` | 1 |
104
- | Default prompt queue | Normal Telegram text/media turns | `kind: prompt`, `queueLane: default` | 2 |
97
+ - Immediate execution: `/compact`, `/queue`, `/stop`, `/help`, and `/start` do not enter the Telegram queue. `/help` opens the same menu as `/start`; `/stop` also clears queued items. Dispatch rank: N/A.
98
+ - Queued prompt command: `/continue` enqueues a priority Telegram-owned `continue` prompt. Prompt-template commands such as `/template_name args` expand the matching π template before entering the normal prompt queue. Dispatch rank: priority for `/continue`, otherwise default.
99
+ - Control queue: model-switch continuation turns and future deferred controls use `queueLane: control`, accept control items and continuation prompts, and dispatch at rank `0`.
100
+ - Priority prompt queue: a waiting prompt promoted by `👍`, `⚡️`, `❤️`, `🕊`, or `🔥` uses `kind: prompt`, `queueLane: priority`, and dispatches at rank `1`.
101
+ - Default prompt queue: normal Telegram text/media turns use `kind: prompt`, `queueLane: default`, and dispatch at rank `2`.
105
102
 
106
103
  The command action itself carries its execution mode, and the queue domain exposes lane contracts for admission mode, dispatch rank, and allowed item kinds. Queue append and planning paths validate lane admission so a malformed control/default or other invalid lane pairing fails predictably instead of silently changing priority. This lets synthetic control actions and Telegram prompts share one stable ordering model while still rendering distinctly in status output. In the π status bar, busy labels distinguish `active`, `dispatching`, `queued`, `tool running`, `model`, and `compacting`; priority prompts and priority control items are marked with `⚡`. If a queue mutation removes the last waiting item while Telegram-owned work still has running tools, the status remains yellow `active` instead of degrading to green `connected`.
107
104
 
@@ -160,7 +157,7 @@ Telegram prompt responses use explicit delivery context to attach outbound text,
160
157
 
161
158
  Outbound files are sent only after the active Telegram turn completes, must be staged through the `telegram_attach` tool, are staged atomically per tool call, are checked against a default 50 MiB limit configurable through `PI_TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES` or `TELEGRAM_MAX_ATTACHMENT_SIZE_BYTES`, and use file-backed multipart blobs so large sends do not require preloading whole files into memory.
162
159
 
163
- Assistant-authored outbound actions use final-message markup instead of agent tool calls. Preview updates strip closed top-level HTML comments and currently open/partial top-level comment starts before rendering, so users do not see transient metadata even when streaming flushes happen after only `<`, `<!`, or `<!--`. On `agent_end`, the bridge removes top-level comments from the Markdown text reply, but treats column-zero top-level `<!-- telegram_voice ... -->` and `<!-- telegram_button ... -->` blocks specially before delivery; comments inside fenced code, quotes, lists, or indented examples stay literal, including fenced blocks with Markdown-valid indented closing fences. Voice maps to the first matching `outboundHandlers[]` entry with `type: "voice"`, synthesizes body text, `text="..."`, or colon shorthand through command-template execution, and uploads the generated OGG/Opus file via Telegram `sendVoice`; when no outbound voice handler is configured, it silently skips voice delivery. The `template: [...]` form can express TTS plus MP3-to-OGG conversion using configured templates and bridge-provided `{text}`, `{mp3}`, and `{ogg}` placeholders. Top-level `args` and `defaults` apply to all composed steps unless a step defines private values, the default command timeout applies automatically, and each step receives the previous step's stdout on stdin by default, without hard-coded filesystem defaults. Button blocks are built in: each `telegram_button` block becomes one inline-keyboard button on the final text, and callback clicks enqueue the configured prompt text as a normal Telegram prompt turn; the `telegram_button: Label` shorthand uses the same text for label and prompt, `prompt="..."` supports explicit one-line prompts, and body-form buttons use the body as the prompt. Unknown callback data that does not match pi-telegram-owned prefixes (`tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`) is forwarded to π as `[callback] <data>` after built-in handlers decline it, giving layered extensions a simple namespaced button channel without separate polling; layered callback payloads should follow the [Callback Namespace Standard](./callback-namespaces.md). When proactive push is enabled, successful local non-Telegram final replies are sent to the paired chat. Local prompt text is not sent because the bot does not own or mirror terminal user messages. This keeps terminal-originated results visible in Telegram without changing Telegram-originated turn delivery.
160
+ Assistant-authored outbound actions use final-message markup instead of agent tool calls. Preview updates strip closed top-level HTML comments and currently open/partial top-level comment starts before rendering, so users do not see transient metadata even when streaming flushes happen after only `<`, `<!`, or `<!--`. On `agent_end`, the bridge removes top-level comments from the Markdown text reply, but treats column-zero top-level `<!-- telegram_voice ... -->` and `<!-- telegram_button ... -->` blocks specially before delivery; comments inside fenced code, quotes, lists, or indented examples stay literal, including fenced blocks with Markdown-valid indented closing fences. Voice maps to the first matching `outboundHandlers[]` entry with `type: "voice"`, synthesizes body text, `text="..."`, or colon shorthand through command-template execution, and uploads the generated OGG/Opus file via Telegram `sendVoice`; when no outbound voice handler is configured, it silently skips voice delivery. The `template: [...]` form can express TTS plus MP3-to-OGG conversion using configured templates and bridge-provided `{text}`, `{mp3}`, and `{ogg}` placeholders. Top-level `args` and `defaults` apply to all composed steps unless a step defines private values, the default command timeout applies automatically, and each step receives the previous step's stdout on stdin by default, without hard-coded filesystem defaults. Button blocks are built in: each `telegram_button` block becomes one inline-keyboard button on the final text, and callback clicks enqueue the configured prompt text as a normal Telegram prompt turn; the `telegram_button: Label` shorthand uses the same text for label and prompt, `prompt="..."` supports explicit one-line prompts, and body-form buttons use the body as the prompt. Unknown callback data that does not match pi-telegram-owned prefixes (`tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`, future `section:`) is forwarded to π as `[callback] <data>` after built-in handlers decline it, giving layered extensions a simple namespaced button channel without separate polling; layered callback payloads should follow the [Callback Namespace Standard](./callback-namespaces.md). Future structured menu integrations should use the [Telegram Extension Sections Standard](./extension-sections.md) instead of hand-rolled fallback callbacks. When proactive push is enabled, successful local non-Telegram final replies are sent to the paired chat. Local prompt text is not sent because the bot does not own or mirror terminal user messages. This keeps terminal-originated results visible in Telegram without changing Telegram-originated turn delivery.
164
161
 
165
162
  This keeps technical Markdown, code, tables, formulas, and numbered lists in the text channel when appropriate while allowing TTS-friendly voice messages and tappable continuations without invoking `telegram_attach` or extra transport tools. Telegram prompt guidance targets about 37 visible cells for tables, dense list items, and compact text blocks because emoji and other wide glyphs make raw character counts misleading on mobile screens.
166
163
 
@@ -20,7 +20,7 @@ myext:page:2
20
20
 
21
21
  - Use a stable extension-owned namespace, preferably the package or extension name without scope punctuation.
22
22
  - Keep the namespace lowercase ASCII: `a-z`, `0-9`, `_`, `-`.
23
- - Do not use `pi-telegram` owned prefixes: `tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`. Current app navigation uses `menu:`; `status:` remains reserved for legacy/owned status callbacks but is not emitted by current UI.
23
+ - Do not use `pi-telegram` owned prefixes: `tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`, `section:`. Current app navigation uses `menu:`; `status:` remains reserved for legacy/owned status callbacks but is not emitted by current UI. `section:` is reserved for the structured extension-section router documented in [Extension Sections](./extension-sections.md).
24
24
  - Keep the full `callback_data` within Telegram's 64-byte limit.
25
25
  - Put only opaque ids or small enum values in payloads; do not store secrets, full prompts, or large state.
26
26
  - Treat callbacks as untrusted input. Validate namespace, action, and payload before executing side effects.
@@ -34,3 +34,15 @@ If `pi-telegram` receives callback data that is not owned by its built-in prefix
34
34
  ```
35
35
 
36
36
  Layered extensions may intercept that message and handle their own namespace. If no extension handles it, the assistant may see the fallback message and should tell the user the callback was not handled and the environment may be misconfigured.
37
+
38
+ ## Extension sections
39
+
40
+ [Telegram Extension Sections](./extension-sections.md) are a higher-level UI contract over this namespace rule. A section owns a canonical extension identity such as `@llblab/pi-telegram-explorer`, but its Telegram `callback_data` should use the `pi-telegram` owned `section:` prefix plus a compact token, because Telegram limits callback payloads to 64 bytes.
41
+
42
+ Conceptual form:
43
+
44
+ ```text
45
+ section:<token>:<action>[:<payload>]
46
+ ```
47
+
48
+ The token maps back to the full section identity inside the section registry. Section authors should not hand-roll `section:` callbacks outside the section context helpers, and ordinary layered extensions should continue using their own namespace plus external handlers or the `[callback]` fallback.
@@ -4,7 +4,7 @@ Command templates are the portable integration format for deterministic local au
4
4
 
5
5
  **Meta-contract:** transportable (bit-for-bit identical across projects), high-density (zero fluff), constant (evolve by crystallizing, not speculating), optimal minimum (add only when it hurts).
6
6
 
7
- **Scope:** portable command execution format — shell-free exec, composition/pipes, timeout (30s default), retry, critical-step branching, output artifact selection, handler-level fallback. Single JSON standard; no platform lock-in.
7
+ **Scope:** portable synchronous command execution format — shell-free exec, composition/pipes, timeout (30s default), delay-before-start, retry, critical-step branching, output artifact selection, and handler-level fallback. Single JSON standard; no platform lock-in.
8
8
 
9
9
  ---
10
10
 
@@ -30,17 +30,18 @@ There is no portable `command` field. The command is derived from `template`: af
30
30
 
31
31
  Common object fields:
32
32
 
33
- | Field | Meaning |
34
- | ---------- | ------------------------------------------------------------------------------------------ |
35
- | `template` | Required command string or ordered composition array |
36
- | `args` | Optional placeholder-name declarations only; never stores defaults |
37
- | `defaults` | Placeholder default values by name |
38
- | `timeout` | Optional execution timeout in milliseconds; default `30000` (30s) |
39
- | `output` | Optional result selector; default `"stdout"`, or a "runtime value", e.g. `"ogg"` |
40
- | `retry` | Optional max attempts (including first); default `1`. Retries immediately on non-zero exit |
41
- | `critical` | Optional boolean; default `false`. When `true`, failure aborts the entire root composition |
33
+ - `label`: Optional human label for diagnostics and parallel branch reports.
34
+ - `mode`: Optional execution mode for array templates. Default is `"sequence"`; `"parallel"` runs children concurrently.
35
+ - `args`: Optional placeholder-name declarations only. Never stores defaults.
36
+ - `defaults`: Placeholder default values by name.
37
+ - `timeout`: Optional execution timeout in milliseconds. Default is `30000`. Long-running agent calls should set this explicitly.
38
+ - `delay`: Optional wait in milliseconds before starting this node. Default is no delay.
39
+ - `output`: Optional result selector. Default is `"stdout"`; runtime values such as `"ogg"` are valid.
40
+ - `retry`: Optional max attempts including the first. Default is `1`.
41
+ - `critical`: Optional boolean. When `true`, failure aborts the root composition.
42
+ - `template`: Required command string or ordered composition array.
42
43
 
43
- Storage paths, labels, selectors, descriptions, and registry-specific metadata belong to each extension's local schema.
44
+ For object form, write `template` last. Read the node flags first, then the executable content. Storage paths, labels, selectors, descriptions, and registry-specific metadata belong to each extension's local schema.
44
45
 
45
46
  ## Execution
46
47
 
@@ -105,7 +106,7 @@ template="echo 'literal words' {text}"
105
106
 
106
107
  ## Composition
107
108
 
108
- `template: [...]` means sequential composition; each leaf is a command template executed with one shared runtime value map:
109
+ `template: [...]` means sequential composition by default; each leaf is a command template executed with one shared runtime value map:
109
110
 
110
111
  ```json
111
112
  {
@@ -119,12 +120,17 @@ template="echo 'literal words' {text}"
119
120
 
120
121
  Composition rules:
121
122
 
122
- - Execute leaves in order; non-critical failures are recorded and execution continues, while `critical: true` failures abort the root composition
123
+ - Execute leaves in order when `mode` is omitted or set to `"sequence"`
124
+ - Execute child templates concurrently when `mode` is set to `"parallel"`
125
+ - Parallel composition uses soft-quorum semantics by default: failed non-critical children are reported but do not abort siblings or the next sequence step
126
+ - Non-critical failures are recorded and execution continues, while `critical: true` failures abort the root composition
123
127
  - Treat the whole composition as one handler for selector matching and fallback
124
128
  - Top-level `args` and `defaults` apply to every leaf unless the leaf defines private values
125
129
  - Leaf `args` replace inherited `args`; leaf `defaults` merge over inherited defaults; `timeout` and `output` are not inherited into leaves
126
130
  - Default `30000` (30s) timeout applies automatically; configure `timeout` only for exceptional long-running commands
127
- - Each leaf receives the previous leaf's stdout on stdin by default, while the final leaf stdout remains the default composition result
131
+ - Each sequence leaf receives the previous leaf's stdout on stdin by default, while the final leaf stdout remains the default composition result
132
+ - Each parallel child receives the same stdin, and child stdout values are joined in stable array order before flowing to the next sequence leaf
133
+ - Parallel branch joins include branch label and status, and tool details include branch metadata plus coverage summary
128
134
  - Each leaf still applies its own inline defaults
129
135
 
130
136
  ```json
@@ -132,8 +138,8 @@ Composition rules:
132
138
  "template": [
133
139
  "/path/to/tts --text {text} --lang {lang} --out {mp3}",
134
140
  {
135
- "template": "ffmpeg -y -i {mp3} -c:a {codec} {ogg}",
136
- "defaults": { "codec": "libopus" }
141
+ "defaults": { "codec": "libopus" },
142
+ "template": "ffmpeg -y -i {mp3} -c:a {codec} {ogg}"
137
143
  }
138
144
  ],
139
145
  "args": ["text", "lang", "mp3", "ogg"],
@@ -144,6 +150,81 @@ Composition rules:
144
150
 
145
151
  `output` selects the primary result channel. Omitted `output` means `"stdout"`, and explicitly writing `"output": "stdout"` is valid standard syntax. Artifact-producing handlers may instead name a runtime value or placeholder path, e.g. `"ogg"` or `"{ogg}"`.
146
152
 
153
+ ### Repeat
154
+
155
+ `repeat` expands one command-template node N times before execution. It works with both sequence and parallel nodes and is useful when many branches differ only by a number.
156
+
157
+ ```json
158
+ {
159
+ "mode": "parallel",
160
+ "repeat": 8,
161
+ "template": "render page{_(index+1)}.html --prev page{_(prev+1)}.html --next page{_(next+1)}.html --zero page{_index}.html"
162
+ }
163
+ ```
164
+
165
+ Reserved repeat placeholders are injected into each repeated node:
166
+
167
+ - `{index}`: current zero-based index, `0..repeat-1`
168
+ - `{prev}` / `{next}`: wrapped zero-based neighbors
169
+ - `{repeat}`: total repeat count
170
+
171
+ Human 1-based numbering is intentionally expressed as limited arithmetic: `{index+1}`, `{prev+1}`, `{next+1}`.
172
+
173
+ Leading underscores on repeat placeholders request zero padding. One underscore means width 2, two underscores mean width 3, and so on:
174
+
175
+ ```text
176
+ {_index} → 00, 01, ...
177
+ {_(index+1)} → 01, 02, ...
178
+ {__(index+1)} → 001, 002, ...
179
+ {_(prev+1)} → wrapped previous page number, padded to width 2
180
+ {_(next+1)} → wrapped next page number, padded to width 2
181
+ ```
182
+
183
+ Repeat expressions support only integers, `index`, `prev`, `next`, `repeat`, parentheses, and `+`, `-`, `*`, `/`, `%`. They are not JavaScript and cannot call functions or access properties.
184
+
185
+ Repeat placeholders are local generated values. Call-time args should not use these reserved names to override the repeat index.
186
+
187
+ Parallel nodes use the same object shape. Flags come first and `template` stays last:
188
+
189
+ ```json
190
+ {
191
+ "template": [
192
+ "prepare {out_dir}",
193
+ {
194
+ "mode": "parallel",
195
+ "template": [
196
+ {
197
+ "label": "gpt-5.5",
198
+ "timeout": 300000,
199
+ "template": "review-gpt {scope}"
200
+ },
201
+ {
202
+ "label": "deepseek-pro",
203
+ "timeout": 300000,
204
+ "template": "review-deepseek {scope}"
205
+ },
206
+ {
207
+ "label": "kimi",
208
+ "timeout": 300000,
209
+ "template": "review-kimi {scope}"
210
+ }
211
+ ]
212
+ },
213
+ "merge {out_dir}"
214
+ ]
215
+ }
216
+ ```
217
+
218
+ A degraded parallel join is still usable when at least one branch succeeds:
219
+
220
+ ```text
221
+ --- branch: gpt-5.5 status: done ---
222
+ review text
223
+ --- branch: deepseek-pro status: failed ---
224
+ exit: 1
225
+ stderr: provider balance exhausted
226
+ ```
227
+
147
228
  Legacy local schemas may accept `pipe` as an alias, but the portable standard is `template: [...]`.
148
229
 
149
230
  ## Fail-Open Default Policy
@@ -159,7 +240,7 @@ Set `critical: true` on any leaf to abort the entire root composition on failure
159
240
  "template": [
160
241
  { "template": "cargo build" },
161
242
  { "template": "cargo fmt --check" },
162
- { "template": "cargo test", "critical": true }
243
+ { "critical": true, "template": "cargo test" }
163
244
  ]
164
245
  }
165
246
  ```
@@ -175,14 +256,31 @@ Set `retry: N` on a leaf to attempt execution up to `N` times (including the fir
175
256
  ```json
176
257
  {
177
258
  "template": [
178
- { "template": "npm install", "retry": 3 },
179
- { "template": "npm test", "critical": true, "retry": 2 }
259
+ { "retry": 3, "template": "npm install" },
260
+ { "retry": 2, "critical": true, "template": "npm test" }
180
261
  ]
181
262
  }
182
263
  ```
183
264
 
184
265
  `npm install` is retried up to 3 times. `npm test` is retried up to 2 times; if all attempts fail, the critical step aborts the pipeline.
185
266
 
267
+ ## Delay
268
+
269
+ Set `delay` to wait before starting a node. The value is milliseconds. Delay is not inherited into child nodes, just like `timeout`.
270
+
271
+ ```json
272
+ {
273
+ "template": [
274
+ "prepare {scope}",
275
+ { "delay": 1000, "template": "review {scope}" }
276
+ ]
277
+ }
278
+ ```
279
+
280
+ On a sequence node, `delay` waits before the sequence begins. On a parallel node, `delay` waits before launching its children. On a branch, `delay` waits before that branch starts, without blocking sibling branches.
281
+
282
+ Use `delay` only for explicit backoff, rate pacing, or staged launch. Do not use it as a scheduler.
283
+
186
284
  ## Progressive Disclosure
187
285
 
188
286
  The standard uses a single `template` field that grows with the user's needs:
@@ -190,11 +288,14 @@ The standard uses a single `template` field that grows with the user's needs:
190
288
  ```text
191
289
  string → leaf command
192
290
  string[] → sequential composition
193
- { template } → leaf with defaults
194
- { template, retry, critical, output } → full leaf
291
+ { template } → leaf command object
292
+ { mode, template } → sequence or parallel subtree
293
+ { mode, args, defaults, delay, retry, critical, output, template } → full node
195
294
  ```
196
295
 
197
- Start with a string. Add composition when needed. Add retry when flaky. Add critical when safety matters. Same contract, growing capability, no dead weight.
296
+ Start with a string. Add composition when needed. Add `mode: "parallel"` when independent work can run concurrently. Add delay when launch pacing matters. Add retry when flaky. Add critical when safety matters. Same contract, growing capability, no dead weight.
297
+
298
+ `mode: "parallel"` is the synchronous fanout shape. Detached lifecycle, logs, cancellation, and durable state belong to host-specific async job or runtime-envelope standards, not to command templates.
198
299
 
199
300
  ## Tool Boundary
200
301