@rubytech/create-maxy-code 0.1.216 → 0.1.221
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/package.json +1 -1
- package/payload/platform/lib/mcp-eager/dist/index.d.ts +0 -55
- package/payload/platform/lib/mcp-eager/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/mcp-eager/dist/index.js +0 -76
- package/payload/platform/lib/mcp-eager/dist/index.js.map +1 -1
- package/payload/platform/lib/mcp-eager/src/index.ts +0 -101
- package/payload/platform/plugins/admin/PLUGIN.md +2 -3
- package/payload/platform/plugins/admin/hooks/__tests__/hook-emit-stale-lock-ttl.test.sh +102 -0
- package/payload/platform/plugins/admin/hooks/archive-ingest-surface-gate.sh +1 -2
- package/payload/platform/plugins/admin/hooks/lib/hook-emit.sh +32 -3
- package/payload/platform/plugins/admin/hooks/post-tool-use-agent.sh +20 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +2 -2
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/admin-user-management/SKILL.md +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +18 -18
- package/payload/platform/plugins/admin/skills/plugin-management/SKILL.md +2 -2
- package/payload/platform/plugins/browser/mcp/dist/index.js +19 -19
- package/payload/platform/plugins/browser/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/docs/references/platform.md +10 -17
- package/payload/platform/plugins/docs/references/troubleshooting.md +7 -0
- package/payload/platform/plugins/email/mcp/dist/index.js +12 -12
- package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/index.js +8 -8
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/references/graph-primitives.md +2 -2
- package/payload/platform/plugins/url-get/mcp/dist/index.js +2 -2
- package/payload/platform/plugins/url-get/mcp/dist/index.js.map +1 -1
- package/payload/platform/scripts/check-no-esm-require.mjs +3 -0
- package/payload/platform/scripts/setup-account.sh +0 -15
- package/payload/platform/services/claude-session-manager/dist/auth-snapshot.d.ts +4 -0
- package/payload/platform/services/claude-session-manager/dist/auth-snapshot.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/auth-snapshot.js +50 -0
- package/payload/platform/services/claude-session-manager/dist/auth-snapshot.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +0 -14
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +44 -71
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +12 -31
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-tail.d.ts +3 -0
- package/payload/platform/services/claude-session-manager/dist/jsonl-tail.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-tail.js +55 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-tail.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +27 -20
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +23 -17
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +94 -73
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-life.d.ts +4 -0
- package/payload/platform/services/claude-session-manager/dist/rc-life.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/rc-life.js +16 -0
- package/payload/platform/services/claude-session-manager/dist/rc-life.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.d.ts +12 -46
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.js +24 -73
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.js.map +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +3 -3
- package/payload/premium-plugins/writer-craft/mcp/dist/index.d.ts.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/index.js +4 -5
- package/payload/premium-plugins/writer-craft/mcp/dist/index.js.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/src/index.ts +4 -5
- package/payload/server/public/assets/AdminShell-BcxEZnpu.js +1 -0
- package/payload/server/public/assets/{admin-DjGGOx2K.js → admin-DMo5DWKH.js} +1 -1
- package/payload/server/public/assets/{data-WBEvtRV-.js → data-D6IQdm0R.js} +1 -1
- package/payload/server/public/assets/{graph-DsOLsIPf.js → graph-PkZKVkWD.js} +1 -1
- package/payload/server/public/data.html +2 -2
- package/payload/server/public/graph.html +2 -2
- package/payload/server/public/index.html +2 -2
- package/payload/server/server.js +49 -12
- package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-admin-tool-gate.test.sh +0 -169
- package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-base64-guard.test.sh +0 -204
- package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-memory-write-passthrough.test.sh +0 -118
- package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +0 -409
- package/payload/premium-plugins/writer-craft/lib/mcp-eager/dist/index.d.ts +0 -116
- package/payload/premium-plugins/writer-craft/lib/mcp-eager/dist/index.d.ts.map +0 -1
- package/payload/premium-plugins/writer-craft/lib/mcp-eager/dist/index.js +0 -125
- package/payload/premium-plugins/writer-craft/lib/mcp-eager/dist/index.js.map +0 -1
- package/payload/premium-plugins/writer-craft/lib/mcp-eager/package.json +0 -7
- package/payload/server/public/assets/AdminShell-CPWbJ_I8.js +0 -1
package/payload/server/server.js
CHANGED
|
@@ -824,13 +824,10 @@ import { monitorEventLoopDelay } from "perf_hooks";
|
|
|
824
824
|
// app/lib/agent-slug-pattern.ts
|
|
825
825
|
var AGENT_SLUG_PATTERN = /^\/([a-z][a-z0-9-]{2,49})$/;
|
|
826
826
|
|
|
827
|
-
// server/routes/health.ts
|
|
828
|
-
import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
|
|
829
|
-
import { createConnection as createConnection2 } from "net";
|
|
830
|
-
|
|
831
827
|
// app/lib/claude-auth.ts
|
|
832
828
|
import { readFileSync, writeFileSync } from "fs";
|
|
833
829
|
import { sep } from "path";
|
|
830
|
+
import { lock } from "proper-lockfile";
|
|
834
831
|
if (!CLAUDE_CREDENTIALS_FILE.startsWith(MAXY_DIR + sep)) {
|
|
835
832
|
console.error(
|
|
836
833
|
`[claude-auth] WARN cross-brand-path-detected resolved=${CLAUDE_CREDENTIALS_FILE} brand=${BRAND_NAME} maxyDir=${MAXY_DIR}`
|
|
@@ -929,11 +926,39 @@ function logRefreshOutcome(outcome) {
|
|
|
929
926
|
);
|
|
930
927
|
}
|
|
931
928
|
async function doRefresh() {
|
|
929
|
+
const pre = readCredentials();
|
|
930
|
+
if (!pre) return { status: "missing" };
|
|
931
|
+
if (!pre.refreshToken) return { status: "dead", expiresAt: pre.expiresAt };
|
|
932
|
+
let release = null;
|
|
933
|
+
try {
|
|
934
|
+
release = await lock(CLAUDE_CREDENTIALS_FILE, {
|
|
935
|
+
retries: { retries: 8, minTimeout: 100, maxTimeout: 1e3, factor: 1.5 },
|
|
936
|
+
stale: 3e4
|
|
937
|
+
});
|
|
938
|
+
} catch (err) {
|
|
939
|
+
console.error(
|
|
940
|
+
`[auth-refresh] op=lock-acquire-failed err=${err instanceof Error ? err.message : String(err)}`
|
|
941
|
+
);
|
|
942
|
+
logRefreshOutcome("fail-network");
|
|
943
|
+
return { status: "expired", expiresAt: pre.expiresAt };
|
|
944
|
+
}
|
|
945
|
+
console.log(`[auth-refresh] op=lock-acquired pid=${process.pid} path=${CLAUDE_CREDENTIALS_FILE}`);
|
|
946
|
+
try {
|
|
947
|
+
return await doRefreshLocked();
|
|
948
|
+
} finally {
|
|
949
|
+
await release().catch(() => {
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
async function doRefreshLocked() {
|
|
932
954
|
const creds = readCredentials();
|
|
933
955
|
if (!creds) return { status: "missing" };
|
|
934
956
|
if (!creds.refreshToken) return { status: "dead", expiresAt: creds.expiresAt };
|
|
935
957
|
const now = Date.now();
|
|
936
958
|
if (creds.expiresAt - now > EXPIRING_THRESHOLD_MS) {
|
|
959
|
+
console.log(
|
|
960
|
+
`[auth-refresh] op=skipped-fresh expiresAt=${new Date(creds.expiresAt).toISOString()}`
|
|
961
|
+
);
|
|
937
962
|
return { status: "ok", expiresAt: creds.expiresAt };
|
|
938
963
|
}
|
|
939
964
|
let res;
|
|
@@ -989,13 +1014,26 @@ async function doRefresh() {
|
|
|
989
1014
|
expiresIn: typeof expiresIn === "number" && expiresIn > 0 ? expiresIn : void 0
|
|
990
1015
|
});
|
|
991
1016
|
if (newExpiresAt) {
|
|
992
|
-
console.log(`[auth-refresh]
|
|
1017
|
+
console.log(`[auth-refresh] op=renewed expiresAt=${new Date(newExpiresAt).toISOString()}`);
|
|
993
1018
|
} else {
|
|
994
|
-
console.warn("[auth-refresh] write-back
|
|
1019
|
+
console.warn("[auth-refresh] op=renewed-write-back-failed");
|
|
995
1020
|
}
|
|
996
1021
|
logRefreshOutcome("ok");
|
|
997
1022
|
return { status: "ok", expiresAt: newExpiresAt ?? Date.now() + 3600 * 1e3 };
|
|
998
1023
|
}
|
|
1024
|
+
function startAuthHealthHeartbeat(intervalMs = 5 * 60 * 1e3) {
|
|
1025
|
+
const tick = () => {
|
|
1026
|
+
const h = computeAuthHealth();
|
|
1027
|
+
const expiresIn = h.expiresAt != null ? Math.round((h.expiresAt - Date.now()) / 1e3) : null;
|
|
1028
|
+
console.log(
|
|
1029
|
+
`[auth-health] brand=${BRAND_NAME} status=${h.status} expiresIn=${expiresIn != null ? expiresIn + "s" : "n/a"}`
|
|
1030
|
+
);
|
|
1031
|
+
};
|
|
1032
|
+
tick();
|
|
1033
|
+
const handle = setInterval(tick, intervalMs);
|
|
1034
|
+
if (typeof handle.unref === "function") handle.unref();
|
|
1035
|
+
return () => clearInterval(handle);
|
|
1036
|
+
}
|
|
999
1037
|
async function ensureAuth() {
|
|
1000
1038
|
const health = assessAuthHealth();
|
|
1001
1039
|
if (health.status === "ok" || health.status === "missing" || health.status === "dead") {
|
|
@@ -1004,6 +1042,10 @@ async function ensureAuth() {
|
|
|
1004
1042
|
return refreshAccessToken();
|
|
1005
1043
|
}
|
|
1006
1044
|
|
|
1045
|
+
// server/routes/health.ts
|
|
1046
|
+
import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
|
|
1047
|
+
import { createConnection as createConnection2 } from "net";
|
|
1048
|
+
|
|
1007
1049
|
// app/lib/network.ts
|
|
1008
1050
|
import { networkInterfaces } from "os";
|
|
1009
1051
|
function getLanIp() {
|
|
@@ -12157,12 +12199,6 @@ app25.post("/", requireAdminSession, async (c) => {
|
|
|
12157
12199
|
const payload = {};
|
|
12158
12200
|
if (typeof body.sessionId === "string" && body.sessionId.length > 0) payload.sessionId = body.sessionId;
|
|
12159
12201
|
if (typeof body.name === "string" && body.name.length > 0) payload.name = body.name;
|
|
12160
|
-
if (body.spawnPath !== void 0) {
|
|
12161
|
-
if (body.spawnPath !== "node-pty" && body.spawnPath !== "script-scope") {
|
|
12162
|
-
return c.json({ error: `spawnPath must be 'node-pty' or 'script-scope'` }, 400);
|
|
12163
|
-
}
|
|
12164
|
-
payload.spawnPath = body.spawnPath;
|
|
12165
|
-
}
|
|
12166
12202
|
let res;
|
|
12167
12203
|
try {
|
|
12168
12204
|
res = await fetch(`${managerBase4()}/rc-spawn`, {
|
|
@@ -15167,6 +15203,7 @@ var port = requirePortEnv("MAXY_UI_INTERNAL_PORT", { tag: "ui-server" });
|
|
|
15167
15203
|
var hostname = process.env.HOSTNAME ?? "127.0.0.1";
|
|
15168
15204
|
var httpServer = serve({ fetch: app41.fetch, port, hostname });
|
|
15169
15205
|
console.log(`${BRAND.productName} listening on http://${hostname}:${port}`);
|
|
15206
|
+
startAuthHealthHeartbeat();
|
|
15170
15207
|
{
|
|
15171
15208
|
const loopHist = monitorEventLoopDelay({ resolution: 50 });
|
|
15172
15209
|
loopHist.enable();
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Task 529 — admin-tool-gate enforces orchestration-only at the PreToolUse
|
|
3
|
-
# hook. Admin-direct invocations of Write/Edit/MultiEdit/Bash/WebFetch/
|
|
4
|
-
# WebSearch are refused with the dispatch hint; subagents pass through.
|
|
5
|
-
#
|
|
6
|
-
# Task 559 — discriminator is `parent_tool_use_id` from the Claude Code
|
|
7
|
-
# hook input. Non-empty → in-window Task subagent (allow).
|
|
8
|
-
#
|
|
9
|
-
# Task 566 — discriminator extended to `$MAXY_SPECIALIST` for maxy MCP
|
|
10
|
-
# `Agent`-spawned specialists (separate top-level `claude rc` processes
|
|
11
|
-
# with no `parent_tool_use_id` but `MAXY_SPECIALIST=<name>` on env, set
|
|
12
|
-
# by pty-spawner.ts:1601). Calls with neither marker AND no
|
|
13
|
-
# `MAXY_SESSION_ROLE=admin` fail closed with `reason=missing-marker` so a
|
|
14
|
-
# future spawn shape cannot silently regress.
|
|
15
|
-
|
|
16
|
-
set -u
|
|
17
|
-
|
|
18
|
-
HOOK="$(cd "$(dirname "$0")/.." && pwd)/pre-tool-use.sh"
|
|
19
|
-
if [[ ! -x "$HOOK" ]]; then
|
|
20
|
-
echo "FAIL: $HOOK not executable" >&2
|
|
21
|
-
exit 1
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
TMPFILES=()
|
|
25
|
-
cleanup() {
|
|
26
|
-
for f in "${TMPFILES[@]:-}"; do
|
|
27
|
-
[[ -n "$f" ]] && rm -f "$f" 2>/dev/null || true
|
|
28
|
-
done
|
|
29
|
-
}
|
|
30
|
-
trap cleanup EXIT
|
|
31
|
-
|
|
32
|
-
PASS=0; FAIL=0
|
|
33
|
-
pass() { PASS=$((PASS+1)); echo "PASS: $1"; }
|
|
34
|
-
fail() { FAIL=$((FAIL+1)); echo "FAIL: $1" >&2; }
|
|
35
|
-
|
|
36
|
-
# The hook receives the parent session's transcript_path even on subagent
|
|
37
|
-
# calls in Claude Code 2.1.x. The fixture mirrors production.
|
|
38
|
-
SESSION_TRANSCRIPT="/tmp/sess/44283ae1-2b82-4bd7-b8af-99a11141ed4b.jsonl"
|
|
39
|
-
SUBAGENT_PARENT_ID="toolu_01QFC4W32wFUyHy6WJMNjHf6"
|
|
40
|
-
|
|
41
|
-
# run <tool> <parent_id> <expected_rc> <expected_pattern> <label>
|
|
42
|
-
# Env knobs (default mirror production admin spawn):
|
|
43
|
-
# MAXY_SESSION_ROLE_OVERRIDE — defaults to "admin"; set empty to drop the role
|
|
44
|
-
# MAXY_SPECIALIST_OVERRIDE — defaults to empty; set to a name to simulate
|
|
45
|
-
# an MCP-Agent-spawned specialist
|
|
46
|
-
run() {
|
|
47
|
-
local tool="$1" parent_id="$2" expected_rc="$3" expected_pattern="$4" label="$5"
|
|
48
|
-
local role="${MAXY_SESSION_ROLE_OVERRIDE-admin}"
|
|
49
|
-
local specialist="${MAXY_SPECIALIST_OVERRIDE-}"
|
|
50
|
-
local input_json
|
|
51
|
-
input_json=$(python3 -c '
|
|
52
|
-
import json, sys
|
|
53
|
-
payload = {
|
|
54
|
-
"hook_event_name": "PreToolUse",
|
|
55
|
-
"tool_name": sys.argv[1],
|
|
56
|
-
"tool_input": {"file_path": "/tmp/x", "content": "x", "command": "echo hi"},
|
|
57
|
-
"transcript_path": sys.argv[2],
|
|
58
|
-
}
|
|
59
|
-
parent = sys.argv[3]
|
|
60
|
-
if parent:
|
|
61
|
-
payload["parent_tool_use_id"] = parent
|
|
62
|
-
print(json.dumps(payload, separators=(",", ":")))
|
|
63
|
-
' "$tool" "$SESSION_TRANSCRIPT" "$parent_id")
|
|
64
|
-
local stdout_file; stdout_file=$(mktemp); TMPFILES+=("$stdout_file")
|
|
65
|
-
local stderr_file; stderr_file=$(mktemp); TMPFILES+=("$stderr_file")
|
|
66
|
-
printf '%s' "$input_json" | \
|
|
67
|
-
env -u MAXY_SESSION_ROLE -u MAXY_SPECIALIST \
|
|
68
|
-
MAXY_SESSION_ROLE="$role" MAXY_SPECIALIST="$specialist" \
|
|
69
|
-
bash "$HOOK" admin >"$stdout_file" 2>"$stderr_file"
|
|
70
|
-
local rc=$?
|
|
71
|
-
if [[ "$rc" -ne "$expected_rc" ]]; then
|
|
72
|
-
fail "$label: expected exit $expected_rc, got $rc. Stderr: $(cat "$stderr_file")"
|
|
73
|
-
return
|
|
74
|
-
fi
|
|
75
|
-
if [[ -n "$expected_pattern" ]] && ! grep -qE "$expected_pattern" "$stderr_file"; then
|
|
76
|
-
fail "$label: stderr missing pattern '$expected_pattern'. Got: $(cat "$stderr_file")"
|
|
77
|
-
return
|
|
78
|
-
fi
|
|
79
|
-
pass "$label"
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
# ── Admin-direct refusals (no parent_tool_use_id, no MAXY_SPECIALIST, role=admin) ───
|
|
83
|
-
run Write "" 2 '\[admin-tool-gate\] role=admin parent_tool_use_id=- specialist=- session_role=admin tool=Write decision=block.*owner=content-producer' \
|
|
84
|
-
"Test 1: admin-direct Write rejected with content-producer dispatch hint"
|
|
85
|
-
run Edit "" 2 '\[admin-tool-gate\] role=admin .*tool=Edit decision=block.*owner=content-producer' \
|
|
86
|
-
"Test 2: admin-direct Edit rejected"
|
|
87
|
-
run MultiEdit "" 2 '\[admin-tool-gate\] role=admin .*tool=MultiEdit decision=block.*owner=content-producer' \
|
|
88
|
-
"Test 3: admin-direct MultiEdit rejected"
|
|
89
|
-
run Bash "" 2 '\[admin-tool-gate\] role=admin .*tool=Bash decision=block' \
|
|
90
|
-
"Test 4: admin-direct Bash rejected"
|
|
91
|
-
run WebFetch "" 2 '\[admin-tool-gate\] role=admin .*tool=WebFetch decision=block.*owner=research-assistant' \
|
|
92
|
-
"Test 5: admin-direct WebFetch rejected"
|
|
93
|
-
run WebSearch "" 2 '\[admin-tool-gate\] role=admin .*tool=WebSearch decision=block.*owner=research-assistant' \
|
|
94
|
-
"Test 6: admin-direct WebSearch rejected"
|
|
95
|
-
|
|
96
|
-
# ── In-window subagent passthrough (parent_tool_use_id non-empty) ───────
|
|
97
|
-
run Write "$SUBAGENT_PARENT_ID" 0 "\[admin-tool-gate\] role=subagent parent_tool_use_id=${SUBAGENT_PARENT_ID} .*tool=Write decision=allow" \
|
|
98
|
-
"Test 7: subagent Write passes through"
|
|
99
|
-
run Edit "$SUBAGENT_PARENT_ID" 0 '\[admin-tool-gate\] role=subagent .*tool=Edit decision=allow' \
|
|
100
|
-
"Test 8: subagent Edit passes through"
|
|
101
|
-
run Bash "$SUBAGENT_PARENT_ID" 0 '\[admin-tool-gate\] role=subagent .*tool=Bash decision=allow' \
|
|
102
|
-
"Test 9: subagent Bash passes through (Task 559 regression — coding-assistant git pull)"
|
|
103
|
-
run WebFetch "$SUBAGENT_PARENT_ID" 0 '\[admin-tool-gate\] role=subagent .*tool=WebFetch decision=allow' \
|
|
104
|
-
"Test 10: subagent WebFetch passes through"
|
|
105
|
-
run MultiEdit "$SUBAGENT_PARENT_ID" 0 '\[admin-tool-gate\] role=subagent .*tool=MultiEdit decision=allow' \
|
|
106
|
-
"Test 11: subagent MultiEdit passes through"
|
|
107
|
-
|
|
108
|
-
# ── Unrelated tools untouched ───────────────────────────────────────────
|
|
109
|
-
run Read "" 0 '' \
|
|
110
|
-
"Test 12: admin-direct Read passes (Read is admin-keep-list)"
|
|
111
|
-
|
|
112
|
-
# ── Task 560: admin-direct block emits propagation record ──────────────
|
|
113
|
-
# Admin-direct path: MAXY_SESSION_ROLE=admin, no MAXY_SPECIALIST, no parent.
|
|
114
|
-
PROP_TMPDIR=$(mktemp -d); TMPFILES+=("$PROP_TMPDIR")
|
|
115
|
-
export MAXY_HOOK_BUFFER_DIR="$PROP_TMPDIR/buffers"
|
|
116
|
-
export MAXY_HOOK_SERVER_LOG="$PROP_TMPDIR/server.log"
|
|
117
|
-
SESSION="task560-test-sess"
|
|
118
|
-
PROP_INPUT=$(python3 -c '
|
|
119
|
-
import json, sys
|
|
120
|
-
print(json.dumps({"hook_event_name":"PreToolUse","tool_name":"Bash",
|
|
121
|
-
"tool_input":{"command":"ls"},"transcript_path":sys.argv[1],
|
|
122
|
-
"session_id":sys.argv[2]}, separators=(",",":")))
|
|
123
|
-
' "$SESSION_TRANSCRIPT" "$SESSION")
|
|
124
|
-
printf '%s' "$PROP_INPUT" | \
|
|
125
|
-
env -u MAXY_SPECIALIST MAXY_SESSION_ROLE=admin \
|
|
126
|
-
bash "$HOOK" admin >/dev/null 2>/dev/null
|
|
127
|
-
BUF="$MAXY_HOOK_BUFFER_DIR/$SESSION.jsonl"
|
|
128
|
-
if [[ -f "$BUF" ]] && grep -q '"hookName":"admin-tool-gate"' "$BUF" \
|
|
129
|
-
&& grep -q '"decision":"block"' "$BUF" \
|
|
130
|
-
&& grep -q '"reason":"orchestration-only"' "$BUF"; then
|
|
131
|
-
pass "Test 13b: admin-direct Bash block emits Task 560 propagation record"
|
|
132
|
-
else
|
|
133
|
-
fail "Test 13b: propagation record missing/malformed — $(cat "$BUF" 2>/dev/null || echo '<no buffer>')"
|
|
134
|
-
fi
|
|
135
|
-
unset MAXY_HOOK_BUFFER_DIR MAXY_HOOK_SERVER_LOG
|
|
136
|
-
|
|
137
|
-
# ── Task 566: maxy-MCP-Agent-spawned specialist passthrough ────────────
|
|
138
|
-
# MCP-Agent specialists are separate top-level claude rc processes with no
|
|
139
|
-
# parent_tool_use_id; pty-spawner stamps MAXY_SPECIALIST=<name> on the env.
|
|
140
|
-
MAXY_SPECIALIST_OVERRIDE=content-producer \
|
|
141
|
-
run Edit "" 0 '\[admin-tool-gate\] role=subagent .*specialist=content-producer .*tool=Edit decision=allow source=mcp-specialist' \
|
|
142
|
-
"Test 14: mcp-specialist Edit passes through (Task 566)"
|
|
143
|
-
|
|
144
|
-
MAXY_SPECIALIST_OVERRIDE=coding-assistant \
|
|
145
|
-
run Bash "" 0 '\[admin-tool-gate\] role=subagent .*specialist=coding-assistant .*tool=Bash decision=allow source=mcp-specialist' \
|
|
146
|
-
"Test 15: mcp-specialist Bash passes through (Task 559 regression class)"
|
|
147
|
-
|
|
148
|
-
MAXY_SPECIALIST_OVERRIDE=research-assistant \
|
|
149
|
-
run WebFetch "" 0 '\[admin-tool-gate\] role=subagent .*specialist=research-assistant .*tool=WebFetch decision=allow source=mcp-specialist' \
|
|
150
|
-
"Test 16: mcp-specialist WebFetch passes through"
|
|
151
|
-
|
|
152
|
-
# ── Task 566: missing-marker fail-closed ───────────────────────────────
|
|
153
|
-
# No parent_tool_use_id, no MAXY_SPECIALIST, MAXY_SESSION_ROLE not 'admin'.
|
|
154
|
-
MAXY_SESSION_ROLE_OVERRIDE="" MAXY_SPECIALIST_OVERRIDE="" \
|
|
155
|
-
run Edit "" 2 '\[admin-tool-gate\] role=unknown .*tool=Edit decision=block reason=missing-marker' \
|
|
156
|
-
"Test 17: missing-marker fail-closed (no positive role marker)"
|
|
157
|
-
|
|
158
|
-
# ── Task 566: combined parent_tool_use_id + MAXY_SPECIALIST is also subagent ──
|
|
159
|
-
MAXY_SPECIALIST_OVERRIDE=database-operator \
|
|
160
|
-
run Bash "$SUBAGENT_PARENT_ID" 0 '\[admin-tool-gate\] role=subagent .*specialist=database-operator .*tool=Bash decision=allow source=task-subagent\+mcp-specialist' \
|
|
161
|
-
"Test 18: combined parent + specialist resolves as task-subagent+mcp-specialist"
|
|
162
|
-
|
|
163
|
-
echo
|
|
164
|
-
echo "──────── pre-tool-use admin-tool-gate test summary ────────"
|
|
165
|
-
echo "PASS: $PASS"
|
|
166
|
-
echo "FAIL: $FAIL"
|
|
167
|
-
|
|
168
|
-
[[ "$FAIL" -gt 0 ]] && exit 1
|
|
169
|
-
exit 0
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Regression test for the base64 context-overflow guard.
|
|
3
|
-
#
|
|
4
|
-
# Covers two PreToolUse rejection paths in pre-tool-use.sh:
|
|
5
|
-
#
|
|
6
|
-
# 1. Bash producer guard — `tool_input.command` invoking `base64` (encode
|
|
7
|
-
# direction) or `xxd -p` is rejected; `base64 -d|-D|--decode` is allowed.
|
|
8
|
-
# 2. Write/Edit consumer guard — `tool_input.content` (Write) or
|
|
9
|
-
# `tool_input.new_string` (Edit) carrying `data:<mime>;base64,<≥4096 chars>`
|
|
10
|
-
# is rejected; small inline data URIs and plain content are allowed.
|
|
11
|
-
#
|
|
12
|
-
# Plus a fail-open case (malformed stdin → exit 0 silent) to pin the contract
|
|
13
|
-
# established by the playwright-file-guard test (terminal-stdin guard + parse-
|
|
14
|
-
# error fail-open).
|
|
15
|
-
|
|
16
|
-
set -u
|
|
17
|
-
|
|
18
|
-
HOOK="$(cd "$(dirname "$0")/.." && pwd)/pre-tool-use.sh"
|
|
19
|
-
if [[ ! -x "$HOOK" ]]; then
|
|
20
|
-
echo "FAIL: $HOOK not executable" >&2
|
|
21
|
-
exit 1
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
TMPFILES=()
|
|
25
|
-
cleanup_test_state() {
|
|
26
|
-
for f in "${TMPFILES[@]:-}"; do
|
|
27
|
-
[[ -n "$f" ]] && rm -f "$f" 2>/dev/null || true
|
|
28
|
-
done
|
|
29
|
-
}
|
|
30
|
-
trap cleanup_test_state EXIT
|
|
31
|
-
|
|
32
|
-
PASS=0
|
|
33
|
-
FAIL=0
|
|
34
|
-
pass() { echo "PASS: $1"; PASS=$((PASS + 1)); }
|
|
35
|
-
fail() { echo "FAIL: $1" >&2; FAIL=$((FAIL + 1)); }
|
|
36
|
-
|
|
37
|
-
# Helper: run hook with Bash tool_input.command and assert exit code + stderr.
|
|
38
|
-
run_bash() {
|
|
39
|
-
local command_text="$1"; local expected_rc="$2"; local stderr_pattern="$3"; local label="$4"
|
|
40
|
-
local input_json
|
|
41
|
-
# Build the input JSON via python3 so command_text with quotes / specials is safe.
|
|
42
|
-
input_json=$(python3 -c '
|
|
43
|
-
import json, sys
|
|
44
|
-
print(json.dumps({"hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": {"command": sys.argv[1]}, "transcript_path": "/tmp/sess/parent.jsonl", "parent_tool_use_id": "toolu_test_subagent"}, separators=(",", ":")))
|
|
45
|
-
' "$command_text")
|
|
46
|
-
local stdout_file; stdout_file=$(mktemp); TMPFILES+=("$stdout_file")
|
|
47
|
-
local stderr_file; stderr_file=$(mktemp); TMPFILES+=("$stderr_file")
|
|
48
|
-
printf '%s' "$input_json" | bash "$HOOK" admin >"$stdout_file" 2>"$stderr_file"
|
|
49
|
-
local rc=$?
|
|
50
|
-
if [[ "$rc" -ne "$expected_rc" ]]; then
|
|
51
|
-
fail "$label: expected exit $expected_rc, got $rc. Stderr: $(cat "$stderr_file")"
|
|
52
|
-
return
|
|
53
|
-
fi
|
|
54
|
-
if [[ -n "$stderr_pattern" ]] && ! grep -qE "$stderr_pattern" "$stderr_file"; then
|
|
55
|
-
fail "$label: stderr missing pattern '$stderr_pattern'. Got: $(cat "$stderr_file")"
|
|
56
|
-
return
|
|
57
|
-
fi
|
|
58
|
-
pass "$label"
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
# Helper: run hook with Write tool_input.content and assert exit code + stderr.
|
|
62
|
-
run_write() {
|
|
63
|
-
local content="$1"; local expected_rc="$2"; local stderr_pattern="$3"; local label="$4"
|
|
64
|
-
local input_json
|
|
65
|
-
input_json=$(python3 -c '
|
|
66
|
-
import json, sys
|
|
67
|
-
print(json.dumps({"hook_event_name": "PreToolUse", "tool_name": "Write", "tool_input": {"file_path": "/tmp/test.html", "content": sys.argv[1]}, "transcript_path": "/tmp/sess/parent.jsonl", "parent_tool_use_id": "toolu_test_subagent"}, separators=(",", ":")))
|
|
68
|
-
' "$content")
|
|
69
|
-
local stdout_file; stdout_file=$(mktemp); TMPFILES+=("$stdout_file")
|
|
70
|
-
local stderr_file; stderr_file=$(mktemp); TMPFILES+=("$stderr_file")
|
|
71
|
-
printf '%s' "$input_json" | bash "$HOOK" admin >"$stdout_file" 2>"$stderr_file"
|
|
72
|
-
local rc=$?
|
|
73
|
-
if [[ "$rc" -ne "$expected_rc" ]]; then
|
|
74
|
-
fail "$label: expected exit $expected_rc, got $rc. Stderr: $(cat "$stderr_file")"
|
|
75
|
-
return
|
|
76
|
-
fi
|
|
77
|
-
if [[ -n "$stderr_pattern" ]] && ! grep -qE "$stderr_pattern" "$stderr_file"; then
|
|
78
|
-
fail "$label: stderr missing pattern '$stderr_pattern'. Got: $(cat "$stderr_file")"
|
|
79
|
-
return
|
|
80
|
-
fi
|
|
81
|
-
pass "$label"
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
# Helper: run hook with Edit tool_input.new_string and assert exit code.
|
|
85
|
-
run_edit() {
|
|
86
|
-
local new_string="$1"; local expected_rc="$2"; local stderr_pattern="$3"; local label="$4"
|
|
87
|
-
local input_json
|
|
88
|
-
input_json=$(python3 -c '
|
|
89
|
-
import json, sys
|
|
90
|
-
print(json.dumps({"hook_event_name": "PreToolUse", "tool_name": "Edit", "tool_input": {"file_path": "/tmp/test.html", "old_string": "OLD", "new_string": sys.argv[1]}, "transcript_path": "/tmp/sess/parent.jsonl", "parent_tool_use_id": "toolu_test_subagent"}, separators=(",", ":")))
|
|
91
|
-
' "$new_string")
|
|
92
|
-
local stdout_file; stdout_file=$(mktemp); TMPFILES+=("$stdout_file")
|
|
93
|
-
local stderr_file; stderr_file=$(mktemp); TMPFILES+=("$stderr_file")
|
|
94
|
-
printf '%s' "$input_json" | bash "$HOOK" admin >"$stdout_file" 2>"$stderr_file"
|
|
95
|
-
local rc=$?
|
|
96
|
-
if [[ "$rc" -ne "$expected_rc" ]]; then
|
|
97
|
-
fail "$label: expected exit $expected_rc, got $rc. Stderr: $(cat "$stderr_file")"
|
|
98
|
-
return
|
|
99
|
-
fi
|
|
100
|
-
if [[ -n "$stderr_pattern" ]] && ! grep -qE "$stderr_pattern" "$stderr_file"; then
|
|
101
|
-
fail "$label: stderr missing pattern '$stderr_pattern'. Got: $(cat "$stderr_file")"
|
|
102
|
-
return
|
|
103
|
-
fi
|
|
104
|
-
pass "$label"
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
# Generate a base64-character blob >= 4096 chars (data-URI body trigger).
|
|
108
|
-
LARGE_B64=$(python3 -c "print('A' * 5000)")
|
|
109
|
-
|
|
110
|
-
# ───────── Bash producer guard ──────────────────────────────────────────────
|
|
111
|
-
run_bash "echo hello world" 0 "" \
|
|
112
|
-
"Test 1: bare Bash command (no base64) allowed"
|
|
113
|
-
|
|
114
|
-
run_bash "ls -la" 0 "" \
|
|
115
|
-
"Test 2: ls -la (no base64 token) allowed"
|
|
116
|
-
|
|
117
|
-
run_bash "base64 /tmp/foo.png" 2 '\[pre-tool-use\] guard=base64-tool-result.*tool=Bash.*reason=base64-encoder.*action=reject' \
|
|
118
|
-
"Test 3: 'base64 file' (encode) rejected"
|
|
119
|
-
|
|
120
|
-
run_bash "cat foo.png | base64" 2 '\[pre-tool-use\] guard=base64-tool-result.*action=reject' \
|
|
121
|
-
"Test 4: 'cat | base64' (encode pipeline) rejected"
|
|
122
|
-
|
|
123
|
-
run_bash "cat foo.png|base64 -w0" 2 '\[pre-tool-use\] guard=base64-tool-result.*action=reject' \
|
|
124
|
-
"Test 5: 'base64 -w0' (encode with line-wrap flag) rejected"
|
|
125
|
-
|
|
126
|
-
run_bash "xxd -p file.bin" 2 '\[pre-tool-use\] guard=base64-tool-result.*reason=xxd-plain-hex.*action=reject' \
|
|
127
|
-
"Test 6: 'xxd -p' (plain hex encode) rejected"
|
|
128
|
-
|
|
129
|
-
run_bash "base64 -d input.b64 > output.bin" 0 "" \
|
|
130
|
-
"Test 7: 'base64 -d' (decode direction) allowed"
|
|
131
|
-
|
|
132
|
-
run_bash "base64 --decode < x.b64 > y.bin" 0 "" \
|
|
133
|
-
"Test 8: 'base64 --decode' (decode long-form) allowed"
|
|
134
|
-
|
|
135
|
-
run_bash "echo Zm9v | base64 -d" 0 "" \
|
|
136
|
-
"Test 9: 'base64 -d' decode pipeline allowed"
|
|
137
|
-
|
|
138
|
-
run_bash "echo '--debug-base64-foo'" 0 "" \
|
|
139
|
-
"Test 10: 'base64' substring inside flag name does NOT false-match"
|
|
140
|
-
|
|
141
|
-
run_bash "ls mybase64tool" 0 "" \
|
|
142
|
-
"Test 11: 'base64' substring inside identifier does NOT false-match"
|
|
143
|
-
|
|
144
|
-
run_bash "cat in.b64 | base64 -d > /tmp/foo.bin; cat /tmp/bar.png | base64" 2 '\[pre-tool-use\] guard=base64-tool-result.*reason=base64-encoder.*action=reject' \
|
|
145
|
-
"Test 11b: compound (decode ; encode) rejects encoder segment (per-segment scan)"
|
|
146
|
-
|
|
147
|
-
run_bash "echo data | base64 -d > x.bin && cat y.png | base64 > y.b64" 2 '\[pre-tool-use\] guard=base64-tool-result.*action=reject' \
|
|
148
|
-
"Test 11c: compound (decode && encode) rejects encoder segment"
|
|
149
|
-
|
|
150
|
-
run_bash "base64 -d in.b64 > out.bin; base64 -d in2.b64 > out2.bin" 0 "" \
|
|
151
|
-
"Test 11d: compound (decode ; decode) allowed"
|
|
152
|
-
|
|
153
|
-
# ───────── Write/Edit consumer guard ────────────────────────────────────────
|
|
154
|
-
run_write "<html><body>hello world</body></html>" 0 "" \
|
|
155
|
-
"Test 12: Write small HTML content (no data URI) allowed"
|
|
156
|
-
|
|
157
|
-
run_write "<img src='data:image/png;base64,AAAA'>" 0 "" \
|
|
158
|
-
"Test 13: Write content with small inline data URI (<4096 chars) allowed"
|
|
159
|
-
|
|
160
|
-
run_write "<img src='data:image/png;base64,${LARGE_B64}'>" 2 '\[pre-tool-use\] guard=base64-write-content.*action=reject' \
|
|
161
|
-
"Test 14: Write content with large inline data URI (>4096 chars) rejected"
|
|
162
|
-
|
|
163
|
-
run_edit "<img src='data:image/png;base64,${LARGE_B64}'>" 2 '\[pre-tool-use\] guard=base64-write-content.*action=reject' \
|
|
164
|
-
"Test 15: Edit new_string with large inline data URI rejected"
|
|
165
|
-
|
|
166
|
-
run_edit "<p>just text replacement</p>" 0 "" \
|
|
167
|
-
"Test 16: Edit new_string with plain text allowed"
|
|
168
|
-
|
|
169
|
-
# ───────── Fail-open / structural ───────────────────────────────────────────
|
|
170
|
-
STDOUT_FILE=$(mktemp); STDERR_FILE=$(mktemp); TMPFILES+=("$STDOUT_FILE" "$STDERR_FILE")
|
|
171
|
-
printf '%s' 'not json at all { ' | bash "$HOOK" admin >"$STDOUT_FILE" 2>"$STDERR_FILE"
|
|
172
|
-
RC=$?
|
|
173
|
-
if [[ "$RC" -ne 0 ]]; then
|
|
174
|
-
fail "Test 17: malformed stdin should fail open (exit 0), got $RC. Stderr: $(cat "$STDERR_FILE")"
|
|
175
|
-
else
|
|
176
|
-
pass "Test 17: malformed stdin → silent passthrough (fail-open)"
|
|
177
|
-
fi
|
|
178
|
-
|
|
179
|
-
# Terminal-stdin guard preserved (no -t 0 test runs in test harness; assert
|
|
180
|
-
# the guard line exists in source).
|
|
181
|
-
if ! grep -q '\[ -t 0 \]' "$HOOK"; then
|
|
182
|
-
fail "Test 18: terminal stdin guard missing from hook source"
|
|
183
|
-
else
|
|
184
|
-
pass "Test 18: terminal stdin guard present in source"
|
|
185
|
-
fi
|
|
186
|
-
|
|
187
|
-
# Pre-existing guards still active — entitlement file edit still rejected.
|
|
188
|
-
ENT_JSON=$(python3 -c 'import json; print(json.dumps({"hook_event_name":"PreToolUse","tool_name":"Write","tool_input":{"file_path":"/srv/entitlement.json","content":"{\"tier\":\"max\"}"},"transcript_path":"/tmp/sess/parent.jsonl","parent_tool_use_id":"toolu_test_subagent"}, separators=(",", ":")))')
|
|
189
|
-
STDOUT_FILE=$(mktemp); STDERR_FILE=$(mktemp); TMPFILES+=("$STDOUT_FILE" "$STDERR_FILE")
|
|
190
|
-
printf '%s' "$ENT_JSON" | bash "$HOOK" admin >"$STDOUT_FILE" 2>"$STDERR_FILE"
|
|
191
|
-
RC=$?
|
|
192
|
-
if [[ "$RC" -ne 2 ]]; then
|
|
193
|
-
fail "Test 19: pre-existing entitlement guard regressed (expected exit 2, got $RC)"
|
|
194
|
-
else
|
|
195
|
-
pass "Test 19: pre-existing entitlement guard still rejects entitlement.json"
|
|
196
|
-
fi
|
|
197
|
-
|
|
198
|
-
echo
|
|
199
|
-
echo "──────── pre-tool-use base64 guard test summary ────────"
|
|
200
|
-
echo "PASS: $PASS"
|
|
201
|
-
echo "FAIL: $FAIL"
|
|
202
|
-
|
|
203
|
-
[[ "$FAIL" -gt 0 ]] && exit 1
|
|
204
|
-
exit 0
|
package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-memory-write-passthrough.test.sh
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Task 225 — passthrough regression test for memory-write / memory-update
|
|
3
|
-
# under the admin agent's pre-tool-use.sh.
|
|
4
|
-
#
|
|
5
|
-
# The Task 213→222 chain previously gated both writers at the top of the
|
|
6
|
-
# admin branch with an exit-2 block (admin direct) and an exit-0 allow
|
|
7
|
-
# (Task-subagent). Task 225 reversed the doctrine: admin holds the
|
|
8
|
-
# writers in its own surface and the case-block was deleted. This test
|
|
9
|
-
# pins the new behaviour so a future re-introduction of the block
|
|
10
|
-
# cannot land silently.
|
|
11
|
-
#
|
|
12
|
-
# Five cases:
|
|
13
|
-
# 1. mcp__plugin_memory_memory__memory-write admin-direct → exit 0, no rejection stderr
|
|
14
|
-
# 2. mcp__plugin_memory_memory__memory-update admin-direct → exit 0, no rejection stderr
|
|
15
|
-
# 3. mcp__plugin_memory_memory__memory-search admin-direct → exit 0, no rejection stderr (read tool control)
|
|
16
|
-
# 4. mcp__memory__memory-write admin-direct → exit 0 (pre-209 namespace, falls through harmlessly)
|
|
17
|
-
# 5. malformed JSON stdin → exit 0 (no crash; case-block removal does not break fall-through)
|
|
18
|
-
|
|
19
|
-
set -u
|
|
20
|
-
|
|
21
|
-
HOOK="$(cd "$(dirname "$0")/.." && pwd)/pre-tool-use.sh"
|
|
22
|
-
if [[ ! -x "$HOOK" ]]; then
|
|
23
|
-
echo "FAIL: $HOOK not executable" >&2
|
|
24
|
-
exit 1
|
|
25
|
-
fi
|
|
26
|
-
|
|
27
|
-
TMPFILES=()
|
|
28
|
-
cleanup() {
|
|
29
|
-
for f in "${TMPFILES[@]:-}"; do
|
|
30
|
-
[[ -n "$f" ]] && rm -f "$f" 2>/dev/null || true
|
|
31
|
-
done
|
|
32
|
-
}
|
|
33
|
-
trap cleanup EXIT
|
|
34
|
-
|
|
35
|
-
PASS=0
|
|
36
|
-
FAIL=0
|
|
37
|
-
pass() { echo "PASS: $1"; PASS=$((PASS + 1)); }
|
|
38
|
-
fail() { echo "FAIL: $1" >&2; FAIL=$((FAIL + 1)); }
|
|
39
|
-
|
|
40
|
-
# Admin-direct calls land with a non-subagent transcript path. A synthetic
|
|
41
|
-
# empty file in a temp dir is enough — the hook no longer reads the path
|
|
42
|
-
# for the deleted writers' case-block, but other gates may still touch it.
|
|
43
|
-
ADMIN_TRANSCRIPT=$(mktemp); TMPFILES+=("$ADMIN_TRANSCRIPT")
|
|
44
|
-
: > "$ADMIN_TRANSCRIPT"
|
|
45
|
-
|
|
46
|
-
run_with_tool() {
|
|
47
|
-
local tool="$1"
|
|
48
|
-
local sid="$2"
|
|
49
|
-
local transcript_path="${3:-}"
|
|
50
|
-
local stdout_file; stdout_file=$(mktemp); TMPFILES+=("$stdout_file")
|
|
51
|
-
local stderr_file; stderr_file=$(mktemp); TMPFILES+=("$stderr_file")
|
|
52
|
-
python3 -c '
|
|
53
|
-
import json, sys
|
|
54
|
-
payload = {
|
|
55
|
-
"hook_event_name": "PreToolUse",
|
|
56
|
-
"session_id": sys.argv[1],
|
|
57
|
-
"tool_name": sys.argv[2],
|
|
58
|
-
"tool_input": {"name":"Smalleys","accountId":"acct-x","scope":"shared"},
|
|
59
|
-
}
|
|
60
|
-
if sys.argv[3]:
|
|
61
|
-
payload["transcript_path"] = sys.argv[3]
|
|
62
|
-
print(json.dumps(payload, separators=(",", ":")))
|
|
63
|
-
' "$sid" "$tool" "$transcript_path" | bash "$HOOK" admin >"$stdout_file" 2>"$stderr_file"
|
|
64
|
-
HOOK_RC=$?
|
|
65
|
-
HOOK_STDERR=$(cat "$stderr_file")
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
assert_passthrough() {
|
|
69
|
-
local label="$1"
|
|
70
|
-
if [[ "$HOOK_RC" -ne 0 ]]; then
|
|
71
|
-
fail "${label}: expected exit 0, got $HOOK_RC stderr: $HOOK_STDERR"
|
|
72
|
-
return
|
|
73
|
-
fi
|
|
74
|
-
if echo "$HOOK_STDERR" | grep -qF "does not write to the graph directly"; then
|
|
75
|
-
fail "${label}: stderr carries deleted-block rejection message: $HOOK_STDERR"
|
|
76
|
-
return
|
|
77
|
-
fi
|
|
78
|
-
pass "${label}: exit 0, no rejection stderr"
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
# --- 1. memory-write admin-direct passthrough ---------------------------
|
|
82
|
-
run_with_tool "mcp__plugin_memory_memory__memory-write" "sess-write-1" "$ADMIN_TRANSCRIPT"
|
|
83
|
-
assert_passthrough "admin-direct memory-write"
|
|
84
|
-
|
|
85
|
-
# --- 2. memory-update admin-direct passthrough --------------------------
|
|
86
|
-
run_with_tool "mcp__plugin_memory_memory__memory-update" "sess-update-1" "$ADMIN_TRANSCRIPT"
|
|
87
|
-
assert_passthrough "admin-direct memory-update"
|
|
88
|
-
|
|
89
|
-
# --- 3. memory-search admin-direct passthrough (read tool control) -----
|
|
90
|
-
run_with_tool "mcp__plugin_memory_memory__memory-search" "sess-search-1" "$ADMIN_TRANSCRIPT"
|
|
91
|
-
assert_passthrough "admin-direct memory-search (read tool control)"
|
|
92
|
-
|
|
93
|
-
# --- 4. pre-209 namespace mcp__memory__memory-write ---------------------
|
|
94
|
-
# The bare mcp__memory__ namespace was never in the admin runtime surface
|
|
95
|
-
# after Task 203's plugin_ prefix rename; this case pins that it still
|
|
96
|
-
# falls through harmlessly after the case-block removal.
|
|
97
|
-
run_with_tool "mcp__memory__memory-write" "sess-old-ns-1" "$ADMIN_TRANSCRIPT"
|
|
98
|
-
assert_passthrough "pre-209 namespace mcp__memory__memory-write"
|
|
99
|
-
|
|
100
|
-
# --- 5. malformed JSON stdin — hook must not crash ----------------------
|
|
101
|
-
stderr_file=$(mktemp); TMPFILES+=("$stderr_file")
|
|
102
|
-
stdout_file=$(mktemp); TMPFILES+=("$stdout_file")
|
|
103
|
-
echo "not-valid-json{{{" | bash "$HOOK" admin >"$stdout_file" 2>"$stderr_file"
|
|
104
|
-
HOOK_RC=$?
|
|
105
|
-
HOOK_STDERR=$(cat "$stderr_file")
|
|
106
|
-
if [[ "$HOOK_RC" -ne 0 ]]; then
|
|
107
|
-
fail "malformed payload: expected exit 0, got $HOOK_RC stderr: $HOOK_STDERR"
|
|
108
|
-
elif echo "$HOOK_STDERR" | grep -qF "does not write to the graph directly"; then
|
|
109
|
-
fail "malformed payload: stderr carries deleted-block rejection message: $HOOK_STDERR"
|
|
110
|
-
else
|
|
111
|
-
pass "malformed payload: exit 0, hook does not crash"
|
|
112
|
-
fi
|
|
113
|
-
|
|
114
|
-
# --- Summary -----------------------------------------------------------
|
|
115
|
-
echo "---"
|
|
116
|
-
echo "PASSED: $PASS FAILED: $FAIL"
|
|
117
|
-
[[ "$FAIL" -eq 0 ]] || exit 1
|
|
118
|
-
exit 0
|