@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.
Files changed (89) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/mcp-eager/dist/index.d.ts +0 -55
  3. package/payload/platform/lib/mcp-eager/dist/index.d.ts.map +1 -1
  4. package/payload/platform/lib/mcp-eager/dist/index.js +0 -76
  5. package/payload/platform/lib/mcp-eager/dist/index.js.map +1 -1
  6. package/payload/platform/lib/mcp-eager/src/index.ts +0 -101
  7. package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
  8. package/payload/platform/plugins/admin/PLUGIN.md +4 -1
  9. package/payload/platform/plugins/admin/hooks/__tests__/hook-emit-stale-lock-ttl.test.sh +102 -0
  10. package/payload/platform/plugins/admin/hooks/__tests__/post-tool-use-agent.test.sh +97 -1
  11. package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-admin-tool-gate.test.sh +55 -74
  12. package/payload/platform/plugins/admin/hooks/lib/hook-emit.sh +58 -0
  13. package/payload/platform/plugins/admin/hooks/post-tool-use-agent.sh +100 -29
  14. package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +0 -55
  15. package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.d.ts +2 -0
  16. package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.d.ts.map +1 -0
  17. package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.js +130 -0
  18. package/payload/platform/plugins/admin/mcp/dist/__tests__/capabilities-here.test.js.map +1 -0
  19. package/payload/platform/plugins/admin/mcp/dist/index.js +63 -0
  20. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  21. package/payload/platform/plugins/admin/mcp/dist/lib/catalogue-census.d.ts +14 -0
  22. package/payload/platform/plugins/admin/mcp/dist/lib/catalogue-census.d.ts.map +1 -0
  23. package/payload/platform/plugins/admin/mcp/dist/lib/catalogue-census.js +50 -0
  24. package/payload/platform/plugins/admin/mcp/dist/lib/catalogue-census.js.map +1 -0
  25. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.d.ts +28 -0
  26. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.d.ts.map +1 -0
  27. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.js +68 -0
  28. package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.js.map +1 -0
  29. package/payload/platform/plugins/admin/skills/capabilities-here/SKILL.md +31 -0
  30. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +39 -30
  31. package/payload/platform/plugins/browser/mcp/dist/index.js +19 -19
  32. package/payload/platform/plugins/browser/mcp/dist/index.js.map +1 -1
  33. package/payload/platform/plugins/docs/references/platform.md +28 -25
  34. package/payload/platform/plugins/docs/references/plugins-guide.md +2 -0
  35. package/payload/platform/plugins/email/mcp/dist/index.js +12 -12
  36. package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
  37. package/payload/platform/plugins/memory/mcp/dist/index.js +8 -8
  38. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  39. package/payload/platform/plugins/url-get/mcp/dist/index.js +2 -2
  40. package/payload/platform/plugins/url-get/mcp/dist/index.js.map +1 -1
  41. package/payload/platform/scripts/check-architecture-skill-no-drift.mjs +89 -42
  42. package/payload/platform/scripts/check-roles-doc-completeness.mjs +96 -0
  43. package/payload/platform/scripts/check-specialist-tool-surface.mjs +1 -1
  44. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  45. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
  46. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  47. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +0 -14
  48. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  49. package/payload/platform/services/claude-session-manager/dist/http-server.js +54 -21
  50. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  51. package/payload/platform/services/claude-session-manager/dist/index.js +20 -9
  52. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  53. package/payload/platform/services/claude-session-manager/dist/jsonl-tail.d.ts +21 -0
  54. package/payload/platform/services/claude-session-manager/dist/jsonl-tail.d.ts.map +1 -1
  55. package/payload/platform/services/claude-session-manager/dist/jsonl-tail.js +118 -1
  56. package/payload/platform/services/claude-session-manager/dist/jsonl-tail.js.map +1 -1
  57. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +1 -1
  58. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +1 -1
  59. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +28 -17
  60. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
  61. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +110 -60
  62. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
  63. package/payload/platform/services/claude-session-manager/dist/rc-life.d.ts +4 -0
  64. package/payload/platform/services/claude-session-manager/dist/rc-life.d.ts.map +1 -0
  65. package/payload/platform/services/claude-session-manager/dist/rc-life.js +16 -0
  66. package/payload/platform/services/claude-session-manager/dist/rc-life.js.map +1 -0
  67. package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.d.ts +80 -0
  68. package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.d.ts.map +1 -0
  69. package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.js +306 -0
  70. package/payload/platform/services/claude-session-manager/dist/rc-script-spawn.js.map +1 -0
  71. package/payload/platform/services/claude-session-manager/dist/specialist-drift.d.ts +9 -0
  72. package/payload/platform/services/claude-session-manager/dist/specialist-drift.d.ts.map +1 -1
  73. package/payload/platform/services/claude-session-manager/dist/specialist-drift.js +6 -2
  74. package/payload/platform/services/claude-session-manager/dist/specialist-drift.js.map +1 -1
  75. package/payload/platform/services/claude-session-manager/dist/systemd-scope.d.ts +12 -46
  76. package/payload/platform/services/claude-session-manager/dist/systemd-scope.d.ts.map +1 -1
  77. package/payload/platform/services/claude-session-manager/dist/systemd-scope.js +24 -73
  78. package/payload/platform/services/claude-session-manager/dist/systemd-scope.js.map +1 -1
  79. package/payload/platform/templates/agents/admin/IDENTITY.md +6 -4
  80. package/payload/platform/templates/specialists/agents/content-producer.md +1 -1
  81. package/payload/premium-plugins/writer-craft/mcp/dist/index.d.ts.map +1 -1
  82. package/payload/premium-plugins/writer-craft/mcp/dist/index.js +4 -5
  83. package/payload/premium-plugins/writer-craft/mcp/dist/index.js.map +1 -1
  84. package/payload/premium-plugins/writer-craft/mcp/src/index.ts +4 -5
  85. package/payload/premium-plugins/writer-craft/lib/mcp-eager/dist/index.d.ts +0 -116
  86. package/payload/premium-plugins/writer-craft/lib/mcp-eager/dist/index.d.ts.map +0 -1
  87. package/payload/premium-plugins/writer-craft/lib/mcp-eager/dist/index.js +0 -125
  88. package/payload/premium-plugins/writer-craft/lib/mcp-eager/dist/index.js.map +0 -1
  89. package/payload/premium-plugins/writer-craft/lib/mcp-eager/package.json +0 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.214",
3
+ "version": "0.1.219",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -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;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAEzE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG;IACrE,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1C,OAAO,EAAE,IAAI,CAAC;CACf,CAYA;AAED;;;;;;;;;GASG;AAEH,wBAAgB,aAAa,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAC7D,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,GACT,CAAC,CAYH;AAED;;;;;;;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;AAED;;;;;GAKG;AAEH,KAAK,qBAAqB,GAAG;IAAE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,KAAK,GAAG,CAAA;CAAE,CAAC;AAEtI;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,IAAI,SAAS,gBAAgB,EAC1D,MAAM,EAAE,kBAAkB,GAAG,qBAAqB,EAClD,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,IAAI,EAEjB,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,GAC/B,IAAI,CAMN"}
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":";;;AAkCA,kCAEC;AAOD,oDAeC;AAaD,sCAgBC;AAuCD,8BAiBC;AAqBD,wCAcC;AAlLD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,0DAA0D;AAC7C,QAAA,oBAAoB,GAAG,sBAA+B,CAAC;AAEpE;;;;;;;;;;GAUG;AACH,SAAgB,WAAW,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC9D,OAAO,GAAG,CAAC,iBAAiB,KAAK,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,eAAe,IAAI,GAAG,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACvG,CAAC;AAED;;;;GAIG;AACH,SAAgB,oBAAoB,CAAC,QAAgB,EAAE,KAAa;IAIlE,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EACF,UAAU,QAAQ,wBAAwB,KAAK,yDAAyD;oBACxG,cAAc,KAAK,wDAAwD;aAC9E;SACF;QACD,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,8DAA8D;AAC9D,SAAgB,aAAa,CAC3B,QAAgB,EAChB,KAAa,EACb,OAAU;IAEV,8DAA8D;IAC9D,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG,IAAW,EAAE,EAAE;QAClC,IAAI,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,qCAAqC,QAAQ,iDAAiD,KAAK,IAAI,CACxG,CAAC;YACF,OAAO,oBAAoB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAiB,CAAC;IACnB,OAAO,OAAO,CAAC;AACjB,CAAC;AA0BD;;;;;;;;;;;;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;AAWD;;;;;;;;;GASG;AACH,SAAgB,cAAc,CAC5B,MAAkD,EAClD,IAAY,EACZ,KAAa,EACb,WAAmB,EACnB,WAAiB;AACjB,8DAA8D;AAC9D,OAAgC;IAEhC,IAAI,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;QACjF,OAAO;IACT,CAAC;IACD,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;AAC7D,CAAC"}
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
- rm -rf "$TMP"
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 ]]