@pushary/agent-hooks 0.86.0 → 0.87.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/CHANGELOG.md +9 -0
- package/data/SKILL.md +31 -11
- package/data/cursor-plugin/.cursor-plugin/plugin.json +1 -1
- package/data/cursor-plugin/rules/pushary.mdc +2 -2
- package/data/cursor-plugin/skills/pushary/SKILL.md +30 -10
- package/data/vscode-plugin/.claude-plugin/plugin.json +1 -1
- package/data/vscode-plugin/skills/pushary/SKILL.md +30 -10
- package/dist/bin/pushary-bell-hook.js +4 -4
- package/dist/bin/pushary-bell.js +6 -6
- package/dist/bin/pushary-claude.js +12 -10
- package/dist/bin/pushary-clean.js +6 -6
- package/dist/bin/pushary-codex-bridge.js +5 -5
- package/dist/bin/pushary-codex-hook.js +13 -29
- package/dist/bin/pushary-codex.js +6 -4
- package/dist/bin/pushary-connect.js +6 -6
- package/dist/bin/pushary-daemon.js +4 -4
- package/dist/bin/pushary-disconnect.js +22 -8
- package/dist/bin/pushary-doctor.js +21 -15
- package/dist/bin/pushary-elicitation-hook.d.ts +1 -0
- package/dist/bin/pushary-elicitation-hook.js +210 -0
- package/dist/bin/pushary-gemini-bridge.js +5 -5
- package/dist/bin/pushary-gemini-hook.js +10 -8
- package/dist/bin/pushary-hook.js +6 -5
- package/dist/bin/pushary-login.js +4 -4
- package/dist/bin/pushary-logout.js +4 -4
- package/dist/bin/pushary-mode.js +3 -3
- package/dist/bin/pushary-notification-hook.js +4 -3
- package/dist/bin/pushary-permission-denied-hook.js +6 -5
- package/dist/bin/pushary-permission-hook.js +6 -5
- package/dist/bin/pushary-post-hook.js +4 -3
- package/dist/bin/pushary-prompt-hook.js +4 -3
- package/dist/bin/pushary-session-end-hook.js +4 -3
- package/dist/bin/pushary-session-start-hook.js +4 -3
- package/dist/bin/pushary-setup.js +19 -15
- package/dist/bin/pushary-stats.js +2 -2
- package/dist/bin/pushary-status.js +5 -5
- package/dist/bin/pushary-stop-hook.js +4 -3
- package/dist/bin/pushary-stopfailure-hook.js +4 -3
- package/dist/bin/pushary-upgrade.js +8 -7
- package/dist/bin/pushary-wait.js +3 -3
- package/dist/{chunk-5EUZYWID.js → chunk-2T3GX7WG.js} +1 -1
- package/dist/{chunk-GK4CED6Z.js → chunk-5646VKR4.js} +7 -3
- package/dist/{chunk-MNQI6ZZU.js → chunk-7MBDFNUC.js} +1 -1
- package/dist/{chunk-CY4TZK5O.js → chunk-7YJS2VTG.js} +31 -4
- package/dist/{chunk-PZR4XMFU.js → chunk-A3PFIWWN.js} +1 -1
- package/dist/{chunk-2TZCZ7CL.js → chunk-A4I5JGCW.js} +1 -1
- package/dist/{chunk-VPJAAGD4.js → chunk-B3V2H6SA.js} +37 -17
- package/dist/{chunk-XPAGN3SC.js → chunk-ECEUMWBH.js} +1 -1
- package/dist/{chunk-2255DZ74.js → chunk-HAUTLCMA.js} +1 -1
- package/dist/{chunk-S3UD7O35.js → chunk-HJ44HIUW.js} +12 -28
- package/dist/chunk-J3YDT4HM.js +128 -0
- package/dist/{chunk-N52CIK47.js → chunk-JAICP7BA.js} +1 -1
- package/dist/{chunk-QW27ZFA4.js → chunk-LT2VLPUK.js} +2 -2
- package/dist/{chunk-HSGEEBBL.js → chunk-M7RYIZX6.js} +3 -3
- package/dist/{chunk-7G5VO76N.js → chunk-OT4YD54Q.js} +1 -1
- package/dist/{chunk-VZNDR3Z7.js → chunk-QBWYNVKY.js} +1 -1
- package/dist/{chunk-MTKR4YLW.js → chunk-SJVLFIYP.js} +1 -1
- package/dist/{chunk-33T5OOKP.js → chunk-V2XPF77V.js} +96 -161
- package/dist/{chunk-FOY5FISQ.js → chunk-WLLBFDVY.js} +1 -1
- package/dist/{chunk-Q7N3XGWM.js → chunk-XW5CHAQX.js} +2 -2
- package/dist/{chunk-7C5CJC3H.js → chunk-Y3UTTFTZ.js} +1 -1
- package/dist/{chunk-2C6B6V2U.js → chunk-ZZE6FN7T.js} +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +10 -8
- package/package.json +3 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
HOOK_BUDGETS,
|
|
3
3
|
HOOK_LOCATOR_BINARY
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-7YJS2VTG.js";
|
|
5
5
|
|
|
6
6
|
// src/codex-config.ts
|
|
7
7
|
import { createHash } from "crypto";
|
|
@@ -61,13 +61,13 @@ var readCodexMcpAuth = (config) => {
|
|
|
61
61
|
if (typeof pushary.bearer_token === "string") return { method: "legacy-bearer" };
|
|
62
62
|
return void 0;
|
|
63
63
|
};
|
|
64
|
+
var isPusharyCommand = (hook) => {
|
|
65
|
+
const command = String(asRecord(hook)?.command ?? "");
|
|
66
|
+
return command.includes(CODEX_HOOK_BINARY) || command.includes(HOOK_LOCATOR_BINARY);
|
|
67
|
+
};
|
|
64
68
|
var isPusharyCodexHook = (entry) => {
|
|
65
69
|
const hooks = asRecord(entry)?.hooks;
|
|
66
|
-
|
|
67
|
-
return hooks.some((hook) => {
|
|
68
|
-
const command = String(asRecord(hook)?.command ?? "");
|
|
69
|
-
return command.includes(CODEX_HOOK_BINARY) || command.includes(HOOK_LOCATOR_BINARY);
|
|
70
|
-
});
|
|
70
|
+
return Array.isArray(hooks) && hooks.some(isPusharyCommand);
|
|
71
71
|
};
|
|
72
72
|
var addCodexHooks = (config, command) => {
|
|
73
73
|
const hooks = ensureRecord(config, "hooks");
|
|
@@ -134,13 +134,28 @@ var codexHookTrustHash = (definition, command) => {
|
|
|
134
134
|
if (definition.matcher) identity.matcher = definition.matcher;
|
|
135
135
|
return "sha256:" + createHash("sha256").update(canonicalJson(identity), "utf8").digest("hex");
|
|
136
136
|
};
|
|
137
|
-
var
|
|
138
|
-
|
|
137
|
+
var codexHookPositions = (hooksConfig) => {
|
|
138
|
+
const hooks = asRecord(hooksConfig?.hooks);
|
|
139
|
+
const positions = {};
|
|
140
|
+
for (const definition of CODEX_HOOK_EVENTS) {
|
|
141
|
+
const entries = hooks?.[definition.event];
|
|
142
|
+
if (!Array.isArray(entries)) continue;
|
|
143
|
+
const entryIndex = entries.findIndex(isPusharyCodexHook);
|
|
144
|
+
if (entryIndex < 0) continue;
|
|
145
|
+
const inner = asRecord(entries[entryIndex])?.hooks;
|
|
146
|
+
const hookIndex = Array.isArray(inner) ? inner.findIndex(isPusharyCommand) : -1;
|
|
147
|
+
positions[definition.event] = [entryIndex, Math.max(hookIndex, 0)];
|
|
148
|
+
}
|
|
149
|
+
return positions;
|
|
150
|
+
};
|
|
151
|
+
var codexHookStateKey = (hooksJsonPath, event, position) => `${hooksJsonPath}:${CODEX_EVENT_KEY[event]}:${position[0]}:${position[1]}`;
|
|
152
|
+
var untrustedCodexHookEvents = (config, hooksJsonPath, command, positions) => {
|
|
139
153
|
const state = asRecord(asRecord(config.hooks)?.state);
|
|
140
154
|
return CODEX_HOOK_EVENTS.filter((definition) => {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
155
|
+
const position = positions[definition.event];
|
|
156
|
+
if (!position) return false;
|
|
157
|
+
const entry = asRecord(state?.[codexHookStateKey(hooksJsonPath, definition.event, position)]);
|
|
158
|
+
return entry?.trusted_hash !== codexHookTrustHash(definition, command);
|
|
144
159
|
}).map((definition) => definition.event);
|
|
145
160
|
};
|
|
146
161
|
var removeCodexMcpServer = (config) => {
|
|
@@ -150,12 +165,14 @@ var removeCodexMcpServer = (config) => {
|
|
|
150
165
|
if (Object.keys(servers).length === 0) delete config.mcp_servers;
|
|
151
166
|
return true;
|
|
152
167
|
};
|
|
153
|
-
var removeCodexHookTrust = (config, hooksJsonPath) => {
|
|
168
|
+
var removeCodexHookTrust = (config, hooksJsonPath, positions) => {
|
|
154
169
|
const state = asRecord(asRecord(config.hooks)?.state);
|
|
155
170
|
if (!state) return false;
|
|
156
171
|
let changed = false;
|
|
157
172
|
for (const definition of CODEX_HOOK_EVENTS) {
|
|
158
|
-
const
|
|
173
|
+
const position = positions[definition.event];
|
|
174
|
+
if (!position) continue;
|
|
175
|
+
const key = codexHookStateKey(hooksJsonPath, definition.event, position);
|
|
159
176
|
if (key in state) {
|
|
160
177
|
delete state[key];
|
|
161
178
|
changed = true;
|
|
@@ -170,16 +187,18 @@ var removeCodexHookTrust = (config, hooksJsonPath) => {
|
|
|
170
187
|
}
|
|
171
188
|
return changed;
|
|
172
189
|
};
|
|
173
|
-
var removeCodexSettings = (config, hooksJsonPath) => {
|
|
190
|
+
var removeCodexSettings = (config, hooksJsonPath, positions) => {
|
|
174
191
|
const mcp = removeCodexMcpServer(config);
|
|
175
|
-
const trust = removeCodexHookTrust(config, hooksJsonPath);
|
|
192
|
+
const trust = removeCodexHookTrust(config, hooksJsonPath, positions);
|
|
176
193
|
return mcp || trust;
|
|
177
194
|
};
|
|
178
|
-
var addCodexHookTrust = (config, hooksJsonPath, command) => {
|
|
195
|
+
var addCodexHookTrust = (config, hooksJsonPath, command, positions) => {
|
|
179
196
|
const hooks = ensureRecord(config, "hooks");
|
|
180
197
|
const state = ensureRecord(hooks, "state");
|
|
181
198
|
for (const definition of CODEX_HOOK_EVENTS) {
|
|
182
|
-
const
|
|
199
|
+
const position = positions[definition.event];
|
|
200
|
+
if (!position) continue;
|
|
201
|
+
const key = codexHookStateKey(hooksJsonPath, definition.event, position);
|
|
183
202
|
const existing = asRecord(state[key]) ?? {};
|
|
184
203
|
state[key] = { ...existing, trusted_hash: codexHookTrustHash(definition, command) };
|
|
185
204
|
}
|
|
@@ -218,6 +237,7 @@ export {
|
|
|
218
237
|
removeCodexHooks,
|
|
219
238
|
hasCodexHooks,
|
|
220
239
|
missingCodexHookEvents,
|
|
240
|
+
codexHookPositions,
|
|
221
241
|
untrustedCodexHookEvents,
|
|
222
242
|
removeCodexSettings,
|
|
223
243
|
addCodexHookTrust,
|
|
@@ -5,15 +5,12 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
isGatingMoment,
|
|
7
7
|
recordKeylessMoment
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-7MBDFNUC.js";
|
|
9
9
|
import {
|
|
10
10
|
denyReasonFrom,
|
|
11
11
|
isDeferAnswer
|
|
12
12
|
} from "./chunk-YHG74UFF.js";
|
|
13
13
|
import {
|
|
14
|
-
DEFAULT_SESSION,
|
|
15
|
-
askUser,
|
|
16
|
-
cancelQuestion,
|
|
17
14
|
deriveAction,
|
|
18
15
|
deriveActionBody,
|
|
19
16
|
deriveBlocker,
|
|
@@ -24,12 +21,17 @@ import {
|
|
|
24
21
|
readLastPrompt,
|
|
25
22
|
readLastUserPrompt,
|
|
26
23
|
repoKeyFor,
|
|
27
|
-
savePendingQuestion,
|
|
28
24
|
scopePathFor,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
throttlePass
|
|
26
|
+
} from "./chunk-V2XPF77V.js";
|
|
27
|
+
import {
|
|
28
|
+
DEFAULT_SESSION,
|
|
29
|
+
askUser,
|
|
30
|
+
cancelQuestion,
|
|
31
|
+
pollForAnswer,
|
|
32
|
+
savePendingQuestion,
|
|
33
|
+
sendNotification
|
|
34
|
+
} from "./chunk-J3YDT4HM.js";
|
|
33
35
|
import {
|
|
34
36
|
getMachineId
|
|
35
37
|
} from "./chunk-RN3NOEJF.js";
|
|
@@ -46,7 +48,7 @@ import {
|
|
|
46
48
|
hookWaitDeadline,
|
|
47
49
|
parseAgentQuestionAnswers,
|
|
48
50
|
resolveGate
|
|
49
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-7YJS2VTG.js";
|
|
50
52
|
|
|
51
53
|
// src/decision-episode.ts
|
|
52
54
|
var EPISODE_PATH = "/api/agent/decision-episode";
|
|
@@ -74,29 +76,11 @@ var reportDecisionEpisode = async (apiKey, input) => {
|
|
|
74
76
|
import { basename, join } from "path";
|
|
75
77
|
import { tmpdir } from "os";
|
|
76
78
|
import { existsSync, writeFileSync } from "fs";
|
|
77
|
-
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
78
79
|
var START_MS = Date.now();
|
|
79
80
|
var allow = () => claudePreToolUseOutput("allow");
|
|
80
81
|
var deny = (reason) => claudePreToolUseOutput("deny", reason);
|
|
81
82
|
var allowWithInput = (updatedInput) => claudePreToolUseOutput("allow", void 0, updatedInput);
|
|
82
83
|
var ask = (reason) => claudePreToolUseOutput("ask", reason);
|
|
83
|
-
var pollForAnswer = async (apiKey, correlationId, deadlineMs, pollInterval = 2e3) => {
|
|
84
|
-
while (Date.now() < deadlineMs) {
|
|
85
|
-
const remaining = Math.min(Math.max(deadlineMs - Date.now(), 1e3), 3e4);
|
|
86
|
-
let answer;
|
|
87
|
-
try {
|
|
88
|
-
answer = await waitForAnswer(apiKey, correlationId, remaining);
|
|
89
|
-
} catch {
|
|
90
|
-
if (Date.now() + pollInterval >= deadlineMs) break;
|
|
91
|
-
await sleep(pollInterval);
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
if (answer.answered) return answer;
|
|
95
|
-
if (Date.now() + pollInterval >= deadlineMs) break;
|
|
96
|
-
await sleep(pollInterval);
|
|
97
|
-
}
|
|
98
|
-
return { answered: false };
|
|
99
|
-
};
|
|
100
84
|
var handlePushOnly = async (apiKey, description, projectName, timeoutSeconds, timeoutAction, sessionId, machineId, toolName, toolTarget, decision, signals) => {
|
|
101
85
|
let result;
|
|
102
86
|
try {
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {
|
|
2
|
+
callMcpTool
|
|
3
|
+
} from "./chunk-BSZYIAZL.js";
|
|
4
|
+
import {
|
|
5
|
+
installProxyDispatcher
|
|
6
|
+
} from "./chunk-SAF6HGAA.js";
|
|
7
|
+
|
|
8
|
+
// src/api.ts
|
|
9
|
+
import { randomUUID } from "crypto";
|
|
10
|
+
|
|
11
|
+
// src/validate.ts
|
|
12
|
+
var isPolicyConfig = (data) => {
|
|
13
|
+
if (!data || typeof data !== "object") return false;
|
|
14
|
+
const d = data;
|
|
15
|
+
return Array.isArray(d.policies) && typeof d.defaultTimeoutSeconds === "number" && typeof d.defaultTimeoutAction === "string";
|
|
16
|
+
};
|
|
17
|
+
var isAskUserResponse = (data) => {
|
|
18
|
+
if (!data || typeof data !== "object") return false;
|
|
19
|
+
const d = data;
|
|
20
|
+
return typeof d.correlationId === "string" && typeof d.status === "string";
|
|
21
|
+
};
|
|
22
|
+
var isWaitForAnswerResponse = (data) => {
|
|
23
|
+
if (!data || typeof data !== "object") return false;
|
|
24
|
+
const d = data;
|
|
25
|
+
return typeof d.answered === "boolean";
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// src/api.ts
|
|
29
|
+
installProxyDispatcher();
|
|
30
|
+
var askUser = async (apiKey, params, timeoutMs = 15e3) => {
|
|
31
|
+
const identified = params.toolUseId || params.requestId ? params : { ...params, requestId: randomUUID() };
|
|
32
|
+
const result = await callMcpTool(apiKey, "ask_user", { ...identified, wait: false }, { maxRetries: 3, timeoutMs });
|
|
33
|
+
if (!isAskUserResponse(result)) throw new Error("Invalid ask_user response");
|
|
34
|
+
return result;
|
|
35
|
+
};
|
|
36
|
+
var waitForAnswer = async (apiKey, correlationId, timeoutMs = 3e4) => {
|
|
37
|
+
const result = await callMcpTool(apiKey, "wait_for_answer", {
|
|
38
|
+
correlationId,
|
|
39
|
+
timeoutMs
|
|
40
|
+
}, { timeoutMs: timeoutMs + 1e4 });
|
|
41
|
+
if (!isWaitForAnswerResponse(result)) throw new Error("Invalid wait_for_answer response");
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
var pollForAnswer = async (apiKey, correlationId, deadlineMs, pollInterval = 2e3) => {
|
|
45
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
46
|
+
while (Date.now() < deadlineMs) {
|
|
47
|
+
const remaining = Math.min(Math.max(deadlineMs - Date.now(), 1e3), 3e4);
|
|
48
|
+
let answer;
|
|
49
|
+
try {
|
|
50
|
+
answer = await waitForAnswer(apiKey, correlationId, remaining);
|
|
51
|
+
} catch {
|
|
52
|
+
if (Date.now() + pollInterval >= deadlineMs) break;
|
|
53
|
+
await sleep(pollInterval);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (answer.answered) return answer;
|
|
57
|
+
if (Date.now() + pollInterval >= deadlineMs) break;
|
|
58
|
+
await sleep(pollInterval);
|
|
59
|
+
}
|
|
60
|
+
return { answered: false };
|
|
61
|
+
};
|
|
62
|
+
var cancelQuestion = async (apiKey, correlationId) => {
|
|
63
|
+
await callMcpTool(apiKey, "cancel_question", { correlationId });
|
|
64
|
+
};
|
|
65
|
+
var sendNotification = async (apiKey, params, timeoutMs = 15e3) => {
|
|
66
|
+
await callMcpTool(apiKey, "send_notification", { ...params }, { maxRetries: 3, timeoutMs });
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// src/pending.ts
|
|
70
|
+
import { join } from "path";
|
|
71
|
+
import { tmpdir } from "os";
|
|
72
|
+
import { existsSync, mkdirSync, writeFileSync, readdirSync, unlinkSync, rmSync, statSync } from "fs";
|
|
73
|
+
var PENDING_DIR = join(tmpdir(), "pushary-pending");
|
|
74
|
+
var DEFAULT_SESSION = "_no_session";
|
|
75
|
+
var GRACE_MS = 10 * 60 * 1e3;
|
|
76
|
+
var sanitize = (sessionId) => sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128) || DEFAULT_SESSION;
|
|
77
|
+
var dirFor = (sessionId) => join(PENDING_DIR, sanitize(sessionId));
|
|
78
|
+
var isDefaultSession = (sessionId) => sanitize(sessionId) === DEFAULT_SESSION;
|
|
79
|
+
var savePendingQuestion = (sessionId, correlationId) => {
|
|
80
|
+
const dir = dirFor(sessionId);
|
|
81
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
82
|
+
writeFileSync(join(dir, correlationId), "", "utf-8");
|
|
83
|
+
};
|
|
84
|
+
var listPendingQuestions = (sessionId) => {
|
|
85
|
+
const dir = dirFor(sessionId);
|
|
86
|
+
let files;
|
|
87
|
+
try {
|
|
88
|
+
files = readdirSync(dir);
|
|
89
|
+
} catch {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
if (!isDefaultSession(sessionId)) return files;
|
|
93
|
+
const cutoff = Date.now() - GRACE_MS;
|
|
94
|
+
return files.filter((name) => {
|
|
95
|
+
try {
|
|
96
|
+
return statSync(join(dir, name)).mtimeMs < cutoff;
|
|
97
|
+
} catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
var removePendingQuestion = (sessionId, correlationId) => {
|
|
103
|
+
try {
|
|
104
|
+
unlinkSync(join(dirFor(sessionId), correlationId));
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
var removePendingSession = (sessionId) => {
|
|
109
|
+
try {
|
|
110
|
+
rmSync(dirFor(sessionId), { recursive: true, force: true });
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export {
|
|
116
|
+
isPolicyConfig,
|
|
117
|
+
askUser,
|
|
118
|
+
waitForAnswer,
|
|
119
|
+
pollForAnswer,
|
|
120
|
+
cancelQuestion,
|
|
121
|
+
sendNotification,
|
|
122
|
+
DEFAULT_SESSION,
|
|
123
|
+
isDefaultSession,
|
|
124
|
+
savePendingQuestion,
|
|
125
|
+
listPendingQuestions,
|
|
126
|
+
removePendingQuestion,
|
|
127
|
+
removePendingSession
|
|
128
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
codexHomeFrom
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-B3V2H6SA.js";
|
|
4
4
|
|
|
5
5
|
// src/vscode-config.ts
|
|
6
6
|
import { homedir } from "os";
|
|
@@ -259,7 +259,7 @@ You have Pushary MCP tools that reach the user on their phone. Use them proactiv
|
|
|
259
259
|
- Use \`cancel_question\` to retract a pending question once it is no longer needed.
|
|
260
260
|
- Respect the user's Pushary delivery mode: it decides where an approval is surfaced (phone, terminal, or awareness only), not whether it is required. In Terminal mode do not expect a phone answer; the approval waits in the terminal. If the Pushary hook already gated an action, a single approval is enough, do not ask again.
|
|
261
261
|
|
|
262
|
-
Pass \`agentName\` as "${label} - <project folder name>" so the user knows which session is asking. If a question times out
|
|
262
|
+
Pass \`agentName\` as "${label} - <project folder name>" so the user knows which session is asking. If a question times out, follow \`handoffAction\` when present, otherwise \`nextAction\`: poll once, then cancel a live phone question before asking in the current client. If cancellation returns \`handoffAction: "stop"\`, stop. Otherwise, if cancellation returns false, poll once for 1 second and honor the answer that won the race.`;
|
|
263
263
|
var renderAgentInstructions = (label) => instructionBody(label);
|
|
264
264
|
var KEYLESS_FALLBACK_LINE = "This repo uses Pushary phone approvals. Your teammates get agent questions on their lock screens; you do not have a key yet, so questions will fall back to the terminal. Get your own key (3-day trial): https://pushary.com/sign-up?utm_source=agents-md&utm_medium=teammate";
|
|
265
265
|
var renderProjectAgentInstructions = (label) => `${instructionBody(label)}
|
|
@@ -7,9 +7,9 @@ var HOOK_SHELL = "/bin/sh -c";
|
|
|
7
7
|
var LOCATE = 'B=$(command -v "$0") || exit 0; [ -f "$B" ] || exit 0; [ -x "$B" ] || exit 0';
|
|
8
8
|
var NODE_PRESENT = "command -v node >/dev/null 2>&1 || exit 0";
|
|
9
9
|
var HOOK_GUARD_SCRIPTS = {
|
|
10
|
-
native_binary: `${LOCATE}; exec "$B"`,
|
|
11
|
-
node_binary: `${LOCATE}; ${NODE_PRESENT}; exec "$B"`,
|
|
12
|
-
node_script: `[ -f "$0" ] || exit 0; ${NODE_PRESENT}; exec node "$0"`
|
|
10
|
+
native_binary: `${LOCATE}; exec "$B" "$@"`,
|
|
11
|
+
node_binary: `${LOCATE}; ${NODE_PRESENT}; exec "$B" "$@"`,
|
|
12
|
+
node_script: `[ -f "$0" ] || exit 0; ${NODE_PRESENT}; exec node "$0" "$@"`
|
|
13
13
|
};
|
|
14
14
|
var BINARY_GUARDS = {
|
|
15
15
|
native: "native_binary",
|