@rubytech/create-maxy-code 0.1.214 → 0.1.219
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/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +4 -1
- package/payload/platform/plugins/admin/hooks/__tests__/hook-emit-stale-lock-ttl.test.sh +102 -0
- package/payload/platform/plugins/admin/hooks/__tests__/post-tool-use-agent.test.sh +97 -1
- package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-admin-tool-gate.test.sh +55 -74
- package/payload/platform/plugins/admin/hooks/lib/hook-emit.sh +58 -0
- package/payload/platform/plugins/admin/hooks/post-tool-use-agent.sh +100 -29
- package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +0 -55
- package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.d.ts +2 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.js +130 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.js.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js +63 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/lib/catalogue-census.d.ts +14 -0
- package/payload/platform/plugins/admin/mcp/dist/lib/catalogue-census.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/lib/catalogue-census.js +50 -0
- package/payload/platform/plugins/admin/mcp/dist/lib/catalogue-census.js.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.d.ts +28 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.js +68 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.js.map +1 -0
- package/payload/platform/plugins/admin/skills/capabilities-here/SKILL.md +31 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +39 -30
- 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 +28 -25
- package/payload/platform/plugins/docs/references/plugins-guide.md +2 -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/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-architecture-skill-no-drift.mjs +89 -42
- package/payload/platform/scripts/check-roles-doc-completeness.mjs +96 -0
- package/payload/platform/scripts/check-specialist-tool-surface.mjs +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- 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 +54 -21
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +20 -9
- 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 +21 -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 +118 -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.js +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +28 -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 +110 -60
- 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/rc-script-spawn.d.ts +80 -0
- package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.js +306 -0
- package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/specialist-drift.d.ts +9 -0
- package/payload/platform/services/claude-session-manager/dist/specialist-drift.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/specialist-drift.js +6 -2
- package/payload/platform/services/claude-session-manager/dist/specialist-drift.js.map +1 -1
- 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 +6 -4
- package/payload/platform/templates/specialists/agents/content-producer.md +1 -1
- 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/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/package.json
CHANGED
|
@@ -20,41 +20,6 @@
|
|
|
20
20
|
*/
|
|
21
21
|
/** Wire-format metadata key the Claude Code SDK reads. */
|
|
22
22
|
export declare const ALWAYS_LOAD_META_KEY: "anthropic/alwaysLoad";
|
|
23
|
-
/**
|
|
24
|
-
* Admin-seat detection. The rc-daemon exports `MAXY_SESSION_ROLE=admin` and
|
|
25
|
-
* `MAXY_SPECIALIST=""` for the orchestrator process and its MCP plugin
|
|
26
|
-
* children; specialist spawns set `MAXY_SPECIALIST=<name>`. A plugin process
|
|
27
|
-
* inheriting the env can therefore decide at registration AND invocation
|
|
28
|
-
* time whether it is loaded inside the admin seat.
|
|
29
|
-
*
|
|
30
|
-
* Mirrors `isAdminSeat` in platform/plugins/admin/mcp/src/skill-resolution.ts
|
|
31
|
-
* — this is a duplicate at the library layer so plugins that cannot import
|
|
32
|
-
* from the admin plugin (a sibling, not a dependency) share the same rule.
|
|
33
|
-
*/
|
|
34
|
-
export declare function isAdminSeat(env?: NodeJS.ProcessEnv): boolean;
|
|
35
|
-
/**
|
|
36
|
-
* MCP refusal envelope returned when the admin seat invokes a specialist-
|
|
37
|
-
* owned tool. The text names the dispatch route so the model's next action
|
|
38
|
-
* is the `Agent` dispatch rather than a dead end.
|
|
39
|
-
*/
|
|
40
|
-
export declare function adminBlockedResponse(toolName: string, owner: string): {
|
|
41
|
-
content: {
|
|
42
|
-
type: "text";
|
|
43
|
-
text: string;
|
|
44
|
-
}[];
|
|
45
|
-
isError: true;
|
|
46
|
-
};
|
|
47
|
-
/**
|
|
48
|
-
* Wrap a tool handler so any admin-seat invocation short-circuits with the
|
|
49
|
-
* refusal envelope above and emits one `[admin-tool-gate]` line to stderr.
|
|
50
|
-
*
|
|
51
|
-
* Specialist seats pass through unchanged: this is the primitive Task 529
|
|
52
|
-
* defines for closing the in-message authoring path that Task 516 could
|
|
53
|
-
* not reach. The handler signature is preserved so existing call sites
|
|
54
|
-
* substitute `eagerTool(... handler)` for `eagerTool(... withAdminGate(name,
|
|
55
|
-
* owner, handler))` mechanically.
|
|
56
|
-
*/
|
|
57
|
-
export declare function withAdminGate<H extends (...args: any[]) => any>(toolName: string, owner: string, handler: H): H;
|
|
58
23
|
/**
|
|
59
24
|
* Loose structural type for any object exposing a `registerTool` method. The
|
|
60
25
|
* MCP SDK ships dual ESM/CJS packages with conditional exports, so importing
|
|
@@ -92,25 +57,5 @@ type LocalZodRawShape = Record<string, unknown>;
|
|
|
92
57
|
* the consumer's own zod version) infers the handler args.
|
|
93
58
|
*/
|
|
94
59
|
export declare function eagerTool<Args extends LocalZodRawShape>(server: EagerCapableServer, name: string, description: string, inputSchema: Args, handler: (...args: any[]) => any): void;
|
|
95
|
-
/**
|
|
96
|
-
* Loose structural type for the legacy four-arg `server.tool(name, desc,
|
|
97
|
-
* schema, handler)` call shape — used when the admin seat must register a
|
|
98
|
-
* specialist-owned tool as deferred (not eager) so it does not appear in
|
|
99
|
-
* the admin's eager tool list.
|
|
100
|
-
*/
|
|
101
|
-
type DeferredCapableServer = {
|
|
102
|
-
tool: (name: string, description: string, inputSchema: any, handler: (...args: any[]) => any) => any;
|
|
103
|
-
};
|
|
104
|
-
/**
|
|
105
|
-
* Register a tool that is eager for specialists but deferred + admin-gated
|
|
106
|
-
* for the admin seat. The owning specialist name is named here so the
|
|
107
|
-
* refusal envelope (and the `[admin-tool-gate]` log line) carries it.
|
|
108
|
-
*
|
|
109
|
-
* Use for every tool whose owner is a specialist (database-operator, writer-
|
|
110
|
-
* craft, research-assistant, personal-assistant, content-producer). The
|
|
111
|
-
* admin seat sees the tool only via ToolSearch — and any invocation from the
|
|
112
|
-
* admin seat refuses with the dispatch hint.
|
|
113
|
-
*/
|
|
114
|
-
export declare function eagerToolGated<Args extends LocalZodRawShape>(server: EagerCapableServer & DeferredCapableServer, name: string, owner: string, description: string, inputSchema: Args, handler: (...args: any[]) => any): void;
|
|
115
60
|
export {};
|
|
116
61
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,0DAA0D;AAC1D,eAAO,MAAM,oBAAoB,EAAG,sBAA+B,CAAC;AAEpE
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,0DAA0D;AAC1D,eAAO,MAAM,oBAAoB,EAAG,sBAA+B,CAAC;AAEpE;;;;;;;GAOG;AAEH,KAAK,kBAAkB,GAAG;IAAE,YAAY,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAA;CAAE,CAAC;AAEpE;;;;;;;;;;GAUG;AACH,KAAK,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEhD;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,IAAI,SAAS,gBAAgB,EACrD,MAAM,EAAE,kBAAkB,EAC1B,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,IAAI,EAEjB,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,GAC/B,IAAI,CAUN"}
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ALWAYS_LOAD_META_KEY = void 0;
|
|
4
|
-
exports.isAdminSeat = isAdminSeat;
|
|
5
|
-
exports.adminBlockedResponse = adminBlockedResponse;
|
|
6
|
-
exports.withAdminGate = withAdminGate;
|
|
7
4
|
exports.eagerTool = eagerTool;
|
|
8
|
-
exports.eagerToolGated = eagerToolGated;
|
|
9
5
|
/**
|
|
10
6
|
* MCP eager-load helper.
|
|
11
7
|
*
|
|
@@ -28,59 +24,6 @@ exports.eagerToolGated = eagerToolGated;
|
|
|
28
24
|
*/
|
|
29
25
|
/** Wire-format metadata key the Claude Code SDK reads. */
|
|
30
26
|
exports.ALWAYS_LOAD_META_KEY = "anthropic/alwaysLoad";
|
|
31
|
-
/**
|
|
32
|
-
* Admin-seat detection. The rc-daemon exports `MAXY_SESSION_ROLE=admin` and
|
|
33
|
-
* `MAXY_SPECIALIST=""` for the orchestrator process and its MCP plugin
|
|
34
|
-
* children; specialist spawns set `MAXY_SPECIALIST=<name>`. A plugin process
|
|
35
|
-
* inheriting the env can therefore decide at registration AND invocation
|
|
36
|
-
* time whether it is loaded inside the admin seat.
|
|
37
|
-
*
|
|
38
|
-
* Mirrors `isAdminSeat` in platform/plugins/admin/mcp/src/skill-resolution.ts
|
|
39
|
-
* — this is a duplicate at the library layer so plugins that cannot import
|
|
40
|
-
* from the admin plugin (a sibling, not a dependency) share the same rule.
|
|
41
|
-
*/
|
|
42
|
-
function isAdminSeat(env = process.env) {
|
|
43
|
-
return env.MAXY_SESSION_ROLE === "admin" && !(env.MAXY_SPECIALIST && env.MAXY_SPECIALIST.length > 0);
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* MCP refusal envelope returned when the admin seat invokes a specialist-
|
|
47
|
-
* owned tool. The text names the dispatch route so the model's next action
|
|
48
|
-
* is the `Agent` dispatch rather than a dead end.
|
|
49
|
-
*/
|
|
50
|
-
function adminBlockedResponse(toolName, owner) {
|
|
51
|
-
return {
|
|
52
|
-
content: [
|
|
53
|
-
{
|
|
54
|
-
type: "text",
|
|
55
|
-
text: `Tool \`${toolName}\` is owned by the \`${owner}\` specialist and is not callable from the admin seat. ` +
|
|
56
|
-
`Dispatch \`${owner}\` with the Agent tool and pass the work as the brief.`,
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
isError: true,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Wrap a tool handler so any admin-seat invocation short-circuits with the
|
|
64
|
-
* refusal envelope above and emits one `[admin-tool-gate]` line to stderr.
|
|
65
|
-
*
|
|
66
|
-
* Specialist seats pass through unchanged: this is the primitive Task 529
|
|
67
|
-
* defines for closing the in-message authoring path that Task 516 could
|
|
68
|
-
* not reach. The handler signature is preserved so existing call sites
|
|
69
|
-
* substitute `eagerTool(... handler)` for `eagerTool(... withAdminGate(name,
|
|
70
|
-
* owner, handler))` mechanically.
|
|
71
|
-
*/
|
|
72
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
73
|
-
function withAdminGate(toolName, owner, handler) {
|
|
74
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
|
-
const wrapped = ((...args) => {
|
|
76
|
-
if (isAdminSeat(process.env)) {
|
|
77
|
-
process.stderr.write(`[admin-tool-gate] role=admin tool=${toolName} decision=block reason=specialist-owned owner=${owner}\n`);
|
|
78
|
-
return adminBlockedResponse(toolName, owner);
|
|
79
|
-
}
|
|
80
|
-
return handler(...args);
|
|
81
|
-
});
|
|
82
|
-
return wrapped;
|
|
83
|
-
}
|
|
84
27
|
/**
|
|
85
28
|
* Register an MCP tool that the Claude Code SDK eager-loads into the
|
|
86
29
|
* model's prompt at session boot, bypassing the ToolSearch round-trip.
|
|
@@ -103,23 +46,4 @@ handler) {
|
|
|
103
46
|
_meta: { [exports.ALWAYS_LOAD_META_KEY]: true },
|
|
104
47
|
}, handler);
|
|
105
48
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Register a tool that is eager for specialists but deferred + admin-gated
|
|
108
|
-
* for the admin seat. The owning specialist name is named here so the
|
|
109
|
-
* refusal envelope (and the `[admin-tool-gate]` log line) carries it.
|
|
110
|
-
*
|
|
111
|
-
* Use for every tool whose owner is a specialist (database-operator, writer-
|
|
112
|
-
* craft, research-assistant, personal-assistant, content-producer). The
|
|
113
|
-
* admin seat sees the tool only via ToolSearch — and any invocation from the
|
|
114
|
-
* admin seat refuses with the dispatch hint.
|
|
115
|
-
*/
|
|
116
|
-
function eagerToolGated(server, name, owner, description, inputSchema,
|
|
117
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
118
|
-
handler) {
|
|
119
|
-
if (isAdminSeat(process.env)) {
|
|
120
|
-
server.tool(name, description, inputSchema, withAdminGate(name, owner, handler));
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
eagerTool(server, name, description, inputSchema, handler);
|
|
124
|
-
}
|
|
125
49
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AA4DA,8BAiBC;AA7ED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,0DAA0D;AAC7C,QAAA,oBAAoB,GAAG,sBAA+B,CAAC;AA0BpE;;;;;;;;;;;;GAYG;AACH,SAAgB,SAAS,CACvB,MAA0B,EAC1B,IAAY,EACZ,WAAmB,EACnB,WAAiB;AACjB,8DAA8D;AAC9D,OAAgC;IAEhC,MAAM,CAAC,YAAY,CACjB,IAAI,EACJ;QACE,WAAW;QACX,WAAW;QACX,KAAK,EAAE,EAAE,CAAC,4BAAoB,CAAC,EAAE,IAAI,EAAE;KACxC,EACD,OAAO,CACR,CAAC;AACJ,CAAC"}
|
|
@@ -21,72 +21,6 @@
|
|
|
21
21
|
/** Wire-format metadata key the Claude Code SDK reads. */
|
|
22
22
|
export const ALWAYS_LOAD_META_KEY = "anthropic/alwaysLoad" as const;
|
|
23
23
|
|
|
24
|
-
/**
|
|
25
|
-
* Admin-seat detection. The rc-daemon exports `MAXY_SESSION_ROLE=admin` and
|
|
26
|
-
* `MAXY_SPECIALIST=""` for the orchestrator process and its MCP plugin
|
|
27
|
-
* children; specialist spawns set `MAXY_SPECIALIST=<name>`. A plugin process
|
|
28
|
-
* inheriting the env can therefore decide at registration AND invocation
|
|
29
|
-
* time whether it is loaded inside the admin seat.
|
|
30
|
-
*
|
|
31
|
-
* Mirrors `isAdminSeat` in platform/plugins/admin/mcp/src/skill-resolution.ts
|
|
32
|
-
* — this is a duplicate at the library layer so plugins that cannot import
|
|
33
|
-
* from the admin plugin (a sibling, not a dependency) share the same rule.
|
|
34
|
-
*/
|
|
35
|
-
export function isAdminSeat(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
36
|
-
return env.MAXY_SESSION_ROLE === "admin" && !(env.MAXY_SPECIALIST && env.MAXY_SPECIALIST.length > 0);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* MCP refusal envelope returned when the admin seat invokes a specialist-
|
|
41
|
-
* owned tool. The text names the dispatch route so the model's next action
|
|
42
|
-
* is the `Agent` dispatch rather than a dead end.
|
|
43
|
-
*/
|
|
44
|
-
export function adminBlockedResponse(toolName: string, owner: string): {
|
|
45
|
-
content: { type: "text"; text: string }[];
|
|
46
|
-
isError: true;
|
|
47
|
-
} {
|
|
48
|
-
return {
|
|
49
|
-
content: [
|
|
50
|
-
{
|
|
51
|
-
type: "text",
|
|
52
|
-
text:
|
|
53
|
-
`Tool \`${toolName}\` is owned by the \`${owner}\` specialist and is not callable from the admin seat. ` +
|
|
54
|
-
`Dispatch \`${owner}\` with the Agent tool and pass the work as the brief.`,
|
|
55
|
-
},
|
|
56
|
-
],
|
|
57
|
-
isError: true,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Wrap a tool handler so any admin-seat invocation short-circuits with the
|
|
63
|
-
* refusal envelope above and emits one `[admin-tool-gate]` line to stderr.
|
|
64
|
-
*
|
|
65
|
-
* Specialist seats pass through unchanged: this is the primitive Task 529
|
|
66
|
-
* defines for closing the in-message authoring path that Task 516 could
|
|
67
|
-
* not reach. The handler signature is preserved so existing call sites
|
|
68
|
-
* substitute `eagerTool(... handler)` for `eagerTool(... withAdminGate(name,
|
|
69
|
-
* owner, handler))` mechanically.
|
|
70
|
-
*/
|
|
71
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
72
|
-
export function withAdminGate<H extends (...args: any[]) => any>(
|
|
73
|
-
toolName: string,
|
|
74
|
-
owner: string,
|
|
75
|
-
handler: H,
|
|
76
|
-
): H {
|
|
77
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
78
|
-
const wrapped = ((...args: any[]) => {
|
|
79
|
-
if (isAdminSeat(process.env)) {
|
|
80
|
-
process.stderr.write(
|
|
81
|
-
`[admin-tool-gate] role=admin tool=${toolName} decision=block reason=specialist-owned owner=${owner}\n`,
|
|
82
|
-
);
|
|
83
|
-
return adminBlockedResponse(toolName, owner);
|
|
84
|
-
}
|
|
85
|
-
return handler(...args);
|
|
86
|
-
}) as unknown as H;
|
|
87
|
-
return wrapped;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
24
|
/**
|
|
91
25
|
* Loose structural type for any object exposing a `registerTool` method. The
|
|
92
26
|
* MCP SDK ships dual ESM/CJS packages with conditional exports, so importing
|
|
@@ -142,38 +76,3 @@ export function eagerTool<Args extends LocalZodRawShape>(
|
|
|
142
76
|
handler,
|
|
143
77
|
);
|
|
144
78
|
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Loose structural type for the legacy four-arg `server.tool(name, desc,
|
|
148
|
-
* schema, handler)` call shape — used when the admin seat must register a
|
|
149
|
-
* specialist-owned tool as deferred (not eager) so it does not appear in
|
|
150
|
-
* the admin's eager tool list.
|
|
151
|
-
*/
|
|
152
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
153
|
-
type DeferredCapableServer = { tool: (name: string, description: string, inputSchema: any, handler: (...args: any[]) => any) => any };
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Register a tool that is eager for specialists but deferred + admin-gated
|
|
157
|
-
* for the admin seat. The owning specialist name is named here so the
|
|
158
|
-
* refusal envelope (and the `[admin-tool-gate]` log line) carries it.
|
|
159
|
-
*
|
|
160
|
-
* Use for every tool whose owner is a specialist (database-operator, writer-
|
|
161
|
-
* craft, research-assistant, personal-assistant, content-producer). The
|
|
162
|
-
* admin seat sees the tool only via ToolSearch — and any invocation from the
|
|
163
|
-
* admin seat refuses with the dispatch hint.
|
|
164
|
-
*/
|
|
165
|
-
export function eagerToolGated<Args extends LocalZodRawShape>(
|
|
166
|
-
server: EagerCapableServer & DeferredCapableServer,
|
|
167
|
-
name: string,
|
|
168
|
-
owner: string,
|
|
169
|
-
description: string,
|
|
170
|
-
inputSchema: Args,
|
|
171
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
172
|
-
handler: (...args: any[]) => any,
|
|
173
|
-
): void {
|
|
174
|
-
if (isAdminSeat(process.env)) {
|
|
175
|
-
server.tool(name, description, inputSchema, withAdminGate(name, owner, handler));
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
eagerTool(server, name, description, inputSchema, handler);
|
|
179
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "admin",
|
|
3
|
-
"description": "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, account-update, admin-add, admin-remove, admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, wifi, action-approval tools (action-pending, action-approve, action-reject, action-edit), and session-retrospective-mark-complete (the deterministic sentinel the session-end Stop hook reads via JSONL grep to release the retrospective gate; admin calls it once per session after running the three-pass retrospective) for managing the Maxy platform.",
|
|
3
|
+
"description": "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, capabilities-here (deterministic install-introspection — returns enabledPlugins, tier, models, installed platform + premium specialists, registered Claude Code agent types, and the brand block as a single JSON object; the canonical tool for grounding any claim about what is enabled on the current account), account-update, admin-add, admin-remove, admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, wifi, action-approval tools (action-pending, action-approve, action-reject, action-edit), and session-retrospective-mark-complete (the deterministic sentinel the session-end Stop hook reads via JSONL grep to release the retrospective gate; admin calls it once per session after running the three-pass retrospective) for managing the Maxy platform.",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Rubytech LLC"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: admin
|
|
3
3
|
surface: platform
|
|
4
|
-
description: "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, account-update, admin-add, admin-remove, admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, wifi, action-approval tools (action-pending, action-approve, action-reject, action-edit), and session-retrospective-mark-complete (the deterministic sentinel the session-end Stop hook reads via JSONL grep to release the retrospective gate; admin calls it once per session after running the three-pass retrospective) for managing the Maxy platform."
|
|
4
|
+
description: "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver, single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), publish-site (move an extracted static-site tree under <accountDir>/sites/<slug>/, emit one canonical path slug, and refresh llms.txt at the site root — typed refusal taxonomy replaces the prior skill-prose contract), brand-settings, account-manage, capabilities-here (deterministic install-introspection — returns enabledPlugins, tier, models, installed platform + premium specialists, registered Claude Code agent types, and the brand block as a single JSON object; the canonical tool for grounding any claim about what is enabled on the current account), account-update, admin-add, admin-remove, admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name, the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), session-reset, session-resume, wifi, action-approval tools (action-pending, action-approve, action-reject, action-edit), and session-retrospective-mark-complete (the deterministic sentinel the session-end Stop hook reads via JSONL grep to release the retrospective gate; admin calls it once per session after running the three-pass retrospective) for managing the Maxy platform."
|
|
5
5
|
tools:
|
|
6
6
|
- name: system-status
|
|
7
7
|
publicAllowlist: false
|
|
@@ -18,6 +18,9 @@ tools:
|
|
|
18
18
|
- name: account-manage
|
|
19
19
|
publicAllowlist: false
|
|
20
20
|
adminAllowlist: false
|
|
21
|
+
- name: capabilities-here
|
|
22
|
+
publicAllowlist: false
|
|
23
|
+
adminAllowlist: false
|
|
21
24
|
- name: account-update
|
|
22
25
|
publicAllowlist: false
|
|
23
26
|
adminAllowlist: false
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Task 569 — stale-lockdir TTL reclamation in _hook_acquire_lock and
|
|
3
|
+
# _drain_acquire_lock. A backdated lockdir is force-removed on acquire;
|
|
4
|
+
# a freshly held lockdir is not.
|
|
5
|
+
set -u
|
|
6
|
+
|
|
7
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
8
|
+
LIB="$ROOT/lib/hook-emit.sh"
|
|
9
|
+
DRAIN="$ROOT/post-tool-use-agent.sh"
|
|
10
|
+
[[ -f "$LIB" ]] || { echo "FAIL: $LIB missing"; exit 1; }
|
|
11
|
+
[[ -f "$DRAIN" ]] || { echo "FAIL: $DRAIN missing"; exit 1; }
|
|
12
|
+
|
|
13
|
+
TMP=$(mktemp -d)
|
|
14
|
+
export MAXY_HOOK_BUFFER_DIR="$TMP/buffers"
|
|
15
|
+
export MAXY_HOOK_SERVER_LOG="$TMP/server.log"
|
|
16
|
+
# Tight test budget — 20 × 10ms = 200ms ceiling so the negative case
|
|
17
|
+
# returns quickly.
|
|
18
|
+
export MAXY_HOOK_LOCK_ATTEMPTS=20
|
|
19
|
+
export MAXY_HOOK_LOCK_SLEEP=0.01
|
|
20
|
+
export MAXY_HOOK_LOCK_TTL_SECONDS=2
|
|
21
|
+
mkdir -p "$MAXY_HOOK_BUFFER_DIR"
|
|
22
|
+
|
|
23
|
+
PASS=0; FAIL=0
|
|
24
|
+
pass() { PASS=$((PASS+1)); echo "PASS: $1"; }
|
|
25
|
+
fail() { FAIL=$((FAIL+1)); echo "FAIL: $1" >&2; }
|
|
26
|
+
|
|
27
|
+
# shellcheck disable=SC1090
|
|
28
|
+
source "$LIB" || { fail "source library"; exit 1; }
|
|
29
|
+
pass "source library"
|
|
30
|
+
|
|
31
|
+
# Test 1: a stale lockdir (mtime backdated 10s; TTL=2s) is reclaimed and
|
|
32
|
+
# acquire succeeds.
|
|
33
|
+
LOCK1="$TMP/lock-stale"
|
|
34
|
+
mkdir "$LOCK1"
|
|
35
|
+
# Backdate mtime by 10s. -t needs YYYYMMDDhhmm.SS form; portable form
|
|
36
|
+
# uses -d on GNU and -A/-r on BSD, so we use python to set it.
|
|
37
|
+
python3 -c "import os,time; os.utime('$LOCK1', (time.time()-10, time.time()-10))"
|
|
38
|
+
if _hook_acquire_lock "$LOCK1"; then
|
|
39
|
+
pass "stale lockdir reclaimed (emit-path)"
|
|
40
|
+
else
|
|
41
|
+
fail "stale lockdir not reclaimed within budget (emit-path)"
|
|
42
|
+
fi
|
|
43
|
+
# Verify observability line.
|
|
44
|
+
if grep -q "\[hook-propagate-error\] reason=stale-lock-reclaimed lockdir=$LOCK1" "$MAXY_HOOK_SERVER_LOG"; then
|
|
45
|
+
pass "stale-lock-reclaimed observability line emitted"
|
|
46
|
+
else
|
|
47
|
+
fail "stale-lock-reclaimed line missing from server.log"
|
|
48
|
+
fi
|
|
49
|
+
rmdir "$LOCK1" 2>/dev/null || true
|
|
50
|
+
|
|
51
|
+
# Test 2: a fresh lockdir (mtime = now) is NOT reclaimed and acquire
|
|
52
|
+
# returns 1 within the attempt budget.
|
|
53
|
+
LOCK2="$TMP/lock-fresh"
|
|
54
|
+
mkdir "$LOCK2"
|
|
55
|
+
START=$(date +%s)
|
|
56
|
+
if _hook_acquire_lock "$LOCK2"; then
|
|
57
|
+
fail "fresh lockdir wrongly reclaimed (emit-path)"
|
|
58
|
+
else
|
|
59
|
+
END=$(date +%s)
|
|
60
|
+
pass "fresh lockdir not reclaimed; acquire timed out (emit-path, ${END}-${START}s)"
|
|
61
|
+
fi
|
|
62
|
+
# Verify no stale-lock-reclaimed line was written for LOCK2.
|
|
63
|
+
if grep -q "lockdir=$LOCK2 ageSeconds=" "$MAXY_HOOK_SERVER_LOG"; then
|
|
64
|
+
fail "fresh lockdir wrongly logged as stale (emit-path)"
|
|
65
|
+
else
|
|
66
|
+
pass "no stale-lock log for fresh lockdir (emit-path)"
|
|
67
|
+
fi
|
|
68
|
+
rmdir "$LOCK2" 2>/dev/null || true
|
|
69
|
+
|
|
70
|
+
# Test 3: same property for the drainer-side function. Extract
|
|
71
|
+
# _drain_acquire_lock + its age helper from post-tool-use-agent.sh and
|
|
72
|
+
# source them in isolation.
|
|
73
|
+
DRAIN_LIB="$TMP/drain-lock.sh"
|
|
74
|
+
awk '
|
|
75
|
+
/^_drain_lockdir_age_seconds\(\)/,/^}/ { print; next }
|
|
76
|
+
/^_drain_acquire_lock\(\)/,/^}/ { print; next }
|
|
77
|
+
' "$DRAIN" > "$DRAIN_LIB"
|
|
78
|
+
[[ -s "$DRAIN_LIB" ]] || { fail "drain lock extract empty"; }
|
|
79
|
+
# shellcheck disable=SC1090
|
|
80
|
+
source "$DRAIN_LIB" || fail "source drain lib"
|
|
81
|
+
|
|
82
|
+
LOCK3="$TMP/lock-drain-stale"
|
|
83
|
+
mkdir "$LOCK3"
|
|
84
|
+
python3 -c "import os,time; os.utime('$LOCK3', (time.time()-10, time.time()-10))"
|
|
85
|
+
if _drain_acquire_lock "$LOCK3"; then
|
|
86
|
+
pass "stale lockdir reclaimed (drain-path)"
|
|
87
|
+
else
|
|
88
|
+
fail "stale lockdir not reclaimed (drain-path)"
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
LOCK4="$TMP/lock-drain-fresh"
|
|
92
|
+
mkdir "$LOCK4"
|
|
93
|
+
if _drain_acquire_lock "$LOCK4"; then
|
|
94
|
+
fail "fresh lockdir wrongly reclaimed (drain-path)"
|
|
95
|
+
else
|
|
96
|
+
pass "fresh lockdir not reclaimed; acquire timed out (drain-path)"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
rm -rf "$TMP"
|
|
100
|
+
echo "----"
|
|
101
|
+
echo "$PASS pass, $FAIL fail"
|
|
102
|
+
[[ "$FAIL" -eq 0 ]]
|
|
@@ -72,6 +72,102 @@ echo "$CENSUS_LOG" | grep -qE 'attachmentsEmitted=2' \
|
|
|
72
72
|
&& pass "census M equals N when stdout open" \
|
|
73
73
|
|| fail "census M wrong: $CENSUS_LOG"
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
# Task 563 — concurrency hardening.
|
|
76
|
+
|
|
77
|
+
# Concurrent emitter + drainer on the same buffer must not drop records.
|
|
78
|
+
# Spawn an emitter that writes 50 records in a tight loop while two
|
|
79
|
+
# drainer fires race against it. Union of attachments must equal 50.
|
|
80
|
+
LIB="$(cd "$(dirname "$0")/.." && pwd)/lib/hook-emit.sh"
|
|
81
|
+
[[ -f "$LIB" ]] || { fail "lib missing"; }
|
|
82
|
+
CONCUR_TMP=$(mktemp -d)
|
|
83
|
+
export MAXY_HOOK_BUFFER_DIR="$CONCUR_TMP/buffers"
|
|
84
|
+
export MAXY_HOOK_SERVER_LOG="$CONCUR_TMP/server.log"
|
|
85
|
+
mkdir -p "$MAXY_HOOK_BUFFER_DIR"
|
|
86
|
+
PARENT_C="parent-CONC"
|
|
87
|
+
SUB_C="sub-CONC"
|
|
88
|
+
INPUT_C=$(python3 -c '
|
|
89
|
+
import json, sys
|
|
90
|
+
print(json.dumps({"hook_event_name":"PostToolUse","tool_name":"Agent",
|
|
91
|
+
"session_id":sys.argv[1]}, separators=(",",":")))
|
|
92
|
+
' "$PARENT_C")
|
|
93
|
+
|
|
94
|
+
# Emitter: 50 records as fast as possible.
|
|
95
|
+
EMIT_OUT="$CONCUR_TMP/emit.log"
|
|
96
|
+
(
|
|
97
|
+
# shellcheck disable=SC1090
|
|
98
|
+
source "$LIB"
|
|
99
|
+
for i in $(seq 1 50); do
|
|
100
|
+
hook_emit_decision "$SUB_C" "h" "Bash" "block" "r$i" "x" 2 1
|
|
101
|
+
done
|
|
102
|
+
echo "done" >"$EMIT_OUT"
|
|
103
|
+
) &
|
|
104
|
+
EMIT_PID=$!
|
|
105
|
+
|
|
106
|
+
# Two drainer fires racing the emitter.
|
|
107
|
+
DRAIN1="$CONCUR_TMP/drain1.out"
|
|
108
|
+
DRAIN2="$CONCUR_TMP/drain2.out"
|
|
109
|
+
(printf '%s' "$INPUT_C" | bash "$HOOK" >"$DRAIN1" 2>/dev/null) &
|
|
110
|
+
D1=$!
|
|
111
|
+
(printf '%s' "$INPUT_C" | bash "$HOOK" >"$DRAIN2" 2>/dev/null) &
|
|
112
|
+
D2=$!
|
|
113
|
+
wait "$EMIT_PID" "$D1" "$D2"
|
|
114
|
+
|
|
115
|
+
# Drain any leftovers after emitter+drainers settled.
|
|
116
|
+
DRAIN3="$CONCUR_TMP/drain3.out"
|
|
117
|
+
printf '%s' "$INPUT_C" | bash "$HOOK" >"$DRAIN3" 2>/dev/null
|
|
118
|
+
# A second sweep catches buffer files created after drainer 3 started.
|
|
119
|
+
DRAIN4="$CONCUR_TMP/drain4.out"
|
|
120
|
+
printf '%s' "$INPUT_C" | bash "$HOOK" >"$DRAIN4" 2>/dev/null
|
|
121
|
+
|
|
122
|
+
TOTAL_ATTACHED=$(cat "$DRAIN1" "$DRAIN2" "$DRAIN3" "$DRAIN4" | grep -c '^\[hook-propagate\] {' || true)
|
|
123
|
+
UNIQUE_R=$(cat "$DRAIN1" "$DRAIN2" "$DRAIN3" "$DRAIN4" | grep '^\[hook-propagate\] {' | \
|
|
124
|
+
python3 -c 'import json,sys
|
|
125
|
+
seen=set()
|
|
126
|
+
for line in sys.stdin:
|
|
127
|
+
rec=line.split(" ",1)[1]
|
|
128
|
+
d=json.loads(rec)
|
|
129
|
+
seen.add(d["reason"])
|
|
130
|
+
print(len(seen))')
|
|
131
|
+
[[ "$TOTAL_ATTACHED" -eq 50 ]] && pass "concurrent: 50 records attached, no duplicates" \
|
|
132
|
+
|| fail "concurrent: expected 50 attached, got $TOTAL_ATTACHED"
|
|
133
|
+
[[ "$UNIQUE_R" -eq 50 ]] && pass "concurrent: 50 unique records, no drops" \
|
|
134
|
+
|| fail "concurrent: expected 50 unique reasons, got $UNIQUE_R"
|
|
135
|
+
|
|
136
|
+
# No cursor file should exist (cursor mechanism removed).
|
|
137
|
+
[[ -z "$(ls "$MAXY_HOOK_BUFFER_DIR"/.*.cursor 2>/dev/null)" ]] \
|
|
138
|
+
&& pass "cursor mechanism removed (no .cursor files)" \
|
|
139
|
+
|| fail "cursor file unexpectedly present: $(ls "$MAXY_HOOK_BUFFER_DIR"/.*.cursor 2>/dev/null)"
|
|
140
|
+
|
|
141
|
+
rm -rf "$CONCUR_TMP"
|
|
142
|
+
|
|
143
|
+
# Unknown-parent fallback emits observability line and does not corrupt
|
|
144
|
+
# state across concurrent fires.
|
|
145
|
+
UNK_TMP=$(mktemp -d)
|
|
146
|
+
export MAXY_HOOK_BUFFER_DIR="$UNK_TMP/buffers"
|
|
147
|
+
export MAXY_HOOK_SERVER_LOG="$UNK_TMP/server.log"
|
|
148
|
+
mkdir -p "$MAXY_HOOK_BUFFER_DIR"
|
|
149
|
+
# Stdin with no session_id at all.
|
|
150
|
+
BAD_INPUT='{"hook_event_name":"PostToolUse","tool_name":"Agent"}'
|
|
151
|
+
UNK1="$UNK_TMP/unk1.out"
|
|
152
|
+
UNK2="$UNK_TMP/unk2.out"
|
|
153
|
+
(printf '%s' "$BAD_INPUT" | bash "$HOOK" >"$UNK1" 2>/dev/null) &
|
|
154
|
+
U1=$!
|
|
155
|
+
(printf '%s' "$BAD_INPUT" | bash "$HOOK" >"$UNK2" 2>/dev/null) &
|
|
156
|
+
U2=$!
|
|
157
|
+
wait "$U1" "$U2"
|
|
158
|
+
|
|
159
|
+
grep -q 'parentSession=unknown reason=session-id-missing' "$UNK1" "$UNK2" \
|
|
160
|
+
&& pass "unknown parent emits observability line" \
|
|
161
|
+
|| fail "unknown parent missing observability line: $(cat "$UNK1" "$UNK2")"
|
|
162
|
+
grep -q 'parentSession=unknown reason=session-id-missing' "$MAXY_HOOK_SERVER_LOG" \
|
|
163
|
+
&& pass "unknown parent line logged to server.log" \
|
|
164
|
+
|| fail "server.log missing unknown-parent line"
|
|
165
|
+
# No .unknown.cursor file — cursor mechanism is gone.
|
|
166
|
+
[[ ! -e "$MAXY_HOOK_BUFFER_DIR/.unknown.cursor" ]] \
|
|
167
|
+
&& pass "no shared .unknown.cursor file" \
|
|
168
|
+
|| fail ".unknown.cursor unexpectedly present"
|
|
169
|
+
|
|
170
|
+
rm -rf "$UNK_TMP"
|
|
171
|
+
|
|
76
172
|
echo "----"; echo "$PASS pass, $FAIL fail"
|
|
77
173
|
[[ "$FAIL" -eq 0 ]]
|