@pushary/agent-hooks 0.87.4 → 0.88.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/data/cursor-plugin/scripts/pushary-gate.mjs +63 -7
  3. package/data/vscode-plugin/scripts/pushary-gate.mjs +56 -18
  4. package/dist/bin/pushary-bell-hook.js +4 -4
  5. package/dist/bin/pushary-bell.js +7 -7
  6. package/dist/bin/pushary-claude.js +9 -9
  7. package/dist/bin/pushary-clean.js +13 -14
  8. package/dist/bin/pushary-codex-bridge.js +6 -6
  9. package/dist/bin/pushary-codex-hook.js +15 -9
  10. package/dist/bin/pushary-codex.js +4 -4
  11. package/dist/bin/pushary-connect.js +6 -6
  12. package/dist/bin/pushary-daemon.js +5 -5
  13. package/dist/bin/pushary-disconnect.js +9 -11
  14. package/dist/bin/pushary-doctor.js +17 -16
  15. package/dist/bin/pushary-elicitation-hook.js +7 -120
  16. package/dist/bin/pushary-gemini-bridge.js +6 -6
  17. package/dist/bin/pushary-gemini-hook.js +15 -9
  18. package/dist/bin/pushary-hook.js +6 -6
  19. package/dist/bin/pushary-login.js +4 -4
  20. package/dist/bin/pushary-logout.js +4 -4
  21. package/dist/bin/pushary-mode.js +3 -3
  22. package/dist/bin/pushary-notification-hook.js +4 -4
  23. package/dist/bin/pushary-opencode-hook.js +5 -5
  24. package/dist/bin/pushary-permission-denied-hook.js +6 -6
  25. package/dist/bin/pushary-permission-hook.js +6 -6
  26. package/dist/bin/pushary-post-hook.js +4 -4
  27. package/dist/bin/pushary-prompt-hook.js +4 -4
  28. package/dist/bin/pushary-session-end-hook.js +4 -4
  29. package/dist/bin/pushary-session-start-hook.js +4 -4
  30. package/dist/bin/pushary-setup.js +26 -25
  31. package/dist/bin/pushary-stats.js +2 -2
  32. package/dist/bin/pushary-status.js +5 -5
  33. package/dist/bin/pushary-stop-hook.js +4 -4
  34. package/dist/bin/pushary-stopfailure-hook.js +4 -4
  35. package/dist/bin/pushary-upgrade.js +9 -11
  36. package/dist/bin/pushary-wait.js +3 -3
  37. package/dist/{chunk-H5PIPIKQ.js → chunk-4FANZF2L.js} +2 -2
  38. package/dist/{chunk-ZGC3W23N.js → chunk-4KMWTOBK.js} +4 -1
  39. package/dist/{chunk-QLMX5LLV.js → chunk-6GBQDZ7K.js} +1 -1
  40. package/dist/{chunk-B7ZBHWOW.js → chunk-AIGDBRIJ.js} +1 -1
  41. package/dist/{chunk-KBS53WOE.js → chunk-AJ4EMW5D.js} +1 -1
  42. package/dist/{chunk-ICERLAE2.js → chunk-DLI5IXSD.js} +1 -1
  43. package/dist/{chunk-YX6ZKCC5.js → chunk-EKNFTIKO.js} +1 -1
  44. package/dist/{chunk-K42RXJPG.js → chunk-F3ZDPL6O.js} +187 -60
  45. package/dist/{chunk-NAPRGHZ3.js → chunk-FRH2QIMY.js} +1 -1
  46. package/dist/{chunk-TV3UFGT7.js → chunk-LNAQWWBP.js} +1 -1
  47. package/dist/{chunk-7J67NYK4.js → chunk-LXTX5CT7.js} +2 -1
  48. package/dist/{chunk-KULUNLRO.js → chunk-O7P7O4VY.js} +3 -7
  49. package/dist/{chunk-VOE4SYPR.js → chunk-OSE7X44J.js} +23 -7
  50. package/dist/{chunk-VIHRP25S.js → chunk-PXEFENML.js} +1 -1
  51. package/dist/{chunk-DMW54VWK.js → chunk-Q6MK2YKV.js} +213 -4
  52. package/dist/{chunk-EJL3PJU7.js → chunk-SHVW5KFR.js} +1 -1
  53. package/dist/{chunk-QXZ2CKRK.js → chunk-SUZF64M4.js} +2 -2
  54. package/dist/{chunk-BFBAHOGR.js → chunk-U44C3ULB.js} +1 -1
  55. package/dist/{chunk-OMA2OZRE.js → chunk-U65EIMUJ.js} +27 -62
  56. package/dist/{chunk-KFAH6KRE.js → chunk-WFFPWJN6.js} +1 -1
  57. package/dist/{chunk-YYTLTEGU.js → chunk-WPDMGNQR.js} +1 -1
  58. package/dist/{chunk-TD3M232T.js → chunk-ZPRF4YLA.js} +1 -1
  59. package/dist/src/index.js +6 -6
  60. package/package.json +1 -1
  61. package/dist/chunk-2JYQAW4B.js +0 -137
  62. /package/dist/{chunk-VT4IVERX.js → chunk-PZA7G4CN.js} +0 -0
@@ -1,6 +1,39 @@
1
1
  // ../contracts/src/index.ts
2
2
  var APPROVAL_MODES = ["push_only", "terminal_only", "push_first", "notify_only"];
3
3
  var isApprovalMode = (value) => typeof value === "string" && APPROVAL_MODES.includes(value);
4
+ var MAX_AGENT_QUESTIONS = 4;
5
+ var parseAgentQuestion = (raw) => {
6
+ const q = raw;
7
+ if (!q || typeof q.question !== "string") return void 0;
8
+ const options = Array.isArray(q.options) ? q.options.flatMap((option) => {
9
+ if (!option || typeof option !== "object") return [];
10
+ const value = option;
11
+ if (typeof value.label !== "string" || value.label.length === 0) return [];
12
+ return [{
13
+ label: value.label,
14
+ ...typeof value.description === "string" ? { description: value.description } : {}
15
+ }];
16
+ }) : [];
17
+ if (options.length < 2) return void 0;
18
+ return {
19
+ question: q.question,
20
+ ...typeof q.header === "string" ? { header: q.header } : {},
21
+ multiSelect: q.multiSelect === true,
22
+ options
23
+ };
24
+ };
25
+ var parseAgentQuestions = (toolInput) => {
26
+ const questions = toolInput.questions;
27
+ if (!Array.isArray(questions) || questions.length === 0) return void 0;
28
+ if (questions.length > MAX_AGENT_QUESTIONS) return void 0;
29
+ const parsed = [];
30
+ for (const raw of questions) {
31
+ const one = parseAgentQuestion(raw);
32
+ if (!one || parsed.some((question) => question.question === one.question)) return void 0;
33
+ parsed.push(one);
34
+ }
35
+ return parsed;
36
+ };
4
37
  var parseAgentQuestionAnswers = (questions, value) => {
5
38
  let parsed;
6
39
  try {
@@ -43,6 +76,8 @@ var INSTALL_SOURCES = [
43
76
  /** The browser onboarding wizard. */
44
77
  "dashboard",
45
78
  /** Reported nothing, or reported something we do not recognise. */
79
+ /** The macOS app pairing a phone from its own onboarding. */
80
+ "mac_app",
46
81
  "unknown"
47
82
  ];
48
83
  var isInstallSource = (value) => typeof value === "string" && INSTALL_SOURCES.includes(value);
@@ -72,6 +107,7 @@ var hookWaitDeadline = (startMs, policyWaitSeconds, agent, nowMs) => {
72
107
  };
73
108
  var hookWaitClamped = (startMs, policyWaitSeconds, agent, nowMs) => startMs + hookMaxWaitSeconds(agent) * 1e3 < nowMs + Math.max(policyWaitSeconds, 0) * 1e3;
74
109
  var effectiveWaitSeconds = (timeoutAction, policySeconds, agent) => timeoutAction === "wait" ? hookMaxWaitSeconds(agent) : policySeconds;
110
+ var HOOK_SOURCES = ["claude", "codex", "gemini", "cursor", "vscode", "opencode"];
75
111
  var AGENT_LABELS_BY_TYPE = {
76
112
  claude_code: "Claude Code",
77
113
  claude_cowork: "Claude Cowork",
@@ -86,6 +122,7 @@ var AGENT_LABELS_BY_TYPE = {
86
122
  unknown: "Agent"
87
123
  };
88
124
  var OPENCODE_APPROVAL_EVENT = "permission.ask";
125
+ var CURSOR_GATE_EVENTS = ["beforeShellExecution", "beforeMCPExecution"];
89
126
  var HOOK_LOCATOR_BINARY = "pushary-bridge";
90
127
  var HOOK_BATCH_MAX_BYTES = 4 * 1024 * 1024;
91
128
  var REPO_KEY_MAX_LENGTH = 200;
@@ -960,8 +997,15 @@ var resolveAutoResolveOrigin = (config, toolName, toolInput, cwd, repoKey, sessi
960
997
  var resolvePolicy = (config, toolName, modeOverride, toolInput, cwd, repoKey, sessionId) => {
961
998
  const args = toolInput ? policyArgForms(toolName, toolInput, cwd) : [];
962
999
  const arg = args[0];
963
- const match = selectGoverningRule(config.policies, toolName, args, repoKey, sessionId);
964
- let base = match?.policy ?? config.policies.find((p) => p.tool === "*") ?? {
1000
+ const governing = (pool) => {
1001
+ if (pool.length === 0) return void 0;
1002
+ const rule = selectGoverningRule(pool, toolName, args, repoKey, sessionId);
1003
+ if (rule) return rule;
1004
+ const wildcard = pool.find((p) => p.tool === "*");
1005
+ return wildcard ? { policy: wildcard, rank: "wildcard" } : void 0;
1006
+ };
1007
+ const match = governing(config.policies.filter((p) => !p.origin)) ?? governing(config.policies.filter((p) => p.origin === "preset")) ?? governing(config.policies.filter((p) => p.origin === "builtin"));
1008
+ let base = match?.policy ?? {
965
1009
  tool: toolName,
966
1010
  timeoutSeconds: config.defaultTimeoutSeconds,
967
1011
  timeoutAction: config.defaultTimeoutAction,
@@ -1013,6 +1057,21 @@ var strictestScopeVerdict = (scope, paths) => {
1013
1057
  return worst;
1014
1058
  };
1015
1059
  var survivesDegraded = (config, toolName, toolInputs, cwd, repoKey, sessionId) => toolInputs.length === 1 && resolveAutoResolveOrigin(config, toolName, toolInputs[0], cwd, repoKey, sessionId) === "safe_readonly";
1060
+ var FULL_DEFER_MODES = /* @__PURE__ */ new Set(["plan", "auto", "dontAsk"]);
1061
+ var ACCEPT_EDITS_DEFER_TOOLS = /* @__PURE__ */ new Set([
1062
+ "Write",
1063
+ "Edit",
1064
+ "MultiEdit",
1065
+ "NotebookEdit"
1066
+ ]);
1067
+ var shouldDeferToNativeMode = (permissionMode, toolName, options = {}) => {
1068
+ if (options.respectPermissionMode === false) return false;
1069
+ if (typeof permissionMode !== "string") return false;
1070
+ if (FULL_DEFER_MODES.has(permissionMode)) return true;
1071
+ if (permissionMode === "acceptEdits" && ACCEPT_EDITS_DEFER_TOOLS.has(toolName)) return true;
1072
+ if (permissionMode === "bypassPermissions") return options.deferBypass === true;
1073
+ return false;
1074
+ };
1016
1075
  var resolveGate = (input) => {
1017
1076
  const { modeState, config, toolName, toolInputs, cwd, repoKey, sessionId } = input;
1018
1077
  if (modeState.kill) return { kind: "kill", reason: KILL_REASON };
@@ -1068,6 +1127,33 @@ var claudePreToolUseOutput = (decision, reason, updatedInput) => ({
1068
1127
  ...updatedInput ? { updatedInput } : {}
1069
1128
  }
1070
1129
  });
1130
+ var PENDING_COMMAND_PREFIX = "The user sent a new instruction from their phone via Pushary:";
1131
+ var pendingCommandEvents = (source) => {
1132
+ switch (source) {
1133
+ case "claude":
1134
+ return ["PostToolUse", "UserPromptSubmit", "Stop"];
1135
+ // Codex carries context on its activity events and NOT on Stop, whose output
1136
+ // schema has no context field at all. Draining for it at the turn boundary
1137
+ // was therefore right to render nothing, and wrong to conclude that Codex
1138
+ // could not be sent a message.
1139
+ case "codex":
1140
+ return ["PostToolUse", "PreToolUse", "UserPromptSubmit"];
1141
+ case "gemini":
1142
+ return ["AfterTool", "BeforeTool", "BeforeAgent"];
1143
+ // Registered events only. Cursor's carriers are `postToolUse` and `stop`,
1144
+ // which this build does not write into the config; the ones it does write
1145
+ // (`afterShellExecution`, `afterFileEdit`, `afterAgentResponse`) are
1146
+ // observational and discard whatever they print.
1147
+ case "cursor":
1148
+ return [];
1149
+ case "vscode":
1150
+ return [];
1151
+ // The prompt is appended through the TUI rather than through a hook return,
1152
+ // so there is nothing for this to print.
1153
+ case "opencode":
1154
+ return [];
1155
+ }
1156
+ };
1071
1157
  var claudePermissionRequestOutput = (decision, reason, updatedInput) => ({
1072
1158
  hookSpecificOutput: {
1073
1159
  hookEventName: "PermissionRequest",
@@ -1103,7 +1189,7 @@ var gateOutputFor = (source, event, decision, reason) => {
1103
1189
  output = decision === "allow" ? { decision: "allow" } : { decision: "deny", reason: reason ?? "Denied from Pushary" };
1104
1190
  } else if (source === "opencode" && event === OPENCODE_APPROVAL_EVENT) {
1105
1191
  output = decision === "deny" ? { status: "deny", reason: reason ?? "Denied from Pushary" } : { status: decision };
1106
- } else if (source === "cursor" && event === "beforeShellExecution") {
1192
+ } else if (source === "cursor" && CURSOR_GATE_EVENTS.includes(event)) {
1107
1193
  output = decision === "allow" ? { permission: "allow" } : decision === "ask" ? { permission: "ask" } : {
1108
1194
  permission: "deny",
1109
1195
  user_message: "Command denied via Pushary.",
@@ -1123,16 +1209,131 @@ var claudeElicitationOutput = (event, action, content) => ({
1123
1209
  }
1124
1210
  });
1125
1211
  var renderElicitationOutput = (event, action, content) => ELICITATION_EVENTS.includes(event) ? JSON.stringify(claudeElicitationOutput(event, action, content)) : void 0;
1212
+ var ELICITATION_ANSWERABLE_MODE = "form";
1213
+ var ELICITATION_MIN_OPTIONS = 2;
1214
+ var ELICITATION_MAX_MULTI_QUESTIONS = 4;
1215
+ var ELICITATION_MAX_MULTI_OPTIONS = 4;
1216
+ var elicitationRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
1217
+ var elicitationLabel = (name, property) => typeof property.title === "string" && property.title.trim() ? property.title.trim() : name;
1218
+ var elicitationEnum = (property) => {
1219
+ if (!Array.isArray(property.enum)) return null;
1220
+ const members = property.enum.filter((member) => typeof member === "string");
1221
+ return members.length === property.enum.length && members.length >= ELICITATION_MIN_OPTIONS ? members : null;
1222
+ };
1223
+ var readElicitationFields = (schema) => {
1224
+ const record = elicitationRecord(schema);
1225
+ const properties = elicitationRecord(record?.properties);
1226
+ if (!properties) return null;
1227
+ const entries = Object.entries(properties);
1228
+ if (entries.length === 0) return null;
1229
+ const fields = [];
1230
+ for (const [name, raw] of entries) {
1231
+ const property = elicitationRecord(raw);
1232
+ if (!property) return null;
1233
+ const label = elicitationLabel(name, property);
1234
+ if ("enum" in property) {
1235
+ const options = elicitationEnum(property);
1236
+ if (!options) return null;
1237
+ fields.push({ kind: "choice", name, label, options });
1238
+ continue;
1239
+ }
1240
+ switch (property.type) {
1241
+ case "string":
1242
+ fields.push({ kind: "text", name, label });
1243
+ break;
1244
+ case "number":
1245
+ fields.push({ kind: "number", name, label, integer: false });
1246
+ break;
1247
+ case "integer":
1248
+ fields.push({ kind: "number", name, label, integer: true });
1249
+ break;
1250
+ case "boolean":
1251
+ fields.push({ kind: "boolean", name, label });
1252
+ break;
1253
+ default:
1254
+ return null;
1255
+ }
1256
+ }
1257
+ return fields;
1258
+ };
1259
+ var elicitationQuestionText = (message, field, alone) => alone ? message : `${message} \u2014 ${field.label}`;
1260
+ var planElicitationAsk = (message, fields) => {
1261
+ if (fields.length === 1) {
1262
+ const field = fields[0];
1263
+ const question = elicitationQuestionText(message, field, true);
1264
+ switch (field.kind) {
1265
+ case "choice":
1266
+ return { kind: "select", field, question, options: field.options };
1267
+ case "boolean":
1268
+ return { kind: "confirm", field, question };
1269
+ case "text":
1270
+ case "number":
1271
+ return { kind: "input", field, question };
1272
+ }
1273
+ }
1274
+ if (fields.length > ELICITATION_MAX_MULTI_QUESTIONS) return null;
1275
+ if (!fields.every((field) => field.kind === "choice" && field.options.length <= ELICITATION_MAX_MULTI_OPTIONS)) return null;
1276
+ const questions = fields.map((field) => ({
1277
+ question: elicitationQuestionText(message, field, false),
1278
+ header: field.label.slice(0, 12),
1279
+ multiSelect: false,
1280
+ options: field.options.map((label) => ({ label }))
1281
+ }));
1282
+ return new Set(questions.map((question) => question.question)).size === questions.length ? { kind: "questions", fields, questions } : null;
1283
+ };
1284
+ var ELICITATION_CONFIRMED = /* @__PURE__ */ new Set(["yes", "true", "approve", "approved", "allow"]);
1285
+ var ELICITATION_REFUSED = /* @__PURE__ */ new Set(["no", "false", "deny", "denied", "reject"]);
1286
+ var elicitationNumber = (raw, integer) => {
1287
+ const trimmed = raw.trim();
1288
+ if (!/^-?\d+(\.\d+)?$/.test(trimmed)) return null;
1289
+ const value = Number(trimmed);
1290
+ if (!Number.isFinite(value)) return null;
1291
+ return integer && !Number.isInteger(value) ? null : value;
1292
+ };
1293
+ var elicitationContent = (ask, answer) => {
1294
+ const value = answer.trim();
1295
+ if (!value) return null;
1296
+ if (ask.kind === "input") {
1297
+ if (ask.field.kind === "number") {
1298
+ const parsed = elicitationNumber(value, ask.field.integer);
1299
+ return parsed === null ? null : { [ask.field.name]: parsed };
1300
+ }
1301
+ return { [ask.field.name]: value };
1302
+ }
1303
+ if (ask.kind === "confirm") {
1304
+ const lowered = value.toLowerCase();
1305
+ if (ELICITATION_CONFIRMED.has(lowered)) return { [ask.field.name]: true };
1306
+ if (ELICITATION_REFUSED.has(lowered)) return { [ask.field.name]: false };
1307
+ return null;
1308
+ }
1309
+ if (ask.kind === "select") {
1310
+ return ask.options.includes(value) ? { [ask.field.name]: value } : null;
1311
+ }
1312
+ return null;
1313
+ };
1314
+ var elicitationContentFromAnswers = (ask, answers) => {
1315
+ const content = {};
1316
+ for (const [index, field] of ask.fields.entries()) {
1317
+ if (field.kind !== "choice") return null;
1318
+ const chosen = answers[ask.questions[index].question];
1319
+ if (typeof chosen !== "string" || !field.options.includes(chosen)) return null;
1320
+ content[field.name] = chosen;
1321
+ }
1322
+ return content;
1323
+ };
1126
1324
 
1127
1325
  export {
1326
+ parseAgentQuestions,
1128
1327
  parseAgentQuestionAnswers,
1129
1328
  normalizeInstallSource,
1130
1329
  HOOK_BUDGETS,
1131
1330
  hookWaitDeadline,
1132
1331
  hookWaitClamped,
1133
1332
  effectiveWaitSeconds,
1333
+ HOOK_SOURCES,
1134
1334
  AGENT_LABELS_BY_TYPE,
1135
1335
  OPENCODE_APPROVAL_EVENT,
1336
+ CURSOR_GATE_EVENTS,
1136
1337
  HOOK_LOCATOR_BINARY,
1137
1338
  normalizeRepoRemote,
1138
1339
  localRepoKey,
@@ -1148,10 +1349,18 @@ export {
1148
1349
  resolveAutoResolveOrigin,
1149
1350
  resolvePolicy,
1150
1351
  KILL_REASON,
1352
+ shouldDeferToNativeMode,
1151
1353
  resolveGate,
1152
1354
  modeStateFromResponse,
1153
1355
  claudePreToolUseOutput,
1356
+ PENDING_COMMAND_PREFIX,
1357
+ pendingCommandEvents,
1154
1358
  claudePermissionRequestOutput,
1155
1359
  gateOutputFor,
1156
- renderElicitationOutput
1360
+ renderElicitationOutput,
1361
+ ELICITATION_ANSWERABLE_MODE,
1362
+ readElicitationFields,
1363
+ planElicitationAsk,
1364
+ elicitationContent,
1365
+ elicitationContentFromAnswers
1157
1366
  };
@@ -11,7 +11,7 @@ import {
11
11
  } from "./chunk-BSZYIAZL.js";
12
12
  import {
13
13
  reportedInstallSource
14
- } from "./chunk-KBS53WOE.js";
14
+ } from "./chunk-AJ4EMW5D.js";
15
15
 
16
16
  // src/reach.ts
17
17
  var reachVerdict = (channels) => {
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  hasGeminiHooks
3
- } from "./chunk-K42RXJPG.js";
3
+ } from "./chunk-F3ZDPL6O.js";
4
4
  import {
5
5
  hasCodexHooks
6
- } from "./chunk-NAPRGHZ3.js";
6
+ } from "./chunk-FRH2QIMY.js";
7
7
 
8
8
  // src/diagnostics/wiring.ts
9
9
  var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  pusharyDir
3
- } from "./chunk-NAPRGHZ3.js";
3
+ } from "./chunk-FRH2QIMY.js";
4
4
  import {
5
5
  readJsonSafe
6
6
  } from "./chunk-6MTNS63X.js";
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  isGatingMoment,
7
7
  recordKeylessMoment
8
- } from "./chunk-VIHRP25S.js";
8
+ } from "./chunk-PXEFENML.js";
9
9
  import {
10
10
  denyReasonFrom,
11
11
  isDeferAnswer
@@ -17,7 +17,7 @@ import {
17
17
  readLastUserPrompt,
18
18
  repoKeyFor,
19
19
  throttlePass
20
- } from "./chunk-VOE4SYPR.js";
20
+ } from "./chunk-OSE7X44J.js";
21
21
  import {
22
22
  DEFAULT_SESSION,
23
23
  askUser,
@@ -33,7 +33,7 @@ import {
33
33
  deriveToolTarget,
34
34
  describeToolCall,
35
35
  scopePathFor
36
- } from "./chunk-7J67NYK4.js";
36
+ } from "./chunk-LXTX5CT7.js";
37
37
  import {
38
38
  getMachineId
39
39
  } from "./chunk-RN3NOEJF.js";
@@ -49,8 +49,10 @@ import {
49
49
  hookWaitClamped,
50
50
  hookWaitDeadline,
51
51
  parseAgentQuestionAnswers,
52
- resolveGate
53
- } from "./chunk-DMW54VWK.js";
52
+ parseAgentQuestions,
53
+ resolveGate,
54
+ shouldDeferToNativeMode
55
+ } from "./chunk-Q6MK2YKV.js";
54
56
 
55
57
  // src/decision-episode.ts
56
58
  var EPISODE_PATH = "/api/agent/decision-episode";
@@ -107,10 +109,10 @@ var handlePushOnly = async (apiKey, description, projectName, timeoutSeconds, ti
107
109
  return void 0;
108
110
  }
109
111
  }
110
- if (result.suppressed) {
112
+ if (result.suppressed || result.status === "terminal") {
111
113
  await cancelQuestion(apiKey, result.correlationId).catch(() => {
112
114
  });
113
- return ask("You are at the keyboard, approve here.");
115
+ return ask(result.suppressed ? "You are at the keyboard, approve here." : "Delivery mode is Terminal, approve here.");
114
116
  }
115
117
  if (result.noDevices) {
116
118
  switch (timeoutAction) {
@@ -300,58 +302,21 @@ var keylessNoticeOnce = (sessionId) => {
300
302
  } catch {
301
303
  }
302
304
  };
303
- var FULL_DEFER_MODES = /* @__PURE__ */ new Set(["plan", "auto", "dontAsk"]);
304
- var ACCEPT_EDITS_DEFER_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
305
- var nativeModeGatingDisabled = () => {
306
- const flag = process.env.PUSHARY_RESPECT_PERMISSION_MODE;
307
- return flag === "0" || flag === "false";
308
- };
309
- var shouldDeferToNativeMode = (permissionMode, toolName) => {
310
- if (nativeModeGatingDisabled()) return false;
311
- if (typeof permissionMode !== "string") return false;
312
- if (FULL_DEFER_MODES.has(permissionMode)) return true;
313
- if (permissionMode === "acceptEdits" && ACCEPT_EDITS_DEFER_TOOLS.has(toolName)) return true;
314
- if (permissionMode === "bypassPermissions") {
315
- const flag = process.env.PUSHARY_DEFER_BYPASS;
316
- return flag === "1" || flag === "true";
317
- }
318
- return false;
319
- };
320
- var MAX_PHONE_QUESTIONS = 4;
321
- var parseQuestion = (raw) => {
322
- const q = raw;
323
- if (!q || typeof q.question !== "string") return void 0;
324
- const options = Array.isArray(q.options) ? q.options.flatMap((option) => {
325
- if (!option || typeof option !== "object") return [];
326
- const value = option;
327
- if (typeof value.label !== "string" || value.label.length === 0) return [];
328
- return [{
329
- label: value.label,
330
- ...typeof value.description === "string" ? { description: value.description } : {}
331
- }];
332
- }) : [];
333
- const labels = options.map((option) => option.label);
334
- if (labels.length < 2) return void 0;
305
+ var nativeDeferOptions = () => {
306
+ const respect = process.env.PUSHARY_RESPECT_PERMISSION_MODE;
307
+ const bypass = process.env.PUSHARY_DEFER_BYPASS;
335
308
  return {
336
- question: q.question,
337
- header: typeof q.header === "string" ? q.header : void 0,
338
- multiSelect: q.multiSelect === true,
339
- options,
340
- labels
309
+ respectPermissionMode: !(respect === "0" || respect === "false"),
310
+ deferBypass: bypass === "1" || bypass === "true"
341
311
  };
342
312
  };
343
- var parseQuestions = (toolInput) => {
344
- const questions = toolInput.questions;
345
- if (!Array.isArray(questions) || questions.length === 0) return void 0;
346
- if (questions.length > MAX_PHONE_QUESTIONS) return void 0;
347
- const parsed = [];
348
- for (const raw of questions) {
349
- const one = parseQuestion(raw);
350
- if (!one || parsed.some((question) => question.question === one.question)) return void 0;
351
- parsed.push(one);
352
- }
353
- return parsed;
354
- };
313
+ var shouldDeferToNativeMode2 = (permissionMode, toolName) => shouldDeferToNativeMode(permissionMode, toolName, nativeDeferOptions());
314
+ var handedToTerminal = (result) => result.suppressed === true || result.noDevices === true || result.status === "terminal";
315
+ var withLabels = (question) => ({
316
+ ...question,
317
+ labels: question.options.map((option) => option.label)
318
+ });
319
+ var parseQuestions = (toolInput) => parseAgentQuestions(toolInput)?.map(withLabels);
355
320
  var questionContext = (question, index, total, project) => {
356
321
  const subject = question.header ? `${question.header}: your agent is asking in ${project}` : `Your agent is asking in ${project}`;
357
322
  return total > 1 ? `${subject} (${index + 1} of ${total})` : subject;
@@ -376,7 +341,7 @@ var askAsOneCard = async (apiKey, input, parsed, projectName, deadline) => {
376
341
  } catch {
377
342
  return void 0;
378
343
  }
379
- if (result.suppressed || result.noDevices) {
344
+ if (handedToTerminal(result)) {
380
345
  await cancelQuestion(apiKey, result.correlationId).catch(() => {
381
346
  });
382
347
  return void 0;
@@ -411,7 +376,7 @@ var askQuestionByQuestion = async (apiKey, input, parsed, projectName, deadline)
411
376
  } catch {
412
377
  return void 0;
413
378
  }
414
- if (result.suppressed || result.noDevices) {
379
+ if (handedToTerminal(result)) {
415
380
  await cancelQuestion(apiKey, result.correlationId).catch(() => {
416
381
  });
417
382
  return void 0;
@@ -461,7 +426,7 @@ var handleExitPlanMode = async (apiKey, input) => {
461
426
  } catch {
462
427
  return void 0;
463
428
  }
464
- if (result.suppressed || result.noDevices) {
429
+ if (handedToTerminal(result)) {
465
430
  await cancelQuestion(apiKey, result.correlationId).catch(() => {
466
431
  });
467
432
  return void 0;
@@ -524,7 +489,7 @@ var handlePreToolUse = async (input) => {
524
489
  if (input.tool_name === "ExitPlanMode") {
525
490
  return handleExitPlanMode(apiKey, input);
526
491
  }
527
- if (shouldDeferToNativeMode(input.permission_mode, input.tool_name)) {
492
+ if (shouldDeferToNativeMode2(input.permission_mode, input.tool_name)) {
528
493
  return void 0;
529
494
  }
530
495
  switch (verdict.kind) {
@@ -555,7 +520,7 @@ var toPermissionRequestOutput = (hookOutput) => {
555
520
  };
556
521
  var handlePermissionRequest = async (input) => {
557
522
  if (PRETOOLUSE_HANDLED_TOOLS.has(input.tool_name)) {
558
- const reclaimDeferredExec = !PRETOOLUSE_PHONE_ANSWERED_TOOLS.includes(input.tool_name) && shouldDeferToNativeMode(input.permission_mode, input.tool_name);
523
+ const reclaimDeferredExec = !PRETOOLUSE_PHONE_ANSWERED_TOOLS.includes(input.tool_name) && shouldDeferToNativeMode2(input.permission_mode, input.tool_name);
559
524
  if (!reclaimDeferredExec) return void 0;
560
525
  }
561
526
  if (input.tool_name.startsWith("mcp__pushary__")) return void 0;
@@ -636,7 +601,7 @@ var handlePermissionDenied = async (input) => {
636
601
  } catch {
637
602
  return void 0;
638
603
  }
639
- if (result.suppressed || result.noDevices) {
604
+ if (handedToTerminal(result)) {
640
605
  await cancelQuestion(apiKey, result.correlationId).catch(() => {
641
606
  });
642
607
  return void 0;
@@ -12,7 +12,7 @@ import {
12
12
  } from "./chunk-2UMNXADU.js";
13
13
  import {
14
14
  isValidApiKey
15
- } from "./chunk-DMW54VWK.js";
15
+ } from "./chunk-Q6MK2YKV.js";
16
16
 
17
17
  // src/session.ts
18
18
  var IDENTITY_PATH = "/api/v1/server/identity";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readConfigFileKey
3
- } from "./chunk-YX6ZKCC5.js";
3
+ } from "./chunk-EKNFTIKO.js";
4
4
 
5
5
  // src/bell/fleet.ts
6
6
  import { closeSync, constants, futimesSync, lstatSync, mkdirSync, openSync, readdirSync, unlinkSync } from "fs";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  describeKeyCheck,
3
3
  keyCheckFromResponse
4
- } from "./chunk-KFAH6KRE.js";
4
+ } from "./chunk-WFFPWJN6.js";
5
5
  import {
6
6
  createIo
7
7
  } from "./chunk-5RUIFDTP.js";
package/dist/src/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-OMA2OZRE.js";
3
+ } from "../chunk-U65EIMUJ.js";
4
4
  import "../chunk-ATBKJNWS.js";
5
- import "../chunk-VIHRP25S.js";
5
+ import "../chunk-PXEFENML.js";
6
6
  import "../chunk-YHG74UFF.js";
7
7
  import {
8
8
  fetchModeOverride,
@@ -12,17 +12,17 @@ import {
12
12
  handlePostToolUse,
13
13
  handleStop,
14
14
  reportEvent
15
- } from "../chunk-VOE4SYPR.js";
15
+ } from "../chunk-OSE7X44J.js";
16
16
  import {
17
17
  askUser,
18
18
  cancelQuestion,
19
19
  waitForAnswer
20
20
  } from "../chunk-J3YDT4HM.js";
21
- import "../chunk-7J67NYK4.js";
21
+ import "../chunk-LXTX5CT7.js";
22
22
  import "../chunk-RN3NOEJF.js";
23
23
  import "../chunk-BSZYIAZL.js";
24
24
  import "../chunk-SAF6HGAA.js";
25
- import "../chunk-KBS53WOE.js";
25
+ import "../chunk-AJ4EMW5D.js";
26
26
  import "../chunk-7QLSKOSU.js";
27
27
  import "../chunk-KZERVKTD.js";
28
28
  import {
@@ -31,7 +31,7 @@ import {
31
31
  } from "../chunk-2UMNXADU.js";
32
32
  import {
33
33
  resolvePolicy
34
- } from "../chunk-DMW54VWK.js";
34
+ } from "../chunk-Q6MK2YKV.js";
35
35
  export {
36
36
  askUser,
37
37
  cancelQuestion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.87.4",
3
+ "version": "0.88.1",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",