agy-context-saver 1.7.0 → 1.8.1

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/mcp/index.js CHANGED
@@ -12,7 +12,6 @@
12
12
  * - Tools:
13
13
  * - check_context_health: Inspects conversation transcripts using async streaming
14
14
  * to diagnose turn count, tool polling loops, and context degradation with minimal memory.
15
- * - subagent_brief: Formulates scope-isolated prompts for delegated subagents.
16
15
  * - get_installation_status: Audits live Antigravity plugin, hook, MCP, and RTK integration.
17
16
  * - sync_installation: Re-verifies and synchronizes installation and tool schemas.
18
17
  * - read_transcript: Clean Markdown streaming reader for compact and full transcripts.
@@ -31,48 +30,57 @@ import { fileURLToPath } from "node:url";
31
30
  import { runInstall, runUninstall, runStatus, runRestoreBackups, detectExistingInstallation } from "../scripts/install-register.mjs";
32
31
  import { findExistingRtk } from "../scripts/provision-rtk.mjs";
33
32
 
33
+ const __filename = fileURLToPath(import.meta.url);
34
+ const realOrSelf = (p) => { try { return fs.realpathSync(p); } catch { return path.resolve(p); } };
35
+ const isEntryPoint = Boolean(
36
+ process.argv[1] && realOrSelf(process.argv[1]) === realOrSelf(__filename)
37
+ );
38
+
34
39
  // Handle CLI subcommands (e.g. npx agy-context-saver install)
35
- const cliArgs = process.argv.slice(2);
36
- const cliArg = cliArgs[0];
37
- const checkFlag = cliArgs.includes("--check") || cliArgs.includes("-c");
38
- const statusFlag = cliArgs.includes("--status") || cliArgs.includes("status");
39
- const uninstallFlag = cliArgs.includes("--uninstall") || cliArgs.includes("uninstall");
40
- const restoreFlag = cliArgs.includes("--restore-backups") || cliArgs.includes("restore-backups");
41
- const helpFlag = cliArgs.includes("--help") || cliArgs.includes("-h") || cliArgs.includes("help");
42
- const allowDegraded = cliArgs.includes("--allow-degraded");
43
-
44
- if (helpFlag) {
45
- printHelp();
46
- process.exit(0);
47
- }
48
- if ((checkFlag || statusFlag) && (uninstallFlag || restoreFlag)) {
49
- console.error("Error: Read-only flags (--check, --status) cannot be combined with mutating flags (--uninstall, --restore-backups).");
50
- process.exit(1);
51
- }
52
- if (checkFlag && statusFlag) {
53
- console.error("Error: Conflicting flags: specify either --check or --status, not both.");
54
- process.exit(1);
55
- }
56
- if (checkFlag) {
57
- await runInstall({ checkOnly: true, allowDegraded });
58
- process.exit(0);
59
- } else if (statusFlag) {
60
- runStatus();
61
- process.exit(0);
62
- } else if (uninstallFlag) {
63
- await runUninstall({ restoreBackups: restoreFlag });
64
- process.exit(0);
65
- } else if (restoreFlag) {
66
- await runRestoreBackups();
67
- process.exit(0);
68
- } else if (cliArg === "install" || cliArg === "--install") {
69
- await runInstall({ allowDegraded });
70
- process.exit(0);
40
+ if (isEntryPoint) {
41
+ const cliArgs = process.argv.slice(2);
42
+ const cliArg = cliArgs[0];
43
+ const checkFlag = cliArgs.includes("--check") || cliArgs.includes("-c");
44
+ const statusFlag = cliArgs.includes("--status") || cliArgs.includes("status");
45
+ const uninstallFlag = cliArgs.includes("--uninstall") || cliArgs.includes("uninstall");
46
+ const restoreFlag = cliArgs.includes("--restore-backups") || cliArgs.includes("restore-backups");
47
+ const helpFlag = cliArgs.includes("--help") || cliArgs.includes("-h") || cliArgs.includes("help");
48
+ const allowDegraded = cliArgs.includes("--allow-degraded");
49
+
50
+ if (helpFlag) {
51
+ printHelp();
52
+ process.exit(0);
53
+ }
54
+ if ((checkFlag || statusFlag) && (uninstallFlag || restoreFlag)) {
55
+ console.error("Error: Read-only flags (--check, --status) cannot be combined with mutating flags (--uninstall, --restore-backups).");
56
+ process.exit(1);
57
+ }
58
+ if (checkFlag && statusFlag) {
59
+ console.error("Error: Conflicting flags: specify either --check or --status, not both.");
60
+ process.exit(1);
61
+ }
62
+ if (checkFlag) {
63
+ await runInstall({ checkOnly: true, allowDegraded });
64
+ process.exit(0);
65
+ } else if (statusFlag) {
66
+ runStatus();
67
+ process.exit(0);
68
+ } else if (uninstallFlag) {
69
+ await runUninstall({ restoreBackups: restoreFlag });
70
+ process.exit(0);
71
+ } else if (restoreFlag) {
72
+ await runRestoreBackups();
73
+ process.exit(0);
74
+ } else if (cliArg === "install" || cliArg === "--install") {
75
+ await runInstall({ allowDegraded });
76
+ process.exit(0);
77
+ }
71
78
  }
72
79
 
80
+
73
81
  function printHelp() {
74
82
  console.log(`
75
- Agy-Context-Saver 🛡️ (v1.7.0)
83
+ Agy-Context-Saver 🛡️ (v1.8.1)
76
84
  Universal MCP Server & Lifecycle Governor for Google Antigravity (Powered by RTK)
77
85
 
78
86
  Usage:
@@ -99,19 +107,17 @@ MCP Server Stdio Mode:
99
107
 
100
108
  Testing & Verification Playbook:
101
109
  npm run status Verify live 4-layer integration status
102
- npm test Run complete 5-suite automated test suite
103
- node tests/test-hook.mjs Run 28 closed-topology governance rule tests
110
+ npm test Run complete 10-suite automated test suite
111
+ node tests/test-hook.mjs Run 27 closed-topology governance rule tests
104
112
  node tests/test-mcp.mjs Run MCP protocol, method, and schema tests
105
113
 
106
- Platform-Aware Inspection Directives (RTK):
107
- Windows:
108
- - Search: rtk rg "<pattern>" . (trailing dot is required to prevent stdin pipe stalls)
109
- - Read: rtk read <file>
110
- - Directory: rtk find <path> (cross-platform; avoids missing ls.exe)
111
- POSIX (macOS/Linux):
112
- - Search: rtk grep "<pattern>" or rtk rg "<pattern>"
113
- - Read: rtk read <file>
114
- - Directory: rtk ls <path> or rtk tree
114
+ Unified 3-Tier Inspection Directives (Waymark + Hybrid view_file + RTK):
115
+ Tier 1 (Structure): waymark_ask(..., plain: true) across all 23 supported formats
116
+ Tier 2 (Read/Edit): view_file(StartLine, EndLine) (1-150L default; or explicit 1..TotalLines full-file audit <= 800L & <= 45 KiB)
117
+ Tier 3 (RTK CLI):
118
+ - Spillovers/Logs: rtk read <path> (--head-lines / --tail-lines)
119
+ - Directory: rtk find <path> (Windows & POSIX) or rtk ls <path> (POSIX)
120
+ - Prose Search: rtk rg "<pattern>" . (Windows trailing dot required) or rtk grep "<pattern>" (POSIX)
115
121
  `);
116
122
  }
117
123
 
@@ -119,7 +125,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
119
125
  const rulesPath = path.resolve(__dirname, "../rules/AGENTS.md");
120
126
 
121
127
  const SERVER_NAME = "agy-context-saver";
122
- const SERVER_VERSION = "1.7.0";
128
+ const SERVER_VERSION = "1.8.1";
123
129
 
124
130
  // --- Tools Specification ---
125
131
  const TOOLS = [
@@ -140,68 +146,6 @@ const TOOLS = [
140
146
  }
141
147
  }
142
148
  },
143
- {
144
- name: "subagent_brief",
145
- description: "Generates scope-isolated, anti-bloat prompts and ready-to-use invoke_subagent JSON payloads for single or multiple delegated subagents. Instructs subagents to absorb intermediate file reads/searches and return only high-signal findings.",
146
- inputSchema: {
147
- type: "object",
148
- properties: {
149
- objective: {
150
- type: "string",
151
- description: "The core research or investigative objective (for single subagent)."
152
- },
153
- role: {
154
- type: "string",
155
- description: "2-5 word role description (e.g. 'Comprehensive Test Engineer', 'Codebase Security Auditor'). Defaults to 'Scope-Isolated Deep Research Subagent'."
156
- },
157
- instructions: {
158
- type: "string",
159
- description: "Optional step-by-step instructions or specialized directives."
160
- },
161
- scopeFiles: {
162
- type: "array",
163
- items: { type: "string" },
164
- description: "List of files or directories to investigate."
165
- },
166
- expectedDeliverable: {
167
- type: "string",
168
- description: "The concise format expected back by the parent agent."
169
- },
170
- typeName: {
171
- type: "string",
172
- description: "Subagent type name: 'self' (default, inherits parent tools/prompt/model) or 'research' (read-only)."
173
- },
174
- model: {
175
- type: "string",
176
- enum: ["inherit", "flash_lite", "flash", "pro"],
177
- description: "Model tier: 'inherit' (default), 'flash_lite', 'flash', or 'pro'."
178
- },
179
- workspace: {
180
- type: "string",
181
- enum: ["inherit", "branch", "share"],
182
- description: "Workspace isolation: 'inherit' (default), 'branch', or 'share'."
183
- },
184
- subagents: {
185
- type: "array",
186
- description: "Optional array of subagent configurations to formulate multiple subagents at once.",
187
- items: {
188
- type: "object",
189
- properties: {
190
- role: { type: "string", description: "2-5 word role description." },
191
- objective: { type: "string", description: "The core objective for this subagent." },
192
- instructions: { type: "string", description: "Optional step-by-step instructions." },
193
- scopeFiles: { type: "array", items: { type: "string" }, description: "Files/directories to investigate." },
194
- expectedDeliverable: { type: "string", description: "Expected deliverable format." },
195
- typeName: { type: "string", description: "Subagent type name: 'self' or 'research'." },
196
- model: { type: "string", enum: ["inherit", "flash_lite", "flash", "pro"], description: "Model tier." },
197
- workspace: { type: "string", enum: ["inherit", "branch", "share"], description: "Workspace isolation." }
198
- },
199
- required: ["objective"]
200
- }
201
- }
202
- }
203
- }
204
- },
205
149
  {
206
150
  name: "get_installation_status",
207
151
  description: "Inspect the live installation status of Agy-Context-Saver across all Antigravity integration points (Native Plugin Link, Governor Hook in hooks.json, MCP Server in mcp_config.json, and Tool Schemas).",
@@ -254,7 +198,7 @@ const TOOLS = [
254
198
  },
255
199
  {
256
200
  name: "query_transcript",
257
- description: "Granular query and forensic filtering engine for Antigravity conversation transcripts. Searches by keyword or regex, filters by role (user, assistant, tool, error), slices step ranges, and automatically dereferences full content when truncated.",
201
+ description: "Granular query and forensic filtering engine for Antigravity conversation transcripts. Searches by keyword or regex, filters by role (user, assistant, subagent, tool, error), slices step ranges, and automatically dereferences full content when truncated.",
258
202
  inputSchema: {
259
203
  type: "object",
260
204
  properties: {
@@ -269,7 +213,7 @@ const TOOLS = [
269
213
  roles: {
270
214
  type: "array",
271
215
  items: { type: "string" },
272
- description: "Filter steps by role/source: 'user', 'assistant', 'tool', 'error', or 'all' (default: ['user', 'assistant'])."
216
+ description: "Filter steps by role/source: 'user', 'assistant', 'subagent', 'tool', 'error', or 'all' (default: ['user', 'assistant'])."
273
217
  },
274
218
  mode: {
275
219
  type: "string",
@@ -323,7 +267,7 @@ const RESOURCES = [
323
267
  {
324
268
  uri: "context-saver://rules/governance",
325
269
  name: "Antigravity Execution & Context Governance Rulebook",
326
- description: "Authoritative 3-Layer rules preventing background polling loops, maximizing synchronous execution, and isolating context via subagents.",
270
+ description: "Authoritative 3-Layer rules preventing background polling loops, maximizing synchronous execution, and keeping command output compact.",
327
271
  mimeType: "text/markdown"
328
272
  }
329
273
  ];
@@ -449,7 +393,7 @@ async function handleCheckContextHealth(args = {}) {
449
393
  reportLines.push("⚠️ Recommendation: Background polling detected! Cease manage_task(status) calls and yield execution to native Reactive Wakeup.");
450
394
  }
451
395
  if (userTurns >= 35) {
452
- reportLines.push("💡 Recommendation: Turn budget approaching threshold. Delegate broad research to subagents to preserve context.");
396
+ reportLines.push("💡 Recommendation: Turn budget approaching threshold. Summarize progress and continue in a fresh conversation to preserve context.");
453
397
  } else {
454
398
  reportLines.push("✓ Context footprint is well-managed.");
455
399
  }
@@ -463,108 +407,10 @@ async function handleCheckContextHealth(args = {}) {
463
407
  }
464
408
  }
465
409
 
466
- function formatSingleBrief({
467
- role = "Scope-Isolated Deep Research Subagent",
468
- objective = "",
469
- instructions = "",
470
- scopeFiles = [],
471
- expectedDeliverable = "Synthesized factual findings"
472
- } = {}) {
473
- const scopeStr = scopeFiles && scopeFiles.length ? `\n\nTarget files:\n${scopeFiles.map(f => `- ${f}`).join("\n")}` : "";
474
- const instrStr = instructions && instructions.trim() ? `\n\nSPECIFIC INSTRUCTIONS:\n${instructions.trim()}` : "";
475
- const hasBrainUuidPath = /[/\\]\.gemini[/\\]antigravity[/\\]brain[/\\][0-9a-f-]{36}/i.test(`${objective}\n${instructions}\n${expectedDeliverable}`);
476
- const brainPathWarning = hasBrainUuidPath
477
- ? `\n\n⚠️ ARTIFACT DIRECTORY WRITE GUARD: Any 'brain/<uuid>/' path referenced above belongs to another conversation. You may READ from it via 'view_file', but when calling 'write_to_file' or 'replace_file_content', you MUST either write to workspace '<workspace>/scratch/...' (OMIT ArtifactMetadata) or substitute YOUR OWN Artifact Directory Path from <user_information> (INCLUDE ArtifactMetadata).`
478
- : "";
479
- return [
480
- `ROLE: ${role}`,
481
- `OBJECTIVE: ${objective}${scopeStr}${instrStr}${brainPathWarning}`,
482
- ``,
483
- `CRITICAL CONTEXT GOVERNANCE INSTRUCTIONS (POINTER-OVER-WIRE):`,
484
- `1. Isolated Workspace & Bounded File Slices: Do all multi-file exploration in this subagent sandbox. Prefer 'view_file' with bounded slices (StartLine and EndLine, max 150 lines) over 'rtk read' so line numbers are preserved and terminal buffers never truncate file headers.`,
485
- `2. Pointer-Over-Wire (POW) Protocol: In-band messaging channels (send_message) have strict transport payload limits (~16-20 KB). If your findings or deliverable exceed 1 KB:`,
486
- ` a. Write the full comprehensive deliverable using 'write_to_file' following Antigravity's strict path rules:`,
487
- ` - Option A (Workspace 'scratch/' — Recommended): Set TargetFile to '<workspace>/scratch/<task>_findings.md' and OMIT ArtifactMetadata completely (passing ArtifactMetadata on a workspace path throws "files must be written to the correct artifact directory").`,
488
- ` - Option B (Your Own Brain 'scratch/'): Set TargetFile to '<your-Artifact-Directory-Path>/scratch/<task>_findings.md' using the exact Artifact Directory Path from YOUR <user_information> block (NEVER a parent's brain/<other-uuid>/ path) and INCLUDE ArtifactMetadata: { Summary: "...", UserFacing: false, RequestFeedback: false }.`,
489
- ` b. In 'send_message', transmit ONLY:`,
490
- ` - Executive Summary (3-5 high-signal sentences)`,
491
- ` - Verdict / Scoreboard Table`,
492
- ` - Clickable File Link: [Full Deliverable](file:///absolute/path/to/findings.md)`,
493
- `3. Fast Turn Termination: Once 'send_message' is dispatched, stop calling tools and conclude your turn immediately so the parent agent wakes up without latency.`,
494
- `4. Format: ${expectedDeliverable}`,
495
- `5. Citation Mandate: Keep your final response concise, structured, and cite exact file:line references.`
496
- ].join("\n");
497
- }
498
-
499
- function handleSubagentBrief(args = {}) {
500
- let specs = [];
501
- if (Array.isArray(args.subagents) && args.subagents.length > 0) {
502
- specs = args.subagents;
503
- } else if (args.objective || args.role || args.instructions) {
504
- specs = [args];
505
- } else {
506
- return {
507
- isError: true,
508
- content: [{ type: "text", text: "subagent_brief error: provide either 'objective' (for a single subagent) or 'subagents' array (for multiple subagents)." }]
509
- };
510
- }
511
-
512
- const subagentsPayload = [];
513
- const renderedBriefs = [];
514
-
515
- for (let i = 0; i < specs.length; i++) {
516
- const spec = specs[i] || {};
517
- const role = spec.role || spec.Role || "Scope-Isolated Deep Research Subagent";
518
- const objective = spec.objective || spec.Objective || "";
519
- const instructions = spec.instructions || spec.Instructions || "";
520
- const scopeFiles = spec.scopeFiles || spec.ScopeFiles || [];
521
- const expectedDeliverable = spec.expectedDeliverable || spec.ExpectedDeliverable || "Synthesized factual findings";
522
- const typeName = spec.typeName || spec.TypeName || "self";
523
- const model = spec.model || spec.Model || "inherit";
524
- const workspace = spec.workspace || spec.Workspace || "inherit";
525
-
526
- const promptText = formatSingleBrief({
527
- role,
528
- objective,
529
- instructions,
530
- scopeFiles,
531
- expectedDeliverable
532
- });
533
-
534
- subagentsPayload.push({
535
- TypeName: typeName,
536
- Role: role,
537
- Model: model,
538
- Workspace: workspace,
539
- Prompt: promptText
540
- });
541
-
542
- if (specs.length === 1) {
543
- renderedBriefs.push(promptText);
544
- } else {
545
- renderedBriefs.push(`### Subagent ${i + 1}: ${role}\n${promptText}`);
546
- }
547
- }
548
-
549
- const payloadJson = JSON.stringify({ Subagents: subagentsPayload }, null, 2);
550
-
551
- const fullText = [
552
- renderedBriefs.join("\n\n---\n\n"),
553
- "",
554
- "==================================================",
555
- "READY-TO-USE invoke_subagent PAYLOAD:",
556
- "```json",
557
- payloadJson,
558
- "```"
559
- ].join("\n");
560
-
561
- return { content: [{ type: "text", text: fullText }] };
562
- }
563
-
564
410
  function handleGetInstallationStatus() {
565
411
  const existing = detectExistingInstallation();
566
412
  const rtkInfo = findExistingRtk();
567
- const schemaText = existing.details.schemas.exists ? "ALL 6 SCHEMAS PRESENT" : "MISSING";
413
+ const schemaText = existing.details.schemas.exists ? "ALL 5 SCHEMAS PRESENT" : "MISSING";
568
414
  const report = [
569
415
  `### Agy-Context-Saver Installation Status: ${existing.isComplete ? "HEALTHY & ACTIVE 🛡️" : existing.isInstalled ? "PARTIAL INSTALLATION ⚠️" : "NOT INSTALLED ❌"}`,
570
416
  `- Native Plugin Link: ${existing.details.plugin.exists ? `ACTIVE (${existing.details.plugin.target})` : "NOT LINKED"}`,
@@ -594,7 +440,7 @@ async function handleSyncInstallation({ checkOnly = false } = {}) {
594
440
  `- Plugin Link: ${result.details?.plugin?.exists ? "Verified" : "Updated"}`,
595
441
  `- Lifecycle Hook: Registered in hooks.json`,
596
442
  `- MCP Server: Registered in mcp_config.json`,
597
- `- Tool Schemas: 6 Schemas mirrored to ~/.gemini/antigravity/mcp/agy-context-saver`,
443
+ `- Tool Schemas: 5 Schemas mirrored to ~/.gemini/antigravity/mcp/agy-context-saver`,
598
444
  `- RTK Binary: ${rtkInfo ? `Verified (${rtkInfo.version})` : "Provisioned"}`,
599
445
  "",
600
446
  checkOnly ? "✓ Pre-flight check complete (dry-run)." : "✓ Installation fully synchronized and up-to-date in Zero-Delay mode."
@@ -740,6 +586,11 @@ function cleanMessageContent(content, maxChars = MAX_TURN_CHARS) {
740
586
  text = text.replace(/[A-Za-z0-9+/=]{200,}/g, "[Binary/Base64 Payload Omitted]");
741
587
  }
742
588
 
589
+ // Strip Git CRLF/LF line-ending diagnostic warning noise (wastes context window tokens)
590
+ if (text.includes("will be replaced by")) {
591
+ text = text.replace(/(?:#\s*)?warning:\s*(?:in the working copy of [^,\r\n]+,\s*)?(?:CRLF will be replaced by LF|LF will be replaced by CRLF)[^\r\n]*(?:\r?\n)?/gi, "");
592
+ }
593
+
743
594
  // Clamp lines exceeding MAX_LINE_CHARS only if newline exists and text is long enough
744
595
  if (text.length > MAX_LINE_CHARS && text.includes("\n")) {
745
596
  const lines = text.split("\n");
@@ -958,13 +809,20 @@ async function handleReadTranscript({ conversationId, mode = "compact", lastTurn
958
809
  const formattedBlocks = [];
959
810
  let currentLength = header.length;
960
811
  let clamped = false;
812
+ const maxWindowChars = mode === "compact" ? 3800 : MAX_OUTPUT_CHARS;
961
813
 
962
814
  for (let i = selected.length - 1; i >= 0; i--) {
963
- const block = formatTranscriptItem(selected[i], {
815
+ const item = selected[i];
816
+ const isDialogueTurn =
817
+ item.type === "USER_INPUT" ||
818
+ item.type === "PLANNER_RESPONSE" ||
819
+ item.type === "SUBAGENT_RESPONSE";
820
+ const block = formatTranscriptItem(item, {
964
821
  includeThinking,
965
- includeToolCalls: true
822
+ includeToolCalls: true,
823
+ maxContentChars: mode === "compact" && !isDialogueTurn ? 300 : MAX_TURN_CHARS
966
824
  });
967
- if (currentLength + block.length + 6 > MAX_OUTPUT_CHARS) {
825
+ if (currentLength + block.length + 6 > maxWindowChars && formattedBlocks.length > 0) {
968
826
  clamped = true;
969
827
  break;
970
828
  }
@@ -974,7 +832,8 @@ async function handleReadTranscript({ conversationId, mode = "compact", lastTurn
974
832
 
975
833
  let fullOutput = header + formattedBlocks.join("\n\n---\n\n");
976
834
  if (clamped) {
977
- fullOutput += "\n\n... [Transcript display clamped at 24 KB safety ceiling] ...";
835
+ const ceilingLabel = mode === "compact" ? "3.8 KB inline" : "24 KB";
836
+ fullOutput += `\n\n... [Transcript display clamped at ${ceilingLabel} safety ceiling; use query_transcript(stepIndex: N) for specific steps] ...`;
978
837
  }
979
838
 
980
839
  return { content: [{ type: "text", text: fullOutput }] };
@@ -1325,8 +1184,6 @@ async function handleRequest(request) {
1325
1184
 
1326
1185
  if (name === "check_context_health") {
1327
1186
  toolResult = await handleCheckContextHealth(args);
1328
- } else if (name === "subagent_brief") {
1329
- toolResult = handleSubagentBrief(args);
1330
1187
  } else if (name === "get_installation_status") {
1331
1188
  toolResult = handleGetInstallationStatus();
1332
1189
  } else if (name === "sync_installation") {
@@ -1414,33 +1271,62 @@ async function handleRequest(request) {
1414
1271
  };
1415
1272
  }
1416
1273
 
1417
- // Stdio JSON-RPC line loop
1418
- const MAX_STDIO_PAYLOAD_BYTES = Number(process.env.AGY_MAX_STDIO_PAYLOAD_BYTES) || (10 * 1024 * 1024); // 10 MB ceiling default
1419
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
1274
+ if (isEntryPoint) {
1275
+ // Stdio JSON-RPC line loop
1276
+ const MAX_STDIO_PAYLOAD_BYTES = Number(process.env.AGY_MAX_STDIO_PAYLOAD_BYTES) || (10 * 1024 * 1024); // 10 MB ceiling default
1277
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
1420
1278
 
1421
- rl.on("line", async (line) => {
1422
- if (!line || !line.trim()) return;
1423
- if (line.length > MAX_STDIO_PAYLOAD_BYTES) {
1424
- const errResp = {
1425
- jsonrpc: "2.0",
1426
- id: null,
1427
- error: { code: -32600, message: "Invalid Request: Payload exceeds 10MB safety ceiling" }
1428
- };
1429
- process.stdout.write(JSON.stringify(errResp) + "\n");
1430
- return;
1431
- }
1432
- try {
1433
- const req = JSON.parse(line);
1434
- const resp = await handleRequest(req);
1435
- if (resp) {
1436
- process.stdout.write(JSON.stringify(resp) + "\n");
1279
+ rl.on("line", async (line) => {
1280
+ if (!line || !line.trim()) return;
1281
+ if (line.length > MAX_STDIO_PAYLOAD_BYTES) {
1282
+ const errResp = {
1283
+ jsonrpc: "2.0",
1284
+ id: null,
1285
+ error: { code: -32600, message: "Invalid Request: Payload exceeds 10MB safety ceiling" }
1286
+ };
1287
+ process.stdout.write(JSON.stringify(errResp) + "\n");
1288
+ return;
1437
1289
  }
1438
- } catch (err) {
1439
- const errResp = {
1440
- jsonrpc: "2.0",
1441
- id: null,
1442
- error: { code: -32700, message: `Parse error: ${err.message}` }
1443
- };
1444
- process.stdout.write(JSON.stringify(errResp) + "\n");
1290
+ try {
1291
+ const req = JSON.parse(line);
1292
+ const resp = await handleRequest(req);
1293
+ if (resp) {
1294
+ process.stdout.write(JSON.stringify(resp) + "\n");
1295
+ }
1296
+ } catch (err) {
1297
+ const errResp = {
1298
+ jsonrpc: "2.0",
1299
+ id: null,
1300
+ error: { code: -32700, message: `Parse error: ${err.message}` }
1301
+ };
1302
+ process.stdout.write(JSON.stringify(errResp) + "\n");
1303
+ }
1304
+ });
1305
+
1306
+ // Anti-Orphan Lifecycle Governance: Prevent lingering Node processes when parent IDE closes
1307
+ rl.on("close", () => process.exit(0));
1308
+ process.stdin.on("end", () => process.exit(0));
1309
+ process.stdin.on("close", () => process.exit(0));
1310
+ process.stdout.on("error", (err) => {
1311
+ if (err && err.code === "EPIPE") {
1312
+ process.exit(0);
1313
+ }
1314
+ });
1315
+
1316
+ // Lightweight parent-process watchdog: If parent IDE process terminates, exit child
1317
+ const initialPpid = process.ppid;
1318
+ if (initialPpid && initialPpid > 1) {
1319
+ const watchdog = setInterval(() => {
1320
+ try {
1321
+ process.kill(initialPpid, 0);
1322
+ } catch (err) {
1323
+ if (err && err.code === "ESRCH") {
1324
+ process.exit(0);
1325
+ }
1326
+ }
1327
+ }, 15000);
1328
+ watchdog.unref();
1445
1329
  }
1446
- });
1330
+ }
1331
+
1332
+ export { handleRequest };
@@ -11,16 +11,18 @@ Agy-Context-Saver is the universal Model Context Protocol server and lifecycle g
11
11
  2. **Never Read Internal State Directly**:
12
12
  - Access to `.system_generated/` via `view_file` is strictly denied to prevent transcript explosion.
13
13
  - Use `read_transcript` or `query_transcript` to inspect conversation history cleanly.
14
- 3. **Platform-Aware Codebase Inspection**:
15
- - **File Reading**: Prefer `view_file` with bounded slices (`StartLine` and `EndLine`, max 150 lines) to preserve line numbers and avoid terminal truncation.
16
- - **Windows Search/Listing**: Use `rtk find <path>` and `rtk rg "<pattern>" .` (the trailing `.` is required to avoid stdin stalls).
17
- - **POSIX Search/Listing**: Use `rtk ls <path>` and `rtk grep "<pattern>"` or `rtk rg "<pattern>"`.
14
+ 3. **Unified 3-Tier Codebase & File Inspection**:
15
+ - **Tier 1 — Structural Discovery (`waymark_ask` with `plain: true`)**: First locate exact `[StartLine, EndLine]` coordinates via `waymark_ask(..., plain: true)` across all 23 supported formats (code ASTs + `markdown`, `yaml`, `json`, `toml`, `bash`). Always set `plain: true` (~19–466 tokens, zero shell quoting or stdin hazards).
16
+ - **Tier 2 — Precision Workspace File Reading (`view_file` Hybrid Slice Policy)**: Pass the `StartLine` and `EndLine` span from `waymark_ask` into `view_file` (bounded slices of `1–150` lines) to preserve 1-indexed line coordinates for `replace_file_content` and avoid terminal stdout truncation. Partial slices of `151–800` lines are denied; an explicit one-pass full-file audit (`StartLine: 1, EndLine: <TotalLines>`) is permitted only when the file on disk is $\le 800$ lines and $\le 45\text{ KiB}$ (`46,080` bytes).
17
+ - **Tier 3 — RTK Boundaries (`rtk read`, `rtk find`, `rtk rg`)**:
18
+ - **Spillovers & Task Logs**: Read `.system_generated/steps/<step>/output.txt` spillovers and completed `tasks/<taskId>.log` files via `rtk read <path>` (with `--head-lines` / `--tail-lines`). Never use `rtk read -l minimal|aggressive` or `-m` to derive line numbers for edits.
19
+ - **Directory/File Discovery**: Use `rtk find <path>` (Windows & POSIX) or `rtk ls <path>` (POSIX).
20
+ - **Prose / Regex Search**: Use `rtk rg "<pattern>" <path>` (explicit `<path>` required on Windows to avoid stdin stalls) strictly for free-text/regex searches inside body prose or string values when the symbol, heading, or config key name is unknown.
18
21
 
19
22
  ## Available MCP Tools
20
23
  - `check_context_health`: Diagnoses transcript turn counts, byte payload, and polling loops. Accepts `transcriptPath` or `conversationId`.
21
- - `subagent_brief`: Generates scope-isolated instructions for delegated subagents to prevent parent context bloat.
22
24
  - `read_transcript`: Streams recent turns from `transcript.jsonl` or `transcript_full.jsonl` formatted as clean Markdown.
23
- - `query_transcript`: Forensic regex search and filtering engine across transcript steps with optional `summaryOnly: true`.
25
+ - `query_transcript`: Forensic regex search and filtering engine across transcript steps with optional `summaryOnly: true`, plus surgical step extraction via `stepIndex` and `field`.
24
26
  - `get_installation_status`: Audits live health of the plugin link, hook, MCP server, tool schemas, and RTK.
25
27
  - `sync_installation`: Synchronizes and repairs all 4 integration layers in ~25ms.
26
28
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agy-context-saver",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
4
4
  "description": "Universal Model Context Protocol (MCP) server & lifecycle governor purpose-built for Google Antigravity",
5
5
  "main": "mcp/index.js",
6
6
  "bin": {
@@ -13,8 +13,7 @@
13
13
  "uninstall": "node scripts/install-register.mjs --uninstall",
14
14
  "status": "node scripts/install-register.mjs --status",
15
15
  "preflight": "node scripts/release-preflight.mjs",
16
- "test:synergy": "node tests/test-waymark-synergy.mjs",
17
- "test": "node tests/test-hook.mjs && node tests/test-mcp.mjs && node tests/test-output-governance.mjs && node tests/test-e2e-probes.mjs && node tests/test-performance-probes.mjs && node tests/test-installed-verification.mjs && node tests/test-lifecycle-rollback.mjs && node tests/test-subagent-trials.mjs && node tests/test-check-only-readonly.mjs && node tests/test-malformed-config-preservation.mjs && node tests/test-waymark-synergy.mjs"
16
+ "test": "node tests/test-hook.mjs && node tests/test-mcp.mjs && node tests/test-output-governance.mjs && node tests/test-e2e-probes.mjs && node tests/test-performance-probes.mjs && node tests/test-installed-verification.mjs && node tests/test-lifecycle-rollback.mjs && node tests/test-check-only-readonly.mjs && node tests/test-malformed-config-preservation.mjs && node tests/test-rtk-ledger-fresh-install.mjs"
18
17
  },
19
18
  "repository": {
20
19
  "type": "git",
package/plugin.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "agy-context-saver",
3
3
  "displayName": "Antigravity Context Shield & Governor",
4
- "version": "1.7.0",
5
- "description": "Deterministic background polling governor, RTK terminal optimizer, and subagent context preservation shield for Antigravity.",
4
+ "version": "1.8.1",
5
+ "description": "Deterministic background polling governor, RTK terminal optimizer, and transcript context shield for Antigravity.",
6
6
  "logo": "assets/logo.svg",
7
7
  "suggestedPrompts": [
8
8
  "Audit context health and token consumption for this session",
9
- "Formulate an isolated subagent brief under Pointer-Over-Wire protocol",
9
+ "Search this conversation's transcript for earlier tool errors",
10
10
  "Inspect recent conversation turns without raw JSON transcript bloat"
11
11
  ]
12
12
  }