@heretyc/subagent-mcp 2.12.16 → 2.12.17
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.
|
@@ -84,6 +84,25 @@ function stringField(value, key) {
|
|
|
84
84
|
const child = value[key];
|
|
85
85
|
return typeof child === "string" ? child : null;
|
|
86
86
|
}
|
|
87
|
+
function isSubagentSourceObject(source) {
|
|
88
|
+
if (!source || typeof source !== "object")
|
|
89
|
+
return false;
|
|
90
|
+
const record = source;
|
|
91
|
+
if (Object.prototype.hasOwnProperty.call(record, "subagent")) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
for (const kind of SUBAGENT_SOURCE_STRINGS) {
|
|
95
|
+
if (Object.prototype.hasOwnProperty.call(record, kind)) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Local Codex rollouts did not verify the subagent object schema. Accept the
|
|
100
|
+
// known subagent kind strings if Codex places them in a discriminator field.
|
|
101
|
+
const kind = record.kind;
|
|
102
|
+
const type = record.type;
|
|
103
|
+
return ((typeof kind === "string" && SUBAGENT_SOURCE_STRINGS.has(kind)) ||
|
|
104
|
+
(typeof type === "string" && SUBAGENT_SOURCE_STRINGS.has(type)));
|
|
105
|
+
}
|
|
87
106
|
function isTokenCountLine(obj) {
|
|
88
107
|
if (obj.type === "token_count")
|
|
89
108
|
return true;
|
|
@@ -166,11 +185,9 @@ export const codexAdapter = {
|
|
|
166
185
|
return true;
|
|
167
186
|
}
|
|
168
187
|
const source = payload.source;
|
|
169
|
-
// 0.131+: source
|
|
170
|
-
if (source
|
|
171
|
-
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
188
|
+
// 0.131+: source may be an object whose keys name the subagent kind.
|
|
189
|
+
if (isSubagentSourceObject(source)) {
|
|
190
|
+
return true;
|
|
174
191
|
}
|
|
175
192
|
// Older: source is a string enum.
|
|
176
193
|
if (typeof source === "string" && SUBAGENT_SOURCE_STRINGS.has(source)) {
|
package/dist/index.js
CHANGED
|
@@ -572,7 +572,7 @@ const ORCHESTRATION_INSTRUCTIONS = "subagent-mcp - CANONICAL OPERATING MODEL (fu
|
|
|
572
572
|
const SUBAGENT_INSTRUCTIONS = "SUB-AGENT SESSION: you are a child process launched by subagent-mcp. Follow the parent prompt. Do not treat yourself as the orchestrator, do not re-trigger orchestration carryover, and do not launch further sub-agents unless the parent prompt explicitly assigns that. launch_agent is code-capped at 2 spawn levels below the main orchestrator: depth 1 may launch depth 2 workers; depth 2 workers cannot spawn further.\n\nMODEL SELECTION MODE (parallel to orchestration-mode, set via the model-selection-mode tool). DEFAULT is \"smart\" and is used whenever unset: in smart, launch_agent REJECTS any call supplying provider/model/effort selectors and the server auto-picks the best model. \"user-approved-overrides\" opens a 30-MINUTE window where selectors are HONORED, enforced LAZILY (the mode reverts to smart on the next launch_agent call after 30 minutes) and re-enabling does NOT extend an active window. HONOR-BASED: you MUST NOT set \"user-approved-overrides\" without explicit interactive USER authorization via the structured-question tool (AskUserQuestion on Claude / request-user-input on Codex); never enable it on your own initiative.";
|
|
573
573
|
const server = new McpServer({
|
|
574
574
|
name: "subagent-mcp",
|
|
575
|
-
version: "2.12.
|
|
575
|
+
version: "2.12.17",
|
|
576
576
|
description: "Launches always-interactive local Claude and Codex sub-agent sessions and is the orchestrator's sole launch channel. Claude runs via the Claude Agent SDK over the local Claude Code executable; Codex via `codex app-server` over stdio. The server never calls Anthropic or OpenAI HTTP APIs directly.",
|
|
577
577
|
}, {
|
|
578
578
|
instructions: process.env.SUBAGENT_MCP_SUBAGENT === "1"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { closeSync, existsSync, openSync, readFileSync, readSync, statSync, } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { dirname, isAbsolute, join } from "node:path";
|
|
4
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
5
|
import * as marker from "./marker.js";
|
|
5
6
|
import * as reminder from "./reminder.js";
|
|
6
7
|
import * as handoff from "./handoff.js";
|
|
@@ -38,11 +39,14 @@ const OWNER_CLAIM_CAP = 8;
|
|
|
38
39
|
/**
|
|
39
40
|
* Resolve the repo-root `directives/` dir at runtime. Honors an explicit plugin
|
|
40
41
|
* root (Claude sets CLAUDE_PLUGIN_ROOT; a generic PLUGIN_ROOT is also accepted)
|
|
41
|
-
* so the bundled plugin finds its assets wherever it is installed
|
|
42
|
-
*
|
|
43
|
-
*
|
|
42
|
+
* so the bundled plugin finds its assets wherever it is installed, but only
|
|
43
|
+
* when that root is under an expected install prefix. Otherwise we walk up from
|
|
44
|
+
* the COMPILED file location: dist/hooks/<x>.js -> ../../directives ===
|
|
45
|
+
* <repoRoot>/directives.
|
|
44
46
|
*/
|
|
45
47
|
export function resolveDirectivesDir(env) {
|
|
48
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
49
|
+
const compiledRoot = resolve(here, "..", "..");
|
|
46
50
|
const rootEnvName = env.CLAUDE_PLUGIN_ROOT !== undefined
|
|
47
51
|
? "CLAUDE_PLUGIN_ROOT"
|
|
48
52
|
: env.PLUGIN_ROOT !== undefined
|
|
@@ -51,17 +55,57 @@ export function resolveDirectivesDir(env) {
|
|
|
51
55
|
if (rootEnvName) {
|
|
52
56
|
const root = env[rootEnvName] ?? "";
|
|
53
57
|
const directivesDir = join(root, "directives");
|
|
54
|
-
if (
|
|
55
|
-
|
|
58
|
+
if (isAbsolute(root) &&
|
|
59
|
+
existsSync(directivesDir) &&
|
|
60
|
+
isTrustedPluginRoot(root, env, compiledRoot)) {
|
|
61
|
+
return directivesDir;
|
|
56
62
|
}
|
|
57
|
-
return directivesDir;
|
|
58
63
|
}
|
|
59
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
60
64
|
// Compiled location is dist/orchestration/hook-core.js, so ../../directives
|
|
61
65
|
// is the repo root's directives dir; the entry shims live at dist/hooks/<x>.js
|
|
62
66
|
// and import this module, but __dirname here is the hook-core module's own
|
|
63
67
|
// dir. Two levels up from dist/orchestration is the repo root either way.
|
|
64
|
-
return join(
|
|
68
|
+
return join(compiledRoot, "directives");
|
|
69
|
+
}
|
|
70
|
+
function normalizePathKey(pathValue) {
|
|
71
|
+
let p = resolve(pathValue);
|
|
72
|
+
p = p.replace(/\\/g, "/");
|
|
73
|
+
if (process.platform === "win32") {
|
|
74
|
+
p = p.toLowerCase();
|
|
75
|
+
}
|
|
76
|
+
if (p.length > 1 && p.endsWith("/")) {
|
|
77
|
+
p = p.slice(0, -1);
|
|
78
|
+
}
|
|
79
|
+
return p;
|
|
80
|
+
}
|
|
81
|
+
function isPathUnder(pathValue, prefix) {
|
|
82
|
+
const child = normalizePathKey(pathValue);
|
|
83
|
+
const parent = normalizePathKey(prefix);
|
|
84
|
+
return child === parent || child.startsWith(parent + "/");
|
|
85
|
+
}
|
|
86
|
+
function installPrefixes(env, compiledRoot) {
|
|
87
|
+
const prefixes = [
|
|
88
|
+
compiledRoot,
|
|
89
|
+
env.npm_config_prefix,
|
|
90
|
+
env.PREFIX,
|
|
91
|
+
process.env.npm_config_prefix,
|
|
92
|
+
process.env.PREFIX,
|
|
93
|
+
process.platform === "win32" && env.APPDATA ? join(env.APPDATA, "npm") : undefined,
|
|
94
|
+
process.platform === "win32" && process.env.APPDATA
|
|
95
|
+
? join(process.env.APPDATA, "npm")
|
|
96
|
+
: undefined,
|
|
97
|
+
join(dirname(process.execPath), ".."),
|
|
98
|
+
join(homedir(), ".claude", "plugins"),
|
|
99
|
+
join(homedir(), ".codex", "plugins", "cache"),
|
|
100
|
+
join(homedir(), ".codex", "plugins"),
|
|
101
|
+
];
|
|
102
|
+
return [...new Set(prefixes.filter((p) => typeof p === "string" && p.length > 0))];
|
|
103
|
+
}
|
|
104
|
+
function isTrustedPluginRoot(root, env, compiledRoot) {
|
|
105
|
+
// Trust assumption: env roots are host-controlled only after they resolve
|
|
106
|
+
// under the package install, npm global prefix, or known plugin cache roots.
|
|
107
|
+
const resolvedRoot = resolve(root);
|
|
108
|
+
return installPrefixes(env, compiledRoot).some((prefix) => isPathUnder(resolvedRoot, prefix));
|
|
65
109
|
}
|
|
66
110
|
/** Read a directive asset by filename. On ANY failure return '' (fail-safe). */
|
|
67
111
|
export function readDirective(env, fileName) {
|
|
@@ -171,7 +215,9 @@ export function sessionKey(payload) {
|
|
|
171
215
|
}
|
|
172
216
|
if (typeof payload.transcript_path === "string" &&
|
|
173
217
|
payload.transcript_path.length > 0) {
|
|
174
|
-
|
|
218
|
+
// Residual caveat: a genuinely moved transcript still re-keys. Prefer the
|
|
219
|
+
// host session_id when present, which remains the precedence above.
|
|
220
|
+
return "tp-" + marker.hashKey(normalizePathKey(payload.transcript_path));
|
|
175
221
|
}
|
|
176
222
|
return undefined;
|
|
177
223
|
}
|
|
@@ -3,7 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
import { readCheckForUpdates } from "../concurrency.js";
|
|
4
4
|
import { atomicWriteJson } from "./atomic-write.js";
|
|
5
5
|
import { stateDir } from "./marker.js";
|
|
6
|
-
export const UPDATE_NOTICE_TEXT = "Notice: An improved version of subagent-mcp is available via the CLI command `subagent-mcp update` and can then be fully installed with `subagent-mcp setup`. This
|
|
6
|
+
export const UPDATE_NOTICE_TEXT = "Notice: An improved version of subagent-mcp is available via the CLI command `subagent-mcp update` and can then be fully installed with `subagent-mcp setup`. This may include security and user experience improvements.";
|
|
7
7
|
export const UPDATE_CHECK_TIMEOUT_MS = 2500;
|
|
8
8
|
export const UPDATE_NOTICE_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
9
9
|
function pendingNoticePath() {
|
|
@@ -165,6 +165,7 @@ export function appendUpdateNotice(out, installedVersion, sessionId, env = proce
|
|
|
165
165
|
notified_at: now,
|
|
166
166
|
...(sessionId ? { session_id: sessionId } : {}),
|
|
167
167
|
});
|
|
168
|
+
// Injection invariant: NEVER interpolate registry-sourced strings into injected text.
|
|
168
169
|
return `${out}\n${UPDATE_NOTICE_TEXT}`;
|
|
169
170
|
}
|
|
170
171
|
catch {
|
package/package.json
CHANGED