@zeph-to/cli 2.5.0 → 2.6.0

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/README.md CHANGED
@@ -146,6 +146,13 @@ an answerable `zeph_ask`).
146
146
  Detection is exact-match: a terminal keystroke racing a phone message
147
147
  can never false-flag. Muted projects are never flagged.
148
148
 
149
+ Agents without that hook (Cursor, Windsurf, Copilot, Cline, Aider) have one
150
+ other way in: a `zeph_ask` answer that is not a Done-like button. Their rule
151
+ files therefore keep the after-real-work `zeph_ask` — the "Entering REMOTE
152
+ without a prompt hook" preamble in `src/templates.ts` — so the phone always
153
+ has a button to tap that starts the loop. Agents with the hook drop that
154
+ obligation while at the terminal.
155
+
149
156
  `zeph cursor` has no remote-origin hook, so it never enters sticky REMOTE
150
157
  mode by itself. **Just ask for it** — one line, once per session:
151
158
 
@@ -1,16 +1,16 @@
1
- /** Cursor — written to ~/.cursor/rules/zeph.mdc (needs .mdc frontmatter). */
1
+ /** Cursor — written to ~/.cursor/rules/zeph.mdc (needs .mdc frontmatter). No prompt hook. */
2
2
  export declare const CURSOR_RULE: string;
3
- /** Windsurf — appended into ~/.codeium/windsurf/memories/global_rules.md. */
3
+ /** Windsurf — appended into ~/.codeium/windsurf/memories/global_rules.md. No prompt hook. */
4
4
  export declare const WINDSURF_RULE: string;
5
- /** Gemini CLI — appended into ~/.gemini/GEMINI.md. */
5
+ /** Gemini CLI — appended into ~/.gemini/GEMINI.md. Has the prompt hook (GEMINI_HOOKS). */
6
6
  export declare const GEMINI_RULE: string;
7
- /** Codex CLI — appended into ~/.codex/AGENTS.md. */
7
+ /** Codex CLI — appended into ~/.codex/AGENTS.md. Has the prompt hook (CODEX_HOOKS). */
8
8
  export declare const CODEX_RULE: string;
9
- /** GitHub Copilot CLI — written to ~/.copilot/instructions/zeph.instructions.md. */
9
+ /** GitHub Copilot CLI — written to ~/.copilot/instructions/zeph.instructions.md. No prompt hook. */
10
10
  export declare const COPILOT_RULE: string;
11
- /** Cline — written to ~/.cline/rules/zeph.md (no Stop hook). */
11
+ /** Cline — written to ~/.cline/rules/zeph.md (no Stop hook, no prompt hook). */
12
12
  export declare const CLINE_RULE: string;
13
- /** Aider — written to a standalone conventions file, loaded via .aider.conf.yml `read:`. */
13
+ /** Aider — standalone conventions file via .aider.conf.yml `read:` (no Stop hook, no prompt hook). */
14
14
  export declare const AIDER_RULE: string;
15
15
  export declare const CURSOR_HOOKS: string;
16
16
  export declare const WINDSURF_HOOKS: string;
@@ -1 +1 @@
1
- {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAsGA,6EAA6E;AAC7E,eAAO,MAAM,WAAW,QAItB,CAAC;AAEH,6EAA6E;AAC7E,eAAO,MAAM,aAAa,QAAyE,CAAC;AAEpG,sDAAsD;AACtD,eAAO,MAAM,WAAW,QAAyE,CAAC;AAElG,oDAAoD;AACpD,eAAO,MAAM,UAAU,QAAyE,CAAC;AAEjG,oFAAoF;AACpF,eAAO,MAAM,YAAY,QAAyE,CAAC;AAEnG,gEAAgE;AAChE,eAAO,MAAM,UAAU,QAAkE,CAAC;AAE1F,4FAA4F;AAC5F,eAAO,MAAM,UAAU,QAAkE,CAAC;AAI1F,eAAO,MAAM,YAAY,QAKd,CAAC;AAEZ,eAAO,MAAM,cAAc,QAOhB,CAAC;AAEZ,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;CAuBxB,CAAC;AAYF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;CASvB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,OAAO,KAAG,OAUhD,CAAC;AAEF,eAAO,MAAM,aAAa,QASf,CAAC;AASZ,eAAO,MAAM,eAAe,oFAA+E,CAAC;AAC5G,eAAO,MAAM,aAAa,sBAAsB,CAAC;AAQjD;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,EAAE,MAAM,MAAM,KAAG,MAWnE,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,KAAG,MAOrD,CAAC"}
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AA0IA,6FAA6F;AAC7F,eAAO,MAAM,WAAW,QAKtB,CAAC;AAEH,6FAA6F;AAC7F,eAAO,MAAM,aAAa,QAIxB,CAAC;AAEH,0FAA0F;AAC1F,eAAO,MAAM,WAAW,QAAyE,CAAC;AAElG,uFAAuF;AACvF,eAAO,MAAM,UAAU,QAAyE,CAAC;AAEjG,oGAAoG;AACpG,eAAO,MAAM,YAAY,QAIvB,CAAC;AAEH,gFAAgF;AAChF,eAAO,MAAM,UAAU,QAIrB,CAAC;AAEH,sGAAsG;AACtG,eAAO,MAAM,UAAU,QAIrB,CAAC;AAIH,eAAO,MAAM,YAAY,QAKd,CAAC;AAEZ,eAAO,MAAM,cAAc,QAOhB,CAAC;AAEZ,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;CAuBxB,CAAC;AAYF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;CASvB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,OAAO,KAAG,OAUhD,CAAC;AAEF,eAAO,MAAM,aAAa,QASf,CAAC;AASZ,eAAO,MAAM,eAAe,oFAA+E,CAAC;AAC5G,eAAO,MAAM,aAAa,sBAAsB,CAAC;AAQjD;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,EAAE,MAAM,MAAM,KAAG,MAWnE,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,KAAG,MAOrD,CAAC"}
package/dist/templates.js CHANGED
@@ -15,6 +15,13 @@
15
15
  // The Ask-Loop / sticky-REMOTE / question-mandate rules are identical for
16
16
  // all of them — that is the whole point of the shared generated core.
17
17
  //
18
+ // One more axis, and it decides who can enter REMOTE at all: the core's
19
+ // Rules 3/4/10/11 are REMOTE-scoped, and REMOTE is entered by a prompt-submit
20
+ // hook note (a phone message) or by a `zeph_ask` answer. Only Gemini and Codex
21
+ // have that hook (remoteHookCmd below). Every other agent's only door is the
22
+ // ask itself — so for them the NORMAL branch must still send one after real
23
+ // work, or the phone loop can never begin. That is REMOTE_ENTRY_NO_HOOK.
24
+ //
18
25
  // Keeping this in one place means a rule change lands everywhere at once
19
26
  // and the agents can't drift apart.
20
27
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -73,9 +80,37 @@ you. After meaningful task completion (build, test, deploy, large
73
80
  refactor, multi-file changes) call zeph_notify. Skip it for trivial
74
81
  operations (file reads, simple searches). Set priority "high" for
75
82
  errors/blockers.`;
76
- /** Assemble a full rule document from optional frontmatter + preamble + core. */
83
+ // REMOTE-entry preamble agents with NO prompt-submit hook (Cursor, Windsurf,
84
+ // Copilot, Cline, Aider). The shared core scopes Rules 3/4/10/11 to REMOTE
85
+ // and tells a NORMAL session it owes no `zeph_ask`. That is right where a
86
+ // hook can announce the phone message that starts REMOTE; here nothing can,
87
+ // and the only remaining entry is a `zeph_ask` answer that is not Done-like.
88
+ // So this preamble restores the one obligation the core dropped: after
89
+ // substantial work, end with `zeph_ask` — with `actions` — so the user has a
90
+ // button to tap that puts them in the driver's seat. Everything else the core
91
+ // says about NORMAL still holds (no ask on trivial turns, questions may go
92
+ // to the local picker, no `zeph_ask` just to mark a turn finished).
93
+ //
94
+ // This overrides the core's Rule 4 NORMAL clause for these agents. It is a
95
+ // per-agent preamble, not a fork of the core, for the reason the core's
96
+ // header gives: the rule text must stay one thing.
97
+ const REMOTE_ENTRY_NO_HOOK = `## Entering REMOTE without a prompt hook
98
+
99
+ This agent has no prompt-submit hook, so nothing can tell you when a
100
+ message arrived from the user's phone. The ONLY way this session enters
101
+ REMOTE is a \`zeph_ask\` answer that is not a Done-like button. So — and
102
+ this overrides the "In NORMAL, end with nothing" clause of Rule 4 below —
103
+ **after substantial work in NORMAL, end the response with \`zeph_ask\`**:
104
+ 2–4 \`actions\` carrying the next-step candidates plus a Done-like
105
+ \`fallback\`, \`timeout\` 300–600 s. "Substantial" = file changes, commits,
106
+ builds, tests, deploys, destructive ops, milestone completions. Skip it on
107
+ trivial turns (read-only exploration, a mid-step in an approved plan, a
108
+ typo-sized fix). Once the answer reports \`zephState: "REMOTE"\`, Rule 9
109
+ takes over.`;
110
+ /** Assemble a full rule document from optional frontmatter + preambles + core. */
77
111
  const buildRule = (opts) => {
78
112
  const fm = opts.frontmatter ? `${opts.frontmatter}\n\n` : '';
113
+ const entry = opts.remoteEntry ? `${opts.remoteEntry}\n\n` : '';
79
114
  return `${fm}# Zeph — Remote-Control Rules
80
115
 
81
116
  Zeph lets the user steer this session from their phone via zeph_ask
@@ -84,7 +119,7 @@ the user.
84
119
 
85
120
  ${opts.notify}
86
121
 
87
- ${opts.core}
122
+ ${entry}${opts.core}
88
123
  `;
89
124
  };
90
125
  // ── Per-agent rule documents ─────────────────────────────────────
@@ -92,24 +127,41 @@ ${opts.core}
92
127
  // The two generated cores are identical today; the split exists so a rule
93
128
  // that only applies to one audience (e.g. Push Signal, once hook-driven
94
129
  // agents' hooks process markers) is a one-line manifest change upstream.
95
- /** Cursor — written to ~/.cursor/rules/zeph.mdc (needs .mdc frontmatter). */
130
+ /** Cursor — written to ~/.cursor/rules/zeph.mdc (needs .mdc frontmatter). No prompt hook. */
96
131
  exports.CURSOR_RULE = buildRule({
97
132
  frontmatter: '---\ndescription: "Zeph remote-control rules"\nalwaysApply: true\n---',
98
133
  notify: HOOK_DRIVEN_NOTIFY,
134
+ remoteEntry: REMOTE_ENTRY_NO_HOOK,
135
+ core: zeph_core_generated_js_1.ZEPH_CORE_HOOK_DRIVEN,
136
+ });
137
+ /** Windsurf — appended into ~/.codeium/windsurf/memories/global_rules.md. No prompt hook. */
138
+ exports.WINDSURF_RULE = buildRule({
139
+ notify: HOOK_DRIVEN_NOTIFY,
140
+ remoteEntry: REMOTE_ENTRY_NO_HOOK,
99
141
  core: zeph_core_generated_js_1.ZEPH_CORE_HOOK_DRIVEN,
100
142
  });
101
- /** Windsurf — appended into ~/.codeium/windsurf/memories/global_rules.md. */
102
- exports.WINDSURF_RULE = buildRule({ notify: HOOK_DRIVEN_NOTIFY, core: zeph_core_generated_js_1.ZEPH_CORE_HOOK_DRIVEN });
103
- /** Gemini CLI — appended into ~/.gemini/GEMINI.md. */
143
+ /** Gemini CLI — appended into ~/.gemini/GEMINI.md. Has the prompt hook (GEMINI_HOOKS). */
104
144
  exports.GEMINI_RULE = buildRule({ notify: HOOK_DRIVEN_NOTIFY, core: zeph_core_generated_js_1.ZEPH_CORE_HOOK_DRIVEN });
105
- /** Codex CLI — appended into ~/.codex/AGENTS.md. */
145
+ /** Codex CLI — appended into ~/.codex/AGENTS.md. Has the prompt hook (CODEX_HOOKS). */
106
146
  exports.CODEX_RULE = buildRule({ notify: HOOK_DRIVEN_NOTIFY, core: zeph_core_generated_js_1.ZEPH_CORE_HOOK_DRIVEN });
107
- /** GitHub Copilot CLI — written to ~/.copilot/instructions/zeph.instructions.md. */
108
- exports.COPILOT_RULE = buildRule({ notify: HOOK_DRIVEN_NOTIFY, core: zeph_core_generated_js_1.ZEPH_CORE_HOOK_DRIVEN });
109
- /** Cline — written to ~/.cline/rules/zeph.md (no Stop hook). */
110
- exports.CLINE_RULE = buildRule({ notify: MANUAL_NOTIFY, core: zeph_core_generated_js_1.ZEPH_CORE_RULE_ONLY });
111
- /** Aider — written to a standalone conventions file, loaded via .aider.conf.yml `read:`. */
112
- exports.AIDER_RULE = buildRule({ notify: MANUAL_NOTIFY, core: zeph_core_generated_js_1.ZEPH_CORE_RULE_ONLY });
147
+ /** GitHub Copilot CLI — written to ~/.copilot/instructions/zeph.instructions.md. No prompt hook. */
148
+ exports.COPILOT_RULE = buildRule({
149
+ notify: HOOK_DRIVEN_NOTIFY,
150
+ remoteEntry: REMOTE_ENTRY_NO_HOOK,
151
+ core: zeph_core_generated_js_1.ZEPH_CORE_HOOK_DRIVEN,
152
+ });
153
+ /** Cline — written to ~/.cline/rules/zeph.md (no Stop hook, no prompt hook). */
154
+ exports.CLINE_RULE = buildRule({
155
+ notify: MANUAL_NOTIFY,
156
+ remoteEntry: REMOTE_ENTRY_NO_HOOK,
157
+ core: zeph_core_generated_js_1.ZEPH_CORE_RULE_ONLY,
158
+ });
159
+ /** Aider — standalone conventions file via .aider.conf.yml `read:` (no Stop hook, no prompt hook). */
160
+ exports.AIDER_RULE = buildRule({
161
+ notify: MANUAL_NOTIFY,
162
+ remoteEntry: REMOTE_ENTRY_NO_HOOK,
163
+ core: zeph_core_generated_js_1.ZEPH_CORE_RULE_ONLY,
164
+ });
113
165
  // ── Hook configs ─────────────────────────────────────────────────
114
166
  exports.CURSOR_HOOKS = JSON.stringify({
115
167
  version: 1,
@@ -1,7 +1,7 @@
1
1
  /** sha256 over the plugin manifest + extracted rule text at generation time. */
2
- export declare const ZEPH_CORE_SOURCE_HASH = "6052db17d759b487af5afcba57059b0b605cb2d50c992a1368d5b75d1467e4af";
2
+ export declare const ZEPH_CORE_SOURCE_HASH = "1ab10b4a3d38dd8ffdb924056b775c16883579724edbd0ab5f25fe0a30191c02";
3
3
  /** Shared rule core for agents with a Stop-equivalent hook (Cursor, Windsurf, Gemini, Codex, Copilot). */
4
- export declare const ZEPH_CORE_HOOK_DRIVEN = "### When zeph_ask is MANDATORY\n\n3. **NEVER end a response with a plain-text question.** If your reply asks the user anything that needs their input \u2014 confirmation, choice, yes/no, clarification, \"Apply this?\", \"Proceed?\", \"Which option?\" \u2014 the FINAL tool call MUST be `zeph_ask`. A \"?\" written in your reply is invisible to a user on their phone. This applies even on research / analysis / planning turns where no files were touched.\n\n Anti-pattern (wrong):\n ```\n \"...build/commit on hold. Apply A?\"\n ```\n Correct:\n ```\n zeph_ask({\n title: \"Apply solution A?\",\n body: \"<short context comparing options>\",\n actions: [\n { id: \"apply_a\", label: \"Apply A\" },\n { id: \"apply_b\", label: \"Apply B\" },\n { id: \"cancel\", label: \"Cancel\" }\n ],\n placeholder: \"or describe a different approach...\",\n fallback: \"cancel\"\n })\n ```\n\n### When zeph_ask is the DEFAULT (substantial work)\n\n4. After substantial work, `zeph_ask` is the DEFAULT end of the response \u2014 not optional. \"Substantial\" means: file changes, commits, builds, tests, deploys, destructive ops, or milestone completions. When unsure, treat the work as substantial \u2014 do not try to guess what the user would find \"reasonable\" to confirm.\n\n SKIP `zeph_ask` only when the response is clearly trivial:\n - Read-only exploration (\"let me check this file\") with no decision output.\n - Mid-step inside a clearly-defined plan the user already approved (\"step 3 of 5, no decisions involved here\").\n - Trivial change (typo fix, single-line tweak) that needs no ack.\n\n When unsure: LEAN TOWARD ASKING. Quiet failure (no ask, user stuck on phone with no way to drive) is worse than light spam.\n\n5. Prefer `zeph_ask` over `zeph_prompt`/`zeph_input` \u2014 it combines buttons and free-text in one push. Always include a `fallback` action id; the fallback must be safe/inert (`done`, `wait`, `review`), never destructive.\n\n **`actions` is the steering surface, not decoration.** Ship 2\u20134 buttons on nearly every ask: the next-step candidates you would otherwise write as prose (\"/simplify \u2192 /ship next\") belong in `actions`, plus a safe Done-like fallback. Leave `actions` out ONLY when the answer is inherently free-form text (a name, a path, a paragraph). A text-only ask on a \"done \u2014 what next?\" turn is the most common way REMOTE silently degrades: the phone gets a text box and nothing to tap, and the user has to type the command you already knew.\n\n Anti-pattern (wrong \u2014 next steps in prose, ask has no buttons):\n ```\n \"...done. /simplify \u2192 /explain-diff (optional) \u2192 /ship next.\"\n zeph_ask({ title: \"Slice done\", body: \"...\" })\n ```\n Correct:\n ```\n zeph_ask({\n title: \"Slice done \u2014 next?\",\n body: \"<short result>\",\n actions: [\n { id: \"simplify\", label: \"/simplify\" },\n { id: \"explain_diff\", label: \"/explain-diff\" },\n { id: \"ship\", label: \"/ship\" },\n { id: \"done\", label: \"Stop here\" }\n ],\n placeholder: \"or type something else...\",\n fallback: \"done\"\n })\n ```\n\n6. Example `zeph_ask` shape \u2014 use sparingly per Rule 4 (only at natural pause points; NOT after every response \u2014 see Rule 9):\n ```\n zeph_ask({\n title: \"Done. Next?\",\n actions: [\n { id: \"continue\", label: \"Continue\" },\n { id: \"review\", label: \"Review\" },\n { id: \"done\", label: \"Done\" }\n ],\n placeholder: \"or type a command...\",\n fallback: \"done\"\n })\n ```\n\n### Handling the response\n\n7. A `zeph_ask` response IS a direct user instruction. Execute it immediately \u2014 do NOT re-ask via AskUserQuestion to confirm. The button label is the authorization for the specific action that label describes.\n\n8. Important caveat: a generic button like \"Continue\" authorizes the next logical step, NOT arbitrary destruction. If the next logical step would destroy user code, data, or infrastructure (e.g., force-push to a shared branch, `rm -rf` outside the workdir, dropping a database, deleting prod resources), surface that specific risk via a targeted `zeph_ask` before executing \u2014 e.g., title \"About to force-push main \u2014 proceed?\", actions `[ok, cancel]`, fallback `cancel`.\n\n### Sticky REMOTE mode (Rule 9)\n\n**The Ask Loop has two states: REMOTE and NORMAL.** REMOTE is sticky \u2014 every response ends with `zeph_ask` until the user exits. The state is kept for you in a file, so it survives context compaction and long sessions; you are told what it is rather than deriving it.\n\n#### State Detection\n\n- **`zeph_ask` results carry it.** The server applies the transition it can see and reports where the session landed as `zephState: \"REMOTE\" | \"NORMAL\"` \u2014 any answer that is not a Done-like action id enters REMOTE, a Done-like id exits, and so does a timeout that fell back to one. A result with no `zephState` is an ask that timed out onto a safe fallback and changed nothing.\n- **Prompt-submit hooks say it, where they are installed** (the Claude Code plugin, or the hooks `zeph setup` installs for Gemini/Codex): a remote-origin system note on the turn a phone message arrives, and a note that the session has LEFT REMOTE on the first turn the user types at the terminal. A prompt with no phone marker came from the user's own keyboard \u2014 a phone answer to a `zeph_ask` comes back as a `tool_result` and never reaches a prompt hook \u2014 so the user is demonstrably back, and staying in REMOTE would answer the terminal with a phone loop. Re-entry costs them one message from the phone.\n- **Neither one present \u2192 NORMAL.**\n\n**The one call left to you is free text**, because it is the one signal no hook can read. The server cannot tell \"run the tests\" from \"thanks, that's it\". When the user's typed answer clearly closes the loop \u2014 an obvious wrap-up, or `done`/`stop`/`exit` as a standalone word (not a substring: \"redo\" is not \"done\") \u2014 flip to NORMAL from that response on, don't send `zeph_ask` on it, and emit `<!-- zeph: exit -->` once so the hooks agree with you. Your own flip is what ends the loop; the marker is how you tell a hook that cannot read your mind. Only the Claude Code plugin's Stop hook consumes it today \u2014 elsewhere it is inert and harmless, and the session still leaves REMOTE on a Done-like button, on the next prompt the user types at the terminal, or when the state expires. The marker is separate from the Push Signal markers (`skip`/`push`/`high`), which steer notifications and say nothing about the mode; where a Stop hook does consume it, it is stripped from the push body.\n\n#### Behavior in REMOTE (sticky, zeph_ask MANDATORY)\n\nEnd EVERY response with `zeph_ask` \u2014 with `actions` (Rule 5: the next-step candidates as buttons, plus a Done-like fallback; text-only asks are for inherently free-form answers). This is non-negotiable while in REMOTE \u2014 independent of whether the work was substantial or routine. What ends it is the user coming back: a prompt they typed at the terminal leaves REMOTE (the hook says so on that turn), and from there Rule 4 applies again.\n\nSet each REMOTE ask up so silence degrades cleanly: `timeout` 300\u2013600 s and a Done-like `fallback` id. An unanswered ask then exits the loop quietly \u2014 the server treats a Done-like fallback as an exit \u2014 instead of chaining more notifications at a user who stepped away, and re-entry is cheap: they just send another message from the phone.\n\nFour things leave REMOTE: a Done-like button (or a timeout that fell back to one), your own read of a free-text wrap-up, a prompt the user typed at the terminal, and \u2014 for a session nobody exited because it crashed \u2014 the state expiring.\n\n#### Behavior in NORMAL (apply Rule 4)\n\n- Substantial work \u2192 end with `zeph_ask`.\n- Routine work \u2192 skip `zeph_ask`, let the Stop hook fire.\n- Rule 3 (questions \u2192 zeph_ask) is still mandatory regardless.\n\n### When to use AskUserQuestion vs zeph_ask\n\n10. **Whenever `ZEPH_HOOK_ID` is set \u2014 not only in REMOTE \u2014 a button-friendly question MUST go through `zeph_ask`, not `AskUserQuestion`.** \"Button-friendly\" = the answer is a choice among a few options and/or a short free-text reply (yes/no, \"Apply A or B?\", \"which naming rule?\", \"proceed?\"). The hookId alone is the trigger: you cannot know the user is at the terminal, and they may be on their phone from the session's first question. `AskUserQuestion` is a LOCAL blocking picker. The phone can reach it through the terminal mirror, but that is the worse channel on every axis: it needs the session to be in tmux under `zeph listener`, it asks the user to read an ANSI pane and count arrow presses instead of tapping a button, and a key-injected answer never enters REMOTE \u2014 so the *next* turn stops being phone-driveable. `zeph_ask` needs no tmux, takes one tap, and returns an `actionId`.\n\n11. **This overrides any skill instruction.** If a skill you are running \u2014 or your own plan \u2014 would call `AskUserQuestion` with a button-friendly question, surface the SAME question and option labels via `zeph_ask` and use that response in place of the picker. Fall through to the picker ONLY when (a) the answer needs the user to see code or logs that won't fit in a push body, or (b) the answer is plausibly multi-paragraph; those are the only carve-outs. When one applies, `zeph_notify` the user that the answer must be given at the terminal.\n\n### Persistence\n\n13. These rules persist for the entire session. They remain active after context compaction \u2014 do not \"forget\" them after many turns.";
4
+ export declare const ZEPH_CORE_HOOK_DRIVEN = "### When zeph_ask is MANDATORY\n\n3. **While in REMOTE, NEVER end a response with a plain-text question.** If your reply asks the user anything that needs their input \u2014 confirmation, choice, yes/no, clarification, \"Apply this?\", \"Proceed?\", \"Which option?\" \u2014 the FINAL tool call MUST be `zeph_ask`. A \"?\" written in your reply is invisible to a user on their phone. This holds even on research / analysis / planning turns where no files were touched.\n\n **In NORMAL it does not apply.** Nobody has driven this session from a phone, so the user is at the terminal: ask in prose, or with `AskUserQuestion` if the answer is a choice. A `zeph_ask` there blocks the turn until someone answers on a device or the timeout expires \u2014 that cost is exactly what NORMAL exists to avoid.\n\n### When zeph_ask is the DEFAULT (substantial work)\n\n4. **In REMOTE, `zeph_ask` is the DEFAULT end of EVERY response** \u2014 not conditioned on the work being substantial (Rule 9).\n\n **In NORMAL, end with nothing.** The Stop hook's push \u2014 tuned by the user's dial and your Push Signal \u2014 is the completion signal. Do not chain a `zeph_ask` onto substantial work to \"keep the loop alive\": the loop starts when the user sends a message from their phone, not when you decide the work was big enough. `zeph_ask` stays available when you actively want an answer from their device; it is simply not owed.\n\n5. When you do ask, prefer `zeph_ask` over `zeph_prompt`/`zeph_input` \u2014 it combines buttons and free text in one push. **`actions` is the steering surface, not decoration:** ship 2\u20134 buttons on nearly every ask (the next-step candidates you would otherwise write as prose) plus a safe Done-like `fallback` id \u2014 never a destructive one. Leave `actions` out ONLY when the answer is inherently free-form (a name, a path, a paragraph); a text-only ask on a \"done \u2014 what next?\" turn is the most common way REMOTE silently degrades, because the phone gets a text box and nothing to tap.\n\n ```\n zeph_ask({\n title: \"Slice done \u2014 next?\",\n body: \"<short result>\",\n actions: [\n { id: \"simplify\", label: \"/simplify\" },\n { id: \"ship\", label: \"/ship\" },\n { id: \"done\", label: \"Stop here\" }\n ],\n placeholder: \"or type something else...\",\n fallback: \"done\"\n })\n ```\n\n6. Rule 9 says when to send one. Never send an ask just to mark a turn finished.\n\n### Handling the response\n\n7. A `zeph_ask` response IS a direct user instruction. Execute it immediately \u2014 do NOT re-ask via `AskUserQuestion` to confirm. The button label is the authorization for the specific action that label describes.\n\n8. Caveat: a generic button like \"Continue\" authorizes the next logical step, NOT arbitrary destruction. If that step would destroy user code, data, or infrastructure (force-push to a shared branch, `rm -rf` outside the workdir, dropping a database, deleting prod resources), surface that specific risk in a targeted `zeph_ask` first \u2014 title \"About to force-push main \u2014 proceed?\", actions `[ok, cancel]`, fallback `cancel`.\n\n### Sticky REMOTE mode (Rule 9)\n\n9. **The Ask Loop has two states: REMOTE and NORMAL.** REMOTE is sticky \u2014 every response ends with `zeph_ask` until the user exits. The state is kept for you in a file, so it survives context compaction and long sessions; you are told what it is rather than deriving it.\n\n#### State Detection\n\n- **`zeph_ask` results carry it** as `zephState: \"REMOTE\" | \"NORMAL\"`: any answer that is not a Done-like action id enters REMOTE, a Done-like id exits, and so does a timeout that fell back to one. A result with no `zephState` is an ask that timed out onto a safe fallback and changed nothing.\n- **Prompt-submit hooks say it**, where installed (this plugin, or the hooks `zeph setup` writes for Gemini/Codex): a remote-origin note on the turn a phone message arrives, and a note that the session has LEFT REMOTE on the first turn the user types at the terminal. A phone answer to a `zeph_ask` comes back as a `tool_result` and never reaches a prompt hook, so a prompt with no phone marker is demonstrably the user's own keyboard \u2014 staying in REMOTE would answer the terminal with a phone loop, and re-entry costs them one message from the phone.\n- **Neither one present \u2192 NORMAL.**\n\n**The one call left to you is free text**, because it is the one signal no hook can read: the server cannot tell \"run the tests\" from \"thanks, that's it\". When the user's typed answer clearly closes the loop \u2014 an obvious wrap-up, or `done`/`stop`/`exit` as a standalone word (not a substring: \"redo\" is not \"done\") \u2014 flip to NORMAL from that response on, don't send `zeph_ask` on it, and emit `<!-- zeph: exit -->` once so the hooks agree with you. Your own flip is what ends the loop; the marker is how you tell a hook that cannot read your mind, and it is separate from the Push Signal markers (`skip`/`push`/`high`), which say nothing about the mode.\n\n#### Behavior in REMOTE (sticky, zeph_ask MANDATORY)\n\nEnd EVERY response with `zeph_ask`, carrying 2\u20134 `actions` (the next-step candidates as buttons) plus a Done-like `fallback` \u2014 never a destructive one, since an unanswered ask resolves to it \u2014 a text-only ask is only for inherently free-form answers. Non-negotiable while in REMOTE, independent of whether the work was substantial or routine.\n\nSet `timeout` 300\u2013600 s so silence degrades cleanly: an unanswered ask exits the loop quietly \u2014 the server treats a Done-like fallback as an exit \u2014 instead of chaining more notifications at a user who stepped away.\n\nFour things leave REMOTE: a Done-like button (or a timeout that fell back to one), your own read of a free-text wrap-up, a prompt the user typed at the terminal, and \u2014 for a session nobody exited because it crashed \u2014 the state expiring.\n\n#### Behavior in NORMAL (no zeph_ask is owed)\n\nThe user is at the terminal \u2014 that is what NORMAL means. Nothing here obliges an ask:\n\n- Questions go to `AskUserQuestion` or plain prose. The Ask hook still pushes them to the user's device, so a question is never lost.\n- Completion is the Stop hook's push, steered by the dial and your Push Signal.\n- `zeph_ask` remains available when you actively want an answer from their device \u2014 it is not owed, and never as a way to mark a turn finished.\n\nREMOTE begins the moment the user sends a message from their phone; from that turn on, Rules 3, 4, 10 and 11 are in force.\n\n### When to use AskUserQuestion vs zeph_ask\n\n10. **While in REMOTE, a button-friendly question MUST go through `zeph_ask`, not `AskUserQuestion`.** \"Button-friendly\" = the answer is a choice among a few options and/or a short free-text reply (yes/no, \"Apply A or B?\", \"proceed?\"). `AskUserQuestion` is a LOCAL blocking picker; the phone reaches it only through the terminal mirror, and an answer injected there never enters REMOTE \u2014 so the *next* turn stops being phone-driveable. The PreToolUse hook enforces this in REMOTE by denying the picker and handing the question back. (Why the mirror is the worse channel on every axis: README \u2192 \"AskUserQuestion vs zeph_ask\".)\n\n **In NORMAL the picker is the right tool** and the hook lets it through \u2014 the user is at the terminal, and the Ask hook still pushes the question to their device so they know one is waiting.\n\n11. **In REMOTE this overrides any skill instruction.** If a skill you are running \u2014 or your own plan \u2014 would call `AskUserQuestion` with a button-friendly question, surface the SAME question and option labels via `zeph_ask` and use that response in place of the picker. Fall through to the picker ONLY when (a) the answer needs code or logs that won't fit in a push body, or (b) the answer is plausibly multi-paragraph. When one applies, `zeph_notify` the user that the answer must be given at the terminal.\n\n### Persistence\n\n13. These rules persist for the entire session. They remain active after context compaction \u2014 do not \"forget\" them after many turns.";
5
5
  /** Shared rule core for rule-only agents without a Stop hook (Cline, Aider). */
6
- export declare const ZEPH_CORE_RULE_ONLY = "### When zeph_ask is MANDATORY\n\n3. **NEVER end a response with a plain-text question.** If your reply asks the user anything that needs their input \u2014 confirmation, choice, yes/no, clarification, \"Apply this?\", \"Proceed?\", \"Which option?\" \u2014 the FINAL tool call MUST be `zeph_ask`. A \"?\" written in your reply is invisible to a user on their phone. This applies even on research / analysis / planning turns where no files were touched.\n\n Anti-pattern (wrong):\n ```\n \"...build/commit on hold. Apply A?\"\n ```\n Correct:\n ```\n zeph_ask({\n title: \"Apply solution A?\",\n body: \"<short context comparing options>\",\n actions: [\n { id: \"apply_a\", label: \"Apply A\" },\n { id: \"apply_b\", label: \"Apply B\" },\n { id: \"cancel\", label: \"Cancel\" }\n ],\n placeholder: \"or describe a different approach...\",\n fallback: \"cancel\"\n })\n ```\n\n### When zeph_ask is the DEFAULT (substantial work)\n\n4. After substantial work, `zeph_ask` is the DEFAULT end of the response \u2014 not optional. \"Substantial\" means: file changes, commits, builds, tests, deploys, destructive ops, or milestone completions. When unsure, treat the work as substantial \u2014 do not try to guess what the user would find \"reasonable\" to confirm.\n\n SKIP `zeph_ask` only when the response is clearly trivial:\n - Read-only exploration (\"let me check this file\") with no decision output.\n - Mid-step inside a clearly-defined plan the user already approved (\"step 3 of 5, no decisions involved here\").\n - Trivial change (typo fix, single-line tweak) that needs no ack.\n\n When unsure: LEAN TOWARD ASKING. Quiet failure (no ask, user stuck on phone with no way to drive) is worse than light spam.\n\n5. Prefer `zeph_ask` over `zeph_prompt`/`zeph_input` \u2014 it combines buttons and free-text in one push. Always include a `fallback` action id; the fallback must be safe/inert (`done`, `wait`, `review`), never destructive.\n\n **`actions` is the steering surface, not decoration.** Ship 2\u20134 buttons on nearly every ask: the next-step candidates you would otherwise write as prose (\"/simplify \u2192 /ship next\") belong in `actions`, plus a safe Done-like fallback. Leave `actions` out ONLY when the answer is inherently free-form text (a name, a path, a paragraph). A text-only ask on a \"done \u2014 what next?\" turn is the most common way REMOTE silently degrades: the phone gets a text box and nothing to tap, and the user has to type the command you already knew.\n\n Anti-pattern (wrong \u2014 next steps in prose, ask has no buttons):\n ```\n \"...done. /simplify \u2192 /explain-diff (optional) \u2192 /ship next.\"\n zeph_ask({ title: \"Slice done\", body: \"...\" })\n ```\n Correct:\n ```\n zeph_ask({\n title: \"Slice done \u2014 next?\",\n body: \"<short result>\",\n actions: [\n { id: \"simplify\", label: \"/simplify\" },\n { id: \"explain_diff\", label: \"/explain-diff\" },\n { id: \"ship\", label: \"/ship\" },\n { id: \"done\", label: \"Stop here\" }\n ],\n placeholder: \"or type something else...\",\n fallback: \"done\"\n })\n ```\n\n6. Example `zeph_ask` shape \u2014 use sparingly per Rule 4 (only at natural pause points; NOT after every response \u2014 see Rule 9):\n ```\n zeph_ask({\n title: \"Done. Next?\",\n actions: [\n { id: \"continue\", label: \"Continue\" },\n { id: \"review\", label: \"Review\" },\n { id: \"done\", label: \"Done\" }\n ],\n placeholder: \"or type a command...\",\n fallback: \"done\"\n })\n ```\n\n### Handling the response\n\n7. A `zeph_ask` response IS a direct user instruction. Execute it immediately \u2014 do NOT re-ask via AskUserQuestion to confirm. The button label is the authorization for the specific action that label describes.\n\n8. Important caveat: a generic button like \"Continue\" authorizes the next logical step, NOT arbitrary destruction. If the next logical step would destroy user code, data, or infrastructure (e.g., force-push to a shared branch, `rm -rf` outside the workdir, dropping a database, deleting prod resources), surface that specific risk via a targeted `zeph_ask` before executing \u2014 e.g., title \"About to force-push main \u2014 proceed?\", actions `[ok, cancel]`, fallback `cancel`.\n\n### Sticky REMOTE mode (Rule 9)\n\n**The Ask Loop has two states: REMOTE and NORMAL.** REMOTE is sticky \u2014 every response ends with `zeph_ask` until the user exits. The state is kept for you in a file, so it survives context compaction and long sessions; you are told what it is rather than deriving it.\n\n#### State Detection\n\n- **`zeph_ask` results carry it.** The server applies the transition it can see and reports where the session landed as `zephState: \"REMOTE\" | \"NORMAL\"` \u2014 any answer that is not a Done-like action id enters REMOTE, a Done-like id exits, and so does a timeout that fell back to one. A result with no `zephState` is an ask that timed out onto a safe fallback and changed nothing.\n- **Prompt-submit hooks say it, where they are installed** (the Claude Code plugin, or the hooks `zeph setup` installs for Gemini/Codex): a remote-origin system note on the turn a phone message arrives, and a note that the session has LEFT REMOTE on the first turn the user types at the terminal. A prompt with no phone marker came from the user's own keyboard \u2014 a phone answer to a `zeph_ask` comes back as a `tool_result` and never reaches a prompt hook \u2014 so the user is demonstrably back, and staying in REMOTE would answer the terminal with a phone loop. Re-entry costs them one message from the phone.\n- **Neither one present \u2192 NORMAL.**\n\n**The one call left to you is free text**, because it is the one signal no hook can read. The server cannot tell \"run the tests\" from \"thanks, that's it\". When the user's typed answer clearly closes the loop \u2014 an obvious wrap-up, or `done`/`stop`/`exit` as a standalone word (not a substring: \"redo\" is not \"done\") \u2014 flip to NORMAL from that response on, don't send `zeph_ask` on it, and emit `<!-- zeph: exit -->` once so the hooks agree with you. Your own flip is what ends the loop; the marker is how you tell a hook that cannot read your mind. Only the Claude Code plugin's Stop hook consumes it today \u2014 elsewhere it is inert and harmless, and the session still leaves REMOTE on a Done-like button, on the next prompt the user types at the terminal, or when the state expires. The marker is separate from the Push Signal markers (`skip`/`push`/`high`), which steer notifications and say nothing about the mode; where a Stop hook does consume it, it is stripped from the push body.\n\n#### Behavior in REMOTE (sticky, zeph_ask MANDATORY)\n\nEnd EVERY response with `zeph_ask` \u2014 with `actions` (Rule 5: the next-step candidates as buttons, plus a Done-like fallback; text-only asks are for inherently free-form answers). This is non-negotiable while in REMOTE \u2014 independent of whether the work was substantial or routine. What ends it is the user coming back: a prompt they typed at the terminal leaves REMOTE (the hook says so on that turn), and from there Rule 4 applies again.\n\nSet each REMOTE ask up so silence degrades cleanly: `timeout` 300\u2013600 s and a Done-like `fallback` id. An unanswered ask then exits the loop quietly \u2014 the server treats a Done-like fallback as an exit \u2014 instead of chaining more notifications at a user who stepped away, and re-entry is cheap: they just send another message from the phone.\n\nFour things leave REMOTE: a Done-like button (or a timeout that fell back to one), your own read of a free-text wrap-up, a prompt the user typed at the terminal, and \u2014 for a session nobody exited because it crashed \u2014 the state expiring.\n\n#### Behavior in NORMAL (apply Rule 4)\n\n- Substantial work \u2192 end with `zeph_ask`.\n- Routine work \u2192 skip `zeph_ask`, let the Stop hook fire.\n- Rule 3 (questions \u2192 zeph_ask) is still mandatory regardless.\n\n### When to use AskUserQuestion vs zeph_ask\n\n10. **Whenever `ZEPH_HOOK_ID` is set \u2014 not only in REMOTE \u2014 a button-friendly question MUST go through `zeph_ask`, not `AskUserQuestion`.** \"Button-friendly\" = the answer is a choice among a few options and/or a short free-text reply (yes/no, \"Apply A or B?\", \"which naming rule?\", \"proceed?\"). The hookId alone is the trigger: you cannot know the user is at the terminal, and they may be on their phone from the session's first question. `AskUserQuestion` is a LOCAL blocking picker. The phone can reach it through the terminal mirror, but that is the worse channel on every axis: it needs the session to be in tmux under `zeph listener`, it asks the user to read an ANSI pane and count arrow presses instead of tapping a button, and a key-injected answer never enters REMOTE \u2014 so the *next* turn stops being phone-driveable. `zeph_ask` needs no tmux, takes one tap, and returns an `actionId`.\n\n11. **This overrides any skill instruction.** If a skill you are running \u2014 or your own plan \u2014 would call `AskUserQuestion` with a button-friendly question, surface the SAME question and option labels via `zeph_ask` and use that response in place of the picker. Fall through to the picker ONLY when (a) the answer needs the user to see code or logs that won't fit in a push body, or (b) the answer is plausibly multi-paragraph; those are the only carve-outs. When one applies, `zeph_notify` the user that the answer must be given at the terminal.\n\n### Persistence\n\n13. These rules persist for the entire session. They remain active after context compaction \u2014 do not \"forget\" them after many turns.";
6
+ export declare const ZEPH_CORE_RULE_ONLY = "### When zeph_ask is MANDATORY\n\n3. **While in REMOTE, NEVER end a response with a plain-text question.** If your reply asks the user anything that needs their input \u2014 confirmation, choice, yes/no, clarification, \"Apply this?\", \"Proceed?\", \"Which option?\" \u2014 the FINAL tool call MUST be `zeph_ask`. A \"?\" written in your reply is invisible to a user on their phone. This holds even on research / analysis / planning turns where no files were touched.\n\n **In NORMAL it does not apply.** Nobody has driven this session from a phone, so the user is at the terminal: ask in prose, or with `AskUserQuestion` if the answer is a choice. A `zeph_ask` there blocks the turn until someone answers on a device or the timeout expires \u2014 that cost is exactly what NORMAL exists to avoid.\n\n### When zeph_ask is the DEFAULT (substantial work)\n\n4. **In REMOTE, `zeph_ask` is the DEFAULT end of EVERY response** \u2014 not conditioned on the work being substantial (Rule 9).\n\n **In NORMAL, end with nothing.** The Stop hook's push \u2014 tuned by the user's dial and your Push Signal \u2014 is the completion signal. Do not chain a `zeph_ask` onto substantial work to \"keep the loop alive\": the loop starts when the user sends a message from their phone, not when you decide the work was big enough. `zeph_ask` stays available when you actively want an answer from their device; it is simply not owed.\n\n5. When you do ask, prefer `zeph_ask` over `zeph_prompt`/`zeph_input` \u2014 it combines buttons and free text in one push. **`actions` is the steering surface, not decoration:** ship 2\u20134 buttons on nearly every ask (the next-step candidates you would otherwise write as prose) plus a safe Done-like `fallback` id \u2014 never a destructive one. Leave `actions` out ONLY when the answer is inherently free-form (a name, a path, a paragraph); a text-only ask on a \"done \u2014 what next?\" turn is the most common way REMOTE silently degrades, because the phone gets a text box and nothing to tap.\n\n ```\n zeph_ask({\n title: \"Slice done \u2014 next?\",\n body: \"<short result>\",\n actions: [\n { id: \"simplify\", label: \"/simplify\" },\n { id: \"ship\", label: \"/ship\" },\n { id: \"done\", label: \"Stop here\" }\n ],\n placeholder: \"or type something else...\",\n fallback: \"done\"\n })\n ```\n\n6. Rule 9 says when to send one. Never send an ask just to mark a turn finished.\n\n### Handling the response\n\n7. A `zeph_ask` response IS a direct user instruction. Execute it immediately \u2014 do NOT re-ask via `AskUserQuestion` to confirm. The button label is the authorization for the specific action that label describes.\n\n8. Caveat: a generic button like \"Continue\" authorizes the next logical step, NOT arbitrary destruction. If that step would destroy user code, data, or infrastructure (force-push to a shared branch, `rm -rf` outside the workdir, dropping a database, deleting prod resources), surface that specific risk in a targeted `zeph_ask` first \u2014 title \"About to force-push main \u2014 proceed?\", actions `[ok, cancel]`, fallback `cancel`.\n\n### Sticky REMOTE mode (Rule 9)\n\n9. **The Ask Loop has two states: REMOTE and NORMAL.** REMOTE is sticky \u2014 every response ends with `zeph_ask` until the user exits. The state is kept for you in a file, so it survives context compaction and long sessions; you are told what it is rather than deriving it.\n\n#### State Detection\n\n- **`zeph_ask` results carry it** as `zephState: \"REMOTE\" | \"NORMAL\"`: any answer that is not a Done-like action id enters REMOTE, a Done-like id exits, and so does a timeout that fell back to one. A result with no `zephState` is an ask that timed out onto a safe fallback and changed nothing.\n- **Prompt-submit hooks say it**, where installed (this plugin, or the hooks `zeph setup` writes for Gemini/Codex): a remote-origin note on the turn a phone message arrives, and a note that the session has LEFT REMOTE on the first turn the user types at the terminal. A phone answer to a `zeph_ask` comes back as a `tool_result` and never reaches a prompt hook, so a prompt with no phone marker is demonstrably the user's own keyboard \u2014 staying in REMOTE would answer the terminal with a phone loop, and re-entry costs them one message from the phone.\n- **Neither one present \u2192 NORMAL.**\n\n**The one call left to you is free text**, because it is the one signal no hook can read: the server cannot tell \"run the tests\" from \"thanks, that's it\". When the user's typed answer clearly closes the loop \u2014 an obvious wrap-up, or `done`/`stop`/`exit` as a standalone word (not a substring: \"redo\" is not \"done\") \u2014 flip to NORMAL from that response on, don't send `zeph_ask` on it, and emit `<!-- zeph: exit -->` once so the hooks agree with you. Your own flip is what ends the loop; the marker is how you tell a hook that cannot read your mind, and it is separate from the Push Signal markers (`skip`/`push`/`high`), which say nothing about the mode.\n\n#### Behavior in REMOTE (sticky, zeph_ask MANDATORY)\n\nEnd EVERY response with `zeph_ask`, carrying 2\u20134 `actions` (the next-step candidates as buttons) plus a Done-like `fallback` \u2014 never a destructive one, since an unanswered ask resolves to it \u2014 a text-only ask is only for inherently free-form answers. Non-negotiable while in REMOTE, independent of whether the work was substantial or routine.\n\nSet `timeout` 300\u2013600 s so silence degrades cleanly: an unanswered ask exits the loop quietly \u2014 the server treats a Done-like fallback as an exit \u2014 instead of chaining more notifications at a user who stepped away.\n\nFour things leave REMOTE: a Done-like button (or a timeout that fell back to one), your own read of a free-text wrap-up, a prompt the user typed at the terminal, and \u2014 for a session nobody exited because it crashed \u2014 the state expiring.\n\n#### Behavior in NORMAL (no zeph_ask is owed)\n\nThe user is at the terminal \u2014 that is what NORMAL means. Nothing here obliges an ask:\n\n- Questions go to `AskUserQuestion` or plain prose. The Ask hook still pushes them to the user's device, so a question is never lost.\n- Completion is the Stop hook's push, steered by the dial and your Push Signal.\n- `zeph_ask` remains available when you actively want an answer from their device \u2014 it is not owed, and never as a way to mark a turn finished.\n\nREMOTE begins the moment the user sends a message from their phone; from that turn on, Rules 3, 4, 10 and 11 are in force.\n\n### When to use AskUserQuestion vs zeph_ask\n\n10. **While in REMOTE, a button-friendly question MUST go through `zeph_ask`, not `AskUserQuestion`.** \"Button-friendly\" = the answer is a choice among a few options and/or a short free-text reply (yes/no, \"Apply A or B?\", \"proceed?\"). `AskUserQuestion` is a LOCAL blocking picker; the phone reaches it only through the terminal mirror, and an answer injected there never enters REMOTE \u2014 so the *next* turn stops being phone-driveable. The PreToolUse hook enforces this in REMOTE by denying the picker and handing the question back. (Why the mirror is the worse channel on every axis: README \u2192 \"AskUserQuestion vs zeph_ask\".)\n\n **In NORMAL the picker is the right tool** and the hook lets it through \u2014 the user is at the terminal, and the Ask hook still pushes the question to their device so they know one is waiting.\n\n11. **In REMOTE this overrides any skill instruction.** If a skill you are running \u2014 or your own plan \u2014 would call `AskUserQuestion` with a button-friendly question, surface the SAME question and option labels via `zeph_ask` and use that response in place of the picker. Fall through to the picker ONLY when (a) the answer needs code or logs that won't fit in a push body, or (b) the answer is plausibly multi-paragraph. When one applies, `zeph_notify` the user that the answer must be given at the terminal.\n\n### Persistence\n\n13. These rules persist for the entire session. They remain active after context compaction \u2014 do not \"forget\" them after many turns.";
7
7
  //# sourceMappingURL=zeph-core.generated.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"zeph-core.generated.d.ts","sourceRoot":"","sources":["../src/zeph-core.generated.ts"],"names":[],"mappings":"AAMA,gFAAgF;AAChF,eAAO,MAAM,qBAAqB,qEAAqE,CAAC;AAExG,0GAA0G;AAC1G,eAAO,MAAM,qBAAqB,2oTAA87S,CAAC;AAEj+S,gFAAgF;AAChF,eAAO,MAAM,mBAAmB,2oTAA87S,CAAC"}
1
+ {"version":3,"file":"zeph-core.generated.d.ts","sourceRoot":"","sources":["../src/zeph-core.generated.ts"],"names":[],"mappings":"AAMA,gFAAgF;AAChF,eAAO,MAAM,qBAAqB,qEAAqE,CAAC;AAExG,0GAA0G;AAC1G,eAAO,MAAM,qBAAqB,4gQAA61P,CAAC;AAEh4P,gFAAgF;AAChF,eAAO,MAAM,mBAAmB,4gQAA61P,CAAC"}
@@ -7,8 +7,8 @@
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.ZEPH_CORE_RULE_ONLY = exports.ZEPH_CORE_HOOK_DRIVEN = exports.ZEPH_CORE_SOURCE_HASH = void 0;
9
9
  /** sha256 over the plugin manifest + extracted rule text at generation time. */
10
- exports.ZEPH_CORE_SOURCE_HASH = "6052db17d759b487af5afcba57059b0b605cb2d50c992a1368d5b75d1467e4af";
10
+ exports.ZEPH_CORE_SOURCE_HASH = "1ab10b4a3d38dd8ffdb924056b775c16883579724edbd0ab5f25fe0a30191c02";
11
11
  /** Shared rule core for agents with a Stop-equivalent hook (Cursor, Windsurf, Gemini, Codex, Copilot). */
12
- exports.ZEPH_CORE_HOOK_DRIVEN = "### When zeph_ask is MANDATORY\n\n3. **NEVER end a response with a plain-text question.** If your reply asks the user anything that needs their input — confirmation, choice, yes/no, clarification, \"Apply this?\", \"Proceed?\", \"Which option?\" — the FINAL tool call MUST be `zeph_ask`. A \"?\" written in your reply is invisible to a user on their phone. This applies even on research / analysis / planning turns where no files were touched.\n\n Anti-pattern (wrong):\n ```\n \"...build/commit on hold. Apply A?\"\n ```\n Correct:\n ```\n zeph_ask({\n title: \"Apply solution A?\",\n body: \"<short context comparing options>\",\n actions: [\n { id: \"apply_a\", label: \"Apply A\" },\n { id: \"apply_b\", label: \"Apply B\" },\n { id: \"cancel\", label: \"Cancel\" }\n ],\n placeholder: \"or describe a different approach...\",\n fallback: \"cancel\"\n })\n ```\n\n### When zeph_ask is the DEFAULT (substantial work)\n\n4. After substantial work, `zeph_ask` is the DEFAULT end of the response — not optional. \"Substantial\" means: file changes, commits, builds, tests, deploys, destructive ops, or milestone completions. When unsure, treat the work as substantial do not try to guess what the user would find \"reasonable\" to confirm.\n\n SKIP `zeph_ask` only when the response is clearly trivial:\n - Read-only exploration (\"let me check this file\") with no decision output.\n - Mid-step inside a clearly-defined plan the user already approved (\"step 3 of 5, no decisions involved here\").\n - Trivial change (typo fix, single-line tweak) that needs no ack.\n\n When unsure: LEAN TOWARD ASKING. Quiet failure (no ask, user stuck on phone with no way to drive) is worse than light spam.\n\n5. Prefer `zeph_ask` over `zeph_prompt`/`zeph_input` — it combines buttons and free-text in one push. Always include a `fallback` action id; the fallback must be safe/inert (`done`, `wait`, `review`), never destructive.\n\n **`actions` is the steering surface, not decoration.** Ship 2–4 buttons on nearly every ask: the next-step candidates you would otherwise write as prose (\"/simplify → /ship next\") belong in `actions`, plus a safe Done-like fallback. Leave `actions` out ONLY when the answer is inherently free-form text (a name, a path, a paragraph). A text-only ask on a \"done — what next?\" turn is the most common way REMOTE silently degrades: the phone gets a text box and nothing to tap, and the user has to type the command you already knew.\n\n Anti-pattern (wrong — next steps in prose, ask has no buttons):\n ```\n \"...done. /simplify → /explain-diff (optional) → /ship next.\"\n zeph_ask({ title: \"Slice done\", body: \"...\" })\n ```\n Correct:\n ```\n zeph_ask({\n title: \"Slice done — next?\",\n body: \"<short result>\",\n actions: [\n { id: \"simplify\", label: \"/simplify\" },\n { id: \"explain_diff\", label: \"/explain-diff\" },\n { id: \"ship\", label: \"/ship\" },\n { id: \"done\", label: \"Stop here\" }\n ],\n placeholder: \"or type something else...\",\n fallback: \"done\"\n })\n ```\n\n6. Example `zeph_ask` shape — use sparingly per Rule 4 (only at natural pause points; NOT after every response — see Rule 9):\n ```\n zeph_ask({\n title: \"Done. Next?\",\n actions: [\n { id: \"continue\", label: \"Continue\" },\n { id: \"review\", label: \"Review\" },\n { id: \"done\", label: \"Done\" }\n ],\n placeholder: \"or type a command...\",\n fallback: \"done\"\n })\n ```\n\n### Handling the response\n\n7. A `zeph_ask` response IS a direct user instruction. Execute it immediately — do NOT re-ask via AskUserQuestion to confirm. The button label is the authorization for the specific action that label describes.\n\n8. Important caveat: a generic button like \"Continue\" authorizes the next logical step, NOT arbitrary destruction. If the next logical step would destroy user code, data, or infrastructure (e.g., force-push to a shared branch, `rm -rf` outside the workdir, dropping a database, deleting prod resources), surface that specific risk via a targeted `zeph_ask` before executing e.g., title \"About to force-push main — proceed?\", actions `[ok, cancel]`, fallback `cancel`.\n\n### Sticky REMOTE mode (Rule 9)\n\n**The Ask Loop has two states: REMOTE and NORMAL.** REMOTE is sticky — every response ends with `zeph_ask` until the user exits. The state is kept for you in a file, so it survives context compaction and long sessions; you are told what it is rather than deriving it.\n\n#### State Detection\n\n- **`zeph_ask` results carry it.** The server applies the transition it can see and reports where the session landed as `zephState: \"REMOTE\" | \"NORMAL\"` any answer that is not a Done-like action id enters REMOTE, a Done-like id exits, and so does a timeout that fell back to one. A result with no `zephState` is an ask that timed out onto a safe fallback and changed nothing.\n- **Prompt-submit hooks say it, where they are installed** (the Claude Code plugin, or the hooks `zeph setup` installs for Gemini/Codex): a remote-origin system note on the turn a phone message arrives, and a note that the session has LEFT REMOTE on the first turn the user types at the terminal. A prompt with no phone marker came from the user's own keyboard — a phone answer to a `zeph_ask` comes back as a `tool_result` and never reaches a prompt hook so the user is demonstrably back, and staying in REMOTE would answer the terminal with a phone loop. Re-entry costs them one message from the phone.\n- **Neither one present → NORMAL.**\n\n**The one call left to you is free text**, because it is the one signal no hook can read. The server cannot tell \"run the tests\" from \"thanks, that's it\". When the user's typed answer clearly closes the loop — an obvious wrap-up, or `done`/`stop`/`exit` as a standalone word (not a substring: \"redo\" is not \"done\") — flip to NORMAL from that response on, don't send `zeph_ask` on it, and emit `<!-- zeph: exit -->` once so the hooks agree with you. Your own flip is what ends the loop; the marker is how you tell a hook that cannot read your mind. Only the Claude Code plugin's Stop hook consumes it today — elsewhere it is inert and harmless, and the session still leaves REMOTE on a Done-like button, on the next prompt the user types at the terminal, or when the state expires. The marker is separate from the Push Signal markers (`skip`/`push`/`high`), which steer notifications and say nothing about the mode; where a Stop hook does consume it, it is stripped from the push body.\n\n#### Behavior in REMOTE (sticky, zeph_ask MANDATORY)\n\nEnd EVERY response with `zeph_ask` with `actions` (Rule 5: the next-step candidates as buttons, plus a Done-like fallback; text-only asks are for inherently free-form answers). This is non-negotiable while in REMOTE independent of whether the work was substantial or routine. What ends it is the user coming back: a prompt they typed at the terminal leaves REMOTE (the hook says so on that turn), and from there Rule 4 applies again.\n\nSet each REMOTE ask up so silence degrades cleanly: `timeout` 300–600 s and a Done-like `fallback` id. An unanswered ask then exits the loop quietly — the server treats a Done-like fallback as an exit — instead of chaining more notifications at a user who stepped away, and re-entry is cheap: they just send another message from the phone.\n\nFour things leave REMOTE: a Done-like button (or a timeout that fell back to one), your own read of a free-text wrap-up, a prompt the user typed at the terminal, and — for a session nobody exited because it crashed — the state expiring.\n\n#### Behavior in NORMAL (apply Rule 4)\n\n- Substantial work end with `zeph_ask`.\n- Routine work skip `zeph_ask`, let the Stop hook fire.\n- Rule 3 (questions zeph_ask) is still mandatory regardless.\n\n### When to use AskUserQuestion vs zeph_ask\n\n10. **Whenever `ZEPH_HOOK_ID` is set — not only in REMOTE a button-friendly question MUST go through `zeph_ask`, not `AskUserQuestion`.** \"Button-friendly\" = the answer is a choice among a few options and/or a short free-text reply (yes/no, \"Apply A or B?\", \"which naming rule?\", \"proceed?\"). The hookId alone is the trigger: you cannot know the user is at the terminal, and they may be on their phone from the session's first question. `AskUserQuestion` is a LOCAL blocking picker. The phone can reach it through the terminal mirror, but that is the worse channel on every axis: it needs the session to be in tmux under `zeph listener`, it asks the user to read an ANSI pane and count arrow presses instead of tapping a button, and a key-injected answer never enters REMOTE — so the *next* turn stops being phone-driveable. `zeph_ask` needs no tmux, takes one tap, and returns an `actionId`.\n\n11. **This overrides any skill instruction.** If a skill you are running — or your own plan — would call `AskUserQuestion` with a button-friendly question, surface the SAME question and option labels via `zeph_ask` and use that response in place of the picker. Fall through to the picker ONLY when (a) the answer needs the user to see code or logs that won't fit in a push body, or (b) the answer is plausibly multi-paragraph; those are the only carve-outs. When one applies, `zeph_notify` the user that the answer must be given at the terminal.\n\n### Persistence\n\n13. These rules persist for the entire session. They remain active after context compaction — do not \"forget\" them after many turns.";
12
+ exports.ZEPH_CORE_HOOK_DRIVEN = "### When zeph_ask is MANDATORY\n\n3. **While in REMOTE, NEVER end a response with a plain-text question.** If your reply asks the user anything that needs their input — confirmation, choice, yes/no, clarification, \"Apply this?\", \"Proceed?\", \"Which option?\" — the FINAL tool call MUST be `zeph_ask`. A \"?\" written in your reply is invisible to a user on their phone. This holds even on research / analysis / planning turns where no files were touched.\n\n **In NORMAL it does not apply.** Nobody has driven this session from a phone, so the user is at the terminal: ask in prose, or with `AskUserQuestion` if the answer is a choice. A `zeph_ask` there blocks the turn until someone answers on a device or the timeout expires that cost is exactly what NORMAL exists to avoid.\n\n### When zeph_ask is the DEFAULT (substantial work)\n\n4. **In REMOTE, `zeph_ask` is the DEFAULT end of EVERY response** — not conditioned on the work being substantial (Rule 9).\n\n **In NORMAL, end with nothing.** The Stop hook's push tuned by the user's dial and your Push Signal is the completion signal. Do not chain a `zeph_ask` onto substantial work to \"keep the loop alive\": the loop starts when the user sends a message from their phone, not when you decide the work was big enough. `zeph_ask` stays available when you actively want an answer from their device; it is simply not owed.\n\n5. When you do ask, prefer `zeph_ask` over `zeph_prompt`/`zeph_input` — it combines buttons and free text in one push. **`actions` is the steering surface, not decoration:** ship 2–4 buttons on nearly every ask (the next-step candidates you would otherwise write as prose) plus a safe Done-like `fallback` id — never a destructive one. Leave `actions` out ONLY when the answer is inherently free-form (a name, a path, a paragraph); a text-only ask on a \"done — what next?\" turn is the most common way REMOTE silently degrades, because the phone gets a text box and nothing to tap.\n\n ```\n zeph_ask({\n title: \"Slice done — next?\",\n body: \"<short result>\",\n actions: [\n { id: \"simplify\", label: \"/simplify\" },\n { id: \"ship\", label: \"/ship\" },\n { id: \"done\", label: \"Stop here\" }\n ],\n placeholder: \"or type something else...\",\n fallback: \"done\"\n })\n ```\n\n6. Rule 9 says when to send one. Never send an ask just to mark a turn finished.\n\n### Handling the response\n\n7. A `zeph_ask` response IS a direct user instruction. Execute it immediately — do NOT re-ask via `AskUserQuestion` to confirm. The button label is the authorization for the specific action that label describes.\n\n8. Caveat: a generic button like \"Continue\" authorizes the next logical step, NOT arbitrary destruction. If that step would destroy user code, data, or infrastructure (force-push to a shared branch, `rm -rf` outside the workdir, dropping a database, deleting prod resources), surface that specific risk in a targeted `zeph_ask` first — title \"About to force-push main — proceed?\", actions `[ok, cancel]`, fallback `cancel`.\n\n### Sticky REMOTE mode (Rule 9)\n\n9. **The Ask Loop has two states: REMOTE and NORMAL.** REMOTE is sticky — every response ends with `zeph_ask` until the user exits. The state is kept for you in a file, so it survives context compaction and long sessions; you are told what it is rather than deriving it.\n\n#### State Detection\n\n- **`zeph_ask` results carry it** as `zephState: \"REMOTE\" | \"NORMAL\"`: any answer that is not a Done-like action id enters REMOTE, a Done-like id exits, and so does a timeout that fell back to one. A result with no `zephState` is an ask that timed out onto a safe fallback and changed nothing.\n- **Prompt-submit hooks say it**, where installed (this plugin, or the hooks `zeph setup` writes for Gemini/Codex): a remote-origin note on the turn a phone message arrives, and a note that the session has LEFT REMOTE on the first turn the user types at the terminal. A phone answer to a `zeph_ask` comes back as a `tool_result` and never reaches a prompt hook, so a prompt with no phone marker is demonstrably the user's own keyboard — staying in REMOTE would answer the terminal with a phone loop, and re-entry costs them one message from the phone.\n- **Neither one present → NORMAL.**\n\n**The one call left to you is free text**, because it is the one signal no hook can read: the server cannot tell \"run the tests\" from \"thanks, that's it\". When the user's typed answer clearly closes the loop — an obvious wrap-up, or `done`/`stop`/`exit` as a standalone word (not a substring: \"redo\" is not \"done\") — flip to NORMAL from that response on, don't send `zeph_ask` on it, and emit `<!-- zeph: exit -->` once so the hooks agree with you. Your own flip is what ends the loop; the marker is how you tell a hook that cannot read your mind, and it is separate from the Push Signal markers (`skip`/`push`/`high`), which say nothing about the mode.\n\n#### Behavior in REMOTE (sticky, zeph_ask MANDATORY)\n\nEnd EVERY response with `zeph_ask`, carrying 2–4 `actions` (the next-step candidates as buttons) plus a Done-like `fallback` — never a destructive one, since an unanswered ask resolves to it — a text-only ask is only for inherently free-form answers. Non-negotiable while in REMOTE, independent of whether the work was substantial or routine.\n\nSet `timeout` 300–600 s so silence degrades cleanly: an unanswered ask exits the loop quietly — the server treats a Done-like fallback as an exit — instead of chaining more notifications at a user who stepped away.\n\nFour things leave REMOTE: a Done-like button (or a timeout that fell back to one), your own read of a free-text wrap-up, a prompt the user typed at the terminal, and — for a session nobody exited because it crashed — the state expiring.\n\n#### Behavior in NORMAL (no zeph_ask is owed)\n\nThe user is at the terminal — that is what NORMAL means. Nothing here obliges an ask:\n\n- Questions go to `AskUserQuestion` or plain prose. The Ask hook still pushes them to the user's device, so a question is never lost.\n- Completion is the Stop hook's push, steered by the dial and your Push Signal.\n- `zeph_ask` remains available when you actively want an answer from their device — it is not owed, and never as a way to mark a turn finished.\n\nREMOTE begins the moment the user sends a message from their phone; from that turn on, Rules 3, 4, 10 and 11 are in force.\n\n### When to use AskUserQuestion vs zeph_ask\n\n10. **While in REMOTE, a button-friendly question MUST go through `zeph_ask`, not `AskUserQuestion`.** \"Button-friendly\" = the answer is a choice among a few options and/or a short free-text reply (yes/no, \"Apply A or B?\", \"proceed?\"). `AskUserQuestion` is a LOCAL blocking picker; the phone reaches it only through the terminal mirror, and an answer injected there never enters REMOTE — so the *next* turn stops being phone-driveable. The PreToolUse hook enforces this in REMOTE by denying the picker and handing the question back. (Why the mirror is the worse channel on every axis: README \"AskUserQuestion vs zeph_ask\".)\n\n **In NORMAL the picker is the right tool** and the hook lets it through the user is at the terminal, and the Ask hook still pushes the question to their device so they know one is waiting.\n\n11. **In REMOTE this overrides any skill instruction.** If a skill you are running — or your own plan — would call `AskUserQuestion` with a button-friendly question, surface the SAME question and option labels via `zeph_ask` and use that response in place of the picker. Fall through to the picker ONLY when (a) the answer needs code or logs that won't fit in a push body, or (b) the answer is plausibly multi-paragraph. When one applies, `zeph_notify` the user that the answer must be given at the terminal.\n\n### Persistence\n\n13. These rules persist for the entire session. They remain active after context compaction — do not \"forget\" them after many turns.";
13
13
  /** Shared rule core for rule-only agents without a Stop hook (Cline, Aider). */
14
- exports.ZEPH_CORE_RULE_ONLY = "### When zeph_ask is MANDATORY\n\n3. **NEVER end a response with a plain-text question.** If your reply asks the user anything that needs their input — confirmation, choice, yes/no, clarification, \"Apply this?\", \"Proceed?\", \"Which option?\" — the FINAL tool call MUST be `zeph_ask`. A \"?\" written in your reply is invisible to a user on their phone. This applies even on research / analysis / planning turns where no files were touched.\n\n Anti-pattern (wrong):\n ```\n \"...build/commit on hold. Apply A?\"\n ```\n Correct:\n ```\n zeph_ask({\n title: \"Apply solution A?\",\n body: \"<short context comparing options>\",\n actions: [\n { id: \"apply_a\", label: \"Apply A\" },\n { id: \"apply_b\", label: \"Apply B\" },\n { id: \"cancel\", label: \"Cancel\" }\n ],\n placeholder: \"or describe a different approach...\",\n fallback: \"cancel\"\n })\n ```\n\n### When zeph_ask is the DEFAULT (substantial work)\n\n4. After substantial work, `zeph_ask` is the DEFAULT end of the response — not optional. \"Substantial\" means: file changes, commits, builds, tests, deploys, destructive ops, or milestone completions. When unsure, treat the work as substantial do not try to guess what the user would find \"reasonable\" to confirm.\n\n SKIP `zeph_ask` only when the response is clearly trivial:\n - Read-only exploration (\"let me check this file\") with no decision output.\n - Mid-step inside a clearly-defined plan the user already approved (\"step 3 of 5, no decisions involved here\").\n - Trivial change (typo fix, single-line tweak) that needs no ack.\n\n When unsure: LEAN TOWARD ASKING. Quiet failure (no ask, user stuck on phone with no way to drive) is worse than light spam.\n\n5. Prefer `zeph_ask` over `zeph_prompt`/`zeph_input` — it combines buttons and free-text in one push. Always include a `fallback` action id; the fallback must be safe/inert (`done`, `wait`, `review`), never destructive.\n\n **`actions` is the steering surface, not decoration.** Ship 2–4 buttons on nearly every ask: the next-step candidates you would otherwise write as prose (\"/simplify → /ship next\") belong in `actions`, plus a safe Done-like fallback. Leave `actions` out ONLY when the answer is inherently free-form text (a name, a path, a paragraph). A text-only ask on a \"done — what next?\" turn is the most common way REMOTE silently degrades: the phone gets a text box and nothing to tap, and the user has to type the command you already knew.\n\n Anti-pattern (wrong — next steps in prose, ask has no buttons):\n ```\n \"...done. /simplify → /explain-diff (optional) → /ship next.\"\n zeph_ask({ title: \"Slice done\", body: \"...\" })\n ```\n Correct:\n ```\n zeph_ask({\n title: \"Slice done — next?\",\n body: \"<short result>\",\n actions: [\n { id: \"simplify\", label: \"/simplify\" },\n { id: \"explain_diff\", label: \"/explain-diff\" },\n { id: \"ship\", label: \"/ship\" },\n { id: \"done\", label: \"Stop here\" }\n ],\n placeholder: \"or type something else...\",\n fallback: \"done\"\n })\n ```\n\n6. Example `zeph_ask` shape — use sparingly per Rule 4 (only at natural pause points; NOT after every response — see Rule 9):\n ```\n zeph_ask({\n title: \"Done. Next?\",\n actions: [\n { id: \"continue\", label: \"Continue\" },\n { id: \"review\", label: \"Review\" },\n { id: \"done\", label: \"Done\" }\n ],\n placeholder: \"or type a command...\",\n fallback: \"done\"\n })\n ```\n\n### Handling the response\n\n7. A `zeph_ask` response IS a direct user instruction. Execute it immediately — do NOT re-ask via AskUserQuestion to confirm. The button label is the authorization for the specific action that label describes.\n\n8. Important caveat: a generic button like \"Continue\" authorizes the next logical step, NOT arbitrary destruction. If the next logical step would destroy user code, data, or infrastructure (e.g., force-push to a shared branch, `rm -rf` outside the workdir, dropping a database, deleting prod resources), surface that specific risk via a targeted `zeph_ask` before executing e.g., title \"About to force-push main — proceed?\", actions `[ok, cancel]`, fallback `cancel`.\n\n### Sticky REMOTE mode (Rule 9)\n\n**The Ask Loop has two states: REMOTE and NORMAL.** REMOTE is sticky — every response ends with `zeph_ask` until the user exits. The state is kept for you in a file, so it survives context compaction and long sessions; you are told what it is rather than deriving it.\n\n#### State Detection\n\n- **`zeph_ask` results carry it.** The server applies the transition it can see and reports where the session landed as `zephState: \"REMOTE\" | \"NORMAL\"` any answer that is not a Done-like action id enters REMOTE, a Done-like id exits, and so does a timeout that fell back to one. A result with no `zephState` is an ask that timed out onto a safe fallback and changed nothing.\n- **Prompt-submit hooks say it, where they are installed** (the Claude Code plugin, or the hooks `zeph setup` installs for Gemini/Codex): a remote-origin system note on the turn a phone message arrives, and a note that the session has LEFT REMOTE on the first turn the user types at the terminal. A prompt with no phone marker came from the user's own keyboard — a phone answer to a `zeph_ask` comes back as a `tool_result` and never reaches a prompt hook so the user is demonstrably back, and staying in REMOTE would answer the terminal with a phone loop. Re-entry costs them one message from the phone.\n- **Neither one present → NORMAL.**\n\n**The one call left to you is free text**, because it is the one signal no hook can read. The server cannot tell \"run the tests\" from \"thanks, that's it\". When the user's typed answer clearly closes the loop — an obvious wrap-up, or `done`/`stop`/`exit` as a standalone word (not a substring: \"redo\" is not \"done\") — flip to NORMAL from that response on, don't send `zeph_ask` on it, and emit `<!-- zeph: exit -->` once so the hooks agree with you. Your own flip is what ends the loop; the marker is how you tell a hook that cannot read your mind. Only the Claude Code plugin's Stop hook consumes it today — elsewhere it is inert and harmless, and the session still leaves REMOTE on a Done-like button, on the next prompt the user types at the terminal, or when the state expires. The marker is separate from the Push Signal markers (`skip`/`push`/`high`), which steer notifications and say nothing about the mode; where a Stop hook does consume it, it is stripped from the push body.\n\n#### Behavior in REMOTE (sticky, zeph_ask MANDATORY)\n\nEnd EVERY response with `zeph_ask` with `actions` (Rule 5: the next-step candidates as buttons, plus a Done-like fallback; text-only asks are for inherently free-form answers). This is non-negotiable while in REMOTE independent of whether the work was substantial or routine. What ends it is the user coming back: a prompt they typed at the terminal leaves REMOTE (the hook says so on that turn), and from there Rule 4 applies again.\n\nSet each REMOTE ask up so silence degrades cleanly: `timeout` 300–600 s and a Done-like `fallback` id. An unanswered ask then exits the loop quietly — the server treats a Done-like fallback as an exit — instead of chaining more notifications at a user who stepped away, and re-entry is cheap: they just send another message from the phone.\n\nFour things leave REMOTE: a Done-like button (or a timeout that fell back to one), your own read of a free-text wrap-up, a prompt the user typed at the terminal, and — for a session nobody exited because it crashed — the state expiring.\n\n#### Behavior in NORMAL (apply Rule 4)\n\n- Substantial work end with `zeph_ask`.\n- Routine work skip `zeph_ask`, let the Stop hook fire.\n- Rule 3 (questions zeph_ask) is still mandatory regardless.\n\n### When to use AskUserQuestion vs zeph_ask\n\n10. **Whenever `ZEPH_HOOK_ID` is set — not only in REMOTE a button-friendly question MUST go through `zeph_ask`, not `AskUserQuestion`.** \"Button-friendly\" = the answer is a choice among a few options and/or a short free-text reply (yes/no, \"Apply A or B?\", \"which naming rule?\", \"proceed?\"). The hookId alone is the trigger: you cannot know the user is at the terminal, and they may be on their phone from the session's first question. `AskUserQuestion` is a LOCAL blocking picker. The phone can reach it through the terminal mirror, but that is the worse channel on every axis: it needs the session to be in tmux under `zeph listener`, it asks the user to read an ANSI pane and count arrow presses instead of tapping a button, and a key-injected answer never enters REMOTE — so the *next* turn stops being phone-driveable. `zeph_ask` needs no tmux, takes one tap, and returns an `actionId`.\n\n11. **This overrides any skill instruction.** If a skill you are running — or your own plan — would call `AskUserQuestion` with a button-friendly question, surface the SAME question and option labels via `zeph_ask` and use that response in place of the picker. Fall through to the picker ONLY when (a) the answer needs the user to see code or logs that won't fit in a push body, or (b) the answer is plausibly multi-paragraph; those are the only carve-outs. When one applies, `zeph_notify` the user that the answer must be given at the terminal.\n\n### Persistence\n\n13. These rules persist for the entire session. They remain active after context compaction — do not \"forget\" them after many turns.";
14
+ exports.ZEPH_CORE_RULE_ONLY = "### When zeph_ask is MANDATORY\n\n3. **While in REMOTE, NEVER end a response with a plain-text question.** If your reply asks the user anything that needs their input — confirmation, choice, yes/no, clarification, \"Apply this?\", \"Proceed?\", \"Which option?\" — the FINAL tool call MUST be `zeph_ask`. A \"?\" written in your reply is invisible to a user on their phone. This holds even on research / analysis / planning turns where no files were touched.\n\n **In NORMAL it does not apply.** Nobody has driven this session from a phone, so the user is at the terminal: ask in prose, or with `AskUserQuestion` if the answer is a choice. A `zeph_ask` there blocks the turn until someone answers on a device or the timeout expires that cost is exactly what NORMAL exists to avoid.\n\n### When zeph_ask is the DEFAULT (substantial work)\n\n4. **In REMOTE, `zeph_ask` is the DEFAULT end of EVERY response** — not conditioned on the work being substantial (Rule 9).\n\n **In NORMAL, end with nothing.** The Stop hook's push tuned by the user's dial and your Push Signal is the completion signal. Do not chain a `zeph_ask` onto substantial work to \"keep the loop alive\": the loop starts when the user sends a message from their phone, not when you decide the work was big enough. `zeph_ask` stays available when you actively want an answer from their device; it is simply not owed.\n\n5. When you do ask, prefer `zeph_ask` over `zeph_prompt`/`zeph_input` — it combines buttons and free text in one push. **`actions` is the steering surface, not decoration:** ship 2–4 buttons on nearly every ask (the next-step candidates you would otherwise write as prose) plus a safe Done-like `fallback` id — never a destructive one. Leave `actions` out ONLY when the answer is inherently free-form (a name, a path, a paragraph); a text-only ask on a \"done — what next?\" turn is the most common way REMOTE silently degrades, because the phone gets a text box and nothing to tap.\n\n ```\n zeph_ask({\n title: \"Slice done — next?\",\n body: \"<short result>\",\n actions: [\n { id: \"simplify\", label: \"/simplify\" },\n { id: \"ship\", label: \"/ship\" },\n { id: \"done\", label: \"Stop here\" }\n ],\n placeholder: \"or type something else...\",\n fallback: \"done\"\n })\n ```\n\n6. Rule 9 says when to send one. Never send an ask just to mark a turn finished.\n\n### Handling the response\n\n7. A `zeph_ask` response IS a direct user instruction. Execute it immediately — do NOT re-ask via `AskUserQuestion` to confirm. The button label is the authorization for the specific action that label describes.\n\n8. Caveat: a generic button like \"Continue\" authorizes the next logical step, NOT arbitrary destruction. If that step would destroy user code, data, or infrastructure (force-push to a shared branch, `rm -rf` outside the workdir, dropping a database, deleting prod resources), surface that specific risk in a targeted `zeph_ask` first — title \"About to force-push main — proceed?\", actions `[ok, cancel]`, fallback `cancel`.\n\n### Sticky REMOTE mode (Rule 9)\n\n9. **The Ask Loop has two states: REMOTE and NORMAL.** REMOTE is sticky — every response ends with `zeph_ask` until the user exits. The state is kept for you in a file, so it survives context compaction and long sessions; you are told what it is rather than deriving it.\n\n#### State Detection\n\n- **`zeph_ask` results carry it** as `zephState: \"REMOTE\" | \"NORMAL\"`: any answer that is not a Done-like action id enters REMOTE, a Done-like id exits, and so does a timeout that fell back to one. A result with no `zephState` is an ask that timed out onto a safe fallback and changed nothing.\n- **Prompt-submit hooks say it**, where installed (this plugin, or the hooks `zeph setup` writes for Gemini/Codex): a remote-origin note on the turn a phone message arrives, and a note that the session has LEFT REMOTE on the first turn the user types at the terminal. A phone answer to a `zeph_ask` comes back as a `tool_result` and never reaches a prompt hook, so a prompt with no phone marker is demonstrably the user's own keyboard — staying in REMOTE would answer the terminal with a phone loop, and re-entry costs them one message from the phone.\n- **Neither one present → NORMAL.**\n\n**The one call left to you is free text**, because it is the one signal no hook can read: the server cannot tell \"run the tests\" from \"thanks, that's it\". When the user's typed answer clearly closes the loop — an obvious wrap-up, or `done`/`stop`/`exit` as a standalone word (not a substring: \"redo\" is not \"done\") — flip to NORMAL from that response on, don't send `zeph_ask` on it, and emit `<!-- zeph: exit -->` once so the hooks agree with you. Your own flip is what ends the loop; the marker is how you tell a hook that cannot read your mind, and it is separate from the Push Signal markers (`skip`/`push`/`high`), which say nothing about the mode.\n\n#### Behavior in REMOTE (sticky, zeph_ask MANDATORY)\n\nEnd EVERY response with `zeph_ask`, carrying 2–4 `actions` (the next-step candidates as buttons) plus a Done-like `fallback` — never a destructive one, since an unanswered ask resolves to it — a text-only ask is only for inherently free-form answers. Non-negotiable while in REMOTE, independent of whether the work was substantial or routine.\n\nSet `timeout` 300–600 s so silence degrades cleanly: an unanswered ask exits the loop quietly — the server treats a Done-like fallback as an exit — instead of chaining more notifications at a user who stepped away.\n\nFour things leave REMOTE: a Done-like button (or a timeout that fell back to one), your own read of a free-text wrap-up, a prompt the user typed at the terminal, and — for a session nobody exited because it crashed — the state expiring.\n\n#### Behavior in NORMAL (no zeph_ask is owed)\n\nThe user is at the terminal — that is what NORMAL means. Nothing here obliges an ask:\n\n- Questions go to `AskUserQuestion` or plain prose. The Ask hook still pushes them to the user's device, so a question is never lost.\n- Completion is the Stop hook's push, steered by the dial and your Push Signal.\n- `zeph_ask` remains available when you actively want an answer from their device — it is not owed, and never as a way to mark a turn finished.\n\nREMOTE begins the moment the user sends a message from their phone; from that turn on, Rules 3, 4, 10 and 11 are in force.\n\n### When to use AskUserQuestion vs zeph_ask\n\n10. **While in REMOTE, a button-friendly question MUST go through `zeph_ask`, not `AskUserQuestion`.** \"Button-friendly\" = the answer is a choice among a few options and/or a short free-text reply (yes/no, \"Apply A or B?\", \"proceed?\"). `AskUserQuestion` is a LOCAL blocking picker; the phone reaches it only through the terminal mirror, and an answer injected there never enters REMOTE — so the *next* turn stops being phone-driveable. The PreToolUse hook enforces this in REMOTE by denying the picker and handing the question back. (Why the mirror is the worse channel on every axis: README \"AskUserQuestion vs zeph_ask\".)\n\n **In NORMAL the picker is the right tool** and the hook lets it through the user is at the terminal, and the Ask hook still pushes the question to their device so they know one is waiting.\n\n11. **In REMOTE this overrides any skill instruction.** If a skill you are running — or your own plan — would call `AskUserQuestion` with a button-friendly question, surface the SAME question and option labels via `zeph_ask` and use that response in place of the picker. Fall through to the picker ONLY when (a) the answer needs code or logs that won't fit in a push body, or (b) the answer is plausibly multi-paragraph. When one applies, `zeph_notify` the user that the answer must be given at the terminal.\n\n### Persistence\n\n13. These rules persist for the entire session. They remain active after context compaction — do not \"forget\" them after many turns.";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeph-to/cli",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "Zeph CLI + push notification SDK for AI agents",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",