Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ All notable changes to taskflow are documented here. This project follows [Keep

### Added

- Pi `/tf web` launches the public local console, opens the browser with a safe URL and provides manual fallback, reuses the owned service, and cleans it up on session exit. The command retains private one-use token login and exposes existing Control Plane evidence.
- Restore the public Pi SDK compatibility guide and its reproducible matrix checks.
- Authenticated multi-project Control Plane: durable project/domain registry, real runtime admission, global concurrency reservations, command-result replay, scoped artifacts, terminal Receipts and evidence explanations.
- Durable approval park/CAS, output and plan edits, restart readmission, and local WebUI for runs, approvals and evidence. Plan edits preserve the DAG.
- Public `taskflow-control` and `taskflow-project-admin` binaries bundled in `taskflow-mcp-core`, including fresh default-auto bootstrap and an installed guide. The release remains ten public packages; the private control source workspace is not a runtime dependency.
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ pnpm exec node --conditions=development --experimental-strip-types --test \

This exercises the checked-in `examples/trusted-effects-write.json` path without a live LLM. For an interactive run, use the host guide for the adapter you already run. The [release guide](./RELEASE.md) covers packed-consumer validation and release gates.

### Local Control Console

In Pi, run `/tf web` to start the bundled local Control Console and open the default browser. If browser opening fails, the command shows a manual loopback URL. Use the one-use token in the reported private (0600) handoff file to log in; tokens are never included in the URL or command notifications.

The console shows Control Plane runs, approvals, Receipts and existing evidence. Legacy Pi run transcripts are outside this view. Repeating the command in the same project reuses its service; changing projects or leaving the Pi session stops only the console process started by that session. Unix local transport is required. If another host already owns the Control home, stop that owner explicitly before launching this console.

See [Pi compatibility](./docs/pi-compatibility.md) for the tested SDK matrix and the distinction between real-process fixtures and live-provider acceptance.

## Declare an effect

Effects are part of the flow contract, not a free-form prompt promise:
Expand Down
8 changes: 8 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ pnpm exec node --conditions=development --experimental-strip-types --test \

它会在没有 live LLM 的情况下执行仓库内的 `examples/trusted-effects-write.json` 路径。要交互式运行,请按当前使用的宿主查看对应指南。[发布指南](./RELEASE.md)说明打包消费验证和正式发布闸门。

### 本地 Control Console

在 Pi 中运行 `/tf web`,启动公开包内附的本地 Control Console 并打开默认浏览器。浏览器打开失败时会显示可手工访问的 loopback URL。使用提示中私有(0600)handoff 文件的一次性令牌登录;URL 和命令通知不会包含令牌。

页面展示 Control Plane 的 runs、审批、Receipt 和已有 evidence;不展示旧 Pi run 的原始 transcript。同一项目重复执行会复用服务;切换项目或退出 Pi 会话时,只清理该会话启动的 Console 进程。需要 Unix 本地传输;已有其他 Host 占用 Control home 时,需先显式停止该 Host。

[Pi 兼容性说明](./docs/pi-compatibility.md)列出已验证的 SDK 矩阵,并区分真实进程 fixture 与 live-provider 验收。

## 声明一个 effect

Effect 是 flow 合同的一部分,不是 prompt 里的自由文本承诺:
Expand Down
4 changes: 2 additions & 2 deletions docs/internal/1.0.0-release-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ the checked-in workflow and final-commit gates, with one release executor.

The owner's subsequent 2026-10-02 instruction makes closure of previously
committed documents/specifications a hard pre-publication gate, alongside real
Pi 1.0 compatibility. No merge, tag or npm publication has occurred. The earlier
Pi 1.0 compatibility. PR #142 merged on 2026-10-08 at `f0a82b5`; tag and npm publication remain pending. The earlier
stable/experimental packaging boundary below does not waive an accepted spec.
Approved Control Plane S3-S7 and accepted ADR requirements must be traced to
implementation and meaningful acceptance evidence before the release resumes.
The owner explicitly selected scope A: the complete approved Control Plane is
required for 1.0. Missing implementation remains blocked; relabeling it experimental is not closure.

## Release track reconciliation (2026-10-07)
## Historical release track reconciliation (2026-10-07)

| Surface | Meaning |
|---|---|
Expand Down
12 changes: 11 additions & 1 deletion docs/internal/1.0.0-spec-closure.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 1.0 specification closure ledger

**Track: unreleased 1.0.0, Draft PR #142. Updated 2026-10-08.** The retained
**Track: unreleased 1.0.0; PR #142 merged at `f0a82b5`. Updated 2026-10-08.** The retained
`rc/0.3.0-beta.2` branch name is historical. The owner authorized full closure through publication on 2026-10-08; final
release checks and registry verification remain mandatory.

Expand Down Expand Up @@ -95,3 +95,13 @@ MCP opt-in semantics; this filter is not an OS sandbox.
- Registry artifact verification, authorized main/tag/npm/GitHub Release and
the single original publisher handoff remain publication gates. See the
[current scoreboard](./1.0.0-ga-scoreboard.md).

## Final command and documentation follow-up

Closure review found that the existing Console did not yet provide the promised
Pi `/tf web` entry, and the #181 public compatibility guide had not been carried
into main. The follow-up adds the direct slash command using the existing public
carrier, safe browser handoff/fallback, bounded owned-process lifecycle, and a
packed-consumer regression. It restores the [compatibility guide](../pi-compatibility.md).
Final follow-up CI and registry publication remain separate gates. #95 OS authority
isolation remains explicitly deferred and is not claimed by this command.
54 changes: 54 additions & 0 deletions docs/pi-compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Pi compatibility

The CI `pi-peers` job tests exact official Pi SDK versions **0.80.3, 0.85.1, and 1.0.4** on Node 22. The four adapter-local `@earendil-works/pi-*` resolutions are asserted before testing; checking only the root SDK is insufficient with pnpm.

The public peer ranges remain `*`. This is installation flexibility, not a promise that every Pi version has been tested. The matrix changes only each disposable job’s adapter dependencies. Normal source and packed-consumer checks retain the committed Pi 1.0.0 development pins.

## Coverage

Each version runs the complete adapter unit suite, compiles the core and Pi extension, and launches the actual installed Pi CLI with the built extension. A local deterministic provider supplies model responses, without credentials or live model requests.

- Extension loading and actual `taskflow` tool dispatch.
- A foreground Taskflow launches `pi --mode json` and the child returns the owning run's ID. Direct runner identity and the shared-context `ctx_*` extension/allowlist are also exercised.
- A genuine Pi retry after a synthetic provider 429 recovers successfully. Event-stream regressions additionally cover exhaustion, overflow/threshold compaction, legacy clean exits, terminal reaping, cancellation and idle timeout.
- RPC approve/reject/cancel/edit/edit-cancel/abort paths, plus JSON-mode rejection that prevents the downstream script. Unit coverage retains the TUI overlay behavior, including missing or failed dialogs.
- Pi 1.0.4 additionally starts a harmless local stdio MCP server and exercises actual codemode calls, explicit MCP wildcard inclusion, and read-only tool-list filtering. Older matrix entries do not claim native MCP/codemode integration coverage.

The compatibility fixture uses compiled workspace output. Separate packed-consumer checks test the installed Pi 1.0.0 extension and crash recovery; the 1.0.4 matrix also runs lifecycle and crash recovery. These deterministic-provider checks do not establish live-provider authentication or interactive terminal visual acceptance.

## Pi 1.0 behavior handled by this adapter

An assistant `message_end` error can represent a failed attempt before Pi retries or compacts. Taskflow waits for recovery or final settlement instead of killing the process immediately. Explicit protocol errors and aborts remain fatal, and the existing idle watchdog remains active. Compaction revokes premature terminal reaping while preserving successful legacy clean-exit evidence when no retry follows.

RPC supports dialogs but not custom TUI overlays. Taskflow uses `ui.select` for RPC, retains the overlay for TUI, and rejects unavailable/cancelled dialogs and cancelled edits. JSON/print execution does not silently approve a human approval phase.

[Pi 1.0.4](https://github.com/earendil-works/pi/releases/tag/v1.0.4) preserves registered MCP tools when `--tools` contains no `mcp__` entry. That includes tools callable indirectly through codemode. Taskflow preserves its explicit tool-list contract by passing exclusions for ambient MCP server and native resource tools unless explicitly selected. `*` and explicit `mcp__...` selectors preserve native MCP opt-in behavior; native resource selectors are handled individually. For example:

| Taskflow tools | MCP behavior |
| --- | --- |
| `read` | MCP excluded, including indirect calls |
| `codemode` | Ambient server and resource tools excluded, even if an MCP extension was loaded |
| `read,codemode` | Ambient MCP resource reads remain excluded |
| `codemode,mcp__compat__*` | Pi filters MCP to the explicitly requested namespace |
| No tool list | Existing Pi/profile behavior retained |

This filter is not an OS sandbox. The default isolated child profile still disables ambient extensions; an allowlist or inherited profile can load explicitly authorized extensions.

## Reproduce

In a disposable checkout, after `pnpm install --frozen-lockfile`:

```sh
PI_VERSION=1.0.4 # Repeat with 0.80.3 and 0.85.1.
pnpm --filter pi-taskflow add --save-dev --save-exact \
@earendil-works/pi-agent-core@$PI_VERSION \
@earendil-works/pi-ai@$PI_VERSION \
@earendil-works/pi-coding-agent@$PI_VERSION \
@earendil-works/pi-tui@$PI_VERSION
PI_TEST_PEER_VERSION=$PI_VERSION pnpm run test:pi:peers
pnpm --filter taskflow-core build
pnpm --filter pi-taskflow build
node --experimental-strip-types packages/pi-taskflow/test/e2e-pi-compat.mts
```

The installation modifies that checkout's adapter manifest and lockfile only for the test. Restore those dependency changes before committing unrelated work.
3 changes: 2 additions & 1 deletion packages/pi-taskflow/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@
"image": "https://raw.githubusercontent.com/heggria/taskflow/main/assets/social-preview.png"
},
"dependencies": {
"taskflow-core": "workspace:*"
"taskflow-core": "workspace:*",
"taskflow-mcp-core": "workspace:*"
},
"peerDependencies": {
"@earendil-works/pi-agent-core": "*",
Expand Down
15 changes: 12 additions & 3 deletions packages/pi-taskflow/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* Registers:
* - tool `taskflow` : run inline / saved flows, save, resume (LLM-callable)
* - command `/tf` : list | run | show | save | resume | runs (user)
* - command `/tf` : list | run | show | save | resume | runs | web (user)
* - command `/tf:<name>` : per-saved-flow shortcut (registered on session_start)
*
* Intermediate phase outputs are held in the runtime and never pushed into the
Expand Down Expand Up @@ -32,6 +32,7 @@ import { renderRunResult, summarizeRun } from "./render.ts";
import { createPiSubagentRunner, PI_TASKFLOW_PI_ENTRY_ENV, resolveParentPiCliEntry, runnerModulePath } from "./runner.ts";
import { RunHistoryComponent, type RunHistoryResult } from "./runs-view.ts";
import { createApprovalRequester } from "./approval-view.ts";
import { createWebCommand } from "./web-command.ts";
import {
executeTaskflow,
recomputeTaskflow,
Expand Down Expand Up @@ -630,6 +631,8 @@ export default function (pi: ExtensionAPI) {
registerCtxTools(pi, ctxDir, nodeId);
return;
}
const webCommand = createWebCommand();
pi.on("session_shutdown", async () => { await webCommand.close(); });

// ---- Register per-saved-flow shortcut commands on session start ----
// Cached for /tf argument completions (getArgumentCompletions has no ctx).
Expand Down Expand Up @@ -1672,9 +1675,9 @@ export default function (pi: ExtensionAPI) {

// ---- The /tf user command ----
pi.registerCommand("tf", {
description: "Taskflow: list | run <name> | show <name> | verify <name> | compile <name> | plan <name> | runs | peek <runId> [phaseId] | reconcile-workspace --ack | init",
description: "Taskflow: list | run <name> | show <name> | verify <name> | compile <name> | plan <name> | runs | web (Control Plane console) | peek <runId> [phaseId] | reconcile-workspace --ack | init",
getArgumentCompletions: (prefix) => {
const subs = ["list", "run", "show", "runs", "peek", "resume", "init", "save", "verify", "compile", "plan", "analytics", "ir", "provenance", "trace", "replay", "why-stale", "recompute", "reconcile-workspace", "version"];
const subs = ["list", "run", "show", "runs", "web", "peek", "resume", "init", "save", "verify", "compile", "plan", "analytics", "ir", "provenance", "trace", "replay", "why-stale", "recompute", "reconcile-workspace", "version"];
// Name-taking subcommands: complete saved flow names after the sub (cwd from session_start).
const nameSubs = new Set(["run", "show", "verify", "compile", "plan", "analytics", "ir"]);
const trimmed = prefix.trimStart();
Expand Down Expand Up @@ -1704,6 +1707,12 @@ export default function (pi: ExtensionAPI) {
const arg = separator < 0 ? "" : trimmedArg.slice(separator).trim();
const rest = arg ? arg.split(/\s+/u) : [];

if (sub === "web") {
if (arg) { ctx.ui.notify("Usage: /tf web", "warning"); return; }
await webCommand.run(ctx);
return;
}

if (!sub || sub === "list") {
const flows = listFlows(ctx.cwd);
if (flows.length === 0) {
Expand Down
Loading
Loading