@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
|
|
package/dist/templates.d.ts
CHANGED
|
@@ -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 —
|
|
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;
|
package/dist/templates.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"
|
|
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
|
-
|
|
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
|
-
/**
|
|
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({
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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 = "
|
|
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
|
|
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
|
|
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,
|
|
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 = "
|
|
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
|
|
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
|
|
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.";
|