@yitom/agy-acp-map 0.1.4 → 0.1.7

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/README.md CHANGED
@@ -300,7 +300,7 @@ Engineering feasibility ≠ legal permission. 即便只用官方 CLI I/O,仍
300
300
 
301
301
  ## ACP V1 vs V2 Draft Specifications / 协议版本说明
302
302
 
303
- - **ACP V1 (Stable)**: Production-grade implementation fully conforming to canonical ACP v1 JSON-RPC specifications. Includes capability purity (extensions encapsulated under `_meta`), deterministic keyset cursor pagination on `session/list`, atomic session deletion, and strict session/resume semantics (`sessionId` + `cwd` required, cwd matching, omitted `additionalDirectories` reset to empty).
303
+ - **ACP V1 (Stable)**: Production-grade implementation fully conforming to canonical ACP v1 JSON-RPC specifications. `initialize` returns a minimal Zed-verified shape (`protocolVersion`, `agentInfo`, `agentCapabilities`, `authMethods: []`; models arrive via `session/new` `configOptions`). Includes deterministic keyset cursor pagination on `session/list`, atomic session deletion, and strict session/resume semantics (`sessionId` + `cwd` required, cwd matching, omitted `additionalDirectories` reset to empty).
304
304
  - **ACP V2 (Draft)**: Experimental v2 draft support built on `@agentclientprotocol/sdk@1.4.0` (`@agentclientprotocol/sdk/experimental/v2`). Conforms to the SDK's schema where `session/prompt` acknowledges prompt acceptance with an immediate `{}` (empty object) response, and subsequent progress is streamed via `session/update` notifications (`state_update: running -> chunks -> state_update: idle`).
305
305
  - **Prompt Validation**: All content blocks (`text`, `resource`, `resource_link`, `image`, `audio`) are validated synchronously before sending ACK, preventing background silent failures.
306
306
 
@@ -311,6 +311,57 @@ Engineering feasibility ≠ legal permission. 即便只用官方 CLI I/O,仍
311
311
  - **Zed / Editor Configuration**: When configuring the bridge in Zed on Windows, point directly to `bun` or `node` with `dist/bin.js` (or a windowless shim) rather than a `.cmd` or `.bat` wrapper. Batch scripts cause `cmd.exe` to flash a console window on launch before passing control to Node/Bun.
312
312
  - **If a custom Zed server still flashes**: use the native wrapper as the custom command itself, for example `command: "<repo>\\dist\\agy-headless.exe"` with args `["<path-to-bun-or-node>", "<repo>\\src\\sdk-server.ts"]` (or `dist\\bin.js`). You can also set `AGY_HEADLESS_LAUNCHER` to an absolute wrapper path. Packaging the bridge on npm improves distribution, but npm packaging alone does not change Windows process creation flags.
313
313
 
314
+ ## Zed via npm (global install) / Zed 全局安装用法
315
+
316
+ No Bun / TypeScript needed at runtime — the published package ships compiled
317
+ `dist/` artifacts (`bin.js` for Node, `agy-headless.exe`, plus the optional
318
+ single-file `agy-acp-win-x64.exe`). A **global** install is recommended so Zed
319
+ gets a stable absolute path (repo moves would break a local path):
320
+
321
+ ```powershell
322
+ npm install -g @yitom/agy-acp-map
323
+ npm root -g # e.g. C:\Users\<you>\AppData\Roaming\npm\node_modules
324
+ ```
325
+
326
+ Recommended Zed `agent_servers` entry (zero console flash: GUI shim starts
327
+ plain `node`, only `node` is required on the machine):
328
+
329
+ ```jsonc
330
+ "agy-acp-map-local": {
331
+ "type": "custom",
332
+ "command": "C:\\Users\\<you>\\AppData\\Roaming\\npm\\node_modules\\@yitom\\agy-acp-map\\dist\\agy-headless.exe",
333
+ "args": ["node", "C:\\Users\\<you>\\AppData\\Roaming\\npm\\node_modules\\@yitom\\agy-acp-map\\dist\\bin.js"],
334
+ "env": {}
335
+ }
336
+ ```
337
+
338
+ Fallback without the shim (works, but `node.exe` flashes one console frame on
339
+ launch because Zed spawns agents without `CREATE_NO_WINDOW`):
340
+
341
+ ```jsonc
342
+ "agy-acp-map-local": {
343
+ "type": "custom",
344
+ "command": "node",
345
+ "args": ["C:\\Users\\<you>\\AppData\\Roaming\\npm\\node_modules\\@yitom\\agy-acp-map\\dist\\bin.js"],
346
+ "env": {}
347
+ }
348
+ ```
349
+
350
+ Do NOT point Zed at the `agy-acp` / `agy-acp.cmd` global bin wrappers (extra
351
+ `cmd.exe` flash), at `src/*.ts` sources (needs Bun, dev only), or at
352
+ `bun`/`node` without an absolute `dist` path. `initialize` intentionally
353
+ returns a minimal response (`protocolVersion`, `agentInfo`,
354
+ `agentCapabilities`, `authMethods: []`); models arrive via `session/new`
355
+ `configOptions`, so the Zed model picker keeps working.
356
+
357
+ Troubleshooting: the bridge hides nothing from disk — append-only file log at
358
+ `%TEMP%\agy-acp.log` (override with `AGY_ACP_LOG`; ~1MB rotation). If Zed says
359
+ `Server exited with status exit code: 0` right after launch, check the tail of
360
+ that file: `START` with no following `V1 initialize called` means Zed never
361
+ sent bytes (spawn/config issue); `V1 initialize called` followed by
362
+ `stdin END` means Zed rejected the handshake (capture the exchange and compare
363
+ against a known-good agent such as `opencode acp`).
364
+
314
365
  ## Bun + TypeScript / Windows
315
366
 
316
367
  - Runtime is **Bun** (not Node). Entry: `bun src/server.ts` (or `bun run start`).
@@ -0,0 +1,4 @@
1
+ [diffend] Oversized file quarantined before diffing.
2
+ name: package/dist/agy-acp-win-x64.exe
3
+ size: 86638080 bytes
4
+ sha256: 4b22437f28bb0b65797110f6b47623dfaf8c98dea06593cb1cc4a0769c2c9d80
Binary file
package/dist/bin.js CHANGED
@@ -9302,8 +9302,8 @@ var legacyClientNotificationMethods = new Set([
9302
9302
 
9303
9303
  // src/core/session-core.ts
9304
9304
  import { randomUUID as randomUUID3 } from "node:crypto";
9305
- import * as fs8 from "node:fs";
9306
- import path9 from "node:path";
9305
+ import * as fs9 from "node:fs";
9306
+ import path10 from "node:path";
9307
9307
  // node_modules/@agentclientprotocol/sdk/dist/acp.js
9308
9308
  function emptyObjectResponse2(response) {
9309
9309
  return response ?? {};
@@ -11747,8 +11747,10 @@ function isAgyExecutable(bin) {
11747
11747
  }
11748
11748
  function findHeadlessLauncher() {
11749
11749
  const moduleDir = import.meta.dir ?? path5.dirname(process.argv[1] ?? "");
11750
+ const execDir = path5.dirname(process.execPath ?? "");
11750
11751
  const candidates = [
11751
11752
  process.env.AGY_HEADLESS_LAUNCHER,
11753
+ execDir ? path5.join(execDir, "agy-headless.exe") : undefined,
11752
11754
  path5.resolve(moduleDir, "..", "..", "dist", "agy-headless.exe"),
11753
11755
  path5.resolve(moduleDir, "agy-headless.exe"),
11754
11756
  path5.resolve(process.cwd(), "dist", "agy-headless.exe")
@@ -12486,7 +12488,7 @@ class SessionHistoryStore {
12486
12488
  var AGENT_INFO = {
12487
12489
  name: "agy-acp",
12488
12490
  title: "agy ACP (stream-json)",
12489
- version: "0.1.4"
12491
+ version: "0.1.7"
12490
12492
  };
12491
12493
  var BRIDGE_CAPABILITIES = {
12492
12494
  prompt: true,
@@ -12552,7 +12554,54 @@ class AsyncSerialQueue {
12552
12554
  }
12553
12555
  }
12554
12556
 
12557
+ // src/lib/debug-log.ts
12558
+ import fs8 from "node:fs";
12559
+ import os4 from "node:os";
12560
+ import path9 from "node:path";
12561
+ function resolveLogPath() {
12562
+ const override = process.env.AGY_ACP_LOG;
12563
+ if (override && override !== "undefined" && override !== "null")
12564
+ return override;
12565
+ return path9.join(os4.tmpdir(), "agy-acp.log");
12566
+ }
12567
+ var logPath = null;
12568
+ try {
12569
+ logPath = resolveLogPath();
12570
+ } catch {
12571
+ logPath = null;
12572
+ }
12573
+ function writeLine(line) {
12574
+ if (!logPath)
12575
+ return;
12576
+ try {
12577
+ let truncate = false;
12578
+ try {
12579
+ const st = fs8.statSync(logPath);
12580
+ if (st.size > 1024 * 1024)
12581
+ truncate = true;
12582
+ } catch {}
12583
+ fs8.appendFileSync(logPath, line + `
12584
+ `, "utf8");
12585
+ if (truncate) {
12586
+ try {
12587
+ const text = fs8.readFileSync(logPath, "utf8");
12588
+ fs8.writeFileSync(logPath, text.slice(-512 * 1024), "utf8");
12589
+ } catch {}
12590
+ }
12591
+ } catch {}
12592
+ }
12593
+ function debugLog(msg) {
12594
+ writeLine(`${new Date().toISOString()} pid=${process.pid} ${msg}`);
12595
+ }
12596
+
12555
12597
  // src/core/session-core.ts
12598
+ var EMPTY_SESSION_MAX_AGE_MS = 60 * 60 * 1000;
12599
+ function deriveTitle(text, maxLen = 60) {
12600
+ const line = String(text || "").split(/\r?\n/).map((s) => s.trim()).find(Boolean) || "";
12601
+ const flat = line.replace(/\s+/g, " ");
12602
+ return flat.length > maxLen ? flat.slice(0, maxLen) + "…" : flat;
12603
+ }
12604
+
12556
12605
  class AgySessionCore {
12557
12606
  sessions = new Map;
12558
12607
  sessionStore;
@@ -12610,8 +12659,8 @@ class AgySessionCore {
12610
12659
  const rawBin = process.env.AGY_BIN;
12611
12660
  let bin = rawBin && rawBin !== "undefined" && rawBin !== "null" ? rawBin : "agy";
12612
12661
  if (bin === "agy" || bin === "agy.exe") {
12613
- const geminiBin = path9.join(process.env.USERPROFILE || process.env.HOME || "", ".gemini", "bin", process.platform === "win32" ? "agy.exe" : "agy");
12614
- if (fs8.existsSync(geminiBin)) {
12662
+ const geminiBin = path10.join(process.env.USERPROFILE || process.env.HOME || "", ".gemini", "bin", process.platform === "win32" ? "agy.exe" : "agy");
12663
+ if (fs9.existsSync(geminiBin)) {
12615
12664
  bin = geminiBin;
12616
12665
  }
12617
12666
  }
@@ -12746,10 +12795,11 @@ class AgySessionCore {
12746
12795
  }
12747
12796
  async createSession(params, protocolVersion = 1) {
12748
12797
  const cwd = params?.cwd;
12749
- if (!cwd || typeof cwd !== "string" || !path9.isAbsolute(cwd)) {
12798
+ debugLog(`createSession v${protocolVersion} cwd=${cwd}`);
12799
+ if (!cwd || typeof cwd !== "string" || !path10.isAbsolute(cwd)) {
12750
12800
  throw new RequestError(-32602, "cwd must be an absolute path");
12751
12801
  }
12752
- if (!fs8.existsSync(cwd) || !fs8.statSync(cwd).isDirectory()) {
12802
+ if (!fs9.existsSync(cwd) || !fs9.statSync(cwd).isDirectory()) {
12753
12803
  throw new RequestError(-32602, `cwd does not exist or is not a directory: ${cwd}`);
12754
12804
  }
12755
12805
  if (params?.additionalDirectories !== undefined) {
@@ -12757,7 +12807,7 @@ class AgySessionCore {
12757
12807
  throw new RequestError(-32602, "additionalDirectories must be an array");
12758
12808
  }
12759
12809
  for (const d of params.additionalDirectories) {
12760
- if (typeof d !== "string" || !path9.isAbsolute(d)) {
12810
+ if (typeof d !== "string" || !path10.isAbsolute(d)) {
12761
12811
  throw new RequestError(-32602, `additionalDirectory must be an absolute path: ${d}`);
12762
12812
  }
12763
12813
  }
@@ -12824,10 +12874,10 @@ class AgySessionCore {
12824
12874
  if (!cwd || typeof cwd !== "string") {
12825
12875
  throw new RequestError(-32602, "cwd is required for session/resume");
12826
12876
  }
12827
- if (!path9.isAbsolute(cwd)) {
12877
+ if (!path10.isAbsolute(cwd)) {
12828
12878
  throw new RequestError(-32602, "cwd must be an absolute path");
12829
12879
  }
12830
- if (!fs8.existsSync(cwd) || !fs8.statSync(cwd).isDirectory()) {
12880
+ if (!fs9.existsSync(cwd) || !fs9.statSync(cwd).isDirectory()) {
12831
12881
  throw new RequestError(-32602, `cwd does not exist or is not a directory: ${cwd}`);
12832
12882
  }
12833
12883
  if (params?.replayFrom !== undefined && params?.replayFrom !== null) {
@@ -12849,7 +12899,7 @@ class AgySessionCore {
12849
12899
  throw new RequestError(-32602, "additionalDirectories must be an array");
12850
12900
  }
12851
12901
  for (const d of params.additionalDirectories) {
12852
- if (typeof d !== "string" || !path9.isAbsolute(d)) {
12902
+ if (typeof d !== "string" || !path10.isAbsolute(d)) {
12853
12903
  throw new RequestError(-32602, `additionalDirectory must be an absolute path: ${d}`);
12854
12904
  }
12855
12905
  }
@@ -12865,7 +12915,7 @@ class AgySessionCore {
12865
12915
  if (recordedVersion !== protocolVersion) {
12866
12916
  throw new RequestError(-32602, `Session ${sessionId} was created with ACP v${recordedVersion} and cannot be resumed with v${protocolVersion}`);
12867
12917
  }
12868
- if (record.cwd && path9.resolve(record.cwd) !== path9.resolve(cwd)) {
12918
+ if (record.cwd && path10.resolve(record.cwd) !== path10.resolve(cwd)) {
12869
12919
  throw new RequestError(-32602, `cwd does not match session cwd: expected ${record.cwd}, got ${cwd}`);
12870
12920
  }
12871
12921
  const richRoots = richRootsFromSession({
@@ -12909,7 +12959,7 @@ class AgySessionCore {
12909
12959
  if (session.protocolVersion !== protocolVersion) {
12910
12960
  throw new RequestError(-32602, `Session ${sessionId} was created with ACP v${session.protocolVersion} and cannot be resumed with v${protocolVersion}`);
12911
12961
  }
12912
- if (path9.resolve(session.cwd) !== path9.resolve(cwd)) {
12962
+ if (path10.resolve(session.cwd) !== path10.resolve(cwd)) {
12913
12963
  throw new RequestError(-32602, `cwd does not match session cwd: expected ${session.cwd}, got ${cwd}`);
12914
12964
  }
12915
12965
  session.additionalDirectories = additionalDirectories;
@@ -12974,10 +13024,10 @@ class AgySessionCore {
12974
13024
  meta: this.sessionMeta(session)
12975
13025
  };
12976
13026
  }
12977
- async listSessions(params) {
13027
+ async listSessions(params, opts) {
12978
13028
  const filterCwd = params?.cwd;
12979
13029
  if (filterCwd !== undefined && filterCwd !== null) {
12980
- if (typeof filterCwd !== "string" || !path9.isAbsolute(filterCwd)) {
13030
+ if (typeof filterCwd !== "string" || !path10.isAbsolute(filterCwd)) {
12981
13031
  throw new RequestError(-32602, "cwd must be an absolute path");
12982
13032
  }
12983
13033
  }
@@ -12999,6 +13049,13 @@ class AgySessionCore {
12999
13049
  diskById.delete(id);
13000
13050
  }
13001
13051
  for (const r of diskById.values()) {
13052
+ if (!r.conversationId) {
13053
+ const updated = r.updatedAt ? new Date(r.updatedAt).getTime() : NaN;
13054
+ const now = opts?.now ?? Date.now();
13055
+ if (Number.isFinite(updated) && now - updated > EMPTY_SESSION_MAX_AGE_MS) {
13056
+ continue;
13057
+ }
13058
+ }
13002
13059
  allSessions.push({
13003
13060
  sessionId: r.sessionId,
13004
13061
  cwd: r.cwd,
@@ -13200,12 +13257,15 @@ class AgySessionCore {
13200
13257
  }
13201
13258
  }
13202
13259
  this.persistTurnHistory(sessionId, text, visibleAssistantText, stopReason, historyTurnEligible);
13260
+ if (!session.title && text.trim()) {
13261
+ session.title = deriveTitle(text);
13262
+ }
13203
13263
  this.persistSession(session);
13204
13264
  if (process.env.AGY_ACP_KEEP_STAGING !== "1" && session.stagedFiles.length) {
13205
13265
  for (const f of session.stagedFiles) {
13206
13266
  try {
13207
- if (fs8.existsSync(f))
13208
- fs8.unlinkSync(f);
13267
+ if (fs9.existsSync(f))
13268
+ fs9.unlinkSync(f);
13209
13269
  } catch {}
13210
13270
  }
13211
13271
  session.stagedFiles = [];
@@ -13330,18 +13390,6 @@ class AgySessionCore {
13330
13390
  }
13331
13391
  }
13332
13392
 
13333
- // src/v1/types.ts
13334
- var V1_AGENT_CAPABILITIES = {
13335
- loadSession: true,
13336
- sessionCapabilities: {
13337
- list: {},
13338
- resume: {},
13339
- close: {},
13340
- delete: {},
13341
- additionalDirectories: {}
13342
- }
13343
- };
13344
-
13345
13393
  // src/v1/config.ts
13346
13394
  function buildV1ConfigOptions(discovery, session) {
13347
13395
  const models = catalogChoices(discovery.availableModels, session?.model);
@@ -13379,22 +13427,16 @@ class AgyAcpV1Service {
13379
13427
  this.core = core;
13380
13428
  }
13381
13429
  async initialize() {
13382
- const discovery = await this.core.getDiscovery();
13383
- const configOptions = buildV1ConfigOptions(discovery);
13430
+ debugLog("V1 initialize called");
13431
+ debugLog("V1 initialize called (minimal response)");
13384
13432
  return {
13385
13433
  protocolVersion: 1,
13386
- agentInfo: AGENT_INFO,
13387
- agentCapabilities: V1_AGENT_CAPABILITIES,
13388
- _meta: {
13389
- bridgeCapabilities: {
13390
- ...BRIDGE_CAPABILITIES,
13391
- availableModels: discovery.availableModels,
13392
- availableAgents: discovery.availableAgents,
13393
- configOptions
13394
- },
13395
- availableModels: discovery.availableModels,
13396
- availableAgents: discovery.availableAgents
13397
- }
13434
+ agentInfo: { name: AGENT_INFO.name, version: AGENT_INFO.version },
13435
+ agentCapabilities: {
13436
+ loadSession: true,
13437
+ sessionCapabilities: { close: {}, list: {}, resume: {} }
13438
+ },
13439
+ authMethods: []
13398
13440
  };
13399
13441
  }
13400
13442
  async newSession(params) {
@@ -13427,8 +13469,8 @@ class AgyAcpV1Service {
13427
13469
  ...meta ? { _meta: meta } : {}
13428
13470
  };
13429
13471
  }
13430
- async listSessions(params) {
13431
- return this.core.listSessions(params);
13472
+ async listSessions(params, opts) {
13473
+ return this.core.listSessions(params, opts);
13432
13474
  }
13433
13475
  async closeSession(params) {
13434
13476
  return this.core.closeSession(params);
@@ -13540,8 +13582,8 @@ class AgyAcpV2Service {
13540
13582
  ...meta ? { _meta: meta } : {}
13541
13583
  };
13542
13584
  }
13543
- async listSessions(params) {
13544
- return this.core.listSessions(params);
13585
+ async listSessions(params, opts) {
13586
+ return this.core.listSessions(params, opts);
13545
13587
  }
13546
13588
  async closeSession(params) {
13547
13589
  return this.core.closeSession(params);
@@ -17752,11 +17794,67 @@ class AgyAcpService {
17752
17794
  }
17753
17795
  }
17754
17796
 
17797
+ // src/lib/debug-log.ts
17798
+ import fs10 from "node:fs";
17799
+ import os5 from "node:os";
17800
+ import path11 from "node:path";
17801
+ function resolveLogPath2() {
17802
+ const override = process.env.AGY_ACP_LOG;
17803
+ if (override && override !== "undefined" && override !== "null")
17804
+ return override;
17805
+ return path11.join(os5.tmpdir(), "agy-acp.log");
17806
+ }
17807
+ var logPath2 = null;
17808
+ try {
17809
+ logPath2 = resolveLogPath2();
17810
+ } catch {
17811
+ logPath2 = null;
17812
+ }
17813
+ function writeLine2(line) {
17814
+ if (!logPath2)
17815
+ return;
17816
+ try {
17817
+ let truncate = false;
17818
+ try {
17819
+ const st = fs10.statSync(logPath2);
17820
+ if (st.size > 1024 * 1024)
17821
+ truncate = true;
17822
+ } catch {}
17823
+ fs10.appendFileSync(logPath2, line + `
17824
+ `, "utf8");
17825
+ if (truncate) {
17826
+ try {
17827
+ const text = fs10.readFileSync(logPath2, "utf8");
17828
+ fs10.writeFileSync(logPath2, text.slice(-512 * 1024), "utf8");
17829
+ } catch {}
17830
+ }
17831
+ } catch {}
17832
+ }
17833
+ function debugLog2(msg) {
17834
+ writeLine2(`${new Date().toISOString()} pid=${process.pid} ${msg}`);
17835
+ }
17836
+ function installFileLogging() {
17837
+ try {
17838
+ process.on("uncaughtException", (err) => {
17839
+ debugLog2(`FATAL uncaughtException: ${err?.stack || err}`);
17840
+ });
17841
+ process.on("unhandledRejection", (reason) => {
17842
+ debugLog2(`FATAL unhandledRejection: ${reason instanceof Error ? reason.stack : reason}`);
17843
+ });
17844
+ process.stdin.on("end", () => debugLog2("stdin END (parent closed pipe)"));
17845
+ process.stdin.on("close", () => debugLog2("stdin CLOSE"));
17846
+ } catch {}
17847
+ return logPath2;
17848
+ }
17849
+
17755
17850
  // src/sdk-server.ts
17756
17851
  hideConsoleWindow();
17852
+ var logPath3 = installFileLogging();
17853
+ debugLog2(`START argv=${JSON.stringify(process.argv.slice(1))} cwd=${process.cwd()} bun=${process.versions?.bun || "no"} node=${process.version} log=${logPath3}`);
17757
17854
  var service = new AgyAcpService;
17758
17855
  var app = createDualAcpApp(service);
17759
17856
  var stream = ndJsonStream2(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin));
17760
17857
  process.stderr.write(`[agy-acp-sdk] Starting official @agentclientprotocol/sdk stdio server...
17761
17858
  `);
17762
17859
  await app.connect(stream);
17860
+ debugLog2("CONNECT-SETUP-DONE (stream established; exit 0 only happens after stdin END/CLOSE)");
@@ -6,6 +6,11 @@ export interface SessionCoreOptions {
6
6
  sessionStore?: SessionStore | string;
7
7
  historyStore?: SessionHistoryStore | string;
8
8
  }
9
+ /** Rows without any completed turn older than this are hidden from
10
+ * session/list (still resumable/deletable by id — the store keeps them). */
11
+ export declare const EMPTY_SESSION_MAX_AGE_MS: number;
12
+ /** First user prompt collapsed to one line, capped for list display. */
13
+ export declare function deriveTitle(text: string, maxLen?: number): string;
9
14
  export declare class AgySessionCore {
10
15
  readonly sessions: Map<string, SdkSession>;
11
16
  readonly sessionStore: SessionStore;
@@ -54,7 +59,9 @@ export declare class AgySessionCore {
54
59
  configOptions: any[];
55
60
  meta?: Record<string, unknown>;
56
61
  }>;
57
- listSessions(params?: any): Promise<{
62
+ listSessions(params?: any, opts?: {
63
+ now?: number;
64
+ }): Promise<{
58
65
  sessions: any[];
59
66
  nextCursor?: string | null;
60
67
  }>;
package/dist/index.d.ts CHANGED
@@ -17,3 +17,4 @@ export * from './lib/agy-discovery.ts';
17
17
  export * from './lib/session-store.ts';
18
18
  export * from './lib/session-history.ts';
19
19
  export * from './lib/win32-console.ts';
20
+ export * from './lib/debug-log.ts';
package/dist/index.js CHANGED
@@ -17,8 +17,8 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
17
17
 
18
18
  // src/core/session-core.ts
19
19
  import { randomUUID as randomUUID3 } from "node:crypto";
20
- import * as fs8 from "node:fs";
21
- import path9 from "node:path";
20
+ import * as fs9 from "node:fs";
21
+ import path10 from "node:path";
22
22
 
23
23
  // node_modules/@agentclientprotocol/sdk/dist/schema/index.js
24
24
  var AGENT_METHODS = {
@@ -11457,8 +11457,10 @@ function isAgyExecutable(bin) {
11457
11457
  }
11458
11458
  function findHeadlessLauncher() {
11459
11459
  const moduleDir = import.meta.dir ?? path5.dirname(process.argv[1] ?? "");
11460
+ const execDir = path5.dirname(process.execPath ?? "");
11460
11461
  const candidates = [
11461
11462
  process.env.AGY_HEADLESS_LAUNCHER,
11463
+ execDir ? path5.join(execDir, "agy-headless.exe") : undefined,
11462
11464
  path5.resolve(moduleDir, "..", "..", "dist", "agy-headless.exe"),
11463
11465
  path5.resolve(moduleDir, "agy-headless.exe"),
11464
11466
  path5.resolve(process.cwd(), "dist", "agy-headless.exe")
@@ -12273,7 +12275,7 @@ class SessionHistoryStore {
12273
12275
  var AGENT_INFO = {
12274
12276
  name: "agy-acp",
12275
12277
  title: "agy ACP (stream-json)",
12276
- version: "0.1.4"
12278
+ version: "0.1.7"
12277
12279
  };
12278
12280
  var BRIDGE_CAPABILITIES = {
12279
12281
  prompt: true,
@@ -12339,7 +12341,67 @@ class AsyncSerialQueue {
12339
12341
  }
12340
12342
  }
12341
12343
 
12344
+ // src/lib/debug-log.ts
12345
+ import fs8 from "node:fs";
12346
+ import os4 from "node:os";
12347
+ import path9 from "node:path";
12348
+ function resolveLogPath() {
12349
+ const override = process.env.AGY_ACP_LOG;
12350
+ if (override && override !== "undefined" && override !== "null")
12351
+ return override;
12352
+ return path9.join(os4.tmpdir(), "agy-acp.log");
12353
+ }
12354
+ var logPath = null;
12355
+ try {
12356
+ logPath = resolveLogPath();
12357
+ } catch {
12358
+ logPath = null;
12359
+ }
12360
+ function writeLine(line) {
12361
+ if (!logPath)
12362
+ return;
12363
+ try {
12364
+ let truncate = false;
12365
+ try {
12366
+ const st = fs8.statSync(logPath);
12367
+ if (st.size > 1024 * 1024)
12368
+ truncate = true;
12369
+ } catch {}
12370
+ fs8.appendFileSync(logPath, line + `
12371
+ `, "utf8");
12372
+ if (truncate) {
12373
+ try {
12374
+ const text = fs8.readFileSync(logPath, "utf8");
12375
+ fs8.writeFileSync(logPath, text.slice(-512 * 1024), "utf8");
12376
+ } catch {}
12377
+ }
12378
+ } catch {}
12379
+ }
12380
+ function debugLog(msg) {
12381
+ writeLine(`${new Date().toISOString()} pid=${process.pid} ${msg}`);
12382
+ }
12383
+ function installFileLogging() {
12384
+ try {
12385
+ process.on("uncaughtException", (err) => {
12386
+ debugLog(`FATAL uncaughtException: ${err?.stack || err}`);
12387
+ });
12388
+ process.on("unhandledRejection", (reason) => {
12389
+ debugLog(`FATAL unhandledRejection: ${reason instanceof Error ? reason.stack : reason}`);
12390
+ });
12391
+ process.stdin.on("end", () => debugLog("stdin END (parent closed pipe)"));
12392
+ process.stdin.on("close", () => debugLog("stdin CLOSE"));
12393
+ } catch {}
12394
+ return logPath;
12395
+ }
12396
+
12342
12397
  // src/core/session-core.ts
12398
+ var EMPTY_SESSION_MAX_AGE_MS = 60 * 60 * 1000;
12399
+ function deriveTitle(text, maxLen = 60) {
12400
+ const line = String(text || "").split(/\r?\n/).map((s) => s.trim()).find(Boolean) || "";
12401
+ const flat = line.replace(/\s+/g, " ");
12402
+ return flat.length > maxLen ? flat.slice(0, maxLen) + "…" : flat;
12403
+ }
12404
+
12343
12405
  class AgySessionCore {
12344
12406
  sessions = new Map;
12345
12407
  sessionStore;
@@ -12397,8 +12459,8 @@ class AgySessionCore {
12397
12459
  const rawBin = process.env.AGY_BIN;
12398
12460
  let bin = rawBin && rawBin !== "undefined" && rawBin !== "null" ? rawBin : "agy";
12399
12461
  if (bin === "agy" || bin === "agy.exe") {
12400
- const geminiBin = path9.join(process.env.USERPROFILE || process.env.HOME || "", ".gemini", "bin", process.platform === "win32" ? "agy.exe" : "agy");
12401
- if (fs8.existsSync(geminiBin)) {
12462
+ const geminiBin = path10.join(process.env.USERPROFILE || process.env.HOME || "", ".gemini", "bin", process.platform === "win32" ? "agy.exe" : "agy");
12463
+ if (fs9.existsSync(geminiBin)) {
12402
12464
  bin = geminiBin;
12403
12465
  }
12404
12466
  }
@@ -12533,10 +12595,11 @@ class AgySessionCore {
12533
12595
  }
12534
12596
  async createSession(params, protocolVersion = 1) {
12535
12597
  const cwd = params?.cwd;
12536
- if (!cwd || typeof cwd !== "string" || !path9.isAbsolute(cwd)) {
12598
+ debugLog(`createSession v${protocolVersion} cwd=${cwd}`);
12599
+ if (!cwd || typeof cwd !== "string" || !path10.isAbsolute(cwd)) {
12537
12600
  throw new RequestError(-32602, "cwd must be an absolute path");
12538
12601
  }
12539
- if (!fs8.existsSync(cwd) || !fs8.statSync(cwd).isDirectory()) {
12602
+ if (!fs9.existsSync(cwd) || !fs9.statSync(cwd).isDirectory()) {
12540
12603
  throw new RequestError(-32602, `cwd does not exist or is not a directory: ${cwd}`);
12541
12604
  }
12542
12605
  if (params?.additionalDirectories !== undefined) {
@@ -12544,7 +12607,7 @@ class AgySessionCore {
12544
12607
  throw new RequestError(-32602, "additionalDirectories must be an array");
12545
12608
  }
12546
12609
  for (const d of params.additionalDirectories) {
12547
- if (typeof d !== "string" || !path9.isAbsolute(d)) {
12610
+ if (typeof d !== "string" || !path10.isAbsolute(d)) {
12548
12611
  throw new RequestError(-32602, `additionalDirectory must be an absolute path: ${d}`);
12549
12612
  }
12550
12613
  }
@@ -12611,10 +12674,10 @@ class AgySessionCore {
12611
12674
  if (!cwd || typeof cwd !== "string") {
12612
12675
  throw new RequestError(-32602, "cwd is required for session/resume");
12613
12676
  }
12614
- if (!path9.isAbsolute(cwd)) {
12677
+ if (!path10.isAbsolute(cwd)) {
12615
12678
  throw new RequestError(-32602, "cwd must be an absolute path");
12616
12679
  }
12617
- if (!fs8.existsSync(cwd) || !fs8.statSync(cwd).isDirectory()) {
12680
+ if (!fs9.existsSync(cwd) || !fs9.statSync(cwd).isDirectory()) {
12618
12681
  throw new RequestError(-32602, `cwd does not exist or is not a directory: ${cwd}`);
12619
12682
  }
12620
12683
  if (params?.replayFrom !== undefined && params?.replayFrom !== null) {
@@ -12636,7 +12699,7 @@ class AgySessionCore {
12636
12699
  throw new RequestError(-32602, "additionalDirectories must be an array");
12637
12700
  }
12638
12701
  for (const d of params.additionalDirectories) {
12639
- if (typeof d !== "string" || !path9.isAbsolute(d)) {
12702
+ if (typeof d !== "string" || !path10.isAbsolute(d)) {
12640
12703
  throw new RequestError(-32602, `additionalDirectory must be an absolute path: ${d}`);
12641
12704
  }
12642
12705
  }
@@ -12652,7 +12715,7 @@ class AgySessionCore {
12652
12715
  if (recordedVersion !== protocolVersion) {
12653
12716
  throw new RequestError(-32602, `Session ${sessionId} was created with ACP v${recordedVersion} and cannot be resumed with v${protocolVersion}`);
12654
12717
  }
12655
- if (record.cwd && path9.resolve(record.cwd) !== path9.resolve(cwd)) {
12718
+ if (record.cwd && path10.resolve(record.cwd) !== path10.resolve(cwd)) {
12656
12719
  throw new RequestError(-32602, `cwd does not match session cwd: expected ${record.cwd}, got ${cwd}`);
12657
12720
  }
12658
12721
  const richRoots = richRootsFromSession({
@@ -12696,7 +12759,7 @@ class AgySessionCore {
12696
12759
  if (session.protocolVersion !== protocolVersion) {
12697
12760
  throw new RequestError(-32602, `Session ${sessionId} was created with ACP v${session.protocolVersion} and cannot be resumed with v${protocolVersion}`);
12698
12761
  }
12699
- if (path9.resolve(session.cwd) !== path9.resolve(cwd)) {
12762
+ if (path10.resolve(session.cwd) !== path10.resolve(cwd)) {
12700
12763
  throw new RequestError(-32602, `cwd does not match session cwd: expected ${session.cwd}, got ${cwd}`);
12701
12764
  }
12702
12765
  session.additionalDirectories = additionalDirectories;
@@ -12761,10 +12824,10 @@ class AgySessionCore {
12761
12824
  meta: this.sessionMeta(session)
12762
12825
  };
12763
12826
  }
12764
- async listSessions(params) {
12827
+ async listSessions(params, opts) {
12765
12828
  const filterCwd = params?.cwd;
12766
12829
  if (filterCwd !== undefined && filterCwd !== null) {
12767
- if (typeof filterCwd !== "string" || !path9.isAbsolute(filterCwd)) {
12830
+ if (typeof filterCwd !== "string" || !path10.isAbsolute(filterCwd)) {
12768
12831
  throw new RequestError(-32602, "cwd must be an absolute path");
12769
12832
  }
12770
12833
  }
@@ -12786,6 +12849,13 @@ class AgySessionCore {
12786
12849
  diskById.delete(id);
12787
12850
  }
12788
12851
  for (const r of diskById.values()) {
12852
+ if (!r.conversationId) {
12853
+ const updated = r.updatedAt ? new Date(r.updatedAt).getTime() : NaN;
12854
+ const now = opts?.now ?? Date.now();
12855
+ if (Number.isFinite(updated) && now - updated > EMPTY_SESSION_MAX_AGE_MS) {
12856
+ continue;
12857
+ }
12858
+ }
12789
12859
  allSessions.push({
12790
12860
  sessionId: r.sessionId,
12791
12861
  cwd: r.cwd,
@@ -12987,12 +13057,15 @@ class AgySessionCore {
12987
13057
  }
12988
13058
  }
12989
13059
  this.persistTurnHistory(sessionId, text, visibleAssistantText, stopReason, historyTurnEligible);
13060
+ if (!session.title && text.trim()) {
13061
+ session.title = deriveTitle(text);
13062
+ }
12990
13063
  this.persistSession(session);
12991
13064
  if (process.env.AGY_ACP_KEEP_STAGING !== "1" && session.stagedFiles.length) {
12992
13065
  for (const f of session.stagedFiles) {
12993
13066
  try {
12994
- if (fs8.existsSync(f))
12995
- fs8.unlinkSync(f);
13067
+ if (fs9.existsSync(f))
13068
+ fs9.unlinkSync(f);
12996
13069
  } catch {}
12997
13070
  }
12998
13071
  session.stagedFiles = [];
@@ -13117,18 +13190,6 @@ class AgySessionCore {
13117
13190
  }
13118
13191
  }
13119
13192
 
13120
- // src/v1/types.ts
13121
- var V1_AGENT_CAPABILITIES = {
13122
- loadSession: true,
13123
- sessionCapabilities: {
13124
- list: {},
13125
- resume: {},
13126
- close: {},
13127
- delete: {},
13128
- additionalDirectories: {}
13129
- }
13130
- };
13131
-
13132
13193
  // src/v1/config.ts
13133
13194
  function buildV1ConfigOptions(discovery, session) {
13134
13195
  const models = catalogChoices(discovery.availableModels, session?.model);
@@ -13166,22 +13227,16 @@ class AgyAcpV1Service {
13166
13227
  this.core = core;
13167
13228
  }
13168
13229
  async initialize() {
13169
- const discovery = await this.core.getDiscovery();
13170
- const configOptions = buildV1ConfigOptions(discovery);
13230
+ debugLog("V1 initialize called");
13231
+ debugLog("V1 initialize called (minimal response)");
13171
13232
  return {
13172
13233
  protocolVersion: 1,
13173
- agentInfo: AGENT_INFO,
13174
- agentCapabilities: V1_AGENT_CAPABILITIES,
13175
- _meta: {
13176
- bridgeCapabilities: {
13177
- ...BRIDGE_CAPABILITIES,
13178
- availableModels: discovery.availableModels,
13179
- availableAgents: discovery.availableAgents,
13180
- configOptions
13181
- },
13182
- availableModels: discovery.availableModels,
13183
- availableAgents: discovery.availableAgents
13184
- }
13234
+ agentInfo: { name: AGENT_INFO.name, version: AGENT_INFO.version },
13235
+ agentCapabilities: {
13236
+ loadSession: true,
13237
+ sessionCapabilities: { close: {}, list: {}, resume: {} }
13238
+ },
13239
+ authMethods: []
13185
13240
  };
13186
13241
  }
13187
13242
  async newSession(params) {
@@ -13214,8 +13269,8 @@ class AgyAcpV1Service {
13214
13269
  ...meta ? { _meta: meta } : {}
13215
13270
  };
13216
13271
  }
13217
- async listSessions(params) {
13218
- return this.core.listSessions(params);
13272
+ async listSessions(params, opts) {
13273
+ return this.core.listSessions(params, opts);
13219
13274
  }
13220
13275
  async closeSession(params) {
13221
13276
  return this.core.closeSession(params);
@@ -13327,8 +13382,8 @@ class AgyAcpV2Service {
13327
13382
  ...meta ? { _meta: meta } : {}
13328
13383
  };
13329
13384
  }
13330
- async listSessions(params) {
13331
- return this.core.listSessions(params);
13385
+ async listSessions(params, opts) {
13386
+ return this.core.listSessions(params, opts);
13332
13387
  }
13333
13388
  async closeSession(params) {
13334
13389
  return this.core.closeSession(params);
@@ -17545,7 +17600,9 @@ __export(exports_core3, {
17545
17600
  AgySessionCore: () => AgySessionCore,
17546
17601
  AsyncSerialQueue: () => AsyncSerialQueue,
17547
17602
  BRIDGE_CAPABILITIES: () => BRIDGE_CAPABILITIES,
17548
- catalogChoices: () => catalogChoices
17603
+ EMPTY_SESSION_MAX_AGE_MS: () => EMPTY_SESSION_MAX_AGE_MS,
17604
+ catalogChoices: () => catalogChoices,
17605
+ deriveTitle: () => deriveTitle
17549
17606
  });
17550
17607
  // src/v1/index.ts
17551
17608
  var exports_v1 = {};
@@ -17555,6 +17612,18 @@ __export(exports_v1, {
17555
17612
  buildV1ConfigOptions: () => buildV1ConfigOptions,
17556
17613
  createAcpV1App: () => createAcpV1App
17557
17614
  });
17615
+
17616
+ // src/v1/types.ts
17617
+ var V1_AGENT_CAPABILITIES = {
17618
+ loadSession: true,
17619
+ sessionCapabilities: {
17620
+ list: {},
17621
+ resume: {},
17622
+ close: {},
17623
+ delete: {},
17624
+ additionalDirectories: {}
17625
+ }
17626
+ };
17558
17627
  // src/v2/index.ts
17559
17628
  var exports_v2 = {};
17560
17629
  __export(exports_v2, {
@@ -17620,6 +17689,7 @@ export {
17620
17689
  createAcpV2App,
17621
17690
  createDualAcpApp,
17622
17691
  createMapperState,
17692
+ debugLog,
17623
17693
  decodeFsPath,
17624
17694
  deleteOnCloseEnabled,
17625
17695
  discoverAgyCatalog,
@@ -17633,6 +17703,7 @@ export {
17633
17703
  getCachedDiscovery,
17634
17704
  guessToolKind,
17635
17705
  hideConsoleWindow,
17706
+ installFileLogging,
17636
17707
  isPathAllowed,
17637
17708
  killAgyChild,
17638
17709
  mapAgyEvent,
@@ -0,0 +1,2 @@
1
+ export declare function debugLog(msg: string): void;
2
+ export declare function installFileLogging(): string | null;
@@ -7,7 +7,9 @@ export declare class AgyAcpV1Service {
7
7
  resumeSession(params: any): Promise<any>;
8
8
  loadSession(params: any, notifyClient: (update: any) => Promise<void> | void): Promise<any>;
9
9
  setConfigOption(params: any): Promise<any>;
10
- listSessions(params?: any): Promise<any>;
10
+ listSessions(params?: any, opts?: {
11
+ now?: number;
12
+ }): Promise<any>;
11
13
  closeSession(params: any): Promise<any>;
12
14
  deleteSession(params: any): Promise<any>;
13
15
  cancelSession(params: any): void;
@@ -6,7 +6,9 @@ export declare class AgyAcpV2Service {
6
6
  newSession(params: any): Promise<any>;
7
7
  resumeSession(params: any, notifyClient?: (update: any) => Promise<void> | void): Promise<any>;
8
8
  setConfigOption(params: any): Promise<any>;
9
- listSessions(params?: any): Promise<any>;
9
+ listSessions(params?: any, opts?: {
10
+ now?: number;
11
+ }): Promise<any>;
10
12
  closeSession(params: any): Promise<any>;
11
13
  deleteSession(params: any): Promise<any>;
12
14
  cancelSession(params: any): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yitom/agy-acp-map",
3
- "version": "0.1.4",
3
+ "version": "0.1.7",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -28,6 +28,7 @@
28
28
  "build": "bun run build:js && bun run build:types && bun run build:headless",
29
29
  "build:headless": "bun scripts/build-headless.ts",
30
30
  "build:js": "bun build src/sdk-server.ts --target node --outfile dist/bin.js && bun build src/index.ts --target node --outfile dist/index.js",
31
+ "build:exe": "bun build src/sdk-server.ts --compile --target=bun-windows-x64 --outfile dist/agy-acp-win-x64.exe",
31
32
  "build:types": "bunx tsc -p tsconfig.build.json",
32
33
  "release:patch": "bun scripts/release.ts",
33
34
  "release:publish": "bun scripts/release.ts --publish",