pushary 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1943 -0
- package/LICENSE +21 -0
- package/README.md +330 -0
- package/data/SKILL.md +591 -0
- package/data/cowork/SKILL.md +77 -0
- package/data/cursor-plugin/.cursor-plugin/plugin.json +25 -0
- package/data/cursor-plugin/CHANGELOG.md +70 -0
- package/data/cursor-plugin/CONTRIBUTING.md +40 -0
- package/data/cursor-plugin/LICENSE +21 -0
- package/data/cursor-plugin/README.md +113 -0
- package/data/cursor-plugin/SECURITY.md +33 -0
- package/data/cursor-plugin/assets/logo.png +0 -0
- package/data/cursor-plugin/commands/notify-when-done.md +14 -0
- package/data/cursor-plugin/commands/pushary-test.md +13 -0
- package/data/cursor-plugin/hooks/hooks.json +75 -0
- package/data/cursor-plugin/mcp.json +11 -0
- package/data/cursor-plugin/rules/pushary.mdc +40 -0
- package/data/cursor-plugin/scripts/pushary-gate.mjs +724 -0
- package/data/cursor-plugin/scripts/pushary-gate.test.mjs +137 -0
- package/data/cursor-plugin/scripts/redaction.mjs +51 -0
- package/data/cursor-plugin/skills/pushary/SKILL.md +584 -0
- package/data/vscode-plugin/.claude-plugin/plugin.json +28 -0
- package/data/vscode-plugin/.mcp.json +11 -0
- package/data/vscode-plugin/CHANGELOG.md +49 -0
- package/data/vscode-plugin/CONTRIBUTING.md +40 -0
- package/data/vscode-plugin/LICENSE +21 -0
- package/data/vscode-plugin/README.md +128 -0
- package/data/vscode-plugin/SECURITY.md +56 -0
- package/data/vscode-plugin/assets/logo.png +0 -0
- package/data/vscode-plugin/commands/notify-when-done.md +14 -0
- package/data/vscode-plugin/commands/pushary-test.md +13 -0
- package/data/vscode-plugin/hooks/hooks.json +60 -0
- package/data/vscode-plugin/scripts/pushary-gate.mjs +762 -0
- package/data/vscode-plugin/scripts/redaction.mjs +51 -0
- package/data/vscode-plugin/skills/pushary/SKILL.md +584 -0
- package/dist/bin/pushary-bell-hook.d.ts +1 -0
- package/dist/bin/pushary-bell-hook.js +46 -0
- package/dist/bin/pushary-bell.d.ts +1 -0
- package/dist/bin/pushary-bell.js +146 -0
- package/dist/bin/pushary-claude.d.ts +1 -0
- package/dist/bin/pushary-claude.js +1916 -0
- package/dist/bin/pushary-clean.d.ts +1 -0
- package/dist/bin/pushary-clean.js +1300 -0
- package/dist/bin/pushary-codex-bridge.d.ts +1 -0
- package/dist/bin/pushary-codex-bridge.js +599 -0
- package/dist/bin/pushary-codex-hook.d.ts +1 -0
- package/dist/bin/pushary-codex-hook.js +886 -0
- package/dist/bin/pushary-codex.d.ts +1 -0
- package/dist/bin/pushary-codex.js +125 -0
- package/dist/bin/pushary-connect.d.ts +1 -0
- package/dist/bin/pushary-connect.js +100 -0
- package/dist/bin/pushary-cowork.d.ts +1 -0
- package/dist/bin/pushary-cowork.js +36 -0
- package/dist/bin/pushary-daemon-supervisor.d.ts +1 -0
- package/dist/bin/pushary-daemon-supervisor.js +27 -0
- package/dist/bin/pushary-daemon.d.ts +1 -0
- package/dist/bin/pushary-daemon.js +878 -0
- package/dist/bin/pushary-disconnect.d.ts +1 -0
- package/dist/bin/pushary-disconnect.js +264 -0
- package/dist/bin/pushary-doctor.d.ts +1 -0
- package/dist/bin/pushary-doctor.js +1433 -0
- package/dist/bin/pushary-elicitation-hook.d.ts +1 -0
- package/dist/bin/pushary-elicitation-hook.js +149 -0
- package/dist/bin/pushary-gemini-bridge.d.ts +1 -0
- package/dist/bin/pushary-gemini-bridge.js +339 -0
- package/dist/bin/pushary-gemini-hook.d.ts +1 -0
- package/dist/bin/pushary-gemini-hook.js +381 -0
- package/dist/bin/pushary-hook.d.ts +1 -0
- package/dist/bin/pushary-hook.js +95 -0
- package/dist/bin/pushary-login.d.ts +1 -0
- package/dist/bin/pushary-login.js +198 -0
- package/dist/bin/pushary-logout.d.ts +1 -0
- package/dist/bin/pushary-logout.js +147 -0
- package/dist/bin/pushary-mcp.d.ts +1 -0
- package/dist/bin/pushary-mcp.js +114 -0
- package/dist/bin/pushary-mode.d.ts +1 -0
- package/dist/bin/pushary-mode.js +130 -0
- package/dist/bin/pushary-notification-hook.d.ts +1 -0
- package/dist/bin/pushary-notification-hook.js +56 -0
- package/dist/bin/pushary-opencode-hook.d.ts +1 -0
- package/dist/bin/pushary-opencode-hook.js +338 -0
- package/dist/bin/pushary-permission-denied-hook.d.ts +1 -0
- package/dist/bin/pushary-permission-denied-hook.js +60 -0
- package/dist/bin/pushary-permission-hook.d.ts +1 -0
- package/dist/bin/pushary-permission-hook.js +65 -0
- package/dist/bin/pushary-post-hook.d.ts +1 -0
- package/dist/bin/pushary-post-hook.js +70 -0
- package/dist/bin/pushary-prompt-hook.d.ts +1 -0
- package/dist/bin/pushary-prompt-hook.js +61 -0
- package/dist/bin/pushary-session-end-hook.d.ts +1 -0
- package/dist/bin/pushary-session-end-hook.js +59 -0
- package/dist/bin/pushary-session-start-hook.d.ts +1 -0
- package/dist/bin/pushary-session-start-hook.js +72 -0
- package/dist/bin/pushary-setup.d.ts +1 -0
- package/dist/bin/pushary-setup.js +2660 -0
- package/dist/bin/pushary-stats.d.ts +1 -0
- package/dist/bin/pushary-stats.js +44 -0
- package/dist/bin/pushary-status.d.ts +1 -0
- package/dist/bin/pushary-status.js +262 -0
- package/dist/bin/pushary-stop-hook.d.ts +1 -0
- package/dist/bin/pushary-stop-hook.js +63 -0
- package/dist/bin/pushary-stopfailure-hook.d.ts +1 -0
- package/dist/bin/pushary-stopfailure-hook.js +56 -0
- package/dist/bin/pushary-suggestions.d.ts +1 -0
- package/dist/bin/pushary-suggestions.js +100 -0
- package/dist/bin/pushary-transcript-register.d.ts +1 -0
- package/dist/bin/pushary-transcript-register.js +40 -0
- package/dist/bin/pushary-transcripts.d.ts +1 -0
- package/dist/bin/pushary-transcripts.js +77 -0
- package/dist/bin/pushary-upgrade.d.ts +1 -0
- package/dist/bin/pushary-upgrade.js +599 -0
- package/dist/bin/pushary-wait.d.ts +1 -0
- package/dist/bin/pushary-wait.js +122 -0
- package/dist/bin/pushary.d.ts +1 -0
- package/dist/bin/pushary.js +74 -0
- package/dist/chunk-2ABTSGFT.js +173 -0
- package/dist/chunk-2OY3ZZ5N.js +124 -0
- package/dist/chunk-3EGEA4KH.js +44 -0
- package/dist/chunk-3EVPNIBE.js +185 -0
- package/dist/chunk-5OP5MQI7.js +45 -0
- package/dist/chunk-6J2JC2RT.js +149 -0
- package/dist/chunk-7QLSKOSU.js +19 -0
- package/dist/chunk-7Z7Q4GGS.js +88 -0
- package/dist/chunk-A2A2YTMG.js +835 -0
- package/dist/chunk-AHV4LNB5.js +377 -0
- package/dist/chunk-AXPCESYO.js +197 -0
- package/dist/chunk-C2WCPBF7.js +86 -0
- package/dist/chunk-CJVVKOSR.js +73 -0
- package/dist/chunk-CULUZTWQ.js +2117 -0
- package/dist/chunk-CVQYGMSR.js +142 -0
- package/dist/chunk-CVWG2N3Y.js +16 -0
- package/dist/chunk-D2FX3EPW.js +15 -0
- package/dist/chunk-DB3ONZB4.js +44 -0
- package/dist/chunk-DE4Y6TCC.js +160 -0
- package/dist/chunk-E4FJTUH4.js +249 -0
- package/dist/chunk-EPCXUP3P.js +41 -0
- package/dist/chunk-EX3GAPA4.js +44 -0
- package/dist/chunk-EYK3GSRH.js +143 -0
- package/dist/chunk-GMXKITVA.js +70 -0
- package/dist/chunk-GPEGOGRJ.js +49 -0
- package/dist/chunk-GU3EJCVV.js +43 -0
- package/dist/chunk-HQ6G3B5R.js +59 -0
- package/dist/chunk-HZVVNGEW.js +22 -0
- package/dist/chunk-IPVKH2ST.js +231 -0
- package/dist/chunk-J5TIV3J6.js +238 -0
- package/dist/chunk-JU6W3XAU.js +64 -0
- package/dist/chunk-JYG4B33G.js +227 -0
- package/dist/chunk-JZTHGAPA.js +503 -0
- package/dist/chunk-KGDWDSNL.js +102 -0
- package/dist/chunk-KVQBWI5T.js +99 -0
- package/dist/chunk-KZERVKTD.js +16 -0
- package/dist/chunk-LXQ5FBBD.js +47 -0
- package/dist/chunk-M5ICYGHT.js +138 -0
- package/dist/chunk-M5JDZ45W.js +943 -0
- package/dist/chunk-MDEPU45E.js +1226 -0
- package/dist/chunk-MPWXGONV.js +157 -0
- package/dist/chunk-N6IOSZOE.js +185 -0
- package/dist/chunk-NGDD6TXY.js +192 -0
- package/dist/chunk-NQTHFF4W.js +2830 -0
- package/dist/chunk-OKXS7WDZ.js +68 -0
- package/dist/chunk-OLQRRFBX.js +21 -0
- package/dist/chunk-P7WQEVWA.js +307 -0
- package/dist/chunk-POFBE6ZY.js +12 -0
- package/dist/chunk-PQ4K74WL.js +30 -0
- package/dist/chunk-PUZZELE6.js +37 -0
- package/dist/chunk-Q7E4I6UA.js +767 -0
- package/dist/chunk-QCGNGEGE.js +15 -0
- package/dist/chunk-R7437YI3.js +151 -0
- package/dist/chunk-RHR6NARM.js +305 -0
- package/dist/chunk-SAXBC4AZ.js +60 -0
- package/dist/chunk-SGZJ5LYH.js +76 -0
- package/dist/chunk-SIFVFP6T.js +70 -0
- package/dist/chunk-SLFABHW3.js +150 -0
- package/dist/chunk-T7E75EKA.js +67 -0
- package/dist/chunk-TY4JT7W3.js +38 -0
- package/dist/chunk-U4TMAANR.js +111 -0
- package/dist/chunk-UI36QSBI.js +177 -0
- package/dist/chunk-VQKRG3AC.js +339 -0
- package/dist/chunk-WE2J62NC.js +1131 -0
- package/dist/chunk-XHKBHWLX.js +14 -0
- package/dist/chunk-ZSZSSJCV.js +275 -0
- package/dist/chunk-ZVSKMPBZ.js +135 -0
- package/dist/reapply-CW3MA66V.js +34 -0
- package/dist/src/index.d.ts +204 -0
- package/dist/src/index.js +64 -0
- package/package.json +108 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import {
|
|
2
|
+
redactSecretsFromContent
|
|
3
|
+
} from "./chunk-NQTHFF4W.js";
|
|
4
|
+
|
|
5
|
+
// src/crypto.ts
|
|
6
|
+
import nacl from "tweetnacl";
|
|
7
|
+
var EPH_PUB_LEN = nacl.box.publicKeyLength;
|
|
8
|
+
var BOX_NONCE_LEN = nacl.box.nonceLength;
|
|
9
|
+
var SECRETBOX_NONCE_LEN = nacl.secretbox.nonceLength;
|
|
10
|
+
var SESSION_KEY_LEN = nacl.secretbox.keyLength;
|
|
11
|
+
var toBase64 = (bytes) => Buffer.from(bytes).toString("base64");
|
|
12
|
+
var fromBase64 = (value) => new Uint8Array(Buffer.from(value, "base64"));
|
|
13
|
+
var utf8Encode = (text) => new Uint8Array(Buffer.from(text, "utf-8"));
|
|
14
|
+
var decodePublicKey = (value) => fromBase64(value);
|
|
15
|
+
var generateSessionKey = () => nacl.randomBytes(SESSION_KEY_LEN);
|
|
16
|
+
var wrapSessionKey = (sessionKey, accountPublicKey) => {
|
|
17
|
+
const ephemeral = nacl.box.keyPair();
|
|
18
|
+
const nonce = nacl.randomBytes(BOX_NONCE_LEN);
|
|
19
|
+
const box = nacl.box(sessionKey, nonce, accountPublicKey, ephemeral.secretKey);
|
|
20
|
+
const bundle = new Uint8Array(EPH_PUB_LEN + BOX_NONCE_LEN + box.length);
|
|
21
|
+
bundle.set(ephemeral.publicKey, 0);
|
|
22
|
+
bundle.set(nonce, EPH_PUB_LEN);
|
|
23
|
+
bundle.set(box, EPH_PUB_LEN + BOX_NONCE_LEN);
|
|
24
|
+
return toBase64(bundle);
|
|
25
|
+
};
|
|
26
|
+
var encryptRecord = (plaintext, sessionKey) => {
|
|
27
|
+
const nonce = nacl.randomBytes(SECRETBOX_NONCE_LEN);
|
|
28
|
+
const box = nacl.secretbox(utf8Encode(plaintext), nonce, sessionKey);
|
|
29
|
+
const bundle = new Uint8Array(SECRETBOX_NONCE_LEN + box.length);
|
|
30
|
+
bundle.set(nonce, 0);
|
|
31
|
+
bundle.set(box, SECRETBOX_NONCE_LEN);
|
|
32
|
+
return toBase64(bundle);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// src/transcript-capture.ts
|
|
36
|
+
import { createHmac } from "crypto";
|
|
37
|
+
|
|
38
|
+
// src/transcript-budget.ts
|
|
39
|
+
var CIPHERTEXT_BUDGET_CHARS = 64 * 1024;
|
|
40
|
+
var SECRETBOX_OVERHEAD_BYTES = 24 + 16;
|
|
41
|
+
var MAX_PLAINTEXT_BYTES = Math.floor(CIPHERTEXT_BUDGET_CHARS / 4) * 3 - SECRETBOX_OVERHEAD_BYTES;
|
|
42
|
+
var MIN_KEEP_CHARS = 200;
|
|
43
|
+
var note = (dropped) => `
|
|
44
|
+
[Pushary truncated ${dropped} bytes to fit the transcript size limit]`;
|
|
45
|
+
var stringSlots = (value, out = []) => {
|
|
46
|
+
if (Array.isArray(value)) {
|
|
47
|
+
value.forEach((inner, index) => {
|
|
48
|
+
if (typeof inner === "string") {
|
|
49
|
+
out.push({ get: () => value[index], set: (v) => {
|
|
50
|
+
value[index] = v;
|
|
51
|
+
} });
|
|
52
|
+
} else {
|
|
53
|
+
stringSlots(inner, out);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
} else if (value && typeof value === "object") {
|
|
57
|
+
const object = value;
|
|
58
|
+
for (const key of Object.keys(object)) {
|
|
59
|
+
const inner = object[key];
|
|
60
|
+
if (typeof inner === "string") {
|
|
61
|
+
out.push({ get: () => object[key], set: (v) => {
|
|
62
|
+
object[key] = v;
|
|
63
|
+
} });
|
|
64
|
+
} else {
|
|
65
|
+
stringSlots(inner, out);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
};
|
|
71
|
+
var jsonBytes = (value) => Buffer.byteLength(JSON.stringify(value), "utf-8");
|
|
72
|
+
var fitWithinPlaintextBudget = (record, budget = MAX_PLAINTEXT_BYTES) => {
|
|
73
|
+
if (jsonBytes(record) <= budget) return false;
|
|
74
|
+
const slots = stringSlots(record).map((slot) => ({
|
|
75
|
+
slot,
|
|
76
|
+
originalBytes: Buffer.byteLength(slot.get(), "utf-8")
|
|
77
|
+
}));
|
|
78
|
+
if (slots.length === 0) return false;
|
|
79
|
+
let truncated = false;
|
|
80
|
+
for (let pass = 0; pass < 64 && jsonBytes(record) > budget; pass += 1) {
|
|
81
|
+
let worst = slots[0];
|
|
82
|
+
for (const candidate of slots) {
|
|
83
|
+
if (candidate.slot.get().length > worst.slot.get().length) worst = candidate;
|
|
84
|
+
}
|
|
85
|
+
const current = worst.slot.get();
|
|
86
|
+
const keep = Math.max(MIN_KEEP_CHARS, Math.floor(current.length / 2));
|
|
87
|
+
const kept = current.slice(0, keep);
|
|
88
|
+
const replacement = kept + note(worst.originalBytes - Buffer.byteLength(kept, "utf-8"));
|
|
89
|
+
if (replacement.length >= current.length) break;
|
|
90
|
+
worst.slot.set(replacement);
|
|
91
|
+
truncated = true;
|
|
92
|
+
}
|
|
93
|
+
return truncated;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// src/transcript-capture.ts
|
|
97
|
+
var INTERNAL_TYPES = /* @__PURE__ */ new Set([
|
|
98
|
+
"file-history-snapshot",
|
|
99
|
+
"change",
|
|
100
|
+
"queue-operation",
|
|
101
|
+
"summary",
|
|
102
|
+
"last-prompt",
|
|
103
|
+
"mode",
|
|
104
|
+
"permission-mode",
|
|
105
|
+
"atis-latch",
|
|
106
|
+
"frame-link",
|
|
107
|
+
"cost-state",
|
|
108
|
+
"agent-name",
|
|
109
|
+
"bridge-session",
|
|
110
|
+
"artifact-autoreact-ledger",
|
|
111
|
+
"artifact-comment-monitor"
|
|
112
|
+
]);
|
|
113
|
+
var redactDeep = (value) => {
|
|
114
|
+
if (typeof value === "string") return redactSecretsFromContent(value);
|
|
115
|
+
if (Array.isArray(value)) return value.map(redactDeep);
|
|
116
|
+
if (value && typeof value === "object") {
|
|
117
|
+
const out = {};
|
|
118
|
+
for (const [key, inner] of Object.entries(value)) {
|
|
119
|
+
out[key] = redactDeep(inner);
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
return value;
|
|
124
|
+
};
|
|
125
|
+
var localIdFor = (parsed, rawLine, sessionKey) => typeof parsed.uuid === "string" && parsed.uuid.length > 0 ? parsed.uuid : createHmac("sha256", sessionKey).update(rawLine).digest("hex").slice(0, 32);
|
|
126
|
+
var buildTranscriptRecords = (lines, sessionKey, seen = /* @__PURE__ */ new Set()) => {
|
|
127
|
+
const records = [];
|
|
128
|
+
const nextSeen = new Set(seen);
|
|
129
|
+
for (const line of lines) {
|
|
130
|
+
const trimmed = line.trim();
|
|
131
|
+
if (!trimmed) continue;
|
|
132
|
+
let parsed;
|
|
133
|
+
try {
|
|
134
|
+
parsed = JSON.parse(trimmed);
|
|
135
|
+
} catch {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
139
|
+
if (INTERNAL_TYPES.has(type)) continue;
|
|
140
|
+
const localId = localIdFor(parsed, trimmed, sessionKey);
|
|
141
|
+
if (nextSeen.has(localId)) continue;
|
|
142
|
+
nextSeen.add(localId);
|
|
143
|
+
const redacted = redactDeep(parsed);
|
|
144
|
+
fitWithinPlaintextBudget(redacted);
|
|
145
|
+
const content = encryptRecord(JSON.stringify(redacted), sessionKey);
|
|
146
|
+
records.push({ localId, content });
|
|
147
|
+
}
|
|
148
|
+
return { records, seen: nextSeen };
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export {
|
|
152
|
+
decodePublicKey,
|
|
153
|
+
generateSessionKey,
|
|
154
|
+
wrapSessionKey,
|
|
155
|
+
encryptRecord,
|
|
156
|
+
buildTranscriptRecords
|
|
157
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// src/pending.ts
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { homedir, tmpdir } from "os";
|
|
4
|
+
import { createHash } from "crypto";
|
|
5
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, renameSync, unlinkSync, rmSync, statSync } from "fs";
|
|
6
|
+
var PENDING_DIR = join(tmpdir(), "pushary-pending");
|
|
7
|
+
var commandRoot = () => process.env.PUSHARY_COMMAND_DIR?.trim() || join(homedir(), ".pushary", "run", "commands");
|
|
8
|
+
var DEFAULT_SESSION = "_no_session";
|
|
9
|
+
var GRACE_MS = 10 * 60 * 1e3;
|
|
10
|
+
var sanitize = (sessionId) => sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128) || DEFAULT_SESSION;
|
|
11
|
+
var dirFor = (sessionId) => join(PENDING_DIR, sanitize(sessionId));
|
|
12
|
+
var commandDirFor = (sessionId, namespace = "") => join(
|
|
13
|
+
commandRoot(),
|
|
14
|
+
createHash("sha256").update(namespace).digest("hex").slice(0, 16),
|
|
15
|
+
sanitize(sessionId)
|
|
16
|
+
);
|
|
17
|
+
var legacyCommandFile = (sessionId, namespace = "") => join(commandDirFor(sessionId, namespace), "command.json");
|
|
18
|
+
var commandFile = (sessionId, id, namespace = "") => join(commandDirFor(sessionId, namespace), `${createHash("sha256").update(id).digest("hex")}.json`);
|
|
19
|
+
var readCommandFile = (file) => {
|
|
20
|
+
try {
|
|
21
|
+
const value = JSON.parse(readFileSync(file, "utf-8"));
|
|
22
|
+
if (typeof value.id !== "string" || typeof value.text !== "string" || typeof value.expiresAtMs !== "number") return null;
|
|
23
|
+
if (value.expiresAtMs <= Date.now()) {
|
|
24
|
+
unlinkSync(file);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
...value,
|
|
29
|
+
delivered: value.delivered === true,
|
|
30
|
+
...value.failed === true ? { failed: true } : {}
|
|
31
|
+
};
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var readPendingCommandById = (sessionId, id, namespace = "") => {
|
|
37
|
+
const current = readCommandFile(commandFile(sessionId, id, namespace));
|
|
38
|
+
if (current?.id === id) return current;
|
|
39
|
+
const legacy = readCommandFile(legacyCommandFile(sessionId, namespace));
|
|
40
|
+
return legacy?.id === id ? legacy : null;
|
|
41
|
+
};
|
|
42
|
+
var pruneExpiredCommands = (dir) => {
|
|
43
|
+
let files;
|
|
44
|
+
try {
|
|
45
|
+
files = readdirSync(dir).filter((file) => file.endsWith(".json"));
|
|
46
|
+
} catch {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
return files.flatMap((file) => {
|
|
50
|
+
const command = readCommandFile(join(dir, file));
|
|
51
|
+
return command ? [command] : [];
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
var pruneExpiredCommandStore = () => {
|
|
55
|
+
let namespaces;
|
|
56
|
+
try {
|
|
57
|
+
namespaces = readdirSync(commandRoot(), { withFileTypes: true });
|
|
58
|
+
} catch {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
for (const namespace of namespaces.filter((entry) => entry.isDirectory())) {
|
|
62
|
+
const namespaceDir = join(commandRoot(), namespace.name);
|
|
63
|
+
let sessions;
|
|
64
|
+
try {
|
|
65
|
+
sessions = readdirSync(namespaceDir, { withFileTypes: true });
|
|
66
|
+
} catch {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
for (const session of sessions.filter((entry) => entry.isDirectory())) {
|
|
70
|
+
pruneExpiredCommands(join(namespaceDir, session.name));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var readPendingCommand = (sessionId, namespace = "") => {
|
|
75
|
+
const commands = pruneExpiredCommands(commandDirFor(sessionId, namespace));
|
|
76
|
+
return commands.find((command) => !command.delivered && !command.failed) ?? commands[0] ?? null;
|
|
77
|
+
};
|
|
78
|
+
var savePendingCommand = (sessionId, command, namespace = "") => {
|
|
79
|
+
const dir = commandDirFor(sessionId, namespace);
|
|
80
|
+
try {
|
|
81
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
82
|
+
pruneExpiredCommandStore();
|
|
83
|
+
if (readPendingCommandById(sessionId, command.id, namespace)) return false;
|
|
84
|
+
writeFileSync(commandFile(sessionId, command.id, namespace), JSON.stringify(command), { encoding: "utf-8", mode: 384, flag: "wx" });
|
|
85
|
+
return true;
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
var updatePendingCommand = (sessionId, id, update, namespace = "") => {
|
|
91
|
+
const command = readPendingCommandById(sessionId, id, namespace);
|
|
92
|
+
if (!command) return false;
|
|
93
|
+
const currentFile = commandFile(sessionId, id, namespace);
|
|
94
|
+
const file = existsSync(currentFile) ? currentFile : legacyCommandFile(sessionId, namespace);
|
|
95
|
+
const temporary = `${file}.${process.pid}.tmp`;
|
|
96
|
+
try {
|
|
97
|
+
writeFileSync(temporary, JSON.stringify({ ...command, ...update, text: "" }), { encoding: "utf-8", mode: 384 });
|
|
98
|
+
renameSync(temporary, file);
|
|
99
|
+
return true;
|
|
100
|
+
} catch {
|
|
101
|
+
try {
|
|
102
|
+
unlinkSync(temporary);
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
var markPendingCommandDelivered = (sessionId, id, namespace = "") => updatePendingCommand(sessionId, id, { delivered: true, failed: void 0 }, namespace);
|
|
109
|
+
var markPendingCommandFailed = (sessionId, id, namespace = "") => updatePendingCommand(sessionId, id, { delivered: false, failed: true }, namespace);
|
|
110
|
+
var removePendingCommand = (sessionId, id, namespace = "") => {
|
|
111
|
+
if (!readPendingCommandById(sessionId, id, namespace)) return;
|
|
112
|
+
try {
|
|
113
|
+
unlinkSync(commandFile(sessionId, id, namespace));
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
if (readCommandFile(legacyCommandFile(sessionId, namespace))?.id === id) {
|
|
118
|
+
unlinkSync(legacyCommandFile(sessionId, namespace));
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
var isDefaultSession = (sessionId) => sanitize(sessionId) === DEFAULT_SESSION;
|
|
124
|
+
var listPendingQuestionsForToolUse = (sessionId, toolUseId) => {
|
|
125
|
+
if (!toolUseId) return [];
|
|
126
|
+
const dir = dirFor(sessionId);
|
|
127
|
+
let files;
|
|
128
|
+
try {
|
|
129
|
+
files = readdirSync(dir);
|
|
130
|
+
} catch {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
return files.filter((name) => {
|
|
134
|
+
try {
|
|
135
|
+
return readFileSync(join(dir, name), "utf-8") === toolUseId;
|
|
136
|
+
} catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
var listPendingQuestions = (sessionId) => {
|
|
142
|
+
const dir = dirFor(sessionId);
|
|
143
|
+
let files;
|
|
144
|
+
try {
|
|
145
|
+
files = readdirSync(dir);
|
|
146
|
+
} catch {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
if (!isDefaultSession(sessionId)) return files;
|
|
150
|
+
const cutoff = Date.now() - GRACE_MS;
|
|
151
|
+
return files.filter((name) => {
|
|
152
|
+
try {
|
|
153
|
+
return statSync(join(dir, name)).mtimeMs < cutoff;
|
|
154
|
+
} catch {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
var removePendingQuestion = (sessionId, correlationId) => {
|
|
160
|
+
try {
|
|
161
|
+
unlinkSync(join(dirFor(sessionId), correlationId));
|
|
162
|
+
} catch {
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
var removePendingSession = (sessionId) => {
|
|
166
|
+
try {
|
|
167
|
+
rmSync(dirFor(sessionId), { recursive: true, force: true });
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export {
|
|
173
|
+
DEFAULT_SESSION,
|
|
174
|
+
readPendingCommandById,
|
|
175
|
+
readPendingCommand,
|
|
176
|
+
savePendingCommand,
|
|
177
|
+
markPendingCommandDelivered,
|
|
178
|
+
markPendingCommandFailed,
|
|
179
|
+
removePendingCommand,
|
|
180
|
+
isDefaultSession,
|
|
181
|
+
listPendingQuestionsForToolUse,
|
|
182
|
+
listPendingQuestions,
|
|
183
|
+
removePendingQuestion,
|
|
184
|
+
removePendingSession
|
|
185
|
+
};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clientErrorKindOf,
|
|
3
|
+
reportClientError
|
|
4
|
+
} from "./chunk-CVQYGMSR.js";
|
|
5
|
+
import {
|
|
6
|
+
SubscriptionRequiredError,
|
|
7
|
+
callMcpTool
|
|
8
|
+
} from "./chunk-M5ICYGHT.js";
|
|
9
|
+
import {
|
|
10
|
+
installNetworkDefaults
|
|
11
|
+
} from "./chunk-2OY3ZZ5N.js";
|
|
12
|
+
import {
|
|
13
|
+
getBaseUrl
|
|
14
|
+
} from "./chunk-GPEGOGRJ.js";
|
|
15
|
+
import {
|
|
16
|
+
ASK_AGENT_NAME_MAX,
|
|
17
|
+
ASK_QUESTION_MAX,
|
|
18
|
+
DECISION_LINE_MAX,
|
|
19
|
+
truncateLine
|
|
20
|
+
} from "./chunk-NQTHFF4W.js";
|
|
21
|
+
|
|
22
|
+
// src/api.ts
|
|
23
|
+
import { randomUUID } from "crypto";
|
|
24
|
+
|
|
25
|
+
// src/validate.ts
|
|
26
|
+
var isPolicyConfig = (data) => {
|
|
27
|
+
if (!data || typeof data !== "object") return false;
|
|
28
|
+
const d = data;
|
|
29
|
+
return (d.resolvedAgent === void 0 || typeof d.resolvedAgent === "string") && Array.isArray(d.policies) && typeof d.defaultTimeoutSeconds === "number" && typeof d.defaultTimeoutAction === "string";
|
|
30
|
+
};
|
|
31
|
+
var isAskUserResponse = (data) => {
|
|
32
|
+
if (!data || typeof data !== "object") return false;
|
|
33
|
+
const d = data;
|
|
34
|
+
return typeof d.correlationId === "string" && typeof d.status === "string" && (d.answered === void 0 || typeof d.answered === "boolean") && (d.value === void 0 || typeof d.value === "string") && (d.note === void 0 || typeof d.note === "string") && (d.waitEndsAt === void 0 || typeof d.waitEndsAt === "string");
|
|
35
|
+
};
|
|
36
|
+
var isWaitForAnswerResponse = (data) => {
|
|
37
|
+
if (!data || typeof data !== "object") return false;
|
|
38
|
+
const d = data;
|
|
39
|
+
return typeof d.answered === "boolean";
|
|
40
|
+
};
|
|
41
|
+
var GATE_REPLY_KINDS = ["kill", "allow", "deny", "ask", "no_opinion"];
|
|
42
|
+
var isSettlementOutputs = (value) => {
|
|
43
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
44
|
+
const outputs = value;
|
|
45
|
+
return (outputs.approve === void 0 || typeof outputs.approve === "string") && (outputs.deny === void 0 || typeof outputs.deny === "string");
|
|
46
|
+
};
|
|
47
|
+
var isGateReply = (data) => {
|
|
48
|
+
if (!data || typeof data !== "object") return false;
|
|
49
|
+
const d = data;
|
|
50
|
+
return GATE_REPLY_KINDS.includes(d.kind) && (d.output === void 0 || typeof d.output === "string") && (d.correlationId === void 0 || typeof d.correlationId === "string") && (d.deadlineSeconds === void 0 || typeof d.deadlineSeconds === "number") && (d.noDevices === void 0 || typeof d.noDevices === "boolean") && (d.suppressed === void 0 || typeof d.suppressed === "boolean") && (d.settlementOutputs === void 0 || isSettlementOutputs(d.settlementOutputs));
|
|
51
|
+
};
|
|
52
|
+
var isBlockedActionClaim = (data) => {
|
|
53
|
+
if (!data || typeof data !== "object") return false;
|
|
54
|
+
return typeof data.granted === "boolean";
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// src/api.ts
|
|
58
|
+
installNetworkDefaults();
|
|
59
|
+
var DECISION_STOP_REASON = "Approval was cancelled or became unavailable. Retry only after resolving it.";
|
|
60
|
+
var isDecisionStopped = (result) => result.handoffAction === "stop" || ["cancelled", "unavailable", "stopped", "missing"].includes(result.status ?? "");
|
|
61
|
+
var isLocalQuestionHandoff = (result) => result.suppressed === true || result.noDevices === true || result.status === "terminal" || result.status === "notified";
|
|
62
|
+
var STOPPED = { answered: false, handoffAction: "stop" };
|
|
63
|
+
var UNVERIFIABLE_APPROVAL_REASON = "Pushary could not create a verifiable approval. Retry the action.";
|
|
64
|
+
var unavailableQuestionUnlessSubscriptionRequired = (error) => {
|
|
65
|
+
if (error instanceof SubscriptionRequiredError) throw error;
|
|
66
|
+
return { correlationId: "", status: "unavailable" };
|
|
67
|
+
};
|
|
68
|
+
var truncateOptional = (text, max) => text === void 0 ? void 0 : truncateLine(text, max);
|
|
69
|
+
var withinAskUserLimits = (params) => ({
|
|
70
|
+
...params,
|
|
71
|
+
question: truncateLine(params.question, ASK_QUESTION_MAX),
|
|
72
|
+
agentName: truncateOptional(params.agentName, ASK_AGENT_NAME_MAX),
|
|
73
|
+
intent: truncateOptional(params.intent, DECISION_LINE_MAX),
|
|
74
|
+
action: truncateOptional(params.action, DECISION_LINE_MAX),
|
|
75
|
+
blocker: truncateOptional(params.blocker, DECISION_LINE_MAX)
|
|
76
|
+
});
|
|
77
|
+
var askUser = async (apiKey, params, timeoutMs = 15e3) => {
|
|
78
|
+
const identified = params.toolUseId || params.requestId ? params : { ...params, requestId: randomUUID() };
|
|
79
|
+
const result = await callMcpTool(apiKey, "ask_user", { ...withinAskUserLimits(identified), wait: false }, { maxRetries: 3, timeoutMs });
|
|
80
|
+
if (!isAskUserResponse(result)) throw new Error("Invalid ask_user response");
|
|
81
|
+
return result;
|
|
82
|
+
};
|
|
83
|
+
var waitForAnswer = async (apiKey, correlationId, timeoutMs = 3e4, signal) => {
|
|
84
|
+
const result = await callMcpTool(apiKey, "wait_for_answer", {
|
|
85
|
+
correlationId,
|
|
86
|
+
timeoutMs
|
|
87
|
+
}, { timeoutMs: timeoutMs + 1e4, signal });
|
|
88
|
+
if (!isWaitForAnswerResponse(result)) throw new Error("Invalid wait_for_answer response");
|
|
89
|
+
return result;
|
|
90
|
+
};
|
|
91
|
+
var WAIT_RESPONSE_GRACE_MS = 2e3;
|
|
92
|
+
var MIN_LONG_POLL_MS = 1e3;
|
|
93
|
+
var MAX_LONG_POLL_MS = 3e4;
|
|
94
|
+
var nextLongPollMs = (deadlineMs, nowMs) => {
|
|
95
|
+
const hold = Math.min(deadlineMs - nowMs - WAIT_RESPONSE_GRACE_MS, MAX_LONG_POLL_MS);
|
|
96
|
+
return hold >= MIN_LONG_POLL_MS ? hold : null;
|
|
97
|
+
};
|
|
98
|
+
var pollForAnswer = async (apiKey, correlationId, deadlineMs, pollInterval = 2e3) => {
|
|
99
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
100
|
+
while (Date.now() < deadlineMs) {
|
|
101
|
+
const hold = nextLongPollMs(deadlineMs, Date.now());
|
|
102
|
+
if (hold === null) {
|
|
103
|
+
await sleep(Math.max(0, deadlineMs - Date.now()));
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
let answer;
|
|
107
|
+
try {
|
|
108
|
+
answer = await waitForAnswer(apiKey, correlationId, hold, AbortSignal.timeout(Math.max(1, deadlineMs - Date.now())));
|
|
109
|
+
} catch {
|
|
110
|
+
const late = await withdrawQuestion(apiKey, correlationId);
|
|
111
|
+
return late.answered ? late : STOPPED;
|
|
112
|
+
}
|
|
113
|
+
if (isDecisionStopped(answer) || answer.answered) return answer;
|
|
114
|
+
if (answer.status === "expired") return STOPPED;
|
|
115
|
+
await sleep(Math.min(pollInterval, Math.max(0, deadlineMs - Date.now())));
|
|
116
|
+
}
|
|
117
|
+
return withdrawQuestion(apiKey, correlationId);
|
|
118
|
+
};
|
|
119
|
+
var cancelQuestion = async (apiKey, correlationId, signal) => {
|
|
120
|
+
return callMcpTool(apiKey, "cancel_question", { correlationId }, { signal, timeoutMs: 4e3, maxRetries: 1 });
|
|
121
|
+
};
|
|
122
|
+
var withdrawQuestion = async (apiKey, correlationId, transport = { cancelQuestion, waitForAnswer }) => {
|
|
123
|
+
try {
|
|
124
|
+
const cancelled = await transport.cancelQuestion(apiKey, correlationId, AbortSignal.timeout(4e3));
|
|
125
|
+
if (!cancelled || isDecisionStopped(cancelled)) return STOPPED;
|
|
126
|
+
if (cancelled.cancelled === true) return { answered: false, status: "withdrawn" };
|
|
127
|
+
const late = await transport.waitForAnswer(apiKey, correlationId, 1e3, AbortSignal.timeout(2e3));
|
|
128
|
+
if (late.answered || isDecisionStopped(late)) return late;
|
|
129
|
+
return late.status === "expired" ? { answered: false, status: "withdrawn" } : STOPPED;
|
|
130
|
+
} catch {
|
|
131
|
+
return STOPPED;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
var waitForQuestion = async (apiKey, question, deadlineMs, pollInterval = 2e3) => {
|
|
135
|
+
if (isDecisionStopped(question) || question.answered) return { ...question, answered: !!question.answered };
|
|
136
|
+
if (question.status === "expired") return STOPPED;
|
|
137
|
+
return isLocalQuestionHandoff(question) ? withdrawQuestion(apiKey, question.correlationId) : pollForAnswer(apiKey, question.correlationId, deadlineMs, pollInterval);
|
|
138
|
+
};
|
|
139
|
+
var sendNotification = async (apiKey, params, timeoutMs = 15e3, signal) => {
|
|
140
|
+
await callMcpTool(apiKey, "send_notification", { ...params }, { maxRetries: signal ? 1 : 3, timeoutMs, signal });
|
|
141
|
+
};
|
|
142
|
+
var GATE_PATH = "/api/agent/gate";
|
|
143
|
+
var BLOCKED_ACTION_CLAIM_PATH = "/api/agent/blocked-action/claim";
|
|
144
|
+
var postAgentRequest = async (apiKey, path, body, timeoutMs) => {
|
|
145
|
+
const response = await fetch(`${getBaseUrl()}${path}`, {
|
|
146
|
+
method: "POST",
|
|
147
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
148
|
+
body: JSON.stringify(body),
|
|
149
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
150
|
+
});
|
|
151
|
+
if (!response.ok) throw new Error(`${path} rejected (${response.status})`);
|
|
152
|
+
return response.json();
|
|
153
|
+
};
|
|
154
|
+
var requestGate = async (apiKey, request, timeoutMs = 15e3) => {
|
|
155
|
+
try {
|
|
156
|
+
const reply = await postAgentRequest(apiKey, GATE_PATH, request, timeoutMs);
|
|
157
|
+
return isGateReply(reply) ? reply : null;
|
|
158
|
+
} catch (error) {
|
|
159
|
+
void reportClientError({ kind: clientErrorKindOf(error), code: "gate_request_failed" });
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
var claimBlockedAction = async (apiKey, request, timeoutMs) => {
|
|
164
|
+
try {
|
|
165
|
+
const reply = await postAgentRequest(apiKey, BLOCKED_ACTION_CLAIM_PATH, request, timeoutMs);
|
|
166
|
+
return isBlockedActionClaim(reply) ? reply.granted : null;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
void reportClientError({ kind: clientErrorKindOf(error), code: "blocked_action_claim_failed" });
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export {
|
|
174
|
+
isPolicyConfig,
|
|
175
|
+
isAskUserResponse,
|
|
176
|
+
isWaitForAnswerResponse,
|
|
177
|
+
DECISION_STOP_REASON,
|
|
178
|
+
isDecisionStopped,
|
|
179
|
+
isLocalQuestionHandoff,
|
|
180
|
+
UNVERIFIABLE_APPROVAL_REASON,
|
|
181
|
+
unavailableQuestionUnlessSubscriptionRequired,
|
|
182
|
+
askUser,
|
|
183
|
+
waitForAnswer,
|
|
184
|
+
nextLongPollMs,
|
|
185
|
+
pollForAnswer,
|
|
186
|
+
cancelQuestion,
|
|
187
|
+
withdrawQuestion,
|
|
188
|
+
waitForQuestion,
|
|
189
|
+
sendNotification,
|
|
190
|
+
requestGate,
|
|
191
|
+
claimBlockedAction
|
|
192
|
+
};
|