@zeph-to/cli 2.3.0 → 2.3.1
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/dist/gate.d.ts +11 -3
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +23 -4
- package/dist/remote-hook.d.ts.map +1 -1
- package/dist/remote-hook.js +33 -35
- package/dist/zeph-core.generated.d.ts +3 -3
- package/dist/zeph-core.generated.d.ts.map +1 -1
- package/dist/zeph-core.generated.js +3 -3
- package/package.json +1 -1
package/dist/gate.d.ts
CHANGED
|
@@ -47,9 +47,11 @@ export declare const remoteDigest: (text: string) => string;
|
|
|
47
47
|
/**
|
|
48
48
|
* How long REMOTE stays live without a refresh. Generous on purpose: the state
|
|
49
49
|
* is refreshed on every phone prompt and every answered `zeph_ask`, so it only
|
|
50
|
-
* has to outlive a working session, never an idle user. It
|
|
51
|
-
*
|
|
52
|
-
*
|
|
50
|
+
* has to outlive a working session, never an idle user. It is the backstop and
|
|
51
|
+
* not the usual exit — a terminal-typed prompt, a Done-like answer and the
|
|
52
|
+
* model's `<!-- zeph: exit -->` all delete the file outright. What none of them
|
|
53
|
+
* covers is a session that simply died: there is no SessionEnd hook, so without
|
|
54
|
+
* a TTL a crash or Ctrl-C would latch REMOTE forever.
|
|
53
55
|
*/
|
|
54
56
|
export declare const REMOTE_TTL_SEC = 14400;
|
|
55
57
|
/** State path for a project hash: `<stateDir>/remote-active-<cksum(dir)>`. */
|
|
@@ -65,6 +67,12 @@ export declare const isRemoteActive: (dir: string, now?: () => number) => boolea
|
|
|
65
67
|
* hooks that call this must never fail a prompt over state IO.
|
|
66
68
|
*/
|
|
67
69
|
export declare const touchRemoteActive: (dir: string, now?: () => number) => void;
|
|
70
|
+
/**
|
|
71
|
+
* Leave REMOTE. Best-effort like its twin: an absent file is success, and the
|
|
72
|
+
* caller is a hook with its own job to finish. Bash twin: gate.sh
|
|
73
|
+
* zeph_remote_clear.
|
|
74
|
+
*/
|
|
75
|
+
export declare const clearRemoteActive: (dir: string) => void;
|
|
68
76
|
/** True when the user ran /zeph-mute for this project. */
|
|
69
77
|
export declare const isMuted: (dir: string) => boolean;
|
|
70
78
|
/**
|
package/dist/gate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AAyBA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAC3D,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEvD,MAAM,WAAW,SAAS;IACxB,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,gBAAgB,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,YAAY,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAC;CAC7B;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa;;;;CAIhB,CAAC;AAEX,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,GAAG,SAAS,KAAG,UACS,CAAC;AAEpE,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,GAAG,SAAS,KAAG,YACR,CAAC;AAErD,eAAO,MAAM,UAAU,GAAI,OAAO,SAAS,KAAG,WAW7C,CAAC;AAeF,eAAO,MAAM,QAAQ,QAAO,MACoD,CAAC;AA2BjF,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,IAOlD,CAAC;AAWF,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,MAA4C,CAAC;AAE7F;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,MAG1B,CAAC;AAiBnB
|
|
1
|
+
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"AAyBA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAC3D,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEvD,MAAM,WAAW,SAAS;IACxB,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,gBAAgB,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,YAAY,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAC;CAC7B;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa;;;;CAIhB,CAAC;AAEX,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,GAAG,SAAS,KAAG,UACS,CAAC;AAEpE,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,GAAG,SAAS,KAAG,YACR,CAAC;AAErD,eAAO,MAAM,UAAU,GAAI,OAAO,SAAS,KAAG,WAW7C,CAAC;AAeF,eAAO,MAAM,QAAQ,QAAO,MACoD,CAAC;AA2BjF,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,IAOlD,CAAC;AAWF,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,MAA4C,CAAC;AAE7F;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,MAG1B,CAAC;AAiBnB;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,QAAQ,CAAC;AAEpC,8EAA8E;AAC9E,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,MACJ,CAAC;AAE5C;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,EAAE,MAAK,MAAM,MAAiB,KAAG,OAsB1E,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,EAAE,MAAK,MAAM,MAAiB,KAAG,IAS7E,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,KAAG,IAQ/C,CAAC;AAEF,0DAA0D;AAC1D,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,KAAG,OAGrC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,YAAsB,CAAC;AAEtD;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AAExD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,YAAY,GACvB,KAAK,MAAM,EACX,WAAU,YAA+B,KACxC,YAUF,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,EAAE,MAAM,MAAM,GAAG,OAAO,GAAG,SAAS,KAAG,YACW,CAAC"}
|
package/dist/gate.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.autoPushMode = exports.readPushMode = exports.PUSHMODE_DEFAULT_FLAG = exports.PUSHMODE_DEFAULT = exports.isMuted = exports.touchRemoteActive = exports.isRemoteActive = exports.remoteStatePath = exports.REMOTE_TTL_SEC = exports.remoteDigest = exports.remoteMarkerPath = exports.projectHash = exports.stateDir = exports.decidePush = exports.normalizePushMode = exports.normalizeMarker = exports.GATE_DEFAULTS = void 0;
|
|
3
|
+
exports.autoPushMode = exports.readPushMode = exports.PUSHMODE_DEFAULT_FLAG = exports.PUSHMODE_DEFAULT = exports.isMuted = exports.clearRemoteActive = exports.touchRemoteActive = exports.isRemoteActive = exports.remoteStatePath = exports.REMOTE_TTL_SEC = exports.remoteDigest = exports.remoteMarkerPath = exports.projectHash = exports.stateDir = exports.decidePush = exports.normalizePushMode = exports.normalizeMarker = exports.GATE_DEFAULTS = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Push-gate decision — the portable half of the Zeph Stop-hook logic.
|
|
6
6
|
*
|
|
@@ -149,9 +149,11 @@ exports.remoteDigest = remoteDigest;
|
|
|
149
149
|
/**
|
|
150
150
|
* How long REMOTE stays live without a refresh. Generous on purpose: the state
|
|
151
151
|
* is refreshed on every phone prompt and every answered `zeph_ask`, so it only
|
|
152
|
-
* has to outlive a working session, never an idle user. It
|
|
153
|
-
*
|
|
154
|
-
*
|
|
152
|
+
* has to outlive a working session, never an idle user. It is the backstop and
|
|
153
|
+
* not the usual exit — a terminal-typed prompt, a Done-like answer and the
|
|
154
|
+
* model's `<!-- zeph: exit -->` all delete the file outright. What none of them
|
|
155
|
+
* covers is a session that simply died: there is no SessionEnd hook, so without
|
|
156
|
+
* a TTL a crash or Ctrl-C would latch REMOTE forever.
|
|
155
157
|
*/
|
|
156
158
|
exports.REMOTE_TTL_SEC = 14400;
|
|
157
159
|
/** State path for a project hash: `<stateDir>/remote-active-<cksum(dir)>`. */
|
|
@@ -206,6 +208,23 @@ const touchRemoteActive = (dir, now = Date.now) => {
|
|
|
206
208
|
}
|
|
207
209
|
};
|
|
208
210
|
exports.touchRemoteActive = touchRemoteActive;
|
|
211
|
+
/**
|
|
212
|
+
* Leave REMOTE. Best-effort like its twin: an absent file is success, and the
|
|
213
|
+
* caller is a hook with its own job to finish. Bash twin: gate.sh
|
|
214
|
+
* zeph_remote_clear.
|
|
215
|
+
*/
|
|
216
|
+
const clearRemoteActive = (dir) => {
|
|
217
|
+
const hash = (0, exports.projectHash)(dir);
|
|
218
|
+
if (!hash)
|
|
219
|
+
return;
|
|
220
|
+
try {
|
|
221
|
+
(0, fs_1.unlinkSync)((0, exports.remoteStatePath)(hash));
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
/* already gone, or unwritable — the prompt still goes through */
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
exports.clearRemoteActive = clearRemoteActive;
|
|
209
228
|
/** True when the user ran /zeph-mute for this project. */
|
|
210
229
|
const isMuted = (dir) => {
|
|
211
230
|
const hash = (0, exports.projectHash)(dir);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-hook.d.ts","sourceRoot":"","sources":["../src/remote-hook.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"remote-hook.d.ts","sourceRoot":"","sources":["../src/remote-hook.ts"],"names":[],"mappings":"AAwCA;wEACwE;AACxE,eAAO,MAAM,kBAAkB,8BAA+B,CAAC;AAC/D,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAoClE,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,KAAG,GAAG,IAAI,eACE,CAAC;AAE1D;;;GAGG;AACH,eAAO,MAAM,aAAa,GACxB,OAAO,eAAe,EACtB,OAAO,MAAM,EACb,MAAK,MAAM,CAAC,UAAwB,EACpC,MAAK,MAAM,MAAiB,KAC3B,MAAM,GAAG,IAwCX,CAAC"}
|
package/dist/remote-hook.js
CHANGED
|
@@ -19,11 +19,13 @@ exports.runRemoteHook = exports.isRemoteHookAgent = exports.REMOTE_HOOK_AGENTS =
|
|
|
19
19
|
* never false-match. Parity with the writer (listener.ts writeRemoteMarker)
|
|
20
20
|
* is by construction — both sides share gate.ts remoteDigest/remoteMarkerPath.
|
|
21
21
|
*
|
|
22
|
-
* The marker is one-shot, but REMOTE is not
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
22
|
+
* The marker is one-shot, but REMOTE is not: entry records the mode (gate.ts
|
|
23
|
+
* touchRemoteActive) so it outlives the entry turn and survives compaction.
|
|
24
|
+
* A prompt that reaches this hook without a marker was typed at the terminal —
|
|
25
|
+
* the only way text becomes a prompt without one is the user's own keyboard,
|
|
26
|
+
* since a phone answer to a zeph_ask comes back as a tool_result and never
|
|
27
|
+
* reaches a prompt hook. The user is back, so the mode ends here (gate.ts
|
|
28
|
+
* clearRemoteActive) and the hook says so once.
|
|
27
29
|
*
|
|
28
30
|
* No marker and no live state → null (silent no-op); this hook only ever adds
|
|
29
31
|
* context and must never block a prompt.
|
|
@@ -49,19 +51,19 @@ const HOOK_EVENT_NAME = {
|
|
|
49
51
|
const FRESH_WINDOW_SEC = 900;
|
|
50
52
|
const TWO_WAY_CONTEXT = `# System note (Zeph remote-origin detect)
|
|
51
53
|
|
|
52
|
-
This user message arrived from the user's phone via Zeph agent chat (verified by the listener — exact text match). The user is driving this session remotely and is NOT at the terminal. Enter sticky REMOTE mode now (CORE_RULES Rule 9): end EVERY response with \`zeph_ask\` (buttons + free-text) until the user
|
|
54
|
+
This user message arrived from the user's phone via Zeph agent chat (verified by the listener — exact text match). The user is driving this session remotely and is NOT at the terminal. Enter sticky REMOTE mode now (CORE_RULES Rule 9): end EVERY response with \`zeph_ask\` (buttons + free-text) until the user exits — an exit signal (done/stop/exit), or a prompt they type at the terminal, which this hook will tell you about. Plain-text questions are invisible to them.`;
|
|
53
55
|
const ONE_WAY_CONTEXT = `# System note (Zeph remote-origin detect)
|
|
54
56
|
|
|
55
57
|
This user message arrived from the user's phone via Zeph agent chat (verified by the listener — exact text match), but ZEPH_HOOK_ID is not set, so two-way tools (zeph_ask/zeph_prompt/zeph_input) are unavailable. Make your final message self-contained — the completion push is the user's only feedback channel. If you have not already mentioned it this session, tell the user once that running \`npx @zeph-to/cli setup\` upgrades this into a two-way remote session (buttons + text replies from the phone).`;
|
|
56
58
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
59
|
+
* The first prompt a remote session's user types at the terminal. Emitted
|
|
60
|
+
* once — the state is cleared with it, so every later terminal turn is a
|
|
61
|
+
* silent no-op. Kept in step with the wording in
|
|
62
|
+
* plugin/hooks/zeph-remote.sh.
|
|
61
63
|
*/
|
|
62
|
-
const
|
|
64
|
+
const EXIT_CONTEXT = `# System note (Zeph)
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
The user typed this prompt at the terminal, so this session has LEFT sticky REMOTE mode — answer normally (CORE_RULES Rule 4) and do not end this response with \`zeph_ask\` just to keep the loop alive. Rule 3 still holds: if you actually ask the user something, ask it with \`zeph_ask\`. Re-entry is automatic the moment they send another message from their phone.`;
|
|
65
67
|
const isRemoteHookAgent = (raw) => exports.REMOTE_HOOK_AGENTS.includes(raw);
|
|
66
68
|
exports.isRemoteHookAgent = isRemoteHookAgent;
|
|
67
69
|
/**
|
|
@@ -79,9 +81,7 @@ const runRemoteHook = (agent, stdin, env = process.env, now = Date.now) => {
|
|
|
79
81
|
catch {
|
|
80
82
|
return null;
|
|
81
83
|
}
|
|
82
|
-
// `
|
|
83
|
-
// gets its reminder either way. `cwd` keys every state file, so without it
|
|
84
|
-
// there is nothing to look up.
|
|
84
|
+
// `cwd` keys every state file, so without it there is nothing to look up.
|
|
85
85
|
if (!cwd)
|
|
86
86
|
return null;
|
|
87
87
|
// Mute outranks everything (Rule 12) — stay silent and leave both the marker
|
|
@@ -91,9 +91,11 @@ const runRemoteHook = (agent, stdin, env = process.env, now = Date.now) => {
|
|
|
91
91
|
const emit = (additionalContext) => JSON.stringify({
|
|
92
92
|
hookSpecificOutput: { hookEventName: HOOK_EVENT_NAME[agent], additionalContext },
|
|
93
93
|
});
|
|
94
|
-
// Exactly one additionalContext per invocation
|
|
95
|
-
//
|
|
96
|
-
|
|
94
|
+
// Exactly one additionalContext per invocation, and the verdict decides
|
|
95
|
+
// which: 'phone' enters, 'keyboard' leaves, 'unclear' says nothing and
|
|
96
|
+
// leaves the mode as it was.
|
|
97
|
+
const origin = remoteOrigin(prompt, cwd, now);
|
|
98
|
+
if (origin === 'phone') {
|
|
97
99
|
if (!env.ZEPH_HOOK_ID)
|
|
98
100
|
return emit(ONE_WAY_CONTEXT);
|
|
99
101
|
// Only a two-way session has a mode to stay in — without zeph_ask there is
|
|
@@ -101,35 +103,31 @@ const runRemoteHook = (agent, stdin, env = process.env, now = Date.now) => {
|
|
|
101
103
|
(0, gate_js_1.touchRemoteActive)(cwd, now);
|
|
102
104
|
return emit(TWO_WAY_CONTEXT);
|
|
103
105
|
}
|
|
104
|
-
if (env.ZEPH_HOOK_ID && (0, gate_js_1.isRemoteActive)(cwd, now))
|
|
105
|
-
|
|
106
|
+
if (origin === 'keyboard' && env.ZEPH_HOOK_ID && (0, gate_js_1.isRemoteActive)(cwd, now)) {
|
|
107
|
+
(0, gate_js_1.clearRemoteActive)(cwd);
|
|
108
|
+
return emit(EXIT_CONTEXT);
|
|
109
|
+
}
|
|
106
110
|
return null;
|
|
107
111
|
};
|
|
108
112
|
exports.runRemoteHook = runRemoteHook;
|
|
109
|
-
|
|
110
|
-
* True when this prompt is the phone injection the listener recorded.
|
|
111
|
-
* Consumes the marker on a match, deletes it once stale, and otherwise leaves
|
|
112
|
-
* it in place so a prompt that simply isn't the injected one can still match
|
|
113
|
-
* on a later turn.
|
|
114
|
-
*/
|
|
115
|
-
const matchesRemoteMarker = (prompt, cwd, now) => {
|
|
116
|
-
if (!prompt)
|
|
117
|
-
return false;
|
|
113
|
+
const remoteOrigin = (prompt, cwd, now) => {
|
|
118
114
|
const hash = (0, gate_js_1.projectHash)(cwd);
|
|
119
115
|
if (!hash)
|
|
120
|
-
return
|
|
116
|
+
return 'keyboard';
|
|
121
117
|
const marker = (0, gate_js_1.remoteMarkerPath)(hash);
|
|
122
118
|
let content;
|
|
123
119
|
try {
|
|
124
120
|
content = (0, fs_1.readFileSync)(marker, 'utf-8');
|
|
125
121
|
}
|
|
126
122
|
catch {
|
|
127
|
-
return
|
|
123
|
+
return 'keyboard';
|
|
128
124
|
}
|
|
125
|
+
if (!prompt)
|
|
126
|
+
return 'unclear';
|
|
129
127
|
// Marker format: "<epochSec> <sha256hex>\n" (listener.ts writeRemoteMarker).
|
|
130
128
|
const record = content.match(/^(\d+) ([0-9a-f]{64})\n?$/);
|
|
131
129
|
if (!record)
|
|
132
|
-
return
|
|
130
|
+
return 'keyboard';
|
|
133
131
|
if (Math.floor(now() / 1000) - Number(record[1]) > FRESH_WINDOW_SEC) {
|
|
134
132
|
// Stale markers are dead weight (can never flag) — delete on sight.
|
|
135
133
|
try {
|
|
@@ -138,10 +136,10 @@ const matchesRemoteMarker = (prompt, cwd, now) => {
|
|
|
138
136
|
catch {
|
|
139
137
|
/* best-effort housekeeping */
|
|
140
138
|
}
|
|
141
|
-
return
|
|
139
|
+
return 'keyboard';
|
|
142
140
|
}
|
|
143
141
|
if ((0, gate_js_1.remoteDigest)(prompt) !== record[2])
|
|
144
|
-
return
|
|
142
|
+
return 'unclear';
|
|
145
143
|
// Matched — consume the marker so an identical later prompt (e.g. typed
|
|
146
144
|
// at the terminal) can't re-flag. Housekeeping is not the verdict: the
|
|
147
145
|
// match is valid whether or not the delete lands.
|
|
@@ -151,5 +149,5 @@ const matchesRemoteMarker = (prompt, cwd, now) => {
|
|
|
151
149
|
catch {
|
|
152
150
|
/* emit anyway — the match itself is valid */
|
|
153
151
|
}
|
|
154
|
-
return
|
|
152
|
+
return 'phone';
|
|
155
153
|
};
|
|
@@ -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 = "d063dc8986f501725eea0d51d18be4ed2e16480e99dfba93f2a320dc630d5eea";
|
|
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\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
|
|
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\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`. 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.";
|
|
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\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
|
|
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\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`. 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.";
|
|
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,2yQAAsoQ,CAAC;AAEzqQ,gFAAgF;AAChF,eAAO,MAAM,mBAAmB,2yQAAsoQ,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 = "d063dc8986f501725eea0d51d18be4ed2e16480e99dfba93f2a320dc630d5eea";
|
|
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\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
|
|
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\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`. 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.";
|
|
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\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
|
|
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\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`. 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.";
|