pi-net-resume1.1.0
Auto-continue a pi session after a network outage: arm on a connectivity error, wait for the link, then resume the same session with full context.
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
pi-net-resume
Auto-continue a pi session after a network outage.
English | 中文
When a phone hotspot or Wi-Fi link drops, the provider request fails, pi retries
it retry.maxRetries times (default 3: backoff 2s/4s/8s, about 14 seconds), and
then the agent settles with an error and simply stops. Nothing happens until a
human types something. If you stepped away, or your SSH session died with the
link, the task is silently stuck.
This extension watches for exactly that situation, waits for the network to come back, and then resumes the same session with its full context.
Install
pi install npm:pi-net-resume # from npm
pi -e npm:pi-net-resume # try it for one run, no settings change
Or from a git ref / a local checkout:
pi install git:github.com/ZYFsir/pi-net-resume@v1.0.0
pi install ./ # from a checkout of this package
This package lives in the pkg/pi-net-resume/ subdirectory of the
repository; the repo root carries a
small package.json that points pi at it, so the git install above works
without extra flags. pi install from a local checkout must point at the
package directory itself.
One setting you should change
The extension only takes over after pi has exhausted its own retries, so
retry.maxRetries decides how long a hiccup pi absorbs by itself. The default
of 3 (about 14 seconds) is short for a hotspot outage; 6 rides out about two
minutes:
// ~/.pi/agent/settings.json
{
"retry": { "maxRetries": 6, "baseDelayMs": 2000 }
}
This is deliberately not done by the package on install. If you skip it the extension still works, it just starts waiting sooner.
How it works
| Step | Hook | Action |
|---|---|---|
| 1 | agent_end |
remember a failure whose assistant message has stopReason: "error" and a connectivity-class errorMessage |
| 2 | agent_settled |
pi fires this only when no automatic retry / compaction retry / queued message is left, i.e. it really gave up |
| 3 | polling | check the link, then TCP-probe the model endpoint until it answers |
| 4 | resume | pi.sendUserMessage(...) in the same session, which starts a fresh turn |
Because the continuation is an ordinary user message in the existing session, all context — including tool results from before the outage — is preserved. This is a resume, not a re-run.
Where the probe target comes from
No provider is hardcoded. The first non-empty result wins:
ctx.model.baseUrl— the endpoint pi is actually configured to talk to.MODEL_BASE_URL/PI_BASE_URL/OPENAI_BASE_URL/ANTHROPIC_BASE_URL— for self-hosted gateways configured through the environment.extraProbeHostsfrom the config.- Nothing (e.g. a local
llama.cppwith no base URL): the system link state alone decides.
Ports follow what the URL means: https://… → 443, http://… → 80, and an
explicit host:port or [::1]:port is taken literally.
Relationship to pi-auto-resume
pi-auto-resume is a sibling
extension that handles a different interruption class:
| Interruption | pi-auto-resume |
pi-net-resume (this) |
|---|---|---|
Output truncated (stopReason: "length") |
yes — sends a continuation | not handled |
| HTTP 429 / rate limit | yes — exponential backoff | never (excluded on purpose) |
| Billing / quota / plan exhaustion | notifies the user | never (excluded on purpose) |
| Incomplete tool call | yes — asks the model to finish it | not handled |
| Connectivity loss | not detected | core case |
| How it decides to retry | a timer (fixed/exponential delay) | the link is actually up: NM state + a TCP probe of the model endpoint |
The two are complementary and can be installed together. The one place they could overlap is a network error whose wording also looks like throttling; the boundary is pinned in both directions by tests:
- A message carrying a hard broken-link signature (
ECONNRESET,ENOTFOUND,fetch failed,socket hang up, …) is treated as an outage even if it also mentions429orquotasomewhere. - A message that only says
unauthorized/quota/rate limitwith no hard signature is left alone, because resuming it would burn tokens on a request that cannot succeed.
If you want the sibling's behaviour too, install both:
pi install npm:pi-auto-resume # truncation, 429, quota
pi install npm:pi-net-resume # connectivity loss
Guards
- Only connectivity-class errors arm it (
errorPattern). - Quota, rate-limit and auth errors never do (
excludePattern) — the extension will not burn your credit on a request that cannot succeed. stopReason: "aborted"(Ctrl+C) never resumes.- Typing anything cancels a pending auto-resume.
armOnlyWhenOffline(defaulttrue): if the link is up when pi settles, the error was not an outage, so nothing is armed.- Capped by
maxAutoResumesandmaxWaitMinutes, throttled byminSecondsBetweenResumes, and it never fires while pi is busy. /net-resume offdisables it for one session.
Known limitation: pressing Escape while pi waits between retries cancels
the retries, and pi reports that identically to "retries exhausted". If the link
is still down, this extension may arm and later resume such a cancelled run.
Typing anything, or /net-resume off, cancels it.
Configure
mkdir -p ~/.pi/agent
cp config.example.json ~/.pi/agent/pi-net-resume.json
The agent dir is ~/.pi/agent unless PI_CODING_AGENT_DIR says otherwise.
Config is searched in this order, first existing file wins:
$PI_NET_RESUME_CONFIG<agent dir>/pi-net-resume.json← what the copy above creates<agent dir>/extensions/pi-net-resume/config.json(manual/legacy install)config.jsonbundled next to the extension
/net-resume prints which file was actually loaded, so there is no guessing.
Most useful keys:
| Key | Default | Meaning |
|---|---|---|
continueMessage |
The network is back… continue |
the text used to restart the turn |
resumeStyle |
hybrid |
hybrid = model sees nothing, you see a note; hidden = neither; visible = send it as a real user message |
armOnlyWhenOffline |
true |
only arm when the link is actually down |
maxAutoResumes |
20 |
hard cap on auto-resumes |
minSecondsBetweenResumes |
15 |
throttle between resumes |
maxWaitMinutes |
120 |
stop waiting after this long offline |
probeIntervalMs |
3000 |
poll cadence while waiting |
probeTimeoutMs |
4000 |
per-probe TCP timeout |
extraProbeHosts |
[] |
extra host:port probes (IPv6 must be bracketed, [::1]:443) |
checkNetworkManager |
true |
ask nmcli for link state before probing TCP |
notify |
true |
desktop notification when resuming |
logFile |
~/.local/state/pi-net-resume/pi-net-resume.log |
JSON-lines evidence log |
enabled |
true |
set false to load nothing at all |
errorPattern / excludePattern |
see config.example.json |
which errors arm it / never arm it |
Commands
| Command | Effect |
|---|---|
/net-resume |
status: armed/waiting, resumes used, link state, probe targets, config path |
/net-resume now |
force a resume immediately (if the link is up) |
/net-resume off / on |
disable / enable for this session |
Verify it works
The JSON-lines log is the evidence trail:
tail -f ~/.local/state/pi-net-resume/pi-net-resume.log
Expected sequence for a real outage:
{"event":"network_error","message":"fetch failed"}
{"event":"armed","detail":"NetworkManager reports disconnected"}
{"event":"waiting_for_network","failureAt":1789538000000}
{"event":"auto_resume","resumeCount":1,"waitedMs":41300,"detail":"api.example.com:443 reachable"}
PI_NET_RESUME_CONFIG=/path/to/config.json overrides the config location, which
is handy for testing.
Headless runs
The extension covers a live pi session. A pi -p (print-mode) process instead
exits when the provider call fails, so it needs a wrapper. The one bundled
here is pi-resume-run.sh:
./pi-resume-run.sh --model <provider>/<model> "run the test suite and fix failures"
It re-launches pi -p with the same session id and a "continue" prompt until the
run succeeds or a limit is reached.
bashandpython3are needed (python3 powers the TCP probe and the default probe-target resolution; the extension itself does not need it).- The last argument is the prompt; everything before it is forwarded to
piunchanged, so options and their values must come first. - Probe target:
PI_RESUME_PROBE_HOST/_PORT, thenPI_RESUME_PROBE_URL/MODEL_BASE_URL/PI_BASE_URL/OPENAI_BASE_URL, then your pisettings.json+models-store.json, then1.1.1.1:443. - Sessions go to
~/.local/state/pi-net-resume/sessions/(override withPI_RESUME_SESSION_DIR), never into the normal project session store — a headless run must not look like a stray file in a live session's project directory, because a cleanup-minded agent once deleted that whole directory and took its own transcript with it.
Portability
| Requirement | Needed for | Without it |
|---|---|---|
| pi (any platform) | everything | — |
nmcli (Linux/NetworkManager) |
the cheap "link is down" short-circuit | set checkNetworkManager: false; TCP probing alone still works |
notify-send |
desktop notification | silently skipped |
No host, path or provider is hardcoded, so this works on macOS and Windows in TCP-probe-only mode.
Credits
The invisible-continuation technique (a hidden custom message that starts the
turn, removed again in a context hook before the provider call) and the
shaping of the "strip trailing failed attempts" helper are adapted from
pi-invisible-continue
by @monotykamary, MIT licensed. That project
solves a different problem (deliberate continuation, via /continue); this one
uses the technique to resume automatically after an outage, and is narrower in
one respect: it strips failed attempts only on the turn it started, not on every
later request.
License
MIT
中文
网络中断后自动继续 pi 会话。
手机热点或 Wi-Fi 掉线时,provider 请求会失败,pi 按 retry.maxRetries
重试若干次(默认 3 次,退避 2s/4s/8s,约 14 秒),然后用一条错误结束并静默停住 ——
除非有人再敲键盘。你若离开键盘,或 SSH 会话随链路一起断掉,任务就卡死了。
本扩展就盯着这个状态:等网络恢复,然后在同一条会话里带着完整上下文继续。
安装
pi install npm:pi-net-resume # 从 npm 安装
pi -e npm:pi-net-resume # 临时试用一次,不改 settings
或从 git / 本地目录:
pi install git:github.com/ZYFsir/pi-net-resume@v1.0.0
pi install ./ # 在包目录的检出里执行
本包位于仓库的 pkg/pi-net-resume/
子目录;仓库根有一个小 package.json 指向它,所以上面的 git 安装命令可以直接用。
从本地检出安装时,路径要指向包目录本身。
有一项设置建议你改
扩展只在 pi 自己的重试全部耗尽之后才接手,所以 retry.maxRetries
决定"多长的抖动由 pi 自己扛"。默认 3 次(约 14 秒)对热点断网太短,改成 6
能扛约两分钟:
// ~/.pi/agent/settings.json
{
"retry": { "maxRetries": 6, "baseDelayMs": 2000 }
}
包在安装时故意不改这项。不改也能用,只是扩展会更早开始等待。
工作原理
| 步 | 钩子 | 动作 |
|---|---|---|
| 1 | agent_end |
记住"助手消息 stopReason: "error" 且错误属于连通性类" |
| 2 | agent_settled |
pi 只在再无自动重试 / 压缩重试 / 排队消息时才触发,即真的放弃了 |
| 3 | 轮询 | 先看链路状态,再对模型端点做 TCP 探测,直到能通 |
| 4 | 续跑 | 在同一会话里 pi.sendUserMessage(...),开新一轮 |
因为续跑是一条普通用户消息,断网前的全部上下文(含工具调用结果)原样保留 —— 这是"续跑",不是"重跑"。
探测目标从哪来
不写死任何 provider,取第一个非空结果:
ctx.model.baseUrl—— pi 实际要访问的端点;MODEL_BASE_URL/PI_BASE_URL/OPENAI_BASE_URL/ANTHROPIC_BASE_URL—— 给自建网关用;- 配置里的
extraProbeHosts; - 都没有(如本地
llama.cpp没有 baseUrl):只看系统链路状态。
端口按 URL 语义解析:https://… → 443,http://… → 80,显式 host:port
或 [::1]:port 按字面值使用。
与 pi-auto-resume 的关系
pi-auto-resume 是另一个扩展,
处理的是不同的中断类别:
| 中断类型 | pi-auto-resume |
pi-net-resume(本扩展) |
|---|---|---|
输出被截断(stopReason: "length") |
处理 —— 发继续提示词 | 不处理 |
| HTTP 429 / 限流 | 处理 —— 指数退避 | 永不(有意排除) |
| 套餐 / 额度耗尽 | 通知用户 | 永不(有意排除) |
| 工具调用被截断 | 处理 —— 让模型补完 | 不处理 |
| 链路断开 | 不识别 | 核心场景 |
| 如何决定重试时机 | 定时器(固定/指数延迟) | 链路真的通了:NM 状态 + 对模型端点做 TCP 探测 |
两者互补,可以同时安装。唯一可能重叠的地方是"措辞看起来像限流的网络错误", 这个边界我用双向测试钉住了:
- 消息里带硬断链特征(
ECONNRESET、ENOTFOUND、fetch failed、socket hang up等)时, 即使别处还提到429或quota,也判定为断网; - 只有
unauthorized/quota/rate limit而没有硬特征的消息一律不动 —— 对注定失败的请求续跑只会烧额度。
想同时要那个包的能力,两个都装:
pi install npm:pi-auto-resume # 截断、429、额度
pi install npm:pi-net-resume # 链路断开
安全阀
- 只对连通性类错误待命(
errorPattern); - 限流 / 欠费 / 鉴权类永不待命(
excludePattern)—— 不会替你在注定失败的请求上烧额度; stopReason: "aborted"(Ctrl+C)永不续跑;- 用户一打字立即取消;
armOnlyWhenOffline(默认true):pi 放弃时若链路正常,说明不是断网,不待命;maxAutoResumes/maxWaitMinutes上限、minSecondsBetweenResumes节流,pi 忙时不触发;/net-resume off可对单个会话关闭。
已知限制:在 pi 重试等待期按 Esc 会取消重试,而 pi 对这一点的上报与"重试耗尽"
完全相同。若此时链路仍是断的,本扩展可能对这样一个被取消的运行待命并在之后续跑。
打字或 /net-resume off 都能立即取消。
配置
mkdir -p ~/.pi/agent
cp config.example.json ~/.pi/agent/pi-net-resume.json
agent 目录默认 ~/.pi/agent,可用 PI_CODING_AGENT_DIR 覆盖。
配置文件按以下顺序查找,首个存在者胜:
$PI_NET_RESUME_CONFIG<agent dir>/pi-net-resume.json← 上面这条命令创建的位置<agent dir>/extensions/pi-net-resume/config.json(手动/旧式安装)- 扩展旁边的
config.json
/net-resume 会打印实际加载的那个文件,不用猜。
常用配置项:
| 键 | 默认 | 含义 |
|---|---|---|
continueMessage |
The network is back… continue |
用于重新启动该轮的文本(可换成任意语言) |
resumeStyle |
hybrid |
hybrid=模型看不到、你能看到一条说明;hidden=两者都看不到;visible=当成真实用户消息发出 |
armOnlyWhenOffline |
true |
只在链路确实断了时才待命 |
maxAutoResumes |
20 |
自动续跑次数上限 |
minSecondsBetweenResumes |
15 |
两次续跑之间的节流 |
maxWaitMinutes |
120 |
离线等待上限 |
probeIntervalMs |
3000 |
等待期间的轮询节奏 |
probeTimeoutMs |
4000 |
单次 TCP 探测超时 |
extraProbeHosts |
[] |
额外 host:port(IPv6 要加方括号,[::1]:443) |
checkNetworkManager |
true |
TCP 探测前先问 nmcli 链路状态 |
notify |
true |
续跑时发桌面通知 |
logFile |
~/.local/state/pi-net-resume/pi-net-resume.log |
JSON lines 证据链 |
enabled |
true |
设 false 则完全不加载 |
errorPattern / excludePattern |
见 config.example.json |
哪些错误触发 / 永不触发 |
命令
| 命令 | 作用 |
|---|---|
/net-resume |
状态:待命/等待中、已续跑次数、链路状态、探测目标、配置路径 |
/net-resume now |
链路已通时立刻续跑一次 |
/net-resume off / on |
对当前会话关闭 / 开启 |
验证
JSON lines 日志就是证据链:
tail -f ~/.local/state/pi-net-resume/pi-net-resume.log
真实断网时应依次出现:
{"event":"network_error","message":"fetch failed"}
{"event":"armed","detail":"NetworkManager reports disconnected"}
{"event":"waiting_for_network","failureAt":1789538000000}
{"event":"auto_resume","resumeCount":1,"waitedMs":41300,"detail":"api.example.com:443 reachable"}
PI_NET_RESUME_CONFIG=/path/to/config.json 可覆盖配置路径,便于测试。
无头模式
扩展只覆盖活着的 pi 会话。pi -p(print 模式)在 provider 调用失败后是退出,
所以需要包装脚本,本包内置 pi-resume-run.sh:
./pi-resume-run.sh --model <provider>/<model> "跑测试并修复失败"
它用同一个 session id 反复拉起 pi -p,并在失败且离线时改用"继续"提示词重试,
直到成功或达到上限。
- 需要
bash与python3(python3 用于 TCP 探测和解析默认探测目标; 扩展本身不需要它); - 最后一个参数是提示词,前面的选项原样转发给 pi,所以选项和它的值必须在前;
- 探测目标:
PI_RESUME_PROBE_HOST/_PORT→PI_RESUME_PROBE_URL/MODEL_BASE_URL/PI_BASE_URL/OPENAI_BASE_URL→ pi 的settings.json+models-store.json→1.1.1.1:443; - 会话写在
~/.local/state/pi-net-resume/sessions/(可用PI_RESUME_SESSION_DIR覆盖),不写进正常的项目 session 库。
可移植性
| 依赖 | 用途 | 缺失时 |
|---|---|---|
| pi(任意平台) | 全部功能 | — |
nmcli(Linux/NetworkManager) |
廉价的"链路已断"短路判断 | 设 checkNetworkManager: false,退化为纯 TCP 探测 |
notify-send |
桌面通知 | 静默跳过 |
没有硬编码的主机、路径或 provider,因此在 macOS 和 Windows 上以纯 TCP 探测模式可用。
致谢
"隐形续跑"这一手法(用一条隐藏的自定义消息启动该轮,再由 context 钩子在发送给
provider 之前移除它)以及"剥掉尾部失败尝试"这个辅助函数的形状,改编自
pi-invisible-continue
(作者 @monotykamary,MIT 许可)。
那个项目解决的是另一个问题(手动 /continue 主动继续);本项目把它用于断网后自动续跑,
并在一点上更收窄:只在它自己启动的那一轮剥掉失败尝试,而不是之后每一次请求。
许可
MIT