@ganglion/xacpx 0.20.0 → 0.20.2

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.
@@ -2102,9 +2102,9 @@ function decodeTarget(value) {
2102
2102
  return null;
2103
2103
  if (parseCanonicalFileTime(item.creationDate) === null)
2104
2104
  return null;
2105
- if (item.commandLine !== undefined && typeof item.commandLine !== "string")
2105
+ if (item.commandLine !== undefined && item.commandLine !== null && typeof item.commandLine !== "string")
2106
2106
  return null;
2107
- if (item.executablePath !== undefined && typeof item.executablePath !== "string")
2107
+ if (item.executablePath !== undefined && item.executablePath !== null && typeof item.executablePath !== "string")
2108
2108
  return null;
2109
2109
  return {
2110
2110
  pid: Number(item.pid),
@@ -2228,8 +2228,9 @@ async function terminateWindowsResidual(target, options = {}) {
2228
2228
  }
2229
2229
  async function runPowerShellWorker(request, deadlineMs) {
2230
2230
  return await new Promise((resolve2, reject) => {
2231
- const child = spawn("powershell.exe", ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", "-"], {
2232
- stdio: ["pipe", "pipe", "pipe"],
2231
+ const encodedScript = Buffer.from(WINDOWS_TREE_WORKER_SCRIPT, "utf16le").toString("base64");
2232
+ const child = spawn("powershell.exe", ["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", encodedScript], {
2233
+ stdio: ["ignore", "pipe", "pipe"],
2233
2234
  windowsHide: true,
2234
2235
  env: {
2235
2236
  ...process.env,
@@ -2266,7 +2267,6 @@ async function runPowerShellWorker(request, deadlineMs) {
2266
2267
  });
2267
2268
  child.once("error", (error) => finish(error));
2268
2269
  child.once("close", (code) => code === 0 ? finish() : finish(new Error(`Windows process worker failed (${code}): ${stderr}`)));
2269
- child.stdin.end(WINDOWS_TREE_WORKER_SCRIPT);
2270
2270
  });
2271
2271
  }
2272
2272
  var UUID, OUTCOMES, WINDOWS_TREE_WORKER_SCRIPT;
@@ -2375,7 +2375,14 @@ if($request.action -eq 'terminate-one-cim'){
2375
2375
  if(!$check.ok){Write-Output (@{outcome=$check.status} | ConvertTo-Json -Compress);exit 0}
2376
2376
  try {
2377
2377
  if(![XacpxNativeProcess]::Alive($check.handle)){$status='already-exited'}
2378
- elseif(![XacpxNativeProcess]::Kill($check.handle)){$status=if([XacpxNativeProcess]::LastError() -eq 5){'access-denied'}else{'query-failed'}}
2378
+ elseif(![XacpxNativeProcess]::Kill($check.handle)){
2379
+ $code=[XacpxNativeProcess]::LastError()
2380
+ # Ancestor kills can cascade through Job objects (libuv/Node children
2381
+ # die with their parent), so a failed kill on an already-dying process
2382
+ # is a confirmed exit, not a denial.
2383
+ if([XacpxNativeProcess]::WaitDead($check.handle)){$status='already-exited'}
2384
+ else{$status=if($code -eq 5){'access-denied'}else{'query-failed'}}
2385
+ }
2379
2386
  else{$status=if([XacpxNativeProcess]::WaitDead($check.handle)){'killed'}else{'kill-requested-unconfirmed'}}
2380
2387
  Write-Output (@{outcome=$status} | ConvertTo-Json -Compress);exit 0
2381
2388
  } finally {[XacpxNativeProcess]::Close($check.handle)}
@@ -2430,7 +2437,15 @@ try {
2430
2437
  foreach($node in $nodes){
2431
2438
  $h=$handles[$node.pid]
2432
2439
  if(![XacpxNativeProcess]::Alive($h)){[void]$outcomes.Add((Outcome $node 'already-exited'));continue}
2433
- if(![XacpxNativeProcess]::Kill($h)){$status=if([XacpxNativeProcess]::LastError() -eq 5){'access-denied'}else{'query-failed'};[void]$outcomes.Add((Outcome $node $status));continue}
2440
+ if(![XacpxNativeProcess]::Kill($h)){
2441
+ $code=[XacpxNativeProcess]::LastError()
2442
+ # Ancestor kills can cascade through Job objects (libuv/Node children
2443
+ # die with their parent), so a failed kill on an already-dying process
2444
+ # is a confirmed exit, not a denial.
2445
+ if([XacpxNativeProcess]::WaitDead($h)){$status='already-exited'}
2446
+ else{$status=if($code -eq 5){'access-denied'}else{'query-failed'}}
2447
+ [void]$outcomes.Add((Outcome $node $status));continue
2448
+ }
2434
2449
  $status=if([XacpxNativeProcess]::WaitDead($h)){'killed'}else{'kill-requested-unconfirmed'}
2435
2450
  [void]$outcomes.Add((Outcome $node $status))
2436
2451
  }
@@ -3245,6 +3260,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
3245
3260
  emittedToolCallIds: new Set,
3246
3261
  positionedToolCallIds: new Set,
3247
3262
  toolCalls: new Map,
3263
+ cursorPlanEntries: new Map,
3248
3264
  toolEventMode,
3249
3265
  driver,
3250
3266
  rawStream,
@@ -3303,13 +3319,17 @@ function parseStreamingChunks(state, line) {
3303
3319
  }
3304
3320
  const wantsStructured = state.toolEventMode === "structured" || state.toolEventMode === "both";
3305
3321
  const wantsText = (state.toolEventMode === "text" || state.toolEventMode === "both") && state.formatToolCalls;
3306
- if (wantsStructured && state.onToolEvent) {
3307
- const merged = update.toolCallId ? mergeToolCallUpdate(state, update.toolCallId, update) : update;
3322
+ const merged = update.toolCallId && (state.onToolEvent || state.onPlan) ? mergeToolCallUpdate(state, update.toolCallId, update) : update;
3323
+ const cursorPlan = normalizeCursorPlanUpdate(state, merged);
3324
+ const consumedAsPlan = cursorPlan !== undefined && state.onPlan !== undefined;
3325
+ if (consumedAsPlan) {
3326
+ state.onPlan?.(cursorPlan);
3327
+ } else if (wantsStructured && state.onToolEvent) {
3308
3328
  const toolEvent = buildToolUseEvent(merged, state.driver);
3309
3329
  if (toolEvent)
3310
3330
  state.onToolEvent(toolEvent);
3311
3331
  }
3312
- if (wantsText) {
3332
+ if (wantsText && !consumedAsPlan) {
3313
3333
  const formatted = formatToolCallEvent(update, update.sessionUpdate);
3314
3334
  if (formatted) {
3315
3335
  const toolCallId = update.toolCallId;
@@ -3325,7 +3345,7 @@ function parseStreamingChunks(state, line) {
3325
3345
  }
3326
3346
  if (update.sessionUpdate === "plan") {
3327
3347
  const entries = Array.isArray(update.entries) ? update.entries.filter((x) => !!x && typeof x === "object" && typeof x.content === "string" && typeof x.status === "string") : [];
3328
- if (entries.length > 0)
3348
+ if (Array.isArray(update.entries))
3329
3349
  state.onPlan?.(entries);
3330
3350
  return;
3331
3351
  }
@@ -3437,7 +3457,7 @@ function isEmptyToolField(v) {
3437
3457
  function mergeToolCallUpdate(state, toolCallId, update) {
3438
3458
  const prev = state.toolCalls.get(toolCallId) ?? { toolCallId };
3439
3459
  const merged = { ...prev };
3440
- for (const key of ["kind", "title", "rawInput", "content", "rawOutput", "locations", "status"]) {
3460
+ for (const key of ["kind", "title", "toolCallId", "parentToolCallId", "rawInput", "content", "rawOutput", "locations", "status"]) {
3441
3461
  const next = update[key];
3442
3462
  if (!isEmptyToolField(next))
3443
3463
  merged[key] = next;
@@ -3469,19 +3489,7 @@ function buildToolUseEvent(update, driver) {
3469
3489
  const toolCallId = update.toolCallId;
3470
3490
  if (!toolCallId)
3471
3491
  return null;
3472
- const kindRaw = update.kind ?? "";
3473
- const kind = (() => {
3474
- switch (kindRaw) {
3475
- case "read":
3476
- case "search":
3477
- case "execute":
3478
- case "edit":
3479
- case "think":
3480
- return kindRaw;
3481
- default:
3482
- return "other";
3483
- }
3484
- })();
3492
+ const kind = normalizeToolKind(update, driver);
3485
3493
  const title = (update.title ?? "").trim();
3486
3494
  const toolName = title || "Tool";
3487
3495
  const summaryRaw = summarizeToolInput(update.rawInput, title) || summarizeToolInput(update.rawOutput, title);
@@ -3497,8 +3505,8 @@ function buildToolUseEvent(update, driver) {
3497
3505
  const content = update.content;
3498
3506
  const rawOutput = update.rawOutput ?? claudeToolResponse;
3499
3507
  const locations = update.locations;
3500
- const parentToolCallId = claudeMeta?.parentToolUseId?.trim();
3501
- const isSubagent = claudeMeta?.toolName === "Agent" || driver === "qoder" && update._meta?.qoder?.toolName === "Agent" || driver === "kimi" && isKimiSubagentInput(rawInput) || driver === "codex" && isCodexSubagentMeta(update._meta?.codex?.subagent);
3508
+ const parentToolCallId = claudeMeta?.parentToolUseId?.trim() || update.parentToolCallId?.trim();
3509
+ const isSubagent = claudeMeta?.toolName === "Agent" || driver === "qoder" && update._meta?.qoder?.toolName === "Agent" || driver === "kimi" && isKimiSubagentInput(rawInput) || driver === "codex" && isCodexSubagentMeta(update._meta?.codex?.subagent) || driver === "cursor" && isCursorSubagentInput(rawInput, title);
3502
3510
  return {
3503
3511
  toolCallId,
3504
3512
  ...parentToolCallId ? { parentToolCallId } : {},
@@ -3513,6 +3521,147 @@ function buildToolUseEvent(update, driver) {
3513
3521
  status
3514
3522
  };
3515
3523
  }
3524
+ function normalizeCursorPlanUpdate(state, update) {
3525
+ if (state.driver !== "cursor")
3526
+ return;
3527
+ const title = normalizeCursorToolName(update.title);
3528
+ if (!CURSOR_PLAN_TOOL_NAMES.has(title))
3529
+ return;
3530
+ const input = cursorToolInput(update.rawInput);
3531
+ if (!Array.isArray(input?.todos))
3532
+ return;
3533
+ const merge = input.merge === true;
3534
+ if (input.todos.length === 0) {
3535
+ state.cursorPlanEntries.clear();
3536
+ return [];
3537
+ }
3538
+ const patches = input.todos.map((todo, index) => parseCursorPlanTodo(todo, index)).filter((todo) => todo !== undefined);
3539
+ if (patches.length === 0)
3540
+ return;
3541
+ if (!merge)
3542
+ state.cursorPlanEntries.clear();
3543
+ for (const patch of patches) {
3544
+ const previous = state.cursorPlanEntries.get(patch.key);
3545
+ const content = patch.content ?? previous?.content;
3546
+ if (!content)
3547
+ continue;
3548
+ const status = patch.status ?? previous?.status ?? "pending";
3549
+ const priority = patch.priority ?? previous?.priority;
3550
+ state.cursorPlanEntries.set(patch.key, {
3551
+ content,
3552
+ status,
3553
+ ...priority ? { priority } : {}
3554
+ });
3555
+ }
3556
+ return [...state.cursorPlanEntries.values()];
3557
+ }
3558
+ function parseCursorPlanTodo(value, index) {
3559
+ if (!isRecord2(value))
3560
+ return;
3561
+ const id = readFirstString(value, ["id", "todoId", "taskId"]);
3562
+ const content = readFirstString(value, ["content", "task", "description", "title", "text"]);
3563
+ const key = id ? `id:${id}` : content ? `content:${content}` : `index:${index}`;
3564
+ const status = normalizeCursorPlanStatus(value.status ?? value.state);
3565
+ const priority = normalizePlanPriority(value.priority);
3566
+ if (!content && !status && !priority && !id)
3567
+ return;
3568
+ return {
3569
+ key,
3570
+ ...content ? { content } : {},
3571
+ ...status ? { status } : {},
3572
+ ...priority ? { priority } : {}
3573
+ };
3574
+ }
3575
+ function normalizeCursorPlanStatus(value) {
3576
+ if (typeof value !== "string")
3577
+ return;
3578
+ const normalized = value.trim().toLowerCase().replace(/[\s-]+/g, "_").replace(/^todo_status_/, "");
3579
+ switch (normalized) {
3580
+ case "pending":
3581
+ case "todo":
3582
+ case "not_started":
3583
+ case "notstarted":
3584
+ case "incomplete":
3585
+ return "pending";
3586
+ case "in_progress":
3587
+ case "inprogress":
3588
+ case "active":
3589
+ case "working":
3590
+ return "in_progress";
3591
+ case "completed":
3592
+ case "complete":
3593
+ case "done":
3594
+ case "finished":
3595
+ return "completed";
3596
+ default:
3597
+ return;
3598
+ }
3599
+ }
3600
+ function normalizePlanPriority(value) {
3601
+ if (value !== "high" && value !== "medium" && value !== "low")
3602
+ return;
3603
+ return value;
3604
+ }
3605
+ function cursorToolInput(rawInput) {
3606
+ if (!isRecord2(rawInput))
3607
+ return;
3608
+ if (isRecord2(rawInput.args))
3609
+ return rawInput.args;
3610
+ return rawInput;
3611
+ }
3612
+ function normalizeCursorToolName(title) {
3613
+ return (title ?? "").trim().toLowerCase().replace(/[\s_-]+/g, "");
3614
+ }
3615
+ function normalizeToolKind(update, driver) {
3616
+ const kindRaw = update?.kind?.trim().toLowerCase() ?? "";
3617
+ switch (kindRaw) {
3618
+ case "read":
3619
+ case "search":
3620
+ case "execute":
3621
+ case "edit":
3622
+ case "think":
3623
+ return kindRaw;
3624
+ }
3625
+ if (driver !== "cursor")
3626
+ return "other";
3627
+ switch (normalizeCursorToolName(update?.title)) {
3628
+ case "read":
3629
+ case "readfile":
3630
+ return "read";
3631
+ case "grep":
3632
+ case "glob":
3633
+ case "search":
3634
+ return "search";
3635
+ case "shell":
3636
+ case "bash":
3637
+ case "terminal":
3638
+ return "execute";
3639
+ case "strreplace":
3640
+ case "replace":
3641
+ case "edit":
3642
+ case "write":
3643
+ case "delete":
3644
+ return "edit";
3645
+ case "task":
3646
+ case "delegate":
3647
+ case "runsubagent":
3648
+ case "switchmode":
3649
+ case "todowrite":
3650
+ case "createplan":
3651
+ case "updateplan":
3652
+ case "plan":
3653
+ return "think";
3654
+ default:
3655
+ return "other";
3656
+ }
3657
+ }
3658
+ function isCursorSubagentInput(rawInput, title) {
3659
+ const normalizedTitle = normalizeCursorToolName(title);
3660
+ if (!CURSOR_SUBAGENT_TOOL_NAMES.has(normalizedTitle))
3661
+ return false;
3662
+ const input = cursorToolInput(rawInput);
3663
+ return input !== undefined && readFirstString(input, ["subagent_type", "subagentType", "agent", "agentType", "prompt", "instructions"]) !== undefined;
3664
+ }
3516
3665
  function isKimiSubagentInput(rawInput) {
3517
3666
  return isRecord2(rawInput) && readFirstString(rawInput, ["prompt"]) !== undefined && readFirstString(rawInput, ["subagent_type", "subagentType"]) !== undefined;
3518
3667
  }
@@ -3527,6 +3676,12 @@ function summarizeToolInput(rawInput, title = "") {
3527
3676
  }
3528
3677
  if (!isRecord2(rawInput))
3529
3678
  return;
3679
+ const nestedInput = cursorToolInput(rawInput);
3680
+ if (nestedInput !== rawInput) {
3681
+ const nestedSummary = summarizeToolInput(nestedInput, title);
3682
+ if (nestedSummary)
3683
+ return nestedSummary;
3684
+ }
3530
3685
  const taskSummary = summarizeTaskInput(rawInput, title);
3531
3686
  if (taskSummary)
3532
3687
  return taskSummary;
@@ -3547,6 +3702,16 @@ function summarizeToolInput(rawInput, title = "") {
3547
3702
  return parts.join(" ");
3548
3703
  }
3549
3704
  }
3705
+ const globPattern = readFirstString(rawInput, ["glob_pattern"]);
3706
+ if (globPattern) {
3707
+ const targetDirectory = readFirstString(rawInput, ["target_directory"]);
3708
+ return targetDirectory ? `${globPattern} in ${targetDirectory}` : globPattern;
3709
+ }
3710
+ const mode = readFirstString(rawInput, ["target_mode_id", "mode_id"]);
3711
+ const explanation = readFirstString(rawInput, ["explanation"]);
3712
+ if (mode || explanation) {
3713
+ return mode && explanation ? `${mode}: ${explanation}` : mode ?? explanation;
3714
+ }
3550
3715
  return readFirstString(rawInput, [
3551
3716
  "path",
3552
3717
  "file",
@@ -3560,6 +3725,7 @@ function summarizeToolInput(rawInput, title = "") {
3560
3725
  "pattern",
3561
3726
  "text",
3562
3727
  "search",
3728
+ "working_directory",
3563
3729
  "name",
3564
3730
  "description"
3565
3731
  ]);
@@ -3666,7 +3832,7 @@ function isGenericToolTitle(kind, title) {
3666
3832
  }
3667
3833
  return false;
3668
3834
  }
3669
- var SENTENCE_TERMINAL_AT_END, PARAGRAPH_BOUNDARY_AT_END, PARAGRAPH_BOUNDARY_AT_START, LINE_BREAK_AT_END, LINE_BREAK_AT_START, PARTIAL_CRLF_PARAGRAPH_BOUNDARY_AT_END, USAGE_BREAKDOWN_FIELDS;
3835
+ var SENTENCE_TERMINAL_AT_END, PARAGRAPH_BOUNDARY_AT_END, PARAGRAPH_BOUNDARY_AT_START, LINE_BREAK_AT_END, LINE_BREAK_AT_START, PARTIAL_CRLF_PARAGRAPH_BOUNDARY_AT_END, CURSOR_PLAN_TOOL_NAMES, CURSOR_SUBAGENT_TOOL_NAMES, USAGE_BREAKDOWN_FIELDS;
3670
3836
  var init_streaming_prompt = __esm(() => {
3671
3837
  init_background_followup();
3672
3838
  init_tool_kind_emoji();
@@ -3676,6 +3842,8 @@ var init_streaming_prompt = __esm(() => {
3676
3842
  LINE_BREAK_AT_END = /\r?\n[\t ]*$/;
3677
3843
  LINE_BREAK_AT_START = /^[\t ]*\r?\n/;
3678
3844
  PARTIAL_CRLF_PARAGRAPH_BOUNDARY_AT_END = /\r?\n[\t ]*\r$/;
3845
+ CURSOR_PLAN_TOOL_NAMES = new Set(["todowrite", "createplan", "updateplan", "plan"]);
3846
+ CURSOR_SUBAGENT_TOOL_NAMES = new Set(["task", "delegate", "runsubagent", "subagent"]);
3679
3847
  USAGE_BREAKDOWN_FIELDS = [
3680
3848
  ["inputTokens", ["inputTokens", "input_tokens"]],
3681
3849
  ["outputTokens", ["outputTokens", "output_tokens"]],
@@ -7210,13 +7378,14 @@ async function verifyAcpInitialize(command, args, options = {}) {
7210
7378
  let stdoutBuffer = "";
7211
7379
  let settled = false;
7212
7380
  let timer;
7213
- let windowsTarget;
7214
- if (process.platform === "win32" && child.pid) {
7381
+ const windowsProbe = (async () => {
7382
+ if (process.platform !== "win32" || !child.pid)
7383
+ return;
7215
7384
  const identity = await probeWindowsProcessIdentity(child.pid);
7216
- if (identity.status === "found") {
7217
- windowsTarget = { pid: child.pid, creationDate: identity.identity.creationDate, executablePath: identity.identity.executablePath };
7218
- }
7219
- }
7385
+ if (identity.status !== "found")
7386
+ return;
7387
+ return { pid: child.pid, creationDate: identity.identity.creationDate, executablePath: identity.identity.executablePath };
7388
+ })();
7220
7389
  try {
7221
7390
  await new Promise((resolve2, reject) => {
7222
7391
  const finish = (error) => {
@@ -7301,6 +7470,7 @@ async function verifyAcpInitialize(command, args, options = {}) {
7301
7470
  child.stdin.destroy();
7302
7471
  child.stdout.destroy();
7303
7472
  child.stderr.destroy();
7473
+ const windowsTarget = await windowsProbe;
7304
7474
  if (process.platform !== "win32" || windowsTarget) {
7305
7475
  await terminateProcessTree(windowsTarget ?? child.pid ?? 0, { detachedProcessGroup: detached });
7306
7476
  }
package/dist/cli.js CHANGED
@@ -4816,9 +4816,9 @@ function decodeTarget(value) {
4816
4816
  return null;
4817
4817
  if (parseCanonicalFileTime(item.creationDate) === null)
4818
4818
  return null;
4819
- if (item.commandLine !== undefined && typeof item.commandLine !== "string")
4819
+ if (item.commandLine !== undefined && item.commandLine !== null && typeof item.commandLine !== "string")
4820
4820
  return null;
4821
- if (item.executablePath !== undefined && typeof item.executablePath !== "string")
4821
+ if (item.executablePath !== undefined && item.executablePath !== null && typeof item.executablePath !== "string")
4822
4822
  return null;
4823
4823
  return {
4824
4824
  pid: Number(item.pid),
@@ -4942,8 +4942,9 @@ async function terminateWindowsResidual(target, options = {}) {
4942
4942
  }
4943
4943
  async function runPowerShellWorker(request, deadlineMs) {
4944
4944
  return await new Promise((resolve2, reject) => {
4945
- const child = spawn2("powershell.exe", ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", "-"], {
4946
- stdio: ["pipe", "pipe", "pipe"],
4945
+ const encodedScript = Buffer.from(WINDOWS_TREE_WORKER_SCRIPT, "utf16le").toString("base64");
4946
+ const child = spawn2("powershell.exe", ["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", encodedScript], {
4947
+ stdio: ["ignore", "pipe", "pipe"],
4947
4948
  windowsHide: true,
4948
4949
  env: {
4949
4950
  ...process.env,
@@ -4980,7 +4981,6 @@ async function runPowerShellWorker(request, deadlineMs) {
4980
4981
  });
4981
4982
  child.once("error", (error) => finish(error));
4982
4983
  child.once("close", (code) => code === 0 ? finish() : finish(new Error(`Windows process worker failed (${code}): ${stderr}`)));
4983
- child.stdin.end(WINDOWS_TREE_WORKER_SCRIPT);
4984
4984
  });
4985
4985
  }
4986
4986
  var UUID, OUTCOMES, WINDOWS_TREE_WORKER_SCRIPT;
@@ -5089,7 +5089,14 @@ if($request.action -eq 'terminate-one-cim'){
5089
5089
  if(!$check.ok){Write-Output (@{outcome=$check.status} | ConvertTo-Json -Compress);exit 0}
5090
5090
  try {
5091
5091
  if(![XacpxNativeProcess]::Alive($check.handle)){$status='already-exited'}
5092
- elseif(![XacpxNativeProcess]::Kill($check.handle)){$status=if([XacpxNativeProcess]::LastError() -eq 5){'access-denied'}else{'query-failed'}}
5092
+ elseif(![XacpxNativeProcess]::Kill($check.handle)){
5093
+ $code=[XacpxNativeProcess]::LastError()
5094
+ # Ancestor kills can cascade through Job objects (libuv/Node children
5095
+ # die with their parent), so a failed kill on an already-dying process
5096
+ # is a confirmed exit, not a denial.
5097
+ if([XacpxNativeProcess]::WaitDead($check.handle)){$status='already-exited'}
5098
+ else{$status=if($code -eq 5){'access-denied'}else{'query-failed'}}
5099
+ }
5093
5100
  else{$status=if([XacpxNativeProcess]::WaitDead($check.handle)){'killed'}else{'kill-requested-unconfirmed'}}
5094
5101
  Write-Output (@{outcome=$status} | ConvertTo-Json -Compress);exit 0
5095
5102
  } finally {[XacpxNativeProcess]::Close($check.handle)}
@@ -5144,7 +5151,15 @@ try {
5144
5151
  foreach($node in $nodes){
5145
5152
  $h=$handles[$node.pid]
5146
5153
  if(![XacpxNativeProcess]::Alive($h)){[void]$outcomes.Add((Outcome $node 'already-exited'));continue}
5147
- if(![XacpxNativeProcess]::Kill($h)){$status=if([XacpxNativeProcess]::LastError() -eq 5){'access-denied'}else{'query-failed'};[void]$outcomes.Add((Outcome $node $status));continue}
5154
+ if(![XacpxNativeProcess]::Kill($h)){
5155
+ $code=[XacpxNativeProcess]::LastError()
5156
+ # Ancestor kills can cascade through Job objects (libuv/Node children
5157
+ # die with their parent), so a failed kill on an already-dying process
5158
+ # is a confirmed exit, not a denial.
5159
+ if([XacpxNativeProcess]::WaitDead($h)){$status='already-exited'}
5160
+ else{$status=if($code -eq 5){'access-denied'}else{'query-failed'}}
5161
+ [void]$outcomes.Add((Outcome $node $status));continue
5162
+ }
5148
5163
  $status=if([XacpxNativeProcess]::WaitDead($h)){'killed'}else{'kill-requested-unconfirmed'}
5149
5164
  [void]$outcomes.Add((Outcome $node $status))
5150
5165
  }
@@ -5222,13 +5237,14 @@ async function verifyAcpInitialize(command, args, options = {}) {
5222
5237
  let stdoutBuffer = "";
5223
5238
  let settled = false;
5224
5239
  let timer;
5225
- let windowsTarget;
5226
- if (process.platform === "win32" && child.pid) {
5240
+ const windowsProbe = (async () => {
5241
+ if (process.platform !== "win32" || !child.pid)
5242
+ return;
5227
5243
  const identity = await probeWindowsProcessIdentity(child.pid);
5228
- if (identity.status === "found") {
5229
- windowsTarget = { pid: child.pid, creationDate: identity.identity.creationDate, executablePath: identity.identity.executablePath };
5230
- }
5231
- }
5244
+ if (identity.status !== "found")
5245
+ return;
5246
+ return { pid: child.pid, creationDate: identity.identity.creationDate, executablePath: identity.identity.executablePath };
5247
+ })();
5232
5248
  try {
5233
5249
  await new Promise((resolve2, reject) => {
5234
5250
  const finish = (error) => {
@@ -5313,6 +5329,7 @@ async function verifyAcpInitialize(command, args, options = {}) {
5313
5329
  child.stdin.destroy();
5314
5330
  child.stdout.destroy();
5315
5331
  child.stderr.destroy();
5332
+ const windowsTarget = await windowsProbe;
5316
5333
  if (process.platform !== "win32" || windowsTarget) {
5317
5334
  await terminateProcessTree(windowsTarget ?? child.pid ?? 0, { detachedProcessGroup: detached });
5318
5335
  }
@@ -52636,6 +52653,7 @@ function createStreamingPromptState(formatToolCalls = false, options) {
52636
52653
  emittedToolCallIds: new Set,
52637
52654
  positionedToolCallIds: new Set,
52638
52655
  toolCalls: new Map,
52656
+ cursorPlanEntries: new Map,
52639
52657
  toolEventMode,
52640
52658
  driver,
52641
52659
  rawStream,
@@ -52694,13 +52712,17 @@ function parseStreamingChunks(state, line) {
52694
52712
  }
52695
52713
  const wantsStructured = state.toolEventMode === "structured" || state.toolEventMode === "both";
52696
52714
  const wantsText = (state.toolEventMode === "text" || state.toolEventMode === "both") && state.formatToolCalls;
52697
- if (wantsStructured && state.onToolEvent) {
52698
- const merged = update.toolCallId ? mergeToolCallUpdate(state, update.toolCallId, update) : update;
52715
+ const merged = update.toolCallId && (state.onToolEvent || state.onPlan) ? mergeToolCallUpdate(state, update.toolCallId, update) : update;
52716
+ const cursorPlan = normalizeCursorPlanUpdate(state, merged);
52717
+ const consumedAsPlan = cursorPlan !== undefined && state.onPlan !== undefined;
52718
+ if (consumedAsPlan) {
52719
+ state.onPlan?.(cursorPlan);
52720
+ } else if (wantsStructured && state.onToolEvent) {
52699
52721
  const toolEvent = buildToolUseEvent(merged, state.driver);
52700
52722
  if (toolEvent)
52701
52723
  state.onToolEvent(toolEvent);
52702
52724
  }
52703
- if (wantsText) {
52725
+ if (wantsText && !consumedAsPlan) {
52704
52726
  const formatted = formatToolCallEvent(update, update.sessionUpdate);
52705
52727
  if (formatted) {
52706
52728
  const toolCallId = update.toolCallId;
@@ -52716,7 +52738,7 @@ function parseStreamingChunks(state, line) {
52716
52738
  }
52717
52739
  if (update.sessionUpdate === "plan") {
52718
52740
  const entries = Array.isArray(update.entries) ? update.entries.filter((x) => !!x && typeof x === "object" && typeof x.content === "string" && typeof x.status === "string") : [];
52719
- if (entries.length > 0)
52741
+ if (Array.isArray(update.entries))
52720
52742
  state.onPlan?.(entries);
52721
52743
  return;
52722
52744
  }
@@ -52828,7 +52850,7 @@ function isEmptyToolField(v) {
52828
52850
  function mergeToolCallUpdate(state, toolCallId, update) {
52829
52851
  const prev = state.toolCalls.get(toolCallId) ?? { toolCallId };
52830
52852
  const merged = { ...prev };
52831
- for (const key of ["kind", "title", "rawInput", "content", "rawOutput", "locations", "status"]) {
52853
+ for (const key of ["kind", "title", "toolCallId", "parentToolCallId", "rawInput", "content", "rawOutput", "locations", "status"]) {
52832
52854
  const next = update[key];
52833
52855
  if (!isEmptyToolField(next))
52834
52856
  merged[key] = next;
@@ -52860,19 +52882,7 @@ function buildToolUseEvent(update, driver) {
52860
52882
  const toolCallId = update.toolCallId;
52861
52883
  if (!toolCallId)
52862
52884
  return null;
52863
- const kindRaw = update.kind ?? "";
52864
- const kind = (() => {
52865
- switch (kindRaw) {
52866
- case "read":
52867
- case "search":
52868
- case "execute":
52869
- case "edit":
52870
- case "think":
52871
- return kindRaw;
52872
- default:
52873
- return "other";
52874
- }
52875
- })();
52885
+ const kind = normalizeToolKind(update, driver);
52876
52886
  const title = (update.title ?? "").trim();
52877
52887
  const toolName = title || "Tool";
52878
52888
  const summaryRaw = summarizeToolInput(update.rawInput, title) || summarizeToolInput(update.rawOutput, title);
@@ -52888,8 +52898,8 @@ function buildToolUseEvent(update, driver) {
52888
52898
  const content = update.content;
52889
52899
  const rawOutput = update.rawOutput ?? claudeToolResponse;
52890
52900
  const locations = update.locations;
52891
- const parentToolCallId = claudeMeta?.parentToolUseId?.trim();
52892
- const isSubagent = claudeMeta?.toolName === "Agent" || driver === "qoder" && update._meta?.qoder?.toolName === "Agent" || driver === "kimi" && isKimiSubagentInput(rawInput) || driver === "codex" && isCodexSubagentMeta(update._meta?.codex?.subagent);
52901
+ const parentToolCallId = claudeMeta?.parentToolUseId?.trim() || update.parentToolCallId?.trim();
52902
+ const isSubagent = claudeMeta?.toolName === "Agent" || driver === "qoder" && update._meta?.qoder?.toolName === "Agent" || driver === "kimi" && isKimiSubagentInput(rawInput) || driver === "codex" && isCodexSubagentMeta(update._meta?.codex?.subagent) || driver === "cursor" && isCursorSubagentInput(rawInput, title);
52893
52903
  return {
52894
52904
  toolCallId,
52895
52905
  ...parentToolCallId ? { parentToolCallId } : {},
@@ -52904,6 +52914,147 @@ function buildToolUseEvent(update, driver) {
52904
52914
  status
52905
52915
  };
52906
52916
  }
52917
+ function normalizeCursorPlanUpdate(state, update) {
52918
+ if (state.driver !== "cursor")
52919
+ return;
52920
+ const title = normalizeCursorToolName(update.title);
52921
+ if (!CURSOR_PLAN_TOOL_NAMES.has(title))
52922
+ return;
52923
+ const input = cursorToolInput(update.rawInput);
52924
+ if (!Array.isArray(input?.todos))
52925
+ return;
52926
+ const merge2 = input.merge === true;
52927
+ if (input.todos.length === 0) {
52928
+ state.cursorPlanEntries.clear();
52929
+ return [];
52930
+ }
52931
+ const patches = input.todos.map((todo, index) => parseCursorPlanTodo(todo, index)).filter((todo) => todo !== undefined);
52932
+ if (patches.length === 0)
52933
+ return;
52934
+ if (!merge2)
52935
+ state.cursorPlanEntries.clear();
52936
+ for (const patch of patches) {
52937
+ const previous = state.cursorPlanEntries.get(patch.key);
52938
+ const content = patch.content ?? previous?.content;
52939
+ if (!content)
52940
+ continue;
52941
+ const status = patch.status ?? previous?.status ?? "pending";
52942
+ const priority = patch.priority ?? previous?.priority;
52943
+ state.cursorPlanEntries.set(patch.key, {
52944
+ content,
52945
+ status,
52946
+ ...priority ? { priority } : {}
52947
+ });
52948
+ }
52949
+ return [...state.cursorPlanEntries.values()];
52950
+ }
52951
+ function parseCursorPlanTodo(value, index) {
52952
+ if (!isRecord4(value))
52953
+ return;
52954
+ const id = readFirstString(value, ["id", "todoId", "taskId"]);
52955
+ const content = readFirstString(value, ["content", "task", "description", "title", "text"]);
52956
+ const key = id ? `id:${id}` : content ? `content:${content}` : `index:${index}`;
52957
+ const status = normalizeCursorPlanStatus(value.status ?? value.state);
52958
+ const priority = normalizePlanPriority(value.priority);
52959
+ if (!content && !status && !priority && !id)
52960
+ return;
52961
+ return {
52962
+ key,
52963
+ ...content ? { content } : {},
52964
+ ...status ? { status } : {},
52965
+ ...priority ? { priority } : {}
52966
+ };
52967
+ }
52968
+ function normalizeCursorPlanStatus(value) {
52969
+ if (typeof value !== "string")
52970
+ return;
52971
+ const normalized = value.trim().toLowerCase().replace(/[\s-]+/g, "_").replace(/^todo_status_/, "");
52972
+ switch (normalized) {
52973
+ case "pending":
52974
+ case "todo":
52975
+ case "not_started":
52976
+ case "notstarted":
52977
+ case "incomplete":
52978
+ return "pending";
52979
+ case "in_progress":
52980
+ case "inprogress":
52981
+ case "active":
52982
+ case "working":
52983
+ return "in_progress";
52984
+ case "completed":
52985
+ case "complete":
52986
+ case "done":
52987
+ case "finished":
52988
+ return "completed";
52989
+ default:
52990
+ return;
52991
+ }
52992
+ }
52993
+ function normalizePlanPriority(value) {
52994
+ if (value !== "high" && value !== "medium" && value !== "low")
52995
+ return;
52996
+ return value;
52997
+ }
52998
+ function cursorToolInput(rawInput) {
52999
+ if (!isRecord4(rawInput))
53000
+ return;
53001
+ if (isRecord4(rawInput.args))
53002
+ return rawInput.args;
53003
+ return rawInput;
53004
+ }
53005
+ function normalizeCursorToolName(title) {
53006
+ return (title ?? "").trim().toLowerCase().replace(/[\s_-]+/g, "");
53007
+ }
53008
+ function normalizeToolKind(update, driver) {
53009
+ const kindRaw = update?.kind?.trim().toLowerCase() ?? "";
53010
+ switch (kindRaw) {
53011
+ case "read":
53012
+ case "search":
53013
+ case "execute":
53014
+ case "edit":
53015
+ case "think":
53016
+ return kindRaw;
53017
+ }
53018
+ if (driver !== "cursor")
53019
+ return "other";
53020
+ switch (normalizeCursorToolName(update?.title)) {
53021
+ case "read":
53022
+ case "readfile":
53023
+ return "read";
53024
+ case "grep":
53025
+ case "glob":
53026
+ case "search":
53027
+ return "search";
53028
+ case "shell":
53029
+ case "bash":
53030
+ case "terminal":
53031
+ return "execute";
53032
+ case "strreplace":
53033
+ case "replace":
53034
+ case "edit":
53035
+ case "write":
53036
+ case "delete":
53037
+ return "edit";
53038
+ case "task":
53039
+ case "delegate":
53040
+ case "runsubagent":
53041
+ case "switchmode":
53042
+ case "todowrite":
53043
+ case "createplan":
53044
+ case "updateplan":
53045
+ case "plan":
53046
+ return "think";
53047
+ default:
53048
+ return "other";
53049
+ }
53050
+ }
53051
+ function isCursorSubagentInput(rawInput, title) {
53052
+ const normalizedTitle = normalizeCursorToolName(title);
53053
+ if (!CURSOR_SUBAGENT_TOOL_NAMES.has(normalizedTitle))
53054
+ return false;
53055
+ const input = cursorToolInput(rawInput);
53056
+ return input !== undefined && readFirstString(input, ["subagent_type", "subagentType", "agent", "agentType", "prompt", "instructions"]) !== undefined;
53057
+ }
52907
53058
  function isKimiSubagentInput(rawInput) {
52908
53059
  return isRecord4(rawInput) && readFirstString(rawInput, ["prompt"]) !== undefined && readFirstString(rawInput, ["subagent_type", "subagentType"]) !== undefined;
52909
53060
  }
@@ -52918,6 +53069,12 @@ function summarizeToolInput(rawInput, title = "") {
52918
53069
  }
52919
53070
  if (!isRecord4(rawInput))
52920
53071
  return;
53072
+ const nestedInput = cursorToolInput(rawInput);
53073
+ if (nestedInput !== rawInput) {
53074
+ const nestedSummary = summarizeToolInput(nestedInput, title);
53075
+ if (nestedSummary)
53076
+ return nestedSummary;
53077
+ }
52921
53078
  const taskSummary = summarizeTaskInput(rawInput, title);
52922
53079
  if (taskSummary)
52923
53080
  return taskSummary;
@@ -52938,6 +53095,16 @@ function summarizeToolInput(rawInput, title = "") {
52938
53095
  return parts.join(" ");
52939
53096
  }
52940
53097
  }
53098
+ const globPattern = readFirstString(rawInput, ["glob_pattern"]);
53099
+ if (globPattern) {
53100
+ const targetDirectory = readFirstString(rawInput, ["target_directory"]);
53101
+ return targetDirectory ? `${globPattern} in ${targetDirectory}` : globPattern;
53102
+ }
53103
+ const mode = readFirstString(rawInput, ["target_mode_id", "mode_id"]);
53104
+ const explanation = readFirstString(rawInput, ["explanation"]);
53105
+ if (mode || explanation) {
53106
+ return mode && explanation ? `${mode}: ${explanation}` : mode ?? explanation;
53107
+ }
52941
53108
  return readFirstString(rawInput, [
52942
53109
  "path",
52943
53110
  "file",
@@ -52951,6 +53118,7 @@ function summarizeToolInput(rawInput, title = "") {
52951
53118
  "pattern",
52952
53119
  "text",
52953
53120
  "search",
53121
+ "working_directory",
52954
53122
  "name",
52955
53123
  "description"
52956
53124
  ]);
@@ -53057,7 +53225,7 @@ function isGenericToolTitle(kind, title) {
53057
53225
  }
53058
53226
  return false;
53059
53227
  }
53060
- var SENTENCE_TERMINAL_AT_END, PARAGRAPH_BOUNDARY_AT_END, PARAGRAPH_BOUNDARY_AT_START, LINE_BREAK_AT_END, LINE_BREAK_AT_START, PARTIAL_CRLF_PARAGRAPH_BOUNDARY_AT_END, USAGE_BREAKDOWN_FIELDS;
53228
+ var SENTENCE_TERMINAL_AT_END, PARAGRAPH_BOUNDARY_AT_END, PARAGRAPH_BOUNDARY_AT_START, LINE_BREAK_AT_END, LINE_BREAK_AT_START, PARTIAL_CRLF_PARAGRAPH_BOUNDARY_AT_END, CURSOR_PLAN_TOOL_NAMES, CURSOR_SUBAGENT_TOOL_NAMES, USAGE_BREAKDOWN_FIELDS;
53061
53229
  var init_streaming_prompt = __esm(() => {
53062
53230
  init_background_followup();
53063
53231
  init_tool_kind_emoji();
@@ -53067,6 +53235,8 @@ var init_streaming_prompt = __esm(() => {
53067
53235
  LINE_BREAK_AT_END = /\r?\n[\t ]*$/;
53068
53236
  LINE_BREAK_AT_START = /^[\t ]*\r?\n/;
53069
53237
  PARTIAL_CRLF_PARAGRAPH_BOUNDARY_AT_END = /\r?\n[\t ]*\r$/;
53238
+ CURSOR_PLAN_TOOL_NAMES = new Set(["todowrite", "createplan", "updateplan", "plan"]);
53239
+ CURSOR_SUBAGENT_TOOL_NAMES = new Set(["task", "delegate", "runsubagent", "subagent"]);
53070
53240
  USAGE_BREAKDOWN_FIELDS = [
53071
53241
  ["inputTokens", ["inputTokens", "input_tokens"]],
53072
53242
  ["outputTokens", ["outputTokens", "output_tokens"]],
@@ -56,3 +56,4 @@ export declare function queryWindowsProcessIdentity(pid: number, options?: Windo
56
56
  export declare function probeWindowsProcessIdentity(pid: number, options?: WindowsProcessWorkerOptions): Promise<WindowsProbeStatus>;
57
57
  export declare function snapshotWindowsProcessesByToken(token: string, options?: WindowsProcessWorkerOptions): Promise<WindowsTokenProcess[] | null>;
58
58
  export declare function terminateWindowsResidual(target: BatchTarget, options?: WindowsProcessWorkerOptions): Promise<KillOutcome>;
59
+ export declare const WINDOWS_TREE_WORKER_SCRIPT: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ganglion/xacpx",
3
- "version": "0.20.0",
3
+ "version": "0.20.2",
4
4
  "description": "随时随地通过聊天频道(微信 / 飞书 / 元宝等)远程控制 `acpx` 上的 Claude Code、Codex 等 Agents。",
5
5
  "keywords": [
6
6
  "acpx",