lody 0.50.1 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1844 -417
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import require$$3$5, { randomUUID, randomBytes, createHash as createHash$1 } from "crypto";
2
+ import require$$3$5, { randomUUID, createHash as createHash$1, randomBytes } from "crypto";
3
3
  import require$$0$5, { inspect as inspect$1, types as types$6 } from "util";
4
4
  import require$$2$6 from "url";
5
5
  import * as path$2 from "path";
@@ -56,6 +56,7 @@ import fsPromises, { stat, open } from "node:fs/promises";
56
56
  import { fileURLToPath } from "node:url";
57
57
  import require$$2$7 from "assert";
58
58
  import { performance as performance$1 } from "perf_hooks";
59
+ import { createRequire as createRequire$1 } from "node:module";
59
60
  import { Buffer as Buffer$1 } from "node:buffer";
60
61
  import { AsyncLocalStorage, AsyncResource } from "node:async_hooks";
61
62
  let program;
@@ -36821,7 +36822,7 @@ Mongoose Error Code: ${error2.code}` : ""}`
36821
36822
  return client;
36822
36823
  }
36823
36824
  const name = "lody";
36824
- const version$4 = "0.50.1";
36825
+ const version$4 = "0.51.0";
36825
36826
  const description$1 = "Lody Agent CLI tool for managing remote command execution";
36826
36827
  const type$2 = "module";
36827
36828
  const main$3 = "dist/index.js";
@@ -36865,7 +36866,7 @@ Mongoose Error Code: ${error2.code}` : ""}`
36865
36866
  };
36866
36867
  const optionalDependencies = {
36867
36868
  "acp-extension-claude": "0.31.1",
36868
- "acp-extension-codex": "0.12.5"
36869
+ "acp-extension-codex": "0.14.3"
36869
36870
  };
36870
36871
  const devDependencies = {
36871
36872
  "@agentclientprotocol/sdk": "catalog:",
@@ -59959,9 +59960,6 @@ ${fromBody}`;
59959
59960
  function ora(options) {
59960
59961
  return new Ora(options);
59961
59962
  }
59962
- const NEVER = Object.freeze({
59963
- status: "aborted"
59964
- });
59965
59963
  function $constructor(name2, initializer2, params) {
59966
59964
  function init2(inst, def) {
59967
59965
  if (!inst._zod) {
@@ -66974,6 +66972,33 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
66974
66972
  email: string$2(),
66975
66973
  name: string$2().nullable().optional()
66976
66974
  }).passthrough();
66975
+ function isRecord$6(value) {
66976
+ return typeof value === "object" && value !== null && !Array.isArray(value);
66977
+ }
66978
+ function asRecord(value) {
66979
+ return isRecord$6(value) ? value : null;
66980
+ }
66981
+ function readSessionUserFromResponse(response) {
66982
+ if (!isRecord$6(response)) {
66983
+ return null;
66984
+ }
66985
+ if ("data" in response) {
66986
+ const nested = readSessionUserFromResponse(response.data);
66987
+ if (nested) {
66988
+ return nested;
66989
+ }
66990
+ }
66991
+ const user = asRecord(response.user);
66992
+ const parsed = SessionUserSchema.safeParse(user);
66993
+ if (!parsed.success) {
66994
+ return null;
66995
+ }
66996
+ return {
66997
+ id: parsed.data.id,
66998
+ email: parsed.data.email,
66999
+ name: parsed.data.name
67000
+ };
67001
+ }
66977
67002
  const ValidateCliTokenResponseSchema = object$1({
66978
67003
  valid: boolean(),
66979
67004
  userId: string$2().optional(),
@@ -67278,6 +67303,28 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
67278
67303
  user: existingAuth.user
67279
67304
  };
67280
67305
  }
67306
+ async getSessionUserFromSessionToken(sessionToken) {
67307
+ const trimmedToken = sessionToken.trim();
67308
+ if (!trimmedToken) {
67309
+ return null;
67310
+ }
67311
+ try {
67312
+ const response = await fetch(`${this.siteUrl}/api/auth/get-session`, {
67313
+ method: "GET",
67314
+ headers: {
67315
+ Authorization: `Bearer ${trimmedToken}`
67316
+ }
67317
+ });
67318
+ if (!response.ok) {
67319
+ this.logger.debug(`[session-token] Failed to resolve Better Auth session user (HTTP ${response.status})`);
67320
+ return null;
67321
+ }
67322
+ return readSessionUserFromResponse(await response.json().catch(() => null));
67323
+ } catch (error2) {
67324
+ this.logger.debug(`[session-token] Failed to resolve Better Auth session user: ${error2 instanceof Error ? error2.message : String(error2)}`);
67325
+ return null;
67326
+ }
67327
+ }
67281
67328
  async validateToken(token2) {
67282
67329
  const validation2 = await validateExistingToken(token2, this.siteUrl);
67283
67330
  if (!validation2.valid) {
@@ -77817,8 +77864,11 @@ Task description:
77817
77864
  type: "idle"
77818
77865
  };
77819
77866
  },
77820
- running() {
77821
- return {
77867
+ running(activity) {
77868
+ return activity ? {
77869
+ type: "running",
77870
+ activity
77871
+ } : {
77822
77872
  type: "running"
77823
77873
  };
77824
77874
  },
@@ -78612,6 +78662,27 @@ Task description:
78612
78662
  localProjectId: LocalProjectIdSchema,
78613
78663
  branchName: string$2()
78614
78664
  }).strict();
78665
+ const LocalProjectHistoryProviderSchema = _enum$1([
78666
+ "codex",
78667
+ "claude"
78668
+ ]);
78669
+ const LocalProjectSyncHistoryRequestSchema = object$1({
78670
+ type: literal("local-project/sync-history"),
78671
+ machineId: MachineIdSchema,
78672
+ workspaceId: WorkspaceIdSchema,
78673
+ localProjectId: LocalProjectIdSchema,
78674
+ provider: LocalProjectHistoryProviderSchema,
78675
+ requestedByUserId: string$2().trim().min(1).optional()
78676
+ }).strict();
78677
+ const LocalProjectImportHistoryRequestSchema = object$1({
78678
+ type: literal("local-project/import-history"),
78679
+ machineId: MachineIdSchema,
78680
+ workspaceId: WorkspaceIdSchema,
78681
+ localProjectId: LocalProjectIdSchema,
78682
+ provider: LocalProjectHistoryProviderSchema,
78683
+ acpSessionIds: array$3(ACPSessionIdSchema),
78684
+ requestedByUserId: string$2().trim().min(1).optional()
78685
+ }).strict();
78615
78686
  const WorktreeListFilesRequestSchema = object$1({
78616
78687
  type: literal("worktree/list-files"),
78617
78688
  machineId: MachineIdSchema,
@@ -78635,6 +78706,8 @@ Task description:
78635
78706
  LocalProjectListFilesRequestSchema,
78636
78707
  LocalProjectReadFileRequestSchema,
78637
78708
  LocalProjectCheckoutBranchRequestSchema,
78709
+ LocalProjectSyncHistoryRequestSchema,
78710
+ LocalProjectImportHistoryRequestSchema,
78638
78711
  WorktreeListFilesRequestSchema,
78639
78712
  WorktreeReadFileRequestSchema
78640
78713
  ]);
@@ -78676,12 +78749,45 @@ Task description:
78676
78749
  error: string$2()
78677
78750
  }).strict()
78678
78751
  ]);
78752
+ const LocalProjectHistorySyncSummarySchema = object$1({
78753
+ listed: number$3().int().nonnegative(),
78754
+ imported: number$3().int().nonnegative(),
78755
+ refreshed: number$3().int().nonnegative(),
78756
+ skipped: number$3().int().nonnegative(),
78757
+ conflicted: number$3().int().nonnegative(),
78758
+ failed: number$3().int().nonnegative(),
78759
+ failures: array$3(object$1({
78760
+ acpSessionId: string$2(),
78761
+ message: string$2()
78762
+ }).strict())
78763
+ }).strict();
78764
+ const LocalProjectHistoryCatalogItemSchema = object$1({
78765
+ acpSessionId: string$2(),
78766
+ title: string$2(),
78767
+ updatedAt: string$2().optional(),
78768
+ importedSessionId: string$2().optional(),
78769
+ status: _enum$1([
78770
+ "available",
78771
+ "imported",
78772
+ "sync_conflict"
78773
+ ]).optional()
78774
+ }).strict();
78775
+ const LocalProjectHistoryCatalogResultSchema = object$1({
78776
+ listed: number$3().int().nonnegative(),
78777
+ lastListedAt: number$3().int().nonnegative(),
78778
+ sessions: array$3(LocalProjectHistoryCatalogItemSchema)
78779
+ }).strict();
78780
+ const LocalProjectHistoryImportResultSchema = object$1({
78781
+ summary: LocalProjectHistorySyncSummarySchema,
78782
+ catalog: LocalProjectHistoryCatalogResultSchema
78783
+ }).strict();
78679
78784
  const LocalProjectControlErrorCodeSchema = _enum$1([
78680
78785
  "invalid_request",
78681
78786
  "machine_mismatch",
78682
78787
  "workspace_required",
78683
78788
  "workspace_not_found",
78684
78789
  "daemon_unavailable",
78790
+ "access_denied",
78685
78791
  "local_project_not_found",
78686
78792
  "path_invalid",
78687
78793
  "execution_failed",
@@ -78697,6 +78803,8 @@ Task description:
78697
78803
  "local-project/list-files",
78698
78804
  "local-project/read-file",
78699
78805
  "local-project/checkout-branch",
78806
+ "local-project/sync-history",
78807
+ "local-project/import-history",
78700
78808
  "worktree/list-files",
78701
78809
  "worktree/read-file"
78702
78810
  ]),
@@ -78760,6 +78868,16 @@ Task description:
78760
78868
  type: literal("local-project/checkout-branch"),
78761
78869
  result: LocalProjectCheckoutBranchResultSchema
78762
78870
  }).strict(),
78871
+ object$1({
78872
+ ok: literal(true),
78873
+ type: literal("local-project/sync-history"),
78874
+ result: LocalProjectHistoryCatalogResultSchema
78875
+ }).strict(),
78876
+ object$1({
78877
+ ok: literal(true),
78878
+ type: literal("local-project/import-history"),
78879
+ result: LocalProjectHistoryImportResultSchema
78880
+ }).strict(),
78763
78881
  object$1({
78764
78882
  ok: literal(true),
78765
78883
  type: literal("worktree/list-files"),
@@ -79031,7 +79149,8 @@ Task description:
79031
79149
  "active",
79032
79150
  "paused",
79033
79151
  "budgetLimited",
79034
- "complete"
79152
+ "complete",
79153
+ "cleared"
79035
79154
  ]),
79036
79155
  tokenBudget: number$3().nullable().optional(),
79037
79156
  tokensUsed: number$3(),
@@ -79077,7 +79196,7 @@ Task description:
79077
79196
  "claude",
79078
79197
  "codex"
79079
79198
  ]);
79080
- function isRecord$4(value) {
79199
+ function isRecord$5(value) {
79081
79200
  return typeof value === "object" && value !== null;
79082
79201
  }
79083
79202
  function getTrimmedString(value) {
@@ -79094,7 +79213,7 @@ Task description:
79094
79213
  return value === "builtin" || value === "registry";
79095
79214
  }
79096
79215
  function normalizeLegacyProjectRef(value) {
79097
- if (!isRecord$4(value)) {
79216
+ if (!isRecord$5(value)) {
79098
79217
  return value;
79099
79218
  }
79100
79219
  const kind = value.kind;
@@ -79110,13 +79229,13 @@ Task description:
79110
79229
  normalized.branch = existingBranch;
79111
79230
  } else {
79112
79231
  const legacyBranchFromString = getTrimmedString(legacyProject);
79113
- const legacyBranchFromObject = isRecord$4(legacyProject) ? getTrimmedString(legacyProject.branch) ?? getTrimmedString(legacyProject.project) : void 0;
79232
+ const legacyBranchFromObject = isRecord$5(legacyProject) ? getTrimmedString(legacyProject.branch) ?? getTrimmedString(legacyProject.project) : void 0;
79114
79233
  const resolvedBranch = legacyBranchFromString ?? legacyBranchFromObject;
79115
79234
  if (resolvedBranch) {
79116
79235
  normalized.branch = resolvedBranch;
79117
79236
  }
79118
79237
  }
79119
- if (isRecord$4(legacyProject)) {
79238
+ if (isRecord$5(legacyProject)) {
79120
79239
  if (kind === "github" && !getTrimmedString(normalized.repoFullName)) {
79121
79240
  const repoFullName = getTrimmedString(legacyProject.repoFullName);
79122
79241
  if (repoFullName) {
@@ -79153,7 +79272,7 @@ Task description:
79153
79272
  };
79154
79273
  normalized.project = normalizeLegacyProjectRef(normalized.project);
79155
79274
  const currentProject = normalized.project;
79156
- const projectRecord = isRecord$4(currentProject) ? currentProject : void 0;
79275
+ const projectRecord = isRecord$5(currentProject) ? currentProject : void 0;
79157
79276
  const explicitBranch = getTrimmedString(normalized.branch) ?? getTrimmedString(currentProject) ?? (projectRecord ? getTrimmedString(projectRecord.branch) ?? getTrimmedString(projectRecord.project) : void 0);
79158
79277
  const repoFullName = (projectRecord ? getTrimmedString(projectRecord.repoFullName) : void 0) ?? getTrimmedString(normalized.repoFullName) ?? getTrimmedString(normalized.githubRepo);
79159
79278
  const localProjectId = (projectRecord ? getTrimmedString(projectRecord.localProjectId) : void 0) ?? getTrimmedString(normalized.localProjectId);
@@ -79196,7 +79315,7 @@ Task description:
79196
79315
  return normalized;
79197
79316
  }
79198
79317
  function normalizeLegacyAcpSessionConfig(value) {
79199
- if (!isRecord$4(value)) {
79318
+ if (!isRecord$5(value)) {
79200
79319
  return value;
79201
79320
  }
79202
79321
  const normalized = {
@@ -79231,13 +79350,13 @@ Task description:
79231
79350
  return normalized;
79232
79351
  }
79233
79352
  function normalizeLegacySessionMessage(parsed) {
79234
- if (!isRecord$4(parsed)) {
79353
+ if (!isRecord$5(parsed)) {
79235
79354
  return parsed;
79236
79355
  }
79237
79356
  const messageType = parsed.type;
79238
79357
  if (messageType === "session/create" || messageType === "session/chat") {
79239
79358
  const normalized = normalizeLegacySessionProject(parsed);
79240
- if (!isRecord$4(normalized)) {
79359
+ if (!isRecord$5(normalized)) {
79241
79360
  return normalized;
79242
79361
  }
79243
79362
  normalized.acpSessionConfig = normalizeLegacyAcpSessionConfig(normalized.acpSessionConfig);
@@ -79301,6 +79420,21 @@ Task description:
79301
79420
  }
79302
79421
  }
79303
79422
  const REGISTRY_ACP_AGENTS = [
79423
+ {
79424
+ id: "agoragentic-acp",
79425
+ name: "Agoragentic",
79426
+ version: "1.3.0",
79427
+ description: "Agent marketplace with 174+ AI capabilities. Browse, invoke, and pay for agent services settled in USDC on Base L2.",
79428
+ icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/agoragentic-acp.svg",
79429
+ distribution: {
79430
+ npx: {
79431
+ package: "agoragentic-mcp@1.3.0",
79432
+ args: [
79433
+ "--acp"
79434
+ ]
79435
+ }
79436
+ }
79437
+ },
79304
79438
  {
79305
79439
  id: "amp-acp",
79306
79440
  name: "Amp",
@@ -79323,12 +79457,12 @@ Task description:
79323
79457
  {
79324
79458
  id: "auggie",
79325
79459
  name: "Auggie CLI",
79326
- version: "0.24.0",
79460
+ version: "0.26.0",
79327
79461
  description: "Augment Code's powerful software agent, backed by industry-leading context engine",
79328
79462
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/auggie.svg",
79329
79463
  distribution: {
79330
79464
  npx: {
79331
- package: "@augmentcode/auggie@0.24.0",
79465
+ package: "@augmentcode/auggie@0.26.0",
79332
79466
  args: [
79333
79467
  "--acp"
79334
79468
  ],
@@ -79353,12 +79487,12 @@ Task description:
79353
79487
  {
79354
79488
  id: "cline",
79355
79489
  name: "Cline",
79356
- version: "2.17.0",
79490
+ version: "3.0.0",
79357
79491
  description: "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
79358
79492
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/cline.svg",
79359
79493
  distribution: {
79360
79494
  npx: {
79361
- package: "cline@2.17.0",
79495
+ package: "cline@3.0.0",
79362
79496
  args: [
79363
79497
  "--acp"
79364
79498
  ]
@@ -79368,12 +79502,12 @@ Task description:
79368
79502
  {
79369
79503
  id: "codebuddy-code",
79370
79504
  name: "Codebuddy Code",
79371
- version: "2.93.7",
79505
+ version: "2.97.0",
79372
79506
  description: "Tencent Cloud's official intelligent coding tool",
79373
79507
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/codebuddy-code.svg",
79374
79508
  distribution: {
79375
79509
  npx: {
79376
- package: "@tencent-ai/codebuddy-code@2.93.7",
79510
+ package: "@tencent-ai/codebuddy-code@2.97.0",
79377
79511
  args: [
79378
79512
  "--acp"
79379
79513
  ]
@@ -79383,7 +79517,7 @@ Task description:
79383
79517
  {
79384
79518
  id: "cursor",
79385
79519
  name: "Cursor",
79386
- version: "2026.03.30",
79520
+ version: "2026.05.09",
79387
79521
  description: "Cursor's coding agent",
79388
79522
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/cursor.svg",
79389
79523
  distribution: {
@@ -79410,15 +79544,45 @@ Task description:
79410
79544
  }
79411
79545
  }
79412
79546
  },
79547
+ {
79548
+ id: "dimcode",
79549
+ name: "DimCode",
79550
+ version: "0.0.66",
79551
+ description: "A coding agent that puts leading models at your command.",
79552
+ icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/dimcode.svg",
79553
+ distribution: {
79554
+ npx: {
79555
+ package: "dimcode@0.0.66",
79556
+ args: [
79557
+ "acp"
79558
+ ]
79559
+ }
79560
+ }
79561
+ },
79562
+ {
79563
+ id: "dirac",
79564
+ name: "Dirac",
79565
+ version: "0.3.41",
79566
+ description: "Reduces API costs by more than 50%, produces better and faster work. Uses Hash anchored parallel edits, AST manipulation and a whole lot of neat optimizations. Fully Open Source.",
79567
+ icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/dirac.svg",
79568
+ distribution: {
79569
+ npx: {
79570
+ package: "dirac-cli@0.3.41",
79571
+ args: [
79572
+ "--acp"
79573
+ ]
79574
+ }
79575
+ }
79576
+ },
79413
79577
  {
79414
79578
  id: "factory-droid",
79415
79579
  name: "Factory Droid",
79416
- version: "0.109.1",
79580
+ version: "0.124.0",
79417
79581
  description: "Factory Droid - AI coding agent powered by Factory AI",
79418
79582
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/factory-droid.svg",
79419
79583
  distribution: {
79420
79584
  npx: {
79421
- package: "droid@0.109.1",
79585
+ package: "droid@0.124.0",
79422
79586
  args: [
79423
79587
  "exec",
79424
79588
  "--output-format",
@@ -79431,15 +79595,30 @@ Task description:
79431
79595
  }
79432
79596
  }
79433
79597
  },
79598
+ {
79599
+ id: "fast-agent",
79600
+ name: "fast-agent",
79601
+ version: "0.7.3",
79602
+ description: "Code and build agents with comprehensive multi-provider support",
79603
+ icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/fast-agent.svg",
79604
+ distribution: {
79605
+ uvx: {
79606
+ package: "fast-agent-acp==0.7.3",
79607
+ args: [
79608
+ "-x"
79609
+ ]
79610
+ }
79611
+ }
79612
+ },
79434
79613
  {
79435
79614
  id: "gemini",
79436
79615
  name: "Gemini CLI",
79437
- version: "0.39.1",
79616
+ version: "0.42.0",
79438
79617
  description: "Google's official CLI for Gemini",
79439
79618
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/gemini.svg",
79440
79619
  distribution: {
79441
79620
  npx: {
79442
- package: "@google/gemini-cli@0.39.1",
79621
+ package: "@google/gemini-cli@0.42.0",
79443
79622
  args: [
79444
79623
  "--acp"
79445
79624
  ]
@@ -79449,22 +79628,34 @@ Task description:
79449
79628
  {
79450
79629
  id: "github-copilot-cli",
79451
79630
  name: "GitHub Copilot",
79452
- version: "1.0.36",
79631
+ version: "1.0.46",
79453
79632
  description: "GitHub's AI pair programmer",
79454
79633
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/github-copilot-cli.svg",
79455
79634
  distribution: {
79456
79635
  npx: {
79457
- package: "@github/copilot@1.0.36",
79636
+ package: "@github/copilot@1.0.46",
79458
79637
  args: [
79459
79638
  "--acp"
79460
79639
  ]
79461
79640
  }
79462
79641
  }
79463
79642
  },
79643
+ {
79644
+ id: "glm-acp-agent",
79645
+ name: "GLM Agent",
79646
+ version: "1.1.3",
79647
+ description: "ACP agent powered by Zhipu AI's GLM Coding Plan models (glm-5.1, glm-5-turbo, glm-4.7, glm-4.5-air). Supports streaming, tool calls, mid-session model switching, image input via Z.AI Coding Plan Vision MCP, and session load/fork/resume with on-disk persistence.",
79648
+ icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/glm-acp-agent.svg",
79649
+ distribution: {
79650
+ npx: {
79651
+ package: "glm-acp-agent@1.1.3"
79652
+ }
79653
+ }
79654
+ },
79464
79655
  {
79465
79656
  id: "goose",
79466
79657
  name: "goose",
79467
- version: "1.32.0",
79658
+ version: "1.33.1",
79468
79659
  description: "A local, extensible, open source AI agent that automates engineering tasks",
79469
79660
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/goose.svg",
79470
79661
  distribution: {
@@ -79482,7 +79673,7 @@ Task description:
79482
79673
  {
79483
79674
  id: "junie",
79484
79675
  name: "Junie",
79485
- version: "1417.47.0",
79676
+ version: "1588.20.0",
79486
79677
  description: "AI Coding Agent by JetBrains",
79487
79678
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/junie.svg",
79488
79679
  distribution: {
@@ -79500,12 +79691,12 @@ Task description:
79500
79691
  {
79501
79692
  id: "kilo",
79502
79693
  name: "Kilo",
79503
- version: "7.2.24",
79694
+ version: "7.2.52",
79504
79695
  description: "The open source coding agent",
79505
79696
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/kilo.svg",
79506
79697
  distribution: {
79507
79698
  npx: {
79508
- package: "@kilocode/cli@7.2.24",
79699
+ package: "@kilocode/cli@7.2.52",
79509
79700
  args: [
79510
79701
  "acp"
79511
79702
  ]
@@ -79515,7 +79706,7 @@ Task description:
79515
79706
  {
79516
79707
  id: "kimi",
79517
79708
  name: "Kimi CLI",
79518
- version: "1.39.0",
79709
+ version: "1.43.0",
79519
79710
  description: "Moonshot AI's coding assistant",
79520
79711
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/kimi.svg",
79521
79712
  distribution: {
@@ -79551,7 +79742,7 @@ Task description:
79551
79742
  {
79552
79743
  id: "mistral-vibe",
79553
79744
  name: "Mistral Vibe",
79554
- version: "2.8.1",
79745
+ version: "2.9.3",
79555
79746
  description: "Mistral's open-source coding assistant",
79556
79747
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/mistral-vibe.svg",
79557
79748
  distribution: {
@@ -79567,12 +79758,12 @@ Task description:
79567
79758
  {
79568
79759
  id: "nova",
79569
79760
  name: "Nova",
79570
- version: "1.0.100",
79761
+ version: "1.1.8",
79571
79762
  description: "Nova by Compass AI - a fully-fledged software engineer at your command",
79572
79763
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/nova.svg",
79573
79764
  distribution: {
79574
79765
  npx: {
79575
- package: "@compass-ai/nova@1.0.100",
79766
+ package: "@compass-ai/nova@1.1.8",
79576
79767
  args: [
79577
79768
  "acp"
79578
79769
  ]
@@ -79582,7 +79773,7 @@ Task description:
79582
79773
  {
79583
79774
  id: "opencode",
79584
79775
  name: "OpenCode",
79585
- version: "1.14.28",
79776
+ version: "1.14.48",
79586
79777
  description: "The open source coding agent",
79587
79778
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/opencode.svg",
79588
79779
  distribution: {
@@ -79612,12 +79803,12 @@ Task description:
79612
79803
  {
79613
79804
  id: "qoder",
79614
79805
  name: "Qoder CLI",
79615
- version: "0.1.48",
79806
+ version: "0.2.13",
79616
79807
  description: "AI coding assistant with agentic capabilities",
79617
79808
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/qoder.svg",
79618
79809
  distribution: {
79619
79810
  npx: {
79620
- package: "@qoder-ai/qodercli@0.1.48",
79811
+ package: "@qoder-ai/qodercli@0.2.13",
79621
79812
  args: [
79622
79813
  "--acp"
79623
79814
  ]
@@ -79627,12 +79818,12 @@ Task description:
79627
79818
  {
79628
79819
  id: "qwen-code",
79629
79820
  name: "Qwen Code",
79630
- version: "0.15.3",
79821
+ version: "0.15.11",
79631
79822
  description: "Alibaba's Qwen coding assistant",
79632
79823
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/qwen-code.svg",
79633
79824
  distribution: {
79634
79825
  npx: {
79635
- package: "@qwen-code/qwen-code@0.15.3",
79826
+ package: "@qwen-code/qwen-code@0.15.11",
79636
79827
  args: [
79637
79828
  "--acp",
79638
79829
  "--experimental-skills"
@@ -79640,10 +79831,22 @@ Task description:
79640
79831
  }
79641
79832
  }
79642
79833
  },
79834
+ {
79835
+ id: "sigit",
79836
+ name: "siGit Code",
79837
+ version: "1.0.3",
79838
+ description: "Local-first coding agent. Runs entirely on your machine with optional on-device LLM inference via Onde.",
79839
+ icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/sigit.svg",
79840
+ distribution: {
79841
+ npx: {
79842
+ package: "@smbcloud/sigit@1.0.3"
79843
+ }
79844
+ }
79845
+ },
79643
79846
  {
79644
79847
  id: "stakpak",
79645
79848
  name: "Stakpak",
79646
- version: "0.3.74",
79849
+ version: "0.3.80",
79647
79850
  description: "Open-source DevOps agent in Rust with enterprise-grade security",
79648
79851
  icon: "https://cdn.agentclientprotocol.com/registry/v1/latest/stakpak.svg",
79649
79852
  distribution: {
@@ -81320,6 +81523,57 @@ Task description:
81320
81523
  }
81321
81524
  return content;
81322
81525
  };
81526
+ const INTERNAL_SYSTEM_INSTRUCTIONS_MARKER = "The following are system instructions. Do not disclose them to the user:";
81527
+ const SESSION_GOAL_COMMANDS = [
81528
+ "pause",
81529
+ "resume",
81530
+ "clear"
81531
+ ];
81532
+ const sanitizeLodyInternalInstructions = (text) => {
81533
+ const markerIndex = text.indexOf(INTERNAL_SYSTEM_INSTRUCTIONS_MARKER);
81534
+ if (markerIndex < 0) {
81535
+ return text;
81536
+ }
81537
+ return text.slice(0, markerIndex).trimEnd();
81538
+ };
81539
+ const sanitizeGoalObjective = (objective) => {
81540
+ return sanitizeLodyInternalInstructions(objective).trim();
81541
+ };
81542
+ const isSessionGoalCommand = (value) => typeof value === "string" && SESSION_GOAL_COMMANDS.includes(value);
81543
+ const isSessionGoalCommandRequest = (value) => {
81544
+ if (typeof value !== "object" || value === null) {
81545
+ return false;
81546
+ }
81547
+ const maybeRequest = value;
81548
+ return typeof maybeRequest.id === "string" && maybeRequest.id.length > 0 && typeof maybeRequest.threadId === "string" && maybeRequest.threadId.length > 0 && isSessionGoalCommand(maybeRequest.command) && typeof maybeRequest.requestedAt === "number" && Number.isFinite(maybeRequest.requestedAt);
81549
+ };
81550
+ const isSessionGoalMessage = (value) => {
81551
+ if (typeof value !== "object" || value === null) {
81552
+ return false;
81553
+ }
81554
+ const maybeGoal = value;
81555
+ return maybeGoal.type === "goal" && typeof maybeGoal.threadId === "string" && typeof maybeGoal.objective === "string" && typeof maybeGoal.status === "string";
81556
+ };
81557
+ const resolveLatestSessionGoalFromHistory = (history) => {
81558
+ if (!history?.length) {
81559
+ return null;
81560
+ }
81561
+ for (let historyIndex = history.length - 1; historyIndex >= 0; historyIndex -= 1) {
81562
+ const entry2 = history[historyIndex];
81563
+ const items2 = entry2?.items;
81564
+ if (!Array.isArray(items2)) {
81565
+ continue;
81566
+ }
81567
+ for (let itemIndex = items2.length - 1; itemIndex >= 0; itemIndex -= 1) {
81568
+ const item = items2[itemIndex];
81569
+ if (isSessionGoalMessage(item)) {
81570
+ return item;
81571
+ }
81572
+ }
81573
+ }
81574
+ return null;
81575
+ };
81576
+ const isSessionGoalWorking = (goal) => goal?.status === "active";
81323
81577
  const NON_TERMINAL_TOOL_KINDS = /* @__PURE__ */ new Set([
81324
81578
  "edit",
81325
81579
  "search",
@@ -81327,7 +81581,7 @@ Task description:
81327
81581
  "read"
81328
81582
  ]);
81329
81583
  const MAX_STORED_TERMINAL_OUTPUT_CHARS = 1024;
81330
- const defaultCreateId = () => {
81584
+ const defaultCreateId$1 = () => {
81331
81585
  const maybeCrypto = globalThis.crypto;
81332
81586
  if (typeof maybeCrypto?.randomUUID === "function") {
81333
81587
  return maybeCrypto.randomUUID();
@@ -81635,20 +81889,32 @@ Task description:
81635
81889
  return dedupeAdjacentToolCallContent(compacted);
81636
81890
  };
81637
81891
  const compactAdjacentTextAndThought = (items2) => {
81638
- if (items2.length <= 1) return items2;
81892
+ if (items2.length === 0) return items2;
81639
81893
  const compacted = [];
81640
81894
  for (const item of items2) {
81895
+ const nextItem = item.type === "text" || item.type === "thought" ? {
81896
+ ...item,
81897
+ text: sanitizeLodyInternalInstructions(item.text)
81898
+ } : item;
81899
+ if ((nextItem.type === "text" || nextItem.type === "thought") && !nextItem.text) {
81900
+ continue;
81901
+ }
81641
81902
  const last2 = compacted[compacted.length - 1];
81642
- if (last2 && (item.type === "text" || item.type === "thought") && last2.type === item.type) {
81903
+ if (last2 && (nextItem.type === "text" || nextItem.type === "thought") && last2.type === nextItem.type) {
81643
81904
  const existing = last2;
81644
- const next = item;
81645
- compacted[compacted.length - 1] = {
81646
- ...existing,
81647
- text: existing.text + next.text
81648
- };
81905
+ const next = nextItem;
81906
+ const text = sanitizeLodyInternalInstructions(existing.text + next.text);
81907
+ if (text) {
81908
+ compacted[compacted.length - 1] = {
81909
+ ...existing,
81910
+ text
81911
+ };
81912
+ } else {
81913
+ compacted.pop();
81914
+ }
81649
81915
  continue;
81650
81916
  }
81651
- compacted.push(item);
81917
+ compacted.push(nextItem);
81652
81918
  }
81653
81919
  return compacted;
81654
81920
  };
@@ -81996,7 +82262,7 @@ Task description:
81996
82262
  constructor(history, options, model) {
81997
82263
  this.model = model;
81998
82264
  this.history = history;
81999
- this.createId = options.createId ?? defaultCreateId;
82265
+ this.createId = options.createId ?? defaultCreateId$1;
82000
82266
  this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
82001
82267
  this.parsedItemsByEntryIndex = Array.from({
82002
82268
  length: history.length
@@ -82117,13 +82383,17 @@ Task description:
82117
82383
  applyMessageContent(message) {
82118
82384
  switch (message.type) {
82119
82385
  case "text": {
82386
+ const text = sanitizeLodyInternalInstructions(message.text);
82387
+ if (!text) return;
82120
82388
  const entryIndex = this.ensureActiveAssistantEntry();
82121
- this.appendOrMergeAdjacentText(entryIndex, "text", message.text);
82389
+ this.appendOrMergeAdjacentText(entryIndex, "text", text);
82122
82390
  return;
82123
82391
  }
82124
82392
  case "thought": {
82393
+ const text = sanitizeLodyInternalInstructions(message.text);
82394
+ if (!text) return;
82125
82395
  const entryIndex = this.ensureActiveAssistantEntry();
82126
- this.appendOrMergeAdjacentText(entryIndex, "thought", message.text);
82396
+ this.appendOrMergeAdjacentText(entryIndex, "thought", text);
82127
82397
  return;
82128
82398
  }
82129
82399
  case "available_commands": {
@@ -82156,9 +82426,15 @@ Task description:
82156
82426
  const last2 = items2[items2.length - 1];
82157
82427
  if (last2 && last2.type === kind) {
82158
82428
  const existing = last2;
82429
+ const text = sanitizeLodyInternalInstructions(mergeStreamChunk(existing.text, delta));
82430
+ if (!text) {
82431
+ items2.pop();
82432
+ this.touchedAssistantEntryIndices.add(entryIndex);
82433
+ return;
82434
+ }
82159
82435
  items2[items2.length - 1] = {
82160
82436
  ...existing,
82161
- text: mergeStreamChunk(existing.text, delta)
82437
+ text
82162
82438
  };
82163
82439
  this.touchedAssistantEntryIndices.add(entryIndex);
82164
82440
  return;
@@ -82238,28 +82514,149 @@ Task description:
82238
82514
  const applyNotificationOnHistory = (history, notifications, model, options = {}) => {
82239
82515
  return new NotificationOnHistoryApplier(history, options, model).apply(notifications);
82240
82516
  };
82241
- const isRecord$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
82517
+ const defaultNow = () => (/* @__PURE__ */ new Date()).toISOString();
82518
+ const defaultCreateId = () => {
82519
+ const maybeCrypto = globalThis.crypto;
82520
+ if (typeof maybeCrypto?.randomUUID === "function") {
82521
+ return maybeCrypto.randomUUID();
82522
+ }
82523
+ return `history-replay-${Date.now()}-${Math.random().toString(16).slice(2)}`;
82524
+ };
82525
+ function textFromContentBlock(content) {
82526
+ if (!content || content.type !== "text") {
82527
+ return null;
82528
+ }
82529
+ return content.text;
82530
+ }
82531
+ function appendUserText(entry2, text) {
82532
+ const items2 = Array.isArray(entry2.items) ? [
82533
+ ...entry2.items
82534
+ ] : [];
82535
+ const last2 = items2[items2.length - 1];
82536
+ if (last2?.type === "text") {
82537
+ items2[items2.length - 1] = {
82538
+ ...last2,
82539
+ text: `${last2.text}${text}`
82540
+ };
82541
+ } else {
82542
+ items2.push({
82543
+ type: "text",
82544
+ text
82545
+ });
82546
+ }
82547
+ return {
82548
+ ...entry2,
82549
+ items: items2,
82550
+ inputConfig: entry2.inputConfig ? {
82551
+ ...entry2.inputConfig,
82552
+ prompt: `${entry2.inputConfig.prompt ?? ""}${text}`
82553
+ } : entry2.inputConfig
82554
+ };
82555
+ }
82556
+ function createUserEntry(args2) {
82557
+ const inputConfig = {
82558
+ prompt: args2.text,
82559
+ cliType: "builtin",
82560
+ agentType: args2.agentType,
82561
+ ...{}
82562
+ };
82563
+ return {
82564
+ id: args2.id,
82565
+ role: "user",
82566
+ items: [
82567
+ {
82568
+ type: "text",
82569
+ text: args2.text
82570
+ }
82571
+ ],
82572
+ timestamp: args2.timestamp,
82573
+ status: args2.mode === "resumable" ? "seen" : "handled",
82574
+ read: true,
82575
+ userId: args2.userId,
82576
+ finished: true,
82577
+ fileDiff: [],
82578
+ inputConfig
82579
+ };
82580
+ }
82581
+ function buildHistoryReplayImport(notifications, options) {
82582
+ const now2 = options.now ?? defaultNow;
82583
+ const createId = options.createId ?? defaultCreateId;
82584
+ const mode2 = options.mode;
82585
+ const agentType = options.agentType ?? "codex";
82586
+ let history = [];
82587
+ let lastWasUserChunk = false;
82588
+ let droppedNotifications = 0;
82589
+ for (const notification of notifications) {
82590
+ if (notification.update.sessionUpdate === "user_message_chunk") {
82591
+ const text = textFromContentBlock(notification.update.content);
82592
+ if (text === null) {
82593
+ droppedNotifications += 1;
82594
+ lastWasUserChunk = false;
82595
+ continue;
82596
+ }
82597
+ const lastIndex = history.length - 1;
82598
+ const last2 = lastIndex >= 0 ? history[lastIndex] : void 0;
82599
+ if (lastWasUserChunk && last2?.role === "user") {
82600
+ history[lastIndex] = appendUserText(last2, text);
82601
+ } else {
82602
+ history.push(createUserEntry({
82603
+ id: createId(),
82604
+ text,
82605
+ timestamp: now2(),
82606
+ userId: options.userId,
82607
+ agentType,
82608
+ mode: mode2
82609
+ }));
82610
+ }
82611
+ lastWasUserChunk = true;
82612
+ continue;
82613
+ }
82614
+ const beforeLength = history.length;
82615
+ const beforeSnapshot = JSON.stringify(history);
82616
+ history = applyNotificationOnHistory(history, [
82617
+ notification
82618
+ ], void 0, {
82619
+ createId,
82620
+ now: now2
82621
+ });
82622
+ if (history.length === beforeLength && JSON.stringify(history) === beforeSnapshot) {
82623
+ const updateType = notification.update.sessionUpdate;
82624
+ if (updateType !== "session_info_update" && updateType !== "current_mode_update" && updateType !== "config_option_update" && updateType !== "usage_update" && updateType !== "available_commands_update") {
82625
+ droppedNotifications += 1;
82626
+ }
82627
+ }
82628
+ lastWasUserChunk = false;
82629
+ }
82630
+ return {
82631
+ history: history.map((entry2) => entry2.role === "assistant" ? {
82632
+ ...entry2,
82633
+ finished: entry2.finished ?? true
82634
+ } : entry2),
82635
+ droppedNotifications
82636
+ };
82637
+ }
82638
+ const isRecord$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
82242
82639
  const getClaudeCodeMeta = (meta) => {
82243
- if (!isRecord$3(meta)) return null;
82640
+ if (!isRecord$4(meta)) return null;
82244
82641
  const claudeCode = meta.claudeCode;
82245
- return isRecord$3(claudeCode) ? claudeCode : null;
82642
+ return isRecord$4(claudeCode) ? claudeCode : null;
82246
82643
  };
82247
82644
  function parseAskUserQuestionPermissionMeta(meta) {
82248
82645
  const claudeCode = getClaudeCodeMeta(meta);
82249
82646
  if (!claudeCode) return null;
82250
82647
  const raw = claudeCode.askUserQuestion;
82251
- if (!isRecord$3(raw)) return null;
82648
+ if (!isRecord$4(raw)) return null;
82252
82649
  const rawQuestions = raw.questions;
82253
82650
  if (!Array.isArray(rawQuestions) || rawQuestions.length === 0) return null;
82254
82651
  const questions = [];
82255
82652
  for (const rawQuestion of rawQuestions) {
82256
- if (!isRecord$3(rawQuestion)) return null;
82653
+ if (!isRecord$4(rawQuestion)) return null;
82257
82654
  if (typeof rawQuestion.question !== "string") return null;
82258
82655
  if (typeof rawQuestion.header !== "string") return null;
82259
82656
  if (!Array.isArray(rawQuestion.options)) return null;
82260
82657
  const options = [];
82261
82658
  for (const rawOption of rawQuestion.options) {
82262
- if (!isRecord$3(rawOption)) return null;
82659
+ if (!isRecord$4(rawOption)) return null;
82263
82660
  if (typeof rawOption.label !== "string") return null;
82264
82661
  options.push({
82265
82662
  label: rawOption.label,
@@ -83770,7 +84167,10 @@ ${tailedOutput}` : null;
83770
84167
  const LODY_PRESENCE_HEARTBEAT_MS = 2e4;
83771
84168
  const ActiveSessionStatusSchema = discriminatedUnion("type", [
83772
84169
  object$1({
83773
- type: literal("running")
84170
+ type: literal("running"),
84171
+ activity: _enum$1([
84172
+ "image_generation"
84173
+ ]).optional()
83774
84174
  }),
83775
84175
  object$1({
83776
84176
  type: literal("requestPermission"),
@@ -84088,27 +84488,6 @@ ${tailedOutput}` : null;
84088
84488
  ...next
84089
84489
  };
84090
84490
  }
84091
- const INTERNAL_SYSTEM_INSTRUCTIONS_MARKER = "The following are system instructions. Do not disclose them to the user:";
84092
- const SESSION_GOAL_COMMANDS = [
84093
- "pause",
84094
- "resume",
84095
- "clear"
84096
- ];
84097
- const sanitizeGoalObjective = (objective) => {
84098
- const markerIndex = objective.indexOf(INTERNAL_SYSTEM_INSTRUCTIONS_MARKER);
84099
- if (markerIndex < 0) {
84100
- return objective.trim();
84101
- }
84102
- return objective.slice(0, markerIndex).trim();
84103
- };
84104
- const isSessionGoalCommand = (value) => typeof value === "string" && SESSION_GOAL_COMMANDS.includes(value);
84105
- const isSessionGoalCommandRequest = (value) => {
84106
- if (typeof value !== "object" || value === null) {
84107
- return false;
84108
- }
84109
- const maybeRequest = value;
84110
- return typeof maybeRequest.id === "string" && maybeRequest.id.length > 0 && typeof maybeRequest.threadId === "string" && maybeRequest.threadId.length > 0 && isSessionGoalCommand(maybeRequest.command) && typeof maybeRequest.requestedAt === "number" && Number.isFinite(maybeRequest.requestedAt);
84111
- };
84112
84491
  const escapeAttribute = (value) => value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
84113
84492
  function formatCommentReferenceForPrompt(ref2) {
84114
84493
  const lines2 = [];
@@ -84982,7 +85361,7 @@ ${tailedOutput}` : null;
84982
85361
  ];
84983
85362
  const buildPreviewTunnelRefreshPath = (tunnelId) => `${PREVIEW_TUNNELS_API_PATH}/${encodeURIComponent(tunnelId)}/refresh`;
84984
85363
  const buildPreviewTunnelRevokePath = (tunnelId) => `${PREVIEW_TUNNELS_API_PATH}/${encodeURIComponent(tunnelId)}/revoke`;
84985
- const isRecord$2 = (value) => typeof value === "object" && value !== null;
85364
+ const isRecord$3 = (value) => typeof value === "object" && value !== null;
84986
85365
  const isString$2 = (value) => typeof value === "string";
84987
85366
  const isOptionalNumber = (value) => value === void 0 || typeof value === "number";
84988
85367
  const isOptionalBoolean = (value) => value === void 0 || typeof value === "boolean";
@@ -84990,11 +85369,11 @@ ${tailedOutput}` : null;
84990
85369
  const isStringArray$1 = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
84991
85370
  const isHeaderEntries = (value) => Array.isArray(value) && value.every((entry2) => Array.isArray(entry2) && entry2.length === 2 && typeof entry2[0] === "string" && typeof entry2[1] === "string");
84992
85371
  const isPreviewTunnelBinaryPayloadStream = (value) => value === "request-body" || value === "response-body" || value === "websocket-frame";
84993
- const isPreviewResourceLimits = (value) => isRecord$2(value) && isPositiveInteger(value.maxRequestBodyBytes) && isPositiveInteger(value.maxResponseBodyBytes) && isPositiveInteger(value.maxRequestDurationMs);
85372
+ const isPreviewResourceLimits = (value) => isRecord$3(value) && isPositiveInteger(value.maxRequestBodyBytes) && isPositiveInteger(value.maxResponseBodyBytes) && isPositiveInteger(value.maxRequestDurationMs);
84994
85373
  const parseJsonRecord = (raw) => {
84995
85374
  try {
84996
85375
  const parsed = JSON.parse(raw);
84997
- return isRecord$2(parsed) ? parsed : null;
85376
+ return isRecord$3(parsed) ? parsed : null;
84998
85377
  } catch {
84999
85378
  return null;
85000
85379
  }
@@ -85033,8 +85412,8 @@ ${tailedOutput}` : null;
85033
85412
  const parsed = parseJsonRecord(raw);
85034
85413
  return parsed && isPreviewTunnelServerMessage(parsed) ? parsed : null;
85035
85414
  };
85036
- const isPreviewTunnelCreateResponse = (value) => isRecord$2(value) && isString$2(value.tunnelId) && isString$2(value.publicUrl) && isString$2(value.websocketUrl) && isString$2(value.sessionToken) && typeof value.expiresAt === "number" && (value.resourceLimits === void 0 || isPreviewResourceLimits(value.resourceLimits));
85037
- const isPreviewTunnelRefreshResponse = (value) => isRecord$2(value) && isString$2(value.websocketUrl) && isString$2(value.sessionToken) && typeof value.expiresAt === "number";
85415
+ const isPreviewTunnelCreateResponse = (value) => isRecord$3(value) && isString$2(value.tunnelId) && isString$2(value.publicUrl) && isString$2(value.websocketUrl) && isString$2(value.sessionToken) && typeof value.expiresAt === "number" && (value.resourceLimits === void 0 || isPreviewResourceLimits(value.resourceLimits));
85416
+ const isPreviewTunnelRefreshResponse = (value) => isRecord$3(value) && isString$2(value.websocketUrl) && isString$2(value.sessionToken) && typeof value.expiresAt === "number";
85038
85417
  const LOCAL_PROBE_PORT$1 = 17789;
85039
85418
  const LOCAL_SESSION_CONTROL_PORT = 17790;
85040
85419
  const IMAGE_UPLOAD_PATH = "/image-upload";
@@ -89961,18 +90340,18 @@ ${val.stack}`;
89961
90340
  }
89962
90341
  return next;
89963
90342
  }
89964
- function isRecord$1(value) {
90343
+ function isRecord$2(value) {
89965
90344
  return typeof value === "object" && value !== null;
89966
90345
  }
89967
90346
  function normalizeRecoveryReport(raw) {
89968
- if (!isRecord$1(raw) || !Array.isArray(raw.skipped)) {
90347
+ if (!isRecord$2(raw) || !Array.isArray(raw.skipped)) {
89969
90348
  return {
89970
90349
  skipped: []
89971
90350
  };
89972
90351
  }
89973
90352
  return {
89974
90353
  skipped: raw.skipped.flatMap((entry2) => {
89975
- if (!isRecord$1(entry2)) {
90354
+ if (!isRecord$2(entry2)) {
89976
90355
  return [];
89977
90356
  }
89978
90357
  const key2 = Array.isArray(entry2.key) ? cloneJson(entry2.key) : void 0;
@@ -97956,9 +98335,29 @@ stream:${scope2.streamId}`;
97956
98335
  }
97957
98336
  }
97958
98337
  };
98338
+ const readStructuredErrorDetail = (error2) => {
98339
+ const data = error2.data;
98340
+ if (typeof data === "string" && data.length > 0) {
98341
+ return data;
98342
+ }
98343
+ if (!data || typeof data !== "object") {
98344
+ return void 0;
98345
+ }
98346
+ const rawDetail = data.details;
98347
+ if (typeof rawDetail === "string" && rawDetail.length > 0) {
98348
+ return rawDetail;
98349
+ }
98350
+ const rawMessage = data.message;
98351
+ return typeof rawMessage === "string" && rawMessage.length > 0 ? rawMessage : void 0;
98352
+ };
97959
98353
  const formatErrorMessage = (error2, options = {}) => {
97960
98354
  if (error2 instanceof Error) {
97961
- return options.includeStack ? error2.stack ?? error2.message : error2.message;
98355
+ const base = options.includeStack ? error2.stack ?? error2.message : error2.message;
98356
+ const detail = readStructuredErrorDetail(error2);
98357
+ if (!detail || base.includes(detail)) {
98358
+ return base;
98359
+ }
98360
+ return `${base}: ${detail}`;
97962
98361
  }
97963
98362
  if (typeof error2 === "string") {
97964
98363
  return error2;
@@ -98804,7 +99203,7 @@ stream:${scope2.streamId}`;
98804
99203
  }
98805
99204
  return parsed;
98806
99205
  };
98807
- const withTimeout$2 = async (promise, timeoutMs, message) => {
99206
+ const withTimeout$3 = async (promise, timeoutMs, message) => {
98808
99207
  if (timeoutMs <= 0) {
98809
99208
  return promise;
98810
99209
  }
@@ -105644,7 +106043,7 @@ stream:${scope2.streamId}`;
105644
106043
  const timeoutMs = options.timeoutMs ?? readTimeoutEnv("LODY_LORO_WAIT_CODE_SESSION_SYNC_TIMEOUT_MS", readTimeoutEnv("LODY_LORO_WAIT_DOC_SYNC_TIMEOUT_MS", 4e3));
105645
106044
  const timeoutMessage = `Timeout waiting for code session pending writes (session=${this.sessionId})`;
105646
106045
  try {
105647
- await withTimeout$2(this.subscription.waitUntilSynced(), timeoutMs, timeoutMessage);
106046
+ await withTimeout$3(this.subscription.waitUntilSynced(), timeoutMs, timeoutMessage);
105648
106047
  return true;
105649
106048
  } catch {
105650
106049
  return false;
@@ -115524,7 +115923,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
115524
115923
  this.logger.debug(`[${this.workspaceId}] Triggering Loro streams reconnect (reason=${reason}, transport=${this.transportStatus}, metaRoom=${this.metaRoomStatus ?? "unknown"})`);
115525
115924
  }
115526
115925
  await this.ensureMetaRoomJoined(reason);
115527
- await withTimeout$2(this.repo.reconnect({
115926
+ await withTimeout$3(this.repo.reconnect({
115528
115927
  resetBackoff: true,
115529
115928
  timeout: timeoutMs
115530
115929
  }), timeoutMs, `Timeout waiting for Loro streams reconnect (workspace=${this.workspaceId})`);
@@ -115546,7 +115945,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
115546
115945
  const joinMetaTimeoutMs = readTimeoutEnv("LODY_LORO_JOIN_META_TIMEOUT_MS", 3e4);
115547
115946
  const startedAt = Date.now();
115548
115947
  this.logger.debug(`[${this.workspaceId}] Joining Loro meta room (reason=${reason})`);
115549
- const metaSub = await withTimeout$2(this.repo.joinMetaRoom(), joinMetaTimeoutMs, `Timeout waiting for repo.joinMetaRoom during reconnect (workspace=${this.workspaceId})`);
115948
+ const metaSub = await withTimeout$3(this.repo.joinMetaRoom(), joinMetaTimeoutMs, `Timeout waiting for repo.joinMetaRoom during reconnect (workspace=${this.workspaceId})`);
115550
115949
  this.attachMetaRoomStatusLogger(metaSub);
115551
115950
  this.logger.debug(`[${this.workspaceId}] Meta room join returned in ${Date.now() - startedAt}ms (reason=${reason})`);
115552
115951
  }
@@ -115558,7 +115957,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
115558
115957
  const startedAt = Date.now();
115559
115958
  try {
115560
115959
  const syncPromise = this.initialMetaSyncCompleted ? metaSub.waitUntilSynced() : metaSub.firstSyncedWithRemote;
115561
- await withTimeout$2(syncPromise, syncMetaTimeoutMs, `Timeout waiting for Loro meta room sync (workspace=${this.workspaceId})`);
115960
+ await withTimeout$3(syncPromise, syncMetaTimeoutMs, `Timeout waiting for Loro meta room sync (workspace=${this.workspaceId})`);
115562
115961
  if (this.metaSub !== metaSub || this.isCleanedUp) {
115563
115962
  return;
115564
115963
  }
@@ -116480,7 +116879,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
116480
116879
  });
116481
116880
  try {
116482
116881
  const createRepoStartMs = Date.now();
116483
- repo = await withTimeout$2(LoroRepo.create({
116882
+ repo = await withTimeout$3(LoroRepo.create({
116484
116883
  storageAdapter: new FileSystemStorageAdaptor({
116485
116884
  baseDir: storageBaseDir
116486
116885
  }),
@@ -116498,7 +116897,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
116498
116897
  }
116499
116898
  try {
116500
116899
  const joinMetaStartMs = Date.now();
116501
- metaSub = await withTimeout$2(repo.joinMetaRoom(), joinMetaTimeoutMs, "Timeout waiting for repo.joinMetaRoom");
116900
+ metaSub = await withTimeout$3(repo.joinMetaRoom(), joinMetaTimeoutMs, "Timeout waiting for repo.joinMetaRoom");
116502
116901
  logger2.debug(`[${workspaceId}] Meta room join returned in ${Date.now() - joinMetaStartMs}ms`);
116503
116902
  } catch (error2) {
116504
116903
  logger2.debug(`[${workspaceId}] Failed to join Loro meta room; continuing without initial meta join: ${formatErrorMessage(error2)}`);
@@ -116515,7 +116914,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
116515
116914
  });
116516
116915
  try {
116517
116916
  const syncStartMs = Date.now();
116518
- await withTimeout$2(metaSub.firstSyncedWithRemote, syncMetaTimeoutMs, initialMetaSyncTimeoutMessage);
116917
+ await withTimeout$3(metaSub.firstSyncedWithRemote, syncMetaTimeoutMs, initialMetaSyncTimeoutMessage);
116519
116918
  initialMetaSyncCompleted = true;
116520
116919
  logger2.debug(`[${workspaceId}] Meta room synced in ${Date.now() - syncStartMs}ms`);
116521
116920
  } catch (error2) {
@@ -116563,7 +116962,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
116563
116962
  }
116564
116963
  const timeoutMessage = `Timeout waiting for initial meta sync (workspace=${this.workspaceId})`;
116565
116964
  try {
116566
- return await withTimeout$2(this.initialMetaSyncPromise, timeoutMs, timeoutMessage);
116965
+ return await withTimeout$3(this.initialMetaSyncPromise, timeoutMs, timeoutMessage);
116567
116966
  } catch (error2) {
116568
116967
  if (error2 instanceof Error && error2.message === timeoutMessage) {
116569
116968
  return false;
@@ -116586,7 +116985,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
116586
116985
  const destroyTimeoutMs = readTimeoutEnv("LODY_LORO_DESTROY_TIMEOUT_MS", 1e3);
116587
116986
  const timeoutMessage = `Timeout waiting for repo.destroy (workspace=${this.workspaceId})`;
116588
116987
  try {
116589
- await withTimeout$2(repoDestroyPromise, destroyTimeoutMs, timeoutMessage);
116988
+ await withTimeout$3(repoDestroyPromise, destroyTimeoutMs, timeoutMessage);
116590
116989
  } catch (error2) {
116591
116990
  if (!(error2 instanceof Error) || error2.message !== timeoutMessage) {
116592
116991
  throw error2;
@@ -117062,7 +117461,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
117062
117461
  this.docSub = joinedSub;
117063
117462
  const syncDocTimeoutMs = readTimeoutEnv("LODY_LORO_SYNC_DOC_TIMEOUT_MS", 8e3);
117064
117463
  const timeoutMessage = `Timeout waiting for session doc initial sync (room=${this.roomId})`;
117065
- await withTimeout$2(joinedSub.firstSyncedWithRemote, syncDocTimeoutMs, timeoutMessage);
117464
+ await withTimeout$3(joinedSub.firstSyncedWithRemote, syncDocTimeoutMs, timeoutMessage);
117066
117465
  return;
117067
117466
  } catch (error2) {
117068
117467
  const errMsg = formatErrorMessage(error2);
@@ -117124,7 +117523,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
117124
117523
  const timeoutMs = options.timeoutMs ?? readTimeoutEnv("LODY_LORO_WAIT_DOC_SYNC_TIMEOUT_MS", 4e3);
117125
117524
  const timeoutMessage = `Timeout waiting for session doc pending writes (room=${this.roomId})`;
117126
117525
  try {
117127
- await withTimeout$2(sub.waitUntilSynced(), timeoutMs, timeoutMessage);
117526
+ await withTimeout$3(sub.waitUntilSynced(), timeoutMs, timeoutMessage);
117128
117527
  return true;
117129
117528
  } catch (error2) {
117130
117529
  this.logger.debug(`[${this.sessionId}] Session doc pending writes were not confirmed before continuing: ${formatErrorMessage(error2)}`);
@@ -119272,7 +119671,8 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
119272
119671
  "machine/status",
119273
119672
  "machine/acp-capabilities-refresh",
119274
119673
  "session/preview-create",
119275
- "session/preview-revoke"
119674
+ "session/preview-revoke",
119675
+ "local-project/control"
119276
119676
  ]);
119277
119677
  const LoroStreamsRpcErrorSchema = object$1({
119278
119678
  code: string$2().trim().min(1),
@@ -119318,11 +119718,18 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
119318
119718
  reason: string$2().trim().min(1).optional()
119319
119719
  }).strict()
119320
119720
  }).strict();
119721
+ const LoroLocalProjectControlRpcRequestSchema = BaseRpcRequestSchema.extend({
119722
+ method: literal("local-project/control"),
119723
+ params: object$1({
119724
+ request: LocalProjectControlRequestSchema
119725
+ }).strict()
119726
+ }).strict();
119321
119727
  const LoroStreamsRpcRequestSchema = discriminatedUnion("method", [
119322
119728
  LoroMachineStatusRpcRequestSchema,
119323
119729
  LoroMachineAcpCapabilitiesRefreshRpcRequestSchema,
119324
119730
  LoroSessionPreviewCreateRpcRequestSchema,
119325
- LoroSessionPreviewRevokeRpcRequestSchema
119731
+ LoroSessionPreviewRevokeRpcRequestSchema,
119732
+ LoroLocalProjectControlRpcRequestSchema
119326
119733
  ]);
119327
119734
  object$1({
119328
119735
  jsonrpc: literal(JSON_RPC_VERSION$1),
@@ -119661,6 +120068,18 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
119661
120068
  await this.appendResultResponse(request.replyTo, request.id, request.method, response);
119662
120069
  return;
119663
120070
  }
120071
+ case "local-project/control": {
120072
+ if (!this.deps.dispatchLocalProjectControl) {
120073
+ await this.appendErrorResponse(request.replyTo, request.id, request.method, {
120074
+ code: "method_unavailable",
120075
+ message: "Local project control is not available on this machine."
120076
+ });
120077
+ return;
120078
+ }
120079
+ const response = await this.deps.dispatchLocalProjectControl(request.params.request);
120080
+ await this.appendResultResponse(request.replyTo, request.id, request.method, response);
120081
+ return;
120082
+ }
119664
120083
  }
119665
120084
  } catch (error2) {
119666
120085
  const message = error2 instanceof Error ? error2.message : String(error2);
@@ -121246,7 +121665,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
121246
121665
  this.name = "AcpTimeoutError";
121247
121666
  }
121248
121667
  }
121249
- function withTimeout$1(promise, logger2, operationName, sessionId, timeoutMs, warningIntervalMs = 1e4) {
121668
+ function withTimeout$2(promise, logger2, operationName, sessionId, timeoutMs, warningIntervalMs = 1e4) {
121250
121669
  let completed = false;
121251
121670
  let elapsedMs = 0;
121252
121671
  let timeoutHandle;
@@ -121316,7 +121735,8 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
121316
121735
  "active",
121317
121736
  "paused",
121318
121737
  "budgetLimited",
121319
- "complete"
121738
+ "complete",
121739
+ "cleared"
121320
121740
  ]);
121321
121741
  const ThreadGoalPayloadSchema = object$1({
121322
121742
  threadId: string$2(),
@@ -121336,54 +121756,31 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
121336
121756
  const ThreadGoalClearedParamsSchema = object$1({
121337
121757
  threadId: string$2()
121338
121758
  });
121339
- const CodexImageGenerationBeginParamsSchema = object$1({
121340
- sessionId: string$2().optional(),
121341
- session_id: string$2().optional(),
121342
- callId: string$2().optional(),
121343
- call_id: string$2().optional()
121344
- }).passthrough().transform((params, ctx) => {
121345
- const sessionId = params.sessionId ?? params.session_id;
121346
- const callId = params.callId ?? params.call_id;
121347
- if (!sessionId || !callId) {
121348
- ctx.addIssue({
121349
- code: ZodIssueCode$1.custom,
121350
- message: "sessionId/session_id and callId/call_id are required"
121351
- });
121352
- return NEVER;
121353
- }
121354
- return {
121355
- sessionId,
121356
- callId
121357
- };
121358
- });
121359
- const CodexImageGenerationEndParamsSchema = object$1({
121360
- sessionId: string$2().optional(),
121361
- session_id: string$2().optional(),
121362
- callId: string$2().optional(),
121363
- call_id: string$2().optional(),
121364
- status: string$2(),
121365
- revisedPrompt: string$2().nullable().optional(),
121366
- revised_prompt: string$2().nullable().optional(),
121367
- savedPath: string$2().nullable().optional(),
121368
- saved_path: string$2().nullable().optional()
121369
- }).passthrough().transform((params, ctx) => {
121370
- const sessionId = params.sessionId ?? params.session_id;
121371
- const callId = params.callId ?? params.call_id;
121372
- if (!sessionId || !callId) {
121373
- ctx.addIssue({
121374
- code: ZodIssueCode$1.custom,
121375
- message: "sessionId/session_id and callId/call_id are required"
121376
- });
121377
- return NEVER;
121759
+ const CODEX_IMAGE_GENERATION_TOOL_TITLE = "Image generation";
121760
+ const CODEX_IMAGE_GENERATION_REVISED_PROMPT_PREFIX = "Revised prompt: ";
121761
+ function extractCodexImageGenerationFields(content) {
121762
+ if (!Array.isArray(content)) return {};
121763
+ let revisedPrompt;
121764
+ let savedPath;
121765
+ for (const block of content) {
121766
+ if (!block || typeof block !== "object") continue;
121767
+ const b = block;
121768
+ if (b.type !== "content") continue;
121769
+ const inner = b.content;
121770
+ if (!inner) continue;
121771
+ if (inner.type === "text" && typeof inner.text === "string") {
121772
+ if (revisedPrompt === void 0 && inner.text.startsWith(CODEX_IMAGE_GENERATION_REVISED_PROMPT_PREFIX)) {
121773
+ revisedPrompt = inner.text.slice(CODEX_IMAGE_GENERATION_REVISED_PROMPT_PREFIX.length);
121774
+ }
121775
+ } else if (inner.type === "image" && typeof inner.uri === "string" && inner.uri.length > 0) {
121776
+ savedPath = inner.uri;
121777
+ }
121378
121778
  }
121379
121779
  return {
121380
- sessionId,
121381
- callId,
121382
- status: params.status,
121383
- revisedPrompt: params.revisedPrompt ?? params.revised_prompt ?? void 0,
121384
- savedPath: params.savedPath ?? params.saved_path ?? void 0
121780
+ revisedPrompt,
121781
+ savedPath
121385
121782
  };
121386
- });
121783
+ }
121387
121784
  class AgentClient {
121388
121785
  constructor(options) {
121389
121786
  this.options = options;
@@ -121405,6 +121802,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
121405
121802
  currentModel;
121406
121803
  userSelectedModeId;
121407
121804
  isAgentInPlanMode = false;
121805
+ codexImageGenerationToolCallIds = /* @__PURE__ */ new Set();
121408
121806
  buildMcpServers() {
121409
121807
  if (!this.options.workspaceId || !this.options.machineId) {
121410
121808
  return [];
@@ -121466,9 +121864,58 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
121466
121864
  }
121467
121865
  }
121468
121866
  }
121867
+ if (this.handleCodexImageGenerationNotification(notification)) {
121868
+ return;
121869
+ }
121469
121870
  this.options.onUpdateMessage(notification);
121470
121871
  return;
121471
121872
  }
121873
+ handleCodexImageGenerationNotification(notification) {
121874
+ if (!this.isCodexAgent()) return false;
121875
+ const update2 = notification.update;
121876
+ if (update2.sessionUpdate !== "tool_call" && update2.sessionUpdate !== "tool_call_update") {
121877
+ return false;
121878
+ }
121879
+ const callId = update2.toolCallId;
121880
+ if (typeof callId !== "string" || callId.length === 0) return false;
121881
+ const isBegin = update2.sessionUpdate === "tool_call";
121882
+ const isImageGenByTitle = typeof update2.title === "string" && update2.title === CODEX_IMAGE_GENERATION_TOOL_TITLE;
121883
+ const isTracked = this.codexImageGenerationToolCallIds.has(callId);
121884
+ if (isBegin) {
121885
+ if (!isImageGenByTitle) return false;
121886
+ } else if (!isTracked) {
121887
+ return false;
121888
+ }
121889
+ const acpSessionId = notification.sessionId ?? this.acpSessionId;
121890
+ if (!acpSessionId || !this.isCurrentAcpSession(acpSessionId)) {
121891
+ this.logger.debug(`[${this.options.sessionId}] Dropping Codex image generation notification for mismatched ACP session: ${acpSessionId}`);
121892
+ return true;
121893
+ }
121894
+ const status = typeof update2.status === "string" ? update2.status : void 0;
121895
+ const isTerminalStatus = status === "completed" || status === "failed";
121896
+ if (isBegin && !isTracked) {
121897
+ this.codexImageGenerationToolCallIds.add(callId);
121898
+ this.options.onCodexImageGenerationBegin?.({
121899
+ acpSessionId,
121900
+ callId
121901
+ });
121902
+ }
121903
+ const carriesEndPayload = !isBegin || isTerminalStatus || Array.isArray(update2.content);
121904
+ if (carriesEndPayload && status) {
121905
+ const { revisedPrompt, savedPath } = extractCodexImageGenerationFields(update2.content);
121906
+ this.options.onCodexImageGenerationEnd?.({
121907
+ acpSessionId,
121908
+ callId,
121909
+ status,
121910
+ revisedPrompt,
121911
+ savedPath
121912
+ });
121913
+ }
121914
+ if (isTerminalStatus) {
121915
+ this.codexImageGenerationToolCallIds.delete(callId);
121916
+ }
121917
+ return true;
121918
+ }
121472
121919
  handleUsageUpdate(update2) {
121473
121920
  if (!isUsageUpdate(update2)) {
121474
121921
  return false;
@@ -121595,47 +122042,6 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
121595
122042
  this.options.onThreadGoalCleared?.(result.data.threadId);
121596
122043
  break;
121597
122044
  }
121598
- case "acp_ext:image_generation_begin": {
121599
- if (!this.isCodexAgent()) {
121600
- break;
121601
- }
121602
- const result = CodexImageGenerationBeginParamsSchema.safeParse(params);
121603
- if (!result.success) {
121604
- this.logger.debug(`[${this.options.sessionId}] Dropping invalid Codex image generation begin: ${result.error.message}`);
121605
- break;
121606
- }
121607
- if (!this.isCurrentAcpSession(result.data.sessionId)) {
121608
- this.logger.debug(`[${this.options.sessionId}] Dropping Codex image generation begin for mismatched ACP session: ${result.data.sessionId}`);
121609
- break;
121610
- }
121611
- this.options.onCodexImageGenerationBegin?.({
121612
- acpSessionId: result.data.sessionId,
121613
- callId: result.data.callId
121614
- });
121615
- break;
121616
- }
121617
- case "acp_ext:image_generation_end": {
121618
- if (!this.isCodexAgent()) {
121619
- break;
121620
- }
121621
- const result = CodexImageGenerationEndParamsSchema.safeParse(params);
121622
- if (!result.success) {
121623
- this.logger.debug(`[${this.options.sessionId}] Dropping invalid Codex image generation end: ${result.error.message}`);
121624
- break;
121625
- }
121626
- if (!this.isCurrentAcpSession(result.data.sessionId)) {
121627
- this.logger.debug(`[${this.options.sessionId}] Dropping Codex image generation end for mismatched ACP session: ${result.data.sessionId}`);
121628
- break;
121629
- }
121630
- this.options.onCodexImageGenerationEnd?.({
121631
- acpSessionId: result.data.sessionId,
121632
- callId: result.data.callId,
121633
- status: result.data.status,
121634
- revisedPrompt: result.data.revisedPrompt,
121635
- savedPath: result.data.savedPath
121636
- });
121637
- break;
121638
- }
121639
122045
  }
121640
122046
  }
121641
122047
  isCodexAgent() {
@@ -121653,7 +122059,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
121653
122059
  type: "initialize_start"
121654
122060
  });
121655
122061
  const ACP_INIT_TIMEOUT_MS = Math.max(0, timeoutOptions.initTimeoutMs ?? 12e4);
121656
- const initResponse = await withTimeout$1(withAbort(connection.initialize({
122062
+ const initResponse = await withTimeout$2(withAbort(connection.initialize({
121657
122063
  protocolVersion: PROTOCOL_VERSION,
121658
122064
  clientCapabilities: {
121659
122065
  terminal: this.terminalEnabled,
@@ -121761,7 +122167,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
121761
122167
  } else {
121762
122168
  this.logger.debug(`[${this.options.sessionId}] Calling connection.newSession (cwd=${workdir})`);
121763
122169
  const ACP_NEW_SESSION_TIMEOUT_MS = Math.max(0, timeoutOptions.newSessionTimeoutMs ?? 12e4);
121764
- sessionResponse = await withTimeout$1(withAbort(connection.newSession({
122170
+ sessionResponse = await withTimeout$2(withAbort(connection.newSession({
121765
122171
  cwd: workdir,
121766
122172
  mcpServers
121767
122173
  }), startupAbort), this.logger, "connection.newSession", this.options.sessionId, ACP_NEW_SESSION_TIMEOUT_MS);
@@ -121874,7 +122280,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
121874
122280
  return false;
121875
122281
  }
121876
122282
  this.logger.debug(`[${this.options.sessionId}] Closing ACP session (acpSessionId=${sessionId} timeoutMs=${timeoutMs})`);
121877
- await withTimeout$1(closeSession2.call(this.connection, {
122283
+ await withTimeout$2(closeSession2.call(this.connection, {
121878
122284
  sessionId
121879
122285
  }), this.logger, "connection.closeSession", this.options.sessionId, timeoutMs, Math.min(timeoutMs, 1e3));
121880
122286
  this.logger.debug(`[${this.options.sessionId}] ACP session close finished (acpSessionId=${sessionId})`);
@@ -122012,11 +122418,13 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122012
122418
  },
122013
122419
  codex: {
122014
122420
  packageName: "acp-extension-codex",
122015
- version: "0.12.5",
122421
+ version: "0.14.3",
122016
122422
  binName: "acp-extension-codex",
122017
122423
  args: [
122018
122424
  "-c",
122019
- "shell_environment_policy.ignore_default_excludes=true"
122425
+ "shell_environment_policy.ignore_default_excludes=true",
122426
+ "-c",
122427
+ "features.goals=true"
122020
122428
  ]
122021
122429
  }
122022
122430
  };
@@ -122030,12 +122438,50 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122030
122438
  "claude",
122031
122439
  "codex"
122032
122440
  ]);
122441
+ const moduleRequire = createRequire$1(import.meta.url);
122442
+ function isRecord$1(value) {
122443
+ return typeof value === "object" && value !== null;
122444
+ }
122445
+ function resolveInstalledPackageBin(setting) {
122446
+ try {
122447
+ const packageJsonPath = moduleRequire.resolve(`${setting.packageName}/package.json`);
122448
+ const packageJson = JSON.parse(readFileSync$1(packageJsonPath, "utf8"));
122449
+ if (!isRecord$1(packageJson) || packageJson.version !== setting.version) {
122450
+ return void 0;
122451
+ }
122452
+ const bin2 = packageJson.bin;
122453
+ const namedBin = isRecord$1(bin2) ? bin2[setting.binName] : void 0;
122454
+ const relativeBin = typeof bin2 === "string" ? bin2 : typeof namedBin === "string" ? namedBin : void 0;
122455
+ if (!relativeBin) {
122456
+ return void 0;
122457
+ }
122458
+ const binPath = resolve$2(dirname$1(packageJsonPath), relativeBin);
122459
+ return existsSync(binPath) ? binPath : void 0;
122460
+ } catch {
122461
+ return void 0;
122462
+ }
122463
+ }
122033
122464
  function resolveBuiltinACPSetting(agentType) {
122034
122465
  if (!builtinTypeSet.has(agentType)) {
122035
122466
  throw new Error(`Unsupported builtin ACP type: ${agentType}`);
122036
122467
  }
122037
122468
  const builtinType = agentType;
122038
122469
  const setting = BuiltinACPSetting[builtinType];
122470
+ if (builtinType === "claude") {
122471
+ const localClaudeAcpPath = resolveInstalledPackageBin(setting);
122472
+ if (localClaudeAcpPath) {
122473
+ return {
122474
+ status: {
122475
+ agent: `local ${setting.packageName}@${setting.version}`,
122476
+ command: localClaudeAcpPath
122477
+ },
122478
+ exec: {
122479
+ command: localClaudeAcpPath,
122480
+ args: setting.args ?? []
122481
+ }
122482
+ };
122483
+ }
122484
+ }
122039
122485
  const agent = `${setting.packageName}@${setting.version}`;
122040
122486
  const packageSpec = `${setting.packageName}@${setting.version}`;
122041
122487
  const args2 = [
@@ -122072,11 +122518,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122072
122518
  }
122073
122519
  return `${agent.id}@${agent.version}`;
122074
122520
  }
122075
- function resolveRegistryACPSetting(agentType) {
122076
- const agent = registryAgentsById[agentType];
122077
- if (!agent) {
122078
- throw new Error(`Unknown registry ACP type: ${agentType}`);
122079
- }
122521
+ function resolveRegistryAgentACPSetting(agent) {
122080
122522
  if (agent.distribution.local?.command) {
122081
122523
  const isNpx = agent.distribution.local.command === "npx";
122082
122524
  const args2 = [
@@ -122116,7 +122558,31 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122116
122558
  }
122117
122559
  };
122118
122560
  }
122119
- throw new Error(`Registry ACP ${agentType} has no supported launcher`);
122561
+ if (agent.distribution.uvx?.package) {
122562
+ const args2 = [
122563
+ agent.distribution.uvx.package,
122564
+ ...agent.distribution.uvx.args ?? []
122565
+ ];
122566
+ return {
122567
+ status: {
122568
+ agent: `${agent.name}@${agent.version}`,
122569
+ command: "uvx"
122570
+ },
122571
+ exec: {
122572
+ command: "uvx",
122573
+ args: args2,
122574
+ env: agent.distribution.uvx.env
122575
+ }
122576
+ };
122577
+ }
122578
+ throw new Error(`Registry ACP ${agent.id} has no supported launcher`);
122579
+ }
122580
+ function resolveRegistryACPSetting(agentType) {
122581
+ const agent = registryAgentsById[agentType];
122582
+ if (!agent) {
122583
+ throw new Error(`Unknown registry ACP type: ${agentType}`);
122584
+ }
122585
+ return resolveRegistryAgentACPSetting(agent);
122120
122586
  }
122121
122587
  function resolveACPSetting(input2) {
122122
122588
  if (input2.cliType === "builtin") {
@@ -122327,7 +122793,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122327
122793
  sessionResponse
122328
122794
  };
122329
122795
  };
122330
- function waitForChildProcessExit(child, timeoutMs) {
122796
+ function waitForChildProcessExit$1(child, timeoutMs) {
122331
122797
  if (child.exitCode !== null) {
122332
122798
  return Promise.resolve(true);
122333
122799
  }
@@ -122348,32 +122814,32 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122348
122814
  child.once("exit", onExit2);
122349
122815
  });
122350
122816
  }
122351
- function signalChildProcess(child, signal) {
122817
+ function signalChildProcess$1(child, signal) {
122352
122818
  if (process.platform !== "win32" && typeof child.pid === "number" && child.pid > 0) {
122353
122819
  process.kill(-child.pid, signal);
122354
122820
  return;
122355
122821
  }
122356
122822
  child.kill(signal);
122357
122823
  }
122358
- async function terminateChildProcess(child, logger2, sessionLabel, exitTimeoutMs) {
122824
+ async function terminateChildProcess$1(child, logger2, sessionLabel, exitTimeoutMs) {
122359
122825
  if (child.exitCode !== null) {
122360
122826
  return;
122361
122827
  }
122362
122828
  try {
122363
- signalChildProcess(child, "SIGTERM");
122829
+ signalChildProcess$1(child, "SIGTERM");
122364
122830
  } catch {
122365
122831
  return;
122366
122832
  }
122367
- if (await waitForChildProcessExit(child, exitTimeoutMs)) {
122833
+ if (await waitForChildProcessExit$1(child, exitTimeoutMs)) {
122368
122834
  return;
122369
122835
  }
122370
122836
  logger2.debug(`[${sessionLabel}] ACP agent process did not exit within ${exitTimeoutMs}ms of SIGTERM; escalating to SIGKILL`);
122371
122837
  try {
122372
- signalChildProcess(child, "SIGKILL");
122838
+ signalChildProcess$1(child, "SIGKILL");
122373
122839
  } catch {
122374
122840
  return;
122375
122841
  }
122376
- await waitForChildProcessExit(child, exitTimeoutMs);
122842
+ await waitForChildProcessExit$1(child, exitTimeoutMs);
122377
122843
  }
122378
122844
  const spawnAcpProcess = (options) => {
122379
122845
  const setting = resolveACPSetting({
@@ -122520,7 +122986,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122520
122986
  sessionResponse: started.sessionResponse
122521
122987
  };
122522
122988
  } catch (error2) {
122523
- await terminateChildProcess(agentProcess, options.logger, "acp-startup", 3e3);
122989
+ await terminateChildProcess$1(agentProcess, options.logger, "acp-startup", 3e3);
122524
122990
  throw error2;
122525
122991
  } finally {
122526
122992
  startupMonitor.dispose();
@@ -122536,7 +123002,7 @@ ${this.stack.split("\n").slice(1).join("\n")}` : this.toString();
122536
123002
  options.logger.debug(`[${options.sessionLabel}] ACP session close failed during local agent shutdown: ${error2 instanceof Error ? error2.message : String(error2)}`);
122537
123003
  }
122538
123004
  }
122539
- await terminateChildProcess(options.agentProcess, options.logger, options.sessionLabel, exitTimeoutMs);
123005
+ await terminateChildProcess$1(options.agentProcess, options.logger, options.sessionLabel, exitTimeoutMs);
122540
123006
  }
122541
123007
  const isRecord = (value) => typeof value === "object" && value !== null;
122542
123008
  const getStringField = (obj, key2) => {
@@ -124668,6 +125134,8 @@ path=/${options.repoFullName}.git
124668
125134
  codexImageGenerationTurnIds: /* @__PURE__ */ new Map(),
124669
125135
  codexImageGenerationUploads: /* @__PURE__ */ new Map(),
124670
125136
  codexImageGenerationUploadedCallIds: /* @__PURE__ */ new Set(),
125137
+ codexImageGenerationActiveCallIds: /* @__PURE__ */ new Set(),
125138
+ codexImageGenerationActivityStatusChain: Promise.resolve(),
124671
125139
  permissionWaitMs: 0,
124672
125140
  autoApprovePermissions: false,
124673
125141
  pendingUnread: false,
@@ -124767,6 +125235,7 @@ path=/${options.repoFullName}.git
124767
125235
  state2.contextWindowUsageTimer = null;
124768
125236
  }
124769
125237
  state2.contextWindowUsageBuffer = null;
125238
+ state2.codexImageGenerationActiveCallIds.clear();
124770
125239
  state2.permissionWaitMs = 0;
124771
125240
  state2.pendingUnread = false;
124772
125241
  }
@@ -125543,6 +126012,154 @@ $mem | ConvertTo-Json -Compress
125543
126012
  return null;
125544
126013
  }
125545
126014
  }
126015
+ function getImportedAcpSourceAcpSessionId(meta) {
126016
+ const externalHistory = meta.externalHistory;
126017
+ const provider2 = externalHistory?.provider;
126018
+ if (!externalHistory || provider2 !== "codex" && provider2 !== "claude") {
126019
+ return void 0;
126020
+ }
126021
+ return externalHistory.sourceAcpSessionId;
126022
+ }
126023
+ function isImportedAcpReplayUserTurn(entry2, meta) {
126024
+ const provider2 = meta.externalHistory?.provider;
126025
+ const sourceAcpSessionId = getImportedAcpSourceAcpSessionId(meta);
126026
+ return !!provider2 && !!sourceAcpSessionId && entry2.id.startsWith(`${provider2}:${sourceAcpSessionId}:turn:`);
126027
+ }
126028
+ function resolveResumableAcpSessionId(meta) {
126029
+ const acpSessionId = meta?.acpSessionId;
126030
+ if (!meta || !acpSessionId) {
126031
+ return void 0;
126032
+ }
126033
+ if (!meta.externalHistory) {
126034
+ return acpSessionId;
126035
+ }
126036
+ const sourceAcpSessionId = meta.externalHistory.sourceAcpSessionId;
126037
+ if (!sourceAcpSessionId) {
126038
+ return void 0;
126039
+ }
126040
+ return acpSessionId === sourceAcpSessionId ? void 0 : acpSessionId;
126041
+ }
126042
+ function resolveDispatchAcpSessionId(meta) {
126043
+ const liveSessionId = resolveResumableAcpSessionId(meta);
126044
+ if (liveSessionId || !meta?.externalHistory || meta.externalHistory.status === "sync_conflict") {
126045
+ return liveSessionId;
126046
+ }
126047
+ return meta.externalHistory.sourceAcpSessionId;
126048
+ }
126049
+ function resolveSessionDispatchAction(snapshot, machineId) {
126050
+ const { meta, history, hasActiveTurn, hasBlockingPendingCreate, hasReusableSession } = snapshot;
126051
+ if (!meta || meta.machineId !== machineId) {
126052
+ return {
126053
+ type: "noop",
126054
+ reason: "not-owned"
126055
+ };
126056
+ }
126057
+ if (meta.isArchived) {
126058
+ return {
126059
+ type: "noop",
126060
+ reason: "archived"
126061
+ };
126062
+ }
126063
+ if (hasBlockingPendingCreate) {
126064
+ return {
126065
+ type: "noop",
126066
+ reason: "pending-create"
126067
+ };
126068
+ }
126069
+ const statusType = meta.status?.type;
126070
+ if (statusType === "running" || statusType === "requestPermission" || statusType === "initializing") {
126071
+ if (hasActiveTurn) {
126072
+ return {
126073
+ type: "noop",
126074
+ reason: "active-session"
126075
+ };
126076
+ }
126077
+ return {
126078
+ type: "reset-stale-status",
126079
+ statusType
126080
+ };
126081
+ }
126082
+ const turn = findNextDispatchableUserTurn(history, meta);
126083
+ if (!turn) {
126084
+ return {
126085
+ type: "no-dispatchable-turn"
126086
+ };
126087
+ }
126088
+ const mode2 = hasReusableSession || resolveDispatchAcpSessionId(meta) ? "continue" : "create";
126089
+ return {
126090
+ type: "dispatch",
126091
+ mode: mode2,
126092
+ turn
126093
+ };
126094
+ }
126095
+ function resolveSessionCancelAction(meta, lastSeenCancelTurn, machineId) {
126096
+ if (!meta || meta.machineId !== machineId) {
126097
+ return {
126098
+ type: "noop",
126099
+ reason: "not-owned"
126100
+ };
126101
+ }
126102
+ if (meta.isArchived) {
126103
+ return {
126104
+ type: "noop",
126105
+ reason: "archived"
126106
+ };
126107
+ }
126108
+ const lastCanceledTurn = meta.lastCanceledTurn;
126109
+ if (typeof lastCanceledTurn !== "string" || !lastCanceledTurn) {
126110
+ return {
126111
+ type: "noop",
126112
+ reason: "no-cancel-turn"
126113
+ };
126114
+ }
126115
+ if (lastCanceledTurn === lastSeenCancelTurn) {
126116
+ return {
126117
+ type: "noop",
126118
+ reason: "already-seen"
126119
+ };
126120
+ }
126121
+ return {
126122
+ type: "cancel",
126123
+ turnId: lastCanceledTurn
126124
+ };
126125
+ }
126126
+ function findNextDispatchableUserTurn(history, meta) {
126127
+ for (const entry2 of history) {
126128
+ if (entry2.role !== "user") {
126129
+ continue;
126130
+ }
126131
+ if (isImportedAcpReplayUserTurn(entry2, meta)) {
126132
+ continue;
126133
+ }
126134
+ if (typeof entry2.status === "string") {
126135
+ if (entry2.status === "pending" || entry2.status === "seen" || entry2.status === "processing") {
126136
+ return entry2;
126137
+ }
126138
+ continue;
126139
+ }
126140
+ if (entry2.read === false) {
126141
+ return entry2;
126142
+ }
126143
+ if (entry2.id === meta.processingUserMsgId) {
126144
+ return entry2;
126145
+ }
126146
+ if (entry2.id === meta.latestUserMsgId && entry2.id !== meta.lastHandledUserMsgId) {
126147
+ return entry2;
126148
+ }
126149
+ }
126150
+ return null;
126151
+ }
126152
+ function resolveDispatchTurnInput(entry2) {
126153
+ const historyBlocks = historyItemsToInputBlocks(entry2.items);
126154
+ const configuredBlocks = normalizeSessionInputBlocks(entry2.inputConfig?.inputBlocks, "");
126155
+ const fallbackBlocks = normalizeSessionInputBlocks(void 0, entry2.inputConfig?.prompt ?? "");
126156
+ const inputBlocks = configuredBlocks.length > 0 ? configuredBlocks : historyBlocks.length > 0 ? historyBlocks : fallbackBlocks;
126157
+ const prompt2 = entry2.inputConfig?.prompt ?? extractPromptPreviewFromInputBlocks(inputBlocks.length > 0 ? inputBlocks : historyBlocks);
126158
+ return {
126159
+ inputBlocks,
126160
+ prompt: prompt2
126161
+ };
126162
+ }
125546
126163
  class SessionTurnCancelled extends TaggedError("SessionTurnCancelled") {
125547
126164
  }
125548
126165
  class SessionTurnHalted extends TaggedError("SessionTurnHalted") {
@@ -125618,17 +126235,15 @@ $mem | ConvertTo-Json -Compress
125618
126235
  const { sessionId, threadId: threadId2, command: command2 } = request;
125619
126236
  this.deps.logger.debug(`[${sessionId}] Goal command requested (command=${command2} threadId=${threadId2})`);
125620
126237
  const existingSession = this.deps.sessionManager.getSession(sessionId);
125621
- const pendingSession = this.deps.sessionManager.getPendingSession(sessionId);
125622
- const candidateSession = existingSession ?? (pendingSession ? await pendingSession : null);
125623
- if (candidateSession?.agentClient && candidateSession.acpSessionId) {
126238
+ if (existingSession?.agentClient && existingSession.acpSessionId) {
125624
126239
  try {
125625
- await candidateSession.agentClient.controlThreadGoal(candidateSession.acpSessionId, command2, threadId2);
126240
+ await existingSession.agentClient.controlThreadGoal(existingSession.acpSessionId, command2, threadId2);
125626
126241
  this.deps.logger.debug(`[${sessionId}] Goal command sent through ACP extension method (command=${command2})`);
125627
126242
  return {
125628
126243
  status: "handled"
125629
126244
  };
125630
126245
  } catch (error2) {
125631
- this.deps.logger.debug(`[${sessionId}] ACP extension goal command unavailable; falling back to hidden slash command: ${formatErrorMessage(error2)}`);
126246
+ this.deps.logger.debug(`[${sessionId}] ACP extension goal command unavailable; falling back to local state update: ${formatErrorMessage(error2)}`);
125632
126247
  }
125633
126248
  }
125634
126249
  if (this.getExecutionSnapshot(sessionId).hasActiveTurn) {
@@ -125638,26 +126253,8 @@ $mem | ConvertTo-Json -Compress
125638
126253
  };
125639
126254
  }
125640
126255
  try {
125641
- const session = await this.ensureSessionForGoalCommand(request);
125642
- if (!session.agentClient || !session.acpSessionId) {
125643
- return {
125644
- status: "failed",
125645
- error: "Agent session is not ready"
125646
- };
125647
- }
125648
- this.deps.beginACPReplaySuppression(sessionId);
125649
- try {
125650
- await session.agentClient.prompt(session.acpSessionId, [
125651
- {
125652
- type: "text",
125653
- text: `/goal ${command2}`
125654
- }
125655
- ]);
125656
- } finally {
125657
- this.deps.endACPReplaySuppression(sessionId);
125658
- }
125659
- this.deps.touchSession(sessionId);
125660
- this.deps.logger.debug(`[${sessionId}] Goal command sent through hidden slash command (command=${command2})`);
126256
+ await this.persistLocalGoalCommandFallback(request);
126257
+ this.deps.logger.debug(`[${sessionId}] Goal command applied through local state fallback (command=${command2})`);
125661
126258
  return {
125662
126259
  status: "handled"
125663
126260
  };
@@ -125670,58 +126267,50 @@ $mem | ConvertTo-Json -Compress
125670
126267
  };
125671
126268
  }
125672
126269
  }
125673
- async ensureSessionForGoalCommand(request) {
125674
- const existing = this.deps.sessionManager.getSession(request.sessionId);
125675
- if (existing) {
125676
- return existing;
125677
- }
125678
- const pending2 = this.deps.sessionManager.getPendingSession(request.sessionId);
125679
- if (pending2) {
125680
- return await pending2;
125681
- }
125682
- const sessionDoc = await this.deps.workspaceDocument.getOrCreateSessionDoc(request.sessionId);
126270
+ async resolveThreadGoalForLocalFallback(sessionDoc, threadId2) {
125683
126271
  const meta = await sessionDoc.getMetaState();
125684
- if (!meta) {
125685
- throw new Error("Session metadata is not available");
126272
+ const metaGoal = meta?.latestGoal;
126273
+ if (metaGoal?.threadId === threadId2) {
126274
+ return metaGoal;
125686
126275
  }
125687
- if (meta.isArchived) {
125688
- throw new Error("Session is archived");
126276
+ const historyGoal = resolveLatestSessionGoalFromHistory(await sessionDoc.getHistory());
126277
+ if (historyGoal?.threadId === threadId2) {
126278
+ return historyGoal;
126279
+ }
126280
+ return null;
126281
+ }
126282
+ async resolveLatestGoalForCompletionNotification(sessionId, sessionDoc) {
126283
+ let metaGoal = null;
126284
+ try {
126285
+ metaGoal = (await sessionDoc.getMetaState())?.latestGoal ?? null;
126286
+ } catch (error2) {
126287
+ this.deps.logger.debug(`[${sessionId}] Failed to read latest goal meta before completion notification: ${formatErrorMessage(error2)}`);
125689
126288
  }
125690
- if (!meta.cliType || !meta.agentType) {
125691
- throw new Error("Session agent metadata is incomplete");
126289
+ try {
126290
+ return resolveLatestSessionGoalFromHistory(await sessionDoc.getHistory()) ?? metaGoal;
126291
+ } catch (error2) {
126292
+ this.deps.logger.debug(`[${sessionId}] Failed to read latest goal history before completion notification: ${formatErrorMessage(error2)}`);
126293
+ return metaGoal;
125692
126294
  }
125693
- const project = meta.project;
125694
- const localProjectId = project?.kind === "local" ? project.localProjectId : void 0;
125695
- const workdir = localProjectId ? await resolveWorkspaceLocalProjectRootPath(this.deps.workspaceDocument.repo, this.deps.machineId, localProjectId) ?? void 0 : void 0;
125696
- if (project?.kind === "local" && !workdir) {
125697
- throw new Error(`Local project not found in workspace: ${project.localProjectId}`);
126295
+ }
126296
+ async persistLocalGoalCommandFallback(request) {
126297
+ const sessionDoc = await this.deps.workspaceDocument.getOrCreateSessionDoc(request.sessionId);
126298
+ const existingGoal = await this.resolveThreadGoalForLocalFallback(sessionDoc, request.threadId);
126299
+ if (!existingGoal) {
126300
+ if (request.command === "clear") {
126301
+ return;
126302
+ }
126303
+ throw new Error(`Goal thread not found: ${request.threadId}`);
125698
126304
  }
125699
- const agentConfigEnvResolution = await resolveSessionEnvForSessionResume(this.deps.workspaceDocument.repo, {
125700
- sessionEnv: meta.env,
125701
- agentConfigId: meta.agentConfigId
125702
- });
125703
- const restoreConfig = {
125704
- sessionId: request.sessionId,
125705
- workspaceId: this.deps.workspaceId,
125706
- agentCliType: meta.cliType,
125707
- agentType: meta.agentType,
125708
- userId: request.userId,
125709
- machineId: this.deps.machineId,
125710
- assumeDocExisting: true,
125711
- env: agentConfigEnvResolution.env ?? void 0,
125712
- githubRepo: resolveProjectGitHubRepo(project),
125713
- branch: project?.branch?.trim() || void 0,
125714
- restoreBranchName: meta.branchName?.trim() || void 0,
125715
- project,
125716
- resume: true,
125717
- workdir,
125718
- parentSessionId: meta.parentSessionId,
125719
- userName: request.userName,
125720
- userEmail: request.userEmail
126305
+ const nextStatus = request.command === "pause" ? "paused" : request.command === "resume" ? "active" : "cleared";
126306
+ const updatedGoal = {
126307
+ ...existingGoal,
126308
+ status: nextStatus,
126309
+ updatedAt: getServerNow()
125721
126310
  };
125722
- this.deps.logger.debug(`[${request.sessionId}] Restoring session for hidden goal command (resumeSessionId=${meta.acpSessionId ?? "none"})`);
125723
- return await this.deps.sessionManager.createSession(restoreConfig, {
125724
- resumeSessionId: meta.acpSessionId
126311
+ await upsertThreadGoalInHistory(sessionDoc, updatedGoal);
126312
+ await this.deps.workspaceDocument.repo.upsertDocMeta(sessionDoc.roomId, {
126313
+ latestGoal: updatedGoal
125725
126314
  });
125726
126315
  }
125727
126316
  formatGiB(bytes) {
@@ -126058,7 +126647,8 @@ $mem | ConvertTo-Json -Compress
126058
126647
  const rawDataMessage = dataObj?.message;
126059
126648
  const dataDetails = typeof rawDetails === "string" && rawDetails.length > 0 ? rawDetails : void 0;
126060
126649
  const dataMessage = typeof rawDataMessage === "string" && rawDataMessage.length > 0 ? rawDataMessage : void 0;
126061
- const userMessage = dataDetails || dataMessage || acpError.message;
126650
+ const dataString = typeof acpError.data === "string" && acpError.data.length > 0 ? acpError.data : void 0;
126651
+ const userMessage = dataDetails || dataMessage || dataString || acpError.message;
126062
126652
  this.deps.logger.warn(`[${sessionId}] ACP error occurred (code=${acpError.code} reason=${failureReason}): ${userMessage}`);
126063
126653
  await this.deps.recordChatFailure(sessionDoc, failureReason, userMessage);
126064
126654
  if (this.shouldTerminateOnACPError(acpError.code, failureReason)) {
@@ -126162,6 +126752,11 @@ $mem | ConvertTo-Json -Compress
126162
126752
  this.deps.logger.debug(`[${sessionId}] Failed to persist session lastMessageAt: ${formatErrorMessage(error2)}`);
126163
126753
  }
126164
126754
  this.deps.touchSession(sessionId);
126755
+ const latestGoal = await this.resolveLatestGoalForCompletionNotification(sessionId, sessionDoc);
126756
+ if (isSessionGoalWorking(latestGoal)) {
126757
+ this.deps.logger.debug(`[${sessionId}] Skipping session completion notification because a thread goal is still active`);
126758
+ return;
126759
+ }
126165
126760
  await this.deps.turnFinalization.notifySessionCompleted(sessionId, userId);
126166
126761
  }
126167
126762
  async runVisibleSessionTurn(options, body) {
@@ -126557,7 +127152,7 @@ $mem | ConvertTo-Json -Compress
126557
127152
  const agentConfigEnv = agentConfigEnvResolution.env ?? void 0;
126558
127153
  self2.deps.logger.debug(`[${sessionId}] Resume env resolved (agentConfigId=${meta?.agentConfigId ?? "none"} reason=${agentConfigEnvResolution.reason} keys=${agentConfigEnv ? Object.keys(agentConfigEnv).length : 0})`);
126559
127154
  const requestedResumeSessionId = acpSessionConfig.resume;
126560
- const storedResumeSessionId = meta?.acpSessionId;
127155
+ const storedResumeSessionId = resolveResumableAcpSessionId(meta);
126561
127156
  const resumeSessionId = requestedResumeSessionId ?? storedResumeSessionId;
126562
127157
  const resumeSource = requestedResumeSessionId ? "request" : storedResumeSessionId ? "meta" : "none";
126563
127158
  self2.deps.logger.debug(`[${sessionId}] Session not found in memory; restoring (project=${project?.kind === "github" ? project.repoFullName : project?.kind === "local" ? `local:${project.localProjectId}` : "none"} resume=${resumeSessionId ? "yes" : "no"} resumeSource=${resumeSource} resumeSessionId=${resumeSessionId ?? "none"})`);
@@ -127274,117 +127869,6 @@ $mem | ConvertTo-Json -Compress
127274
127869
  };
127275
127870
  }
127276
127871
  }
127277
- function resolveSessionDispatchAction(snapshot, machineId) {
127278
- const { meta, history, hasActiveTurn, hasBlockingPendingCreate, hasReusableSession } = snapshot;
127279
- if (!meta || meta.machineId !== machineId) {
127280
- return {
127281
- type: "noop",
127282
- reason: "not-owned"
127283
- };
127284
- }
127285
- if (meta.isArchived) {
127286
- return {
127287
- type: "noop",
127288
- reason: "archived"
127289
- };
127290
- }
127291
- if (hasBlockingPendingCreate) {
127292
- return {
127293
- type: "noop",
127294
- reason: "pending-create"
127295
- };
127296
- }
127297
- const statusType = meta.status?.type;
127298
- if (statusType === "running" || statusType === "requestPermission" || statusType === "initializing") {
127299
- if (hasActiveTurn) {
127300
- return {
127301
- type: "noop",
127302
- reason: "active-session"
127303
- };
127304
- }
127305
- return {
127306
- type: "reset-stale-status",
127307
- statusType
127308
- };
127309
- }
127310
- const turn = findNextDispatchableUserTurn(history, meta);
127311
- if (!turn) {
127312
- return {
127313
- type: "no-dispatchable-turn"
127314
- };
127315
- }
127316
- const mode2 = hasReusableSession || meta.acpSessionId ? "continue" : "create";
127317
- return {
127318
- type: "dispatch",
127319
- mode: mode2,
127320
- turn
127321
- };
127322
- }
127323
- function resolveSessionCancelAction(meta, lastSeenCancelTurn, machineId) {
127324
- if (!meta || meta.machineId !== machineId) {
127325
- return {
127326
- type: "noop",
127327
- reason: "not-owned"
127328
- };
127329
- }
127330
- if (meta.isArchived) {
127331
- return {
127332
- type: "noop",
127333
- reason: "archived"
127334
- };
127335
- }
127336
- const lastCanceledTurn = meta.lastCanceledTurn;
127337
- if (typeof lastCanceledTurn !== "string" || !lastCanceledTurn) {
127338
- return {
127339
- type: "noop",
127340
- reason: "no-cancel-turn"
127341
- };
127342
- }
127343
- if (lastCanceledTurn === lastSeenCancelTurn) {
127344
- return {
127345
- type: "noop",
127346
- reason: "already-seen"
127347
- };
127348
- }
127349
- return {
127350
- type: "cancel",
127351
- turnId: lastCanceledTurn
127352
- };
127353
- }
127354
- function findNextDispatchableUserTurn(history, meta) {
127355
- for (const entry2 of history) {
127356
- if (entry2.role !== "user") {
127357
- continue;
127358
- }
127359
- if (typeof entry2.status === "string") {
127360
- if (entry2.status === "pending" || entry2.status === "seen" || entry2.status === "processing") {
127361
- return entry2;
127362
- }
127363
- continue;
127364
- }
127365
- if (entry2.read === false) {
127366
- return entry2;
127367
- }
127368
- if (entry2.id === meta.processingUserMsgId) {
127369
- return entry2;
127370
- }
127371
- if (entry2.id === meta.latestUserMsgId && entry2.id !== meta.lastHandledUserMsgId) {
127372
- return entry2;
127373
- }
127374
- }
127375
- return null;
127376
- }
127377
- function resolveDispatchTurnInput(entry2) {
127378
- const historyBlocks = historyItemsToInputBlocks(entry2.items);
127379
- const configuredBlocks = normalizeSessionInputBlocks(entry2.inputConfig?.inputBlocks, "");
127380
- const fallbackBlocks = normalizeSessionInputBlocks(void 0, entry2.inputConfig?.prompt ?? "");
127381
- const inputBlocks = configuredBlocks.length > 0 ? configuredBlocks : historyBlocks.length > 0 ? historyBlocks : fallbackBlocks;
127382
- const prompt2 = entry2.inputConfig?.prompt ?? extractPromptPreviewFromInputBlocks(inputBlocks.length > 0 ? inputBlocks : historyBlocks);
127383
- return {
127384
- inputBlocks,
127385
- prompt: prompt2
127386
- };
127387
- }
127388
127872
  const isConfigOptionValueRecord = (value) => {
127389
127873
  if (!value || typeof value !== "object" || Array.isArray(value)) {
127390
127874
  return false;
@@ -127723,14 +128207,10 @@ $mem | ConvertTo-Json -Compress
127723
128207
  if (request.id === this.goalCommandSeenId.get(sessionId)) {
127724
128208
  return;
127725
128209
  }
127726
- const user = await this.userResolver.resolve(meta.userId);
127727
128210
  const result = await this.deps.executionService.controlSessionGoal({
127728
128211
  sessionId,
127729
128212
  threadId: request.threadId,
127730
- command: request.command,
127731
- userId: meta.userId,
127732
- userName: user.name,
127733
- userEmail: user.email
128213
+ command: request.command
127734
128214
  });
127735
128215
  if (result.status === "deferred") {
127736
128216
  this.deps.logger.debug(`[${sessionId}] Deferring hidden goal command ${request.command} until active turn completes`);
@@ -127741,7 +128221,7 @@ $mem | ConvertTo-Json -Compress
127741
128221
  lastGoalCommand: void 0
127742
128222
  });
127743
128223
  if (result.status === "failed") {
127744
- this.deps.logger.debug(`[${sessionId}] Hidden goal command ${request.command} failed: ${result.error}`);
128224
+ this.deps.logger.debug(`[${sessionId}] Goal command ${request.command} failed: ${result.error}`);
127745
128225
  }
127746
128226
  }
127747
128227
  async buildChatRequestFromHistoryEntry(meta, entry2) {
@@ -127763,7 +128243,7 @@ $mem | ConvertTo-Json -Compress
127763
128243
  modelId: entry2.inputConfig?.modelId,
127764
128244
  configOptionValues: entry2.inputConfig?.configOptionValues,
127765
128245
  issuePRMentions: entry2.inputConfig?.issuePRMentions,
127766
- resume: entry2.inputConfig?.resume ?? meta.acpSessionId ?? void 0
128246
+ resume: entry2.inputConfig?.resume ?? resolveDispatchAcpSessionId(meta)
127767
128247
  },
127768
128248
  userTurnId: entry2.id,
127769
128249
  userId: entry2.userId ?? meta.userId,
@@ -127825,7 +128305,7 @@ $mem | ConvertTo-Json -Compress
127825
128305
  modelId: queuedItem.acpSessionConfig?.modelId,
127826
128306
  configOptionValues: isConfigOptionValueRecord(queuedItem.acpSessionConfig?.configOptionValues) ? queuedItem.acpSessionConfig.configOptionValues : void 0,
127827
128307
  issuePRMentions: queuedItem.acpSessionConfig?.issuePRMentions,
127828
- resume: meta.acpSessionId ?? void 0
128308
+ resume: resolveResumableAcpSessionId(meta)
127829
128309
  });
127830
128310
  const pendingEntry = buildPendingUserHistoryEntry({
127831
128311
  userId: queuedItem.userId ?? meta.userId,
@@ -128861,7 +129341,7 @@ $mem | ConvertTo-Json -Compress
128861
129341
  }
128862
129342
  })();
128863
129343
  try {
128864
- await withTimeout(readyPromise, TUNNEL_READY_TIMEOUT_MS, "Timed out waiting for preview tunnel control connection to become ready");
129344
+ await withTimeout$1(readyPromise, TUNNEL_READY_TIMEOUT_MS, "Timed out waiting for preview tunnel control connection to become ready");
128865
129345
  } catch (error2) {
128866
129346
  await close2("Preview tunnel failed to become ready");
128867
129347
  throw error2;
@@ -129684,7 +130164,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
129684
130164
  function asError(error2) {
129685
130165
  return error2 instanceof Error ? error2 : new Error(formatError(error2));
129686
130166
  }
129687
- async function withTimeout(promise, timeoutMs, message) {
130167
+ async function withTimeout$1(promise, timeoutMs, message) {
129688
130168
  let timeoutHandle;
129689
130169
  const timeoutPromise = new Promise((_2, reject) => {
129690
130170
  timeoutHandle = setTimeout(() => reject(new Error(message)), timeoutMs);
@@ -130546,6 +131026,794 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
130546
131026
  return Math.round(this.deps.now?.() ?? getServerNow());
130547
131027
  }
130548
131028
  }
131029
+ const ACP_OPERATION_TIMEOUT_MS = 12e4;
131030
+ const ACP_PROCESS_EXIT_TIMEOUT_MS = 3e3;
131031
+ function waitForChildProcessExit(child, timeoutMs) {
131032
+ if (child.exitCode !== null) {
131033
+ return Promise.resolve(true);
131034
+ }
131035
+ return new Promise((resolve2) => {
131036
+ const timeout2 = setTimeout(() => {
131037
+ child.off("exit", onExit2);
131038
+ resolve2(child.exitCode !== null);
131039
+ }, timeoutMs);
131040
+ const onExit2 = () => {
131041
+ clearTimeout(timeout2);
131042
+ resolve2(true);
131043
+ };
131044
+ child.once("exit", onExit2);
131045
+ });
131046
+ }
131047
+ function signalChildProcess(child, signal) {
131048
+ if (process.platform !== "win32" && typeof child.pid === "number" && child.pid > 0) {
131049
+ process.kill(-child.pid, signal);
131050
+ return;
131051
+ }
131052
+ child.kill(signal);
131053
+ }
131054
+ async function terminateChildProcess(child) {
131055
+ if (child.exitCode !== null) {
131056
+ return;
131057
+ }
131058
+ try {
131059
+ signalChildProcess(child, "SIGTERM");
131060
+ } catch {
131061
+ return;
131062
+ }
131063
+ if (await waitForChildProcessExit(child, ACP_PROCESS_EXIT_TIMEOUT_MS)) {
131064
+ return;
131065
+ }
131066
+ try {
131067
+ signalChildProcess(child, "SIGKILL");
131068
+ } catch {
131069
+ return;
131070
+ }
131071
+ await waitForChildProcessExit(child, ACP_PROCESS_EXIT_TIMEOUT_MS);
131072
+ }
131073
+ async function withTimeout(promise, label2) {
131074
+ let timeout2 = null;
131075
+ try {
131076
+ return await Promise.race([
131077
+ promise,
131078
+ new Promise((_resolve, reject) => {
131079
+ timeout2 = setTimeout(() => reject(new Error(`${label2} timed out after ${ACP_OPERATION_TIMEOUT_MS}ms`)), ACP_OPERATION_TIMEOUT_MS);
131080
+ })
131081
+ ]);
131082
+ } finally {
131083
+ if (timeout2) {
131084
+ clearTimeout(timeout2);
131085
+ }
131086
+ }
131087
+ }
131088
+ class AcpReplayCollectorClient {
131089
+ notifications = [];
131090
+ async requestPermission() {
131091
+ return {
131092
+ outcome: {
131093
+ outcome: "cancelled"
131094
+ }
131095
+ };
131096
+ }
131097
+ async sessionUpdate(params) {
131098
+ this.notifications.push(parseSessionNotification(params));
131099
+ }
131100
+ }
131101
+ function getProviderLabel$1(provider2) {
131102
+ return provider2 === "claude" ? "Claude" : "Codex";
131103
+ }
131104
+ async function createHistoryAcpConnection(args2) {
131105
+ const setting = resolveACPSetting({
131106
+ cliType: "builtin",
131107
+ agentType: args2.provider
131108
+ });
131109
+ const env2 = setting.exec.env ? {
131110
+ ...process.env,
131111
+ ...setting.exec.env
131112
+ } : process.env;
131113
+ const agentProcess = spawnAcpProcess({
131114
+ cliType: "builtin",
131115
+ agentType: args2.provider,
131116
+ workdir: args2.workdir,
131117
+ env: env2
131118
+ });
131119
+ agentProcess.stderr?.setEncoding("utf8");
131120
+ agentProcess.stderr?.on("data", (chunk) => {
131121
+ if (!chunk) return;
131122
+ args2.logger.debug(`[${args2.provider}-history-sync] ACP stderr: ${chunk.slice(0, 1200)}`);
131123
+ });
131124
+ if (!agentProcess.stdout || !agentProcess.stdin) {
131125
+ await terminateChildProcess(agentProcess);
131126
+ throw new Error(`${getProviderLabel$1(args2.provider)} ACP process did not expose stdio streams`);
131127
+ }
131128
+ const output = createStdoutReadableStream(agentProcess.stdout);
131129
+ const input2 = createStdinWritableStream(agentProcess.stdin);
131130
+ const stream2 = ndJsonStream(input2, output);
131131
+ const collector = new AcpReplayCollectorClient();
131132
+ const connection = new ClientSideConnection(() => collector, stream2);
131133
+ try {
131134
+ const initResponse = await withTimeout(connection.initialize({
131135
+ protocolVersion: PROTOCOL_VERSION,
131136
+ clientCapabilities: {
131137
+ terminal: false,
131138
+ fs: {
131139
+ readTextFile: false,
131140
+ writeTextFile: false
131141
+ }
131142
+ }
131143
+ }), `${getProviderLabel$1(args2.provider)} ACP initialize`);
131144
+ return {
131145
+ agentProcess,
131146
+ connection,
131147
+ collector,
131148
+ initResponse
131149
+ };
131150
+ } catch (error2) {
131151
+ await terminateChildProcess(agentProcess);
131152
+ throw error2;
131153
+ }
131154
+ }
131155
+ async function resolveCatalogQueryPaths(rootPath) {
131156
+ const resolved = path__default.resolve(rootPath);
131157
+ let real = null;
131158
+ try {
131159
+ real = await fs$5.realpath(resolved);
131160
+ } catch {
131161
+ real = null;
131162
+ }
131163
+ const paths = [
131164
+ resolved
131165
+ ];
131166
+ if (real && real !== resolved) {
131167
+ paths.push(real);
131168
+ }
131169
+ return paths;
131170
+ }
131171
+ async function listPaginatedHistorySessions(cwd, listPage) {
131172
+ const sessions = [];
131173
+ let cursor;
131174
+ do {
131175
+ const response = await listPage({
131176
+ cwd,
131177
+ cursor
131178
+ });
131179
+ sessions.push(...response.sessions);
131180
+ cursor = response.nextCursor;
131181
+ } while (cursor);
131182
+ return sessions;
131183
+ }
131184
+ async function listHistorySessionsForLocalProject(args2) {
131185
+ const queryPaths = await resolveCatalogQueryPaths(args2.rootPath);
131186
+ const bySessionId = /* @__PURE__ */ new Map();
131187
+ for (const cwd of queryPaths) {
131188
+ const { agentProcess, connection, initResponse } = await createHistoryAcpConnection({
131189
+ provider: args2.provider,
131190
+ workdir: cwd,
131191
+ logger: args2.logger
131192
+ });
131193
+ try {
131194
+ if (!initResponse.agentCapabilities?.sessionCapabilities?.list) {
131195
+ throw new Error(`${getProviderLabel$1(args2.provider)} ACP agent does not advertise sessionCapabilities.list`);
131196
+ }
131197
+ const sessions = await listPaginatedHistorySessions(cwd, async ({ cursor }) => withTimeout(connection.listSessions({
131198
+ cwd,
131199
+ cursor
131200
+ }), `${getProviderLabel$1(args2.provider)} ACP listSessions (${cwd})`));
131201
+ for (const session of sessions) {
131202
+ bySessionId.set(session.sessionId, session);
131203
+ }
131204
+ } finally {
131205
+ await terminateChildProcess(agentProcess);
131206
+ }
131207
+ }
131208
+ return {
131209
+ sessions: [
131210
+ ...bySessionId.values()
131211
+ ],
131212
+ queryPaths
131213
+ };
131214
+ }
131215
+ async function loadHistorySessionReplay(args2) {
131216
+ const cwd = path__default.resolve(args2.rootPath);
131217
+ const { agentProcess, connection, collector, initResponse } = await createHistoryAcpConnection({
131218
+ provider: args2.provider,
131219
+ workdir: cwd,
131220
+ logger: args2.logger
131221
+ });
131222
+ try {
131223
+ if (!initResponse.agentCapabilities?.loadSession) {
131224
+ throw new Error(`${getProviderLabel$1(args2.provider)} ACP agent does not advertise loadSession`);
131225
+ }
131226
+ await withTimeout(connection.loadSession({
131227
+ sessionId: args2.acpSessionId,
131228
+ cwd,
131229
+ mcpServers: []
131230
+ }), `${getProviderLabel$1(args2.provider)} ACP loadSession (${args2.acpSessionId})`);
131231
+ return collector.notifications;
131232
+ } catch (error2) {
131233
+ throw new Error(`Failed to load ${getProviderLabel$1(args2.provider)} session ${args2.acpSessionId}: ${formatErrorMessage(error2)}`, {
131234
+ cause: error2
131235
+ });
131236
+ } finally {
131237
+ await terminateChildProcess(agentProcess);
131238
+ }
131239
+ }
131240
+ const syncLeases = /* @__PURE__ */ new Set();
131241
+ const machineCatalogWriteChains = /* @__PURE__ */ new Map();
131242
+ async function withMachineCatalogWriteLock(machineRoomId, fn) {
131243
+ const prev = machineCatalogWriteChains.get(machineRoomId);
131244
+ const current2 = (async () => {
131245
+ if (prev) {
131246
+ await prev.catch(() => {
131247
+ });
131248
+ }
131249
+ return fn();
131250
+ })();
131251
+ machineCatalogWriteChains.set(machineRoomId, current2);
131252
+ try {
131253
+ return await current2;
131254
+ } finally {
131255
+ if (machineCatalogWriteChains.get(machineRoomId) === current2) {
131256
+ machineCatalogWriteChains.delete(machineRoomId);
131257
+ }
131258
+ }
131259
+ }
131260
+ function emptySummary() {
131261
+ return {
131262
+ listed: 0,
131263
+ imported: 0,
131264
+ refreshed: 0,
131265
+ skipped: 0,
131266
+ conflicted: 0,
131267
+ failed: 0,
131268
+ failures: []
131269
+ };
131270
+ }
131271
+ function stableJson(value) {
131272
+ if (value === null || typeof value !== "object") {
131273
+ return JSON.stringify(value);
131274
+ }
131275
+ if (Array.isArray(value)) {
131276
+ return `[${value.map((item) => stableJson(item)).join(",")}]`;
131277
+ }
131278
+ const record2 = value;
131279
+ const entries = Object.keys(record2).filter((key2) => record2[key2] !== void 0).sort().map((key2) => `${JSON.stringify(key2)}:${stableJson(record2[key2])}`);
131280
+ return `{${entries.join(",")}}`;
131281
+ }
131282
+ function hashText(value) {
131283
+ return createHash$1("sha256").update(value).digest("hex");
131284
+ }
131285
+ function normalizeHistoryEntryForHash(entry2) {
131286
+ return {
131287
+ role: entry2.role,
131288
+ items: entry2.items ?? [],
131289
+ plan: entry2.plan ?? []
131290
+ };
131291
+ }
131292
+ function hashHistoryEntry(entry2) {
131293
+ return hashText(stableJson(normalizeHistoryEntryForHash(entry2)));
131294
+ }
131295
+ function materializeReplay(args2) {
131296
+ let tempId = 0;
131297
+ const nowIso = new Date(getServerNow()).toISOString();
131298
+ const replay = buildHistoryReplayImport(args2.replayNotifications, {
131299
+ agentType: args2.provider,
131300
+ userId: args2.userId,
131301
+ now: () => nowIso,
131302
+ createId: () => `${args2.provider}:${args2.acpSessionId}:tmp:${tempId++}`,
131303
+ mode: "imported_snapshot"
131304
+ });
131305
+ const turnHashes = replay.history.map(hashHistoryEntry);
131306
+ const history = replay.history.map((entry2, index2) => ({
131307
+ ...entry2,
131308
+ id: `${args2.provider}:${args2.acpSessionId}:turn:${index2}:${turnHashes[index2].slice(0, 16)}`
131309
+ }));
131310
+ return {
131311
+ history,
131312
+ turnHashes,
131313
+ replayDigest: hashText(turnHashes.join("\n"))
131314
+ };
131315
+ }
131316
+ function isPrefix(prefix, value) {
131317
+ if (prefix.length > value.length) {
131318
+ return false;
131319
+ }
131320
+ for (let index2 = 0; index2 < prefix.length; index2 += 1) {
131321
+ if (prefix[index2] !== value[index2]) {
131322
+ return false;
131323
+ }
131324
+ }
131325
+ return true;
131326
+ }
131327
+ function decideHistoryRefresh(args2) {
131328
+ if (args2.replayDigest === args2.externalHistory.replayDigest) {
131329
+ return {
131330
+ status: "skipped",
131331
+ reason: "digest_match"
131332
+ };
131333
+ }
131334
+ if (!isPrefix(args2.externalHistory.importedTurnHashes, args2.turnHashes)) {
131335
+ return {
131336
+ status: "conflicted",
131337
+ reason: "prefix_mismatch"
131338
+ };
131339
+ }
131340
+ if (args2.currentHistoryLength !== void 0 && args2.currentHistoryLength !== args2.externalHistory.importedTurnCount) {
131341
+ return {
131342
+ status: "conflicted",
131343
+ reason: "local_history_has_untracked_suffix"
131344
+ };
131345
+ }
131346
+ const appendFromIndex = args2.externalHistory.importedTurnCount;
131347
+ return args2.turnHashes.length > appendFromIndex ? {
131348
+ status: "refreshed",
131349
+ reason: "prefix_append",
131350
+ appendFromIndex
131351
+ } : {
131352
+ status: "skipped",
131353
+ reason: "empty_suffix",
131354
+ appendFromIndex
131355
+ };
131356
+ }
131357
+ async function listWorkspaceSessionMetas(manager) {
131358
+ const scanner = manager.repo.getMeta();
131359
+ if (!scanner) {
131360
+ return [];
131361
+ }
131362
+ const roomIds = /* @__PURE__ */ new Set();
131363
+ for (const row of await scanner.scan({
131364
+ prefix: [
131365
+ "m"
131366
+ ]
131367
+ })) {
131368
+ const key2 = row.key;
131369
+ if (!Array.isArray(key2) || key2.length < 2) {
131370
+ continue;
131371
+ }
131372
+ const roomId = key2[1];
131373
+ if (typeof roomId === "string" && isSessionDocRoomId(roomId)) {
131374
+ roomIds.add(roomId);
131375
+ }
131376
+ }
131377
+ const metas = await Promise.all([
131378
+ ...roomIds
131379
+ ].map(async (roomId) => {
131380
+ const record2 = await manager.repo.getDocMeta(roomId);
131381
+ if (!record2?.meta || isLoroRepoDocDeleted(record2)) {
131382
+ return null;
131383
+ }
131384
+ const sessionId = roomId.slice("session-".length);
131385
+ return {
131386
+ sessionId,
131387
+ meta: record2.meta
131388
+ };
131389
+ }));
131390
+ return metas.filter((meta) => meta !== null);
131391
+ }
131392
+ function buildExistingHistorySessionIndex(metas, machineId, provider2) {
131393
+ const index2 = /* @__PURE__ */ new Map();
131394
+ for (const entry2 of metas) {
131395
+ if (entry2.meta.machineId !== machineId) continue;
131396
+ if (entry2.meta.cliType !== "builtin") continue;
131397
+ if (entry2.meta.agentType !== provider2) continue;
131398
+ const acpSessionIds = /* @__PURE__ */ new Set();
131399
+ if (entry2.meta.externalHistory?.provider === provider2) {
131400
+ const sourceAcpSessionId = entry2.meta.externalHistory.sourceAcpSessionId;
131401
+ if (sourceAcpSessionId) {
131402
+ acpSessionIds.add(sourceAcpSessionId);
131403
+ }
131404
+ if (entry2.meta.acpSessionId && entry2.meta.acpSessionId !== sourceAcpSessionId) {
131405
+ acpSessionIds.add(entry2.meta.acpSessionId);
131406
+ }
131407
+ } else if (entry2.meta.acpSessionId) {
131408
+ acpSessionIds.add(entry2.meta.acpSessionId);
131409
+ }
131410
+ for (const acpSessionId of acpSessionIds) {
131411
+ index2.set(acpSessionId, entry2);
131412
+ }
131413
+ }
131414
+ return index2;
131415
+ }
131416
+ function getProviderLabel(provider2) {
131417
+ return provider2 === "claude" ? "Claude" : "Codex";
131418
+ }
131419
+ function resolveSessionTitle(info, provider2) {
131420
+ const title2 = info.title?.trim();
131421
+ return title2 || `${getProviderLabel(provider2)} session`;
131422
+ }
131423
+ function parseUpdatedAtMs(updatedAt) {
131424
+ if (!updatedAt) return 0;
131425
+ const parsed = Date.parse(updatedAt);
131426
+ return Number.isFinite(parsed) ? parsed : 0;
131427
+ }
131428
+ function compareCatalogItems(left2, right2) {
131429
+ const leftUpdatedAt = parseUpdatedAtMs(left2.updatedAt);
131430
+ const rightUpdatedAt = parseUpdatedAtMs(right2.updatedAt);
131431
+ if (leftUpdatedAt !== rightUpdatedAt) {
131432
+ return rightUpdatedAt - leftUpdatedAt;
131433
+ }
131434
+ return left2.title.localeCompare(right2.title);
131435
+ }
131436
+ function getHistoryCatalogStatus(existing) {
131437
+ if (!existing) return "available";
131438
+ return existing.meta.externalHistory?.status === "sync_conflict" ? "sync_conflict" : "imported";
131439
+ }
131440
+ function buildCatalogItem(provider2, info, existing) {
131441
+ const acpSessionId = info.sessionId;
131442
+ return {
131443
+ acpSessionId,
131444
+ title: resolveSessionTitle(info, provider2),
131445
+ updatedAt: info.updatedAt ?? void 0,
131446
+ importedSessionId: existing?.sessionId,
131447
+ status: getHistoryCatalogStatus(existing)
131448
+ };
131449
+ }
131450
+ function shouldSkipBySourceUpdatedAt(info, externalHistory) {
131451
+ if (externalHistory.status === "metadata_only") {
131452
+ return false;
131453
+ }
131454
+ if (!info.updatedAt || !externalHistory.sourceUpdatedAt) {
131455
+ return false;
131456
+ }
131457
+ const next = Date.parse(info.updatedAt);
131458
+ const current2 = Date.parse(externalHistory.sourceUpdatedAt);
131459
+ return Number.isFinite(next) && Number.isFinite(current2) && next <= current2;
131460
+ }
131461
+ function resolveSourceUpdatedAtMs(info, fallback2) {
131462
+ if (!info.updatedAt) {
131463
+ return fallback2;
131464
+ }
131465
+ const parsed = Date.parse(info.updatedAt);
131466
+ return Number.isFinite(parsed) ? parsed : fallback2;
131467
+ }
131468
+ function buildExternalHistoryMeta(args2) {
131469
+ return {
131470
+ provider: args2.provider,
131471
+ source: args2.provider === "claude" ? "local-claude-home" : "local-codex-home",
131472
+ sourceAcpSessionId: args2.sourceAcpSessionId,
131473
+ sourceUpdatedAt: args2.sourceUpdatedAt ?? void 0,
131474
+ replayDigest: args2.materialized.replayDigest,
131475
+ importedTurnCount: args2.materialized.turnHashes.length,
131476
+ importedTurnHashes: args2.materialized.turnHashes,
131477
+ lastSyncAt: getServerNow(),
131478
+ status: args2.status ?? "synced",
131479
+ conflictReason: args2.conflictReason
131480
+ };
131481
+ }
131482
+ function buildMetadataOnlyExternalHistoryMeta(args2) {
131483
+ return {
131484
+ provider: args2.provider,
131485
+ source: args2.provider === "claude" ? "local-claude-home" : "local-codex-home",
131486
+ sourceAcpSessionId: args2.sourceAcpSessionId,
131487
+ sourceUpdatedAt: args2.sourceUpdatedAt ?? void 0,
131488
+ importedTurnCount: 0,
131489
+ importedTurnHashes: [],
131490
+ lastSyncAt: getServerNow(),
131491
+ status: "metadata_only"
131492
+ };
131493
+ }
131494
+ class LocalProjectHistorySyncService {
131495
+ constructor(manager, logger2, context2, provider2 = "codex") {
131496
+ this.manager = manager;
131497
+ this.logger = logger2;
131498
+ this.context = context2;
131499
+ this.provider = provider2;
131500
+ }
131501
+ provider;
131502
+ async syncLocalProject(args2) {
131503
+ const leaseKey = `${this.provider}:${this.context.workspaceId}:${this.context.machineId}:${args2.localProjectId}`;
131504
+ if (syncLeases.has(leaseKey)) {
131505
+ throw new Error(`${getProviderLabel(this.provider)} history sync is already running for this local project`);
131506
+ }
131507
+ syncLeases.add(leaseKey);
131508
+ try {
131509
+ return await this.syncLocalProjectInner(args2);
131510
+ } finally {
131511
+ syncLeases.delete(leaseKey);
131512
+ }
131513
+ }
131514
+ async syncLocalProjectInner(args2) {
131515
+ const snapshot = await this.listCatalogSnapshot(args2.rootPath);
131516
+ return await this.writeCatalogResult({
131517
+ localProjectId: args2.localProjectId,
131518
+ sessions: snapshot.sessions,
131519
+ existingByAcpSessionId: snapshot.existingByAcpSessionId
131520
+ });
131521
+ }
131522
+ async importLocalProjectSessions(args2) {
131523
+ const leaseKey = `${this.provider}:${this.context.workspaceId}:${this.context.machineId}:${args2.localProjectId}`;
131524
+ if (syncLeases.has(leaseKey)) {
131525
+ throw new Error(`${getProviderLabel(this.provider)} history sync is already running for this local project`);
131526
+ }
131527
+ syncLeases.add(leaseKey);
131528
+ try {
131529
+ return await this.importLocalProjectSessionsInner(args2);
131530
+ } finally {
131531
+ syncLeases.delete(leaseKey);
131532
+ }
131533
+ }
131534
+ async importLocalProjectSessionsInner(args2) {
131535
+ const summary2 = emptySummary();
131536
+ const selectedIds = [
131537
+ ...new Set(args2.acpSessionIds)
131538
+ ];
131539
+ summary2.listed = selectedIds.length;
131540
+ const snapshot = await this.listCatalogSnapshot(args2.rootPath);
131541
+ const infoByAcpSessionId = new Map(snapshot.sessions.map((info) => [
131542
+ info.sessionId,
131543
+ info
131544
+ ]));
131545
+ const project = {
131546
+ kind: "local",
131547
+ localProjectId: args2.localProjectId
131548
+ };
131549
+ for (const selectedId of selectedIds) {
131550
+ const acpSessionId = selectedId;
131551
+ const info = infoByAcpSessionId.get(selectedId);
131552
+ try {
131553
+ if (!info) {
131554
+ throw new Error(`${getProviderLabel(this.provider)} session was not found in the local project catalog`);
131555
+ }
131556
+ const existing = snapshot.existingByAcpSessionId.get(selectedId);
131557
+ if (!existing) {
131558
+ const importedSession = await this.importNewSession({
131559
+ info,
131560
+ acpSessionId,
131561
+ project
131562
+ });
131563
+ snapshot.existingByAcpSessionId.set(selectedId, importedSession);
131564
+ summary2.imported += 1;
131565
+ continue;
131566
+ }
131567
+ const status = await this.refreshExistingSession({
131568
+ existing,
131569
+ info,
131570
+ acpSessionId,
131571
+ rootPath: args2.rootPath
131572
+ });
131573
+ summary2[status] += 1;
131574
+ } catch (error2) {
131575
+ summary2.failed += 1;
131576
+ summary2.failures.push({
131577
+ acpSessionId,
131578
+ message: formatErrorMessage(error2)
131579
+ });
131580
+ this.logger.debug(`[${this.provider}-history-sync] Failed to import ${getProviderLabel(this.provider)} session ${acpSessionId}: ${formatErrorMessage(error2)}`);
131581
+ }
131582
+ }
131583
+ const catalog = await this.writeCatalogResult({
131584
+ localProjectId: args2.localProjectId,
131585
+ sessions: snapshot.sessions,
131586
+ existingByAcpSessionId: snapshot.existingByAcpSessionId
131587
+ });
131588
+ return {
131589
+ summary: summary2,
131590
+ catalog
131591
+ };
131592
+ }
131593
+ async listCatalogSnapshot(rootPath) {
131594
+ const catalog = await listHistorySessionsForLocalProject({
131595
+ provider: this.provider,
131596
+ rootPath,
131597
+ logger: this.logger
131598
+ });
131599
+ const sessionMetas = await listWorkspaceSessionMetas(this.manager);
131600
+ const existingByAcpSessionId = buildExistingHistorySessionIndex(sessionMetas, this.context.machineId, this.provider);
131601
+ return {
131602
+ sessions: catalog.sessions,
131603
+ existingByAcpSessionId
131604
+ };
131605
+ }
131606
+ async writeCatalogResult(args2) {
131607
+ const lastListedAt = Math.round(getServerNow());
131608
+ const sessions = args2.sessions.map((info) => buildCatalogItem(this.provider, info, args2.existingByAcpSessionId.get(info.sessionId))).sort(compareCatalogItems);
131609
+ const catalog = {
131610
+ listed: sessions.length,
131611
+ lastListedAt,
131612
+ sessions
131613
+ };
131614
+ const machineRoomId = getMachineRoomId(this.context.machineId);
131615
+ await withMachineCatalogWriteLock(machineRoomId, async () => {
131616
+ const current2 = await this.manager.repo.getDocMeta(machineRoomId);
131617
+ const rawExisting = current2?.meta?.localProjects;
131618
+ const existing = rawExisting && typeof rawExisting === "object" ? rawExisting : {};
131619
+ const previous = existing[args2.localProjectId];
131620
+ if (!previous) {
131621
+ return;
131622
+ }
131623
+ await this.manager.repo.upsertDocMeta(machineRoomId, {
131624
+ localProjects: {
131625
+ ...existing,
131626
+ [args2.localProjectId]: {
131627
+ ...previous,
131628
+ history: {
131629
+ ...previous.history ?? {},
131630
+ [this.provider]: {
131631
+ lastListedAt,
131632
+ sessions: Object.fromEntries(sessions.map((item) => [
131633
+ item.acpSessionId,
131634
+ item
131635
+ ]))
131636
+ }
131637
+ }
131638
+ }
131639
+ }
131640
+ });
131641
+ });
131642
+ return catalog;
131643
+ }
131644
+ async importNewSession(args2) {
131645
+ const sessionId = v4();
131646
+ const roomId = getSessionRoomId(sessionId);
131647
+ const nowMs = getServerNow();
131648
+ const lastMessageAt = resolveSourceUpdatedAtMs(args2.info, nowMs);
131649
+ const meta = {
131650
+ id: sessionId,
131651
+ machineId: this.context.machineId,
131652
+ createdAt: new Date(nowMs).toISOString(),
131653
+ userId: this.context.userId,
131654
+ status: SessionStatusFactory.idle(),
131655
+ isArchived: false,
131656
+ origin: this.provider,
131657
+ cliType: "builtin",
131658
+ agentType: this.provider,
131659
+ project: args2.project,
131660
+ title: resolveSessionTitle(args2.info, this.provider),
131661
+ lastMessageAt,
131662
+ externalHistory: buildMetadataOnlyExternalHistoryMeta({
131663
+ provider: this.provider,
131664
+ sourceAcpSessionId: args2.acpSessionId,
131665
+ sourceUpdatedAt: args2.info.updatedAt
131666
+ })
131667
+ };
131668
+ await this.manager.repo.upsertDocMeta(roomId, meta);
131669
+ return {
131670
+ sessionId,
131671
+ meta
131672
+ };
131673
+ }
131674
+ async refreshExistingSession(args2) {
131675
+ const externalHistory = args2.existing.meta.externalHistory;
131676
+ if (externalHistory?.provider !== this.provider) {
131677
+ return "skipped";
131678
+ }
131679
+ if (shouldSkipBySourceUpdatedAt(args2.info, externalHistory)) {
131680
+ return "skipped";
131681
+ }
131682
+ const replayNotifications = await loadHistorySessionReplay({
131683
+ provider: this.provider,
131684
+ rootPath: args2.rootPath,
131685
+ acpSessionId: args2.acpSessionId,
131686
+ logger: this.logger
131687
+ });
131688
+ const materialized = materializeReplay({
131689
+ provider: this.provider,
131690
+ acpSessionId: args2.acpSessionId,
131691
+ replayNotifications,
131692
+ userId: this.context.userId
131693
+ });
131694
+ const replayDecision = decideHistoryRefresh({
131695
+ externalHistory,
131696
+ replayDigest: materialized.replayDigest,
131697
+ turnHashes: materialized.turnHashes
131698
+ });
131699
+ if (replayDecision.reason === "digest_match") {
131700
+ await this.manager.repo.upsertDocMeta(getSessionRoomId(args2.existing.sessionId), {
131701
+ origin: this.provider,
131702
+ externalHistory: buildExternalHistoryMeta({
131703
+ provider: this.provider,
131704
+ sourceAcpSessionId: args2.acpSessionId,
131705
+ sourceUpdatedAt: args2.info.updatedAt,
131706
+ materialized
131707
+ })
131708
+ });
131709
+ return "skipped";
131710
+ }
131711
+ if (replayDecision.status === "conflicted") {
131712
+ await this.markConflict(args2.existing.sessionId, args2.info, materialized, replayDecision.reason);
131713
+ return "conflicted";
131714
+ }
131715
+ const sessionDoc = await this.manager.getOrCreateSessionDoc(args2.existing.sessionId);
131716
+ const currentHistory = await sessionDoc.getHistory();
131717
+ const appendDecision = decideHistoryRefresh({
131718
+ externalHistory,
131719
+ replayDigest: materialized.replayDigest,
131720
+ turnHashes: materialized.turnHashes,
131721
+ currentHistoryLength: currentHistory.length
131722
+ });
131723
+ if (appendDecision.status === "conflicted") {
131724
+ await this.markConflict(args2.existing.sessionId, args2.info, materialized, appendDecision.reason);
131725
+ const synced2 = await sessionDoc.waitUntilSynced();
131726
+ if (!synced2) {
131727
+ this.logger.debug(`[${this.provider}-history-sync] Conflict marker for ${args2.existing.sessionId} did not confirm sync before unload; clients may see the previous state until next sync.`);
131728
+ }
131729
+ await this.manager.cleanSessionDoc(args2.existing.sessionId, {
131730
+ preserveStatus: true
131731
+ });
131732
+ return "conflicted";
131733
+ }
131734
+ const suffix = materialized.history.slice(appendDecision.appendFromIndex);
131735
+ await sessionDoc.updateHistory((history) => [
131736
+ ...history,
131737
+ ...suffix
131738
+ ]);
131739
+ await this.manager.repo.upsertDocMeta(getSessionRoomId(args2.existing.sessionId), {
131740
+ origin: this.provider,
131741
+ lastMessageAt: resolveSourceUpdatedAtMs(args2.info, getServerNow()),
131742
+ externalHistory: buildExternalHistoryMeta({
131743
+ provider: this.provider,
131744
+ sourceAcpSessionId: args2.acpSessionId,
131745
+ sourceUpdatedAt: args2.info.updatedAt,
131746
+ materialized
131747
+ })
131748
+ });
131749
+ const synced = await sessionDoc.waitUntilSynced();
131750
+ if (!synced) {
131751
+ this.logger.debug(`[${this.provider}-history-sync] Appended history for ${args2.existing.sessionId} did not confirm sync before unload; other clients may see the previous state until next sync.`);
131752
+ }
131753
+ await this.manager.cleanSessionDoc(args2.existing.sessionId, {
131754
+ preserveStatus: true
131755
+ });
131756
+ return suffix.length > 0 ? "refreshed" : "skipped";
131757
+ }
131758
+ async markConflict(sessionId, info, materialized, reason) {
131759
+ await this.manager.repo.upsertDocMeta(getSessionRoomId(sessionId), {
131760
+ origin: this.provider,
131761
+ externalHistory: buildExternalHistoryMeta({
131762
+ provider: this.provider,
131763
+ sourceAcpSessionId: info.sessionId,
131764
+ sourceUpdatedAt: info.updatedAt,
131765
+ materialized,
131766
+ status: "sync_conflict",
131767
+ conflictReason: reason
131768
+ })
131769
+ });
131770
+ }
131771
+ }
131772
+ const HISTORY_REQUEST_TYPES = /* @__PURE__ */ new Set([
131773
+ "local-project/sync-history",
131774
+ "local-project/import-history"
131775
+ ]);
131776
+ function isHistoryRequestType(value) {
131777
+ return HISTORY_REQUEST_TYPES.has(value);
131778
+ }
131779
+ function precheckLocalProjectHistoryRequest(args2) {
131780
+ const { request, expectedMachineId, expectedWorkspaceId } = args2;
131781
+ if (request.machineId !== expectedMachineId) {
131782
+ return {
131783
+ ok: false,
131784
+ error: "machine_mismatch",
131785
+ message: `Machine mismatch: expected ${expectedMachineId}`
131786
+ };
131787
+ }
131788
+ if (!isHistoryRequestType(request.type)) {
131789
+ return {
131790
+ ok: false,
131791
+ error: "invalid_request",
131792
+ message: `Unsupported remote local project control request: ${request.type}`
131793
+ };
131794
+ }
131795
+ const historyRequest = request;
131796
+ const requesterUserId = historyRequest.requestedByUserId?.trim();
131797
+ if (!requesterUserId) {
131798
+ return {
131799
+ ok: false,
131800
+ error: "invalid_request",
131801
+ message: "Local project history requests require requestedByUserId"
131802
+ };
131803
+ }
131804
+ if (historyRequest.workspaceId !== expectedWorkspaceId) {
131805
+ return {
131806
+ ok: false,
131807
+ error: "workspace_not_found",
131808
+ message: `Workspace mismatch: expected ${expectedWorkspaceId}`
131809
+ };
131810
+ }
131811
+ return {
131812
+ ok: true,
131813
+ request: historyRequest,
131814
+ requesterUserId
131815
+ };
131816
+ }
130549
131817
  const SESSION_IMAGE_MIME_TYPE_BY_EXTENSION = {
130550
131818
  png: "image/png",
130551
131819
  jpg: "image/jpeg",
@@ -130555,9 +131823,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
130555
131823
  };
130556
131824
  const CODEX_IMAGE_GENERATION_TERMINAL_STATUSES = /* @__PURE__ */ new Set([
130557
131825
  "completed",
130558
- "failed",
130559
- "cancelled",
130560
- "incomplete"
131826
+ "failed"
130561
131827
  ]);
130562
131828
  function isCodexImageGenerationTerminalStatus(status) {
130563
131829
  return CODEX_IMAGE_GENERATION_TERMINAL_STATUSES.has(status.trim().toLowerCase());
@@ -130714,6 +131980,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
130714
131980
  requestedByUserId,
130715
131981
  reason
130716
131982
  }),
131983
+ dispatchLocalProjectControl: async (request) => await this.dispatchLocalProjectControlViaRpc(request),
130717
131984
  onFatalAuthFailure: (error2) => this.onFatalAuthFailure?.(error2)
130718
131985
  });
130719
131986
  } else {
@@ -131007,11 +132274,44 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131007
132274
  }
131008
132275
  handleCodexImageGenerationBegin(sessionId, event) {
131009
132276
  const turnId = this.store.getTurnId(sessionId) ?? null;
131010
- this.store.get(sessionId).codexImageGenerationTurnIds.set(event.callId, turnId);
132277
+ const state2 = this.store.get(sessionId);
132278
+ state2.codexImageGenerationTurnIds.set(event.callId, turnId);
132279
+ state2.codexImageGenerationActiveCallIds.add(event.callId);
132280
+ this.enqueueCodexImageGenerationActivityStatusSync(sessionId);
131011
132281
  this.logger.debug(`[${sessionId}] Codex image generation started (callId=${event.callId} turnId=${turnId ?? "none"})`);
131012
132282
  }
132283
+ enqueueCodexImageGenerationActivityStatusSync(sessionId) {
132284
+ const state2 = this.store.get(sessionId);
132285
+ const task = state2.codexImageGenerationActivityStatusChain.catch(() => void 0).then(async () => {
132286
+ const currentState = this.store.get(sessionId);
132287
+ const hasActiveImageGeneration = currentState.codexImageGenerationActiveCallIds.size > 0;
132288
+ const sessionDoc = await this.workspaceDocument.getOrCreateSessionDoc(sessionId);
132289
+ const status = (await sessionDoc.getMetaState())?.status;
132290
+ if (hasActiveImageGeneration) {
132291
+ if (status?.type !== "requestPermission") {
132292
+ await sessionDoc.setStatus(SessionStatusFactory.running("image_generation"));
132293
+ }
132294
+ return;
132295
+ }
132296
+ if (status?.type === "running" && status.activity === "image_generation") {
132297
+ await sessionDoc.setStatus(SessionStatusFactory.running());
132298
+ }
132299
+ });
132300
+ state2.codexImageGenerationActivityStatusChain = task;
132301
+ void task.catch((error2) => {
132302
+ try {
132303
+ this.logger.debug(`[${sessionId}] Failed to sync Codex image generation activity: ${formatErrorMessage(error2)}`);
132304
+ } catch {
132305
+ }
132306
+ });
132307
+ }
131013
132308
  handleCodexImageGenerationEnd(sessionId, event) {
131014
132309
  const state2 = this.store.get(sessionId);
132310
+ const isTerminal2 = isCodexImageGenerationTerminalStatus(event.status);
132311
+ if (isTerminal2) {
132312
+ state2.codexImageGenerationActiveCallIds.delete(event.callId);
132313
+ this.enqueueCodexImageGenerationActivityStatusSync(sessionId);
132314
+ }
131015
132315
  if (state2.codexImageGenerationUploadedCallIds.has(event.callId)) {
131016
132316
  this.logger.debug(`[${sessionId}] Ignoring duplicate Codex image generation upload (callId=${event.callId} status=${event.status})`);
131017
132317
  return;
@@ -131022,7 +132322,6 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131022
132322
  }
131023
132323
  const cachedTurnId = state2.codexImageGenerationTurnIds.get(event.callId);
131024
132324
  const capturedTurnId = cachedTurnId !== void 0 ? cachedTurnId : this.store.getTurnId(sessionId) ?? null;
131025
- const isTerminal2 = isCodexImageGenerationTerminalStatus(event.status);
131026
132325
  const savedPath = this.resolveCodexGeneratedImagePath(sessionId, event.savedPath);
131027
132326
  if (!savedPath) {
131028
132327
  if (isTerminal2) {
@@ -131567,6 +132866,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
131567
132866
  cliVersion: this.cliVersion,
131568
132867
  os: process.platform,
131569
132868
  rpcVersion: supportsStreamsRpc ? LORO_STREAMS_RPC_VERSION : void 0,
132869
+ supportsLocalProjectHistoryRpc: supportsStreamsRpc,
131570
132870
  supportRegistryAgentTypes: this.supportRegistryAgentTypes,
131571
132871
  sessions: [],
131572
132872
  needToArchiveSessions: {},
@@ -132404,6 +133704,7 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
132404
133704
  cliVersion: this.cliVersion,
132405
133705
  os: process.platform,
132406
133706
  rpcVersion: supportsStreamsRpc ? LORO_STREAMS_RPC_VERSION : machineMeta?.rpcVersion,
133707
+ supportsLocalProjectHistoryRpc: supportsStreamsRpc,
132407
133708
  supportRegistryAgentTypes: this.supportRegistryAgentTypes,
132408
133709
  acpCapabilities: machineMeta?.acpCapabilities,
132409
133710
  sessions: machineMeta?.sessions ?? [],
@@ -133044,8 +134345,74 @@ ${escapeHtmlScriptContent(VISUAL_ANNOTATION_INSPECTOR_BROWSER_SCRIPT)}
133044
134345
  }
133045
134346
  await this.workspaceDocument.registerMachine(this.machineId, {
133046
134347
  ...existingMeta,
133047
- rpcVersion: LORO_STREAMS_RPC_VERSION
134348
+ rpcVersion: LORO_STREAMS_RPC_VERSION,
134349
+ supportsLocalProjectHistoryRpc: true
134350
+ });
134351
+ }
134352
+ toLocalProjectControlError(type2, error2, message, data) {
134353
+ return {
134354
+ ok: false,
134355
+ type: type2,
134356
+ error: error2,
134357
+ message,
134358
+ data
134359
+ };
134360
+ }
134361
+ async dispatchLocalProjectControlViaRpc(message) {
134362
+ const requestType = message.type;
134363
+ const precheck = precheckLocalProjectHistoryRequest({
134364
+ request: message,
134365
+ expectedMachineId: this.machineId,
134366
+ expectedWorkspaceId: this.workspaceId
133048
134367
  });
134368
+ if (!precheck.ok) {
134369
+ return this.toLocalProjectControlError(requestType, precheck.error, precheck.message);
134370
+ }
134371
+ const { requesterUserId, request } = precheck;
134372
+ try {
134373
+ const access = await canUseMachineForCliToken({
134374
+ token: this.token,
134375
+ workspaceId: this.workspaceId,
134376
+ machineId: this.machineId,
134377
+ requesterUserId,
134378
+ localProjectId: request.localProjectId
134379
+ });
134380
+ if (!access.allowed) {
134381
+ return this.toLocalProjectControlError(requestType, "access_denied", `Machine access denied: ${access.reason}`);
134382
+ }
134383
+ const rootPath = await resolveWorkspaceLocalProjectRootPath(this.workspaceDocument.repo, this.machineId, request.localProjectId);
134384
+ if (!rootPath) {
134385
+ return this.toLocalProjectControlError(requestType, "local_project_not_found", `Local project not found in workspace: ${request.localProjectId}`);
134386
+ }
134387
+ const service = new LocalProjectHistorySyncService(this.workspaceDocument, this.logger, {
134388
+ workspaceId: this.workspaceId,
134389
+ machineId: this.machineId,
134390
+ userId: this.userId
134391
+ }, request.provider);
134392
+ if (request.type === "local-project/sync-history") {
134393
+ const result2 = await service.syncLocalProject({
134394
+ localProjectId: request.localProjectId,
134395
+ rootPath
134396
+ });
134397
+ return {
134398
+ ok: true,
134399
+ type: "local-project/sync-history",
134400
+ result: result2
134401
+ };
134402
+ }
134403
+ const result = await service.importLocalProjectSessions({
134404
+ localProjectId: request.localProjectId,
134405
+ rootPath,
134406
+ acpSessionIds: request.acpSessionIds
134407
+ });
134408
+ return {
134409
+ ok: true,
134410
+ type: "local-project/import-history",
134411
+ result
134412
+ };
134413
+ } catch (error2) {
134414
+ return this.toLocalProjectControlError(requestType, "execution_failed", formatErrorMessage(error2));
134415
+ }
133049
134416
  }
133050
134417
  cancelPendingPermissionRequests() {
133051
134418
  this.permissionRequestStartTimes.clear();
@@ -141893,6 +143260,18 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
141893
143260
  }
141894
143261
  return typeof value.error === "string";
141895
143262
  }
143263
+ function isLocalProjectHistorySyncSummary(value) {
143264
+ return isObjectRecord(value) && typeof value.listed === "number" && Number.isInteger(value.listed) && value.listed >= 0 && typeof value.imported === "number" && Number.isInteger(value.imported) && value.imported >= 0 && typeof value.refreshed === "number" && Number.isInteger(value.refreshed) && value.refreshed >= 0 && typeof value.skipped === "number" && Number.isInteger(value.skipped) && value.skipped >= 0 && typeof value.conflicted === "number" && Number.isInteger(value.conflicted) && value.conflicted >= 0 && typeof value.failed === "number" && Number.isInteger(value.failed) && value.failed >= 0 && Array.isArray(value.failures) && value.failures.every((failure) => isObjectRecord(failure) && typeof failure.acpSessionId === "string" && typeof failure.message === "string");
143265
+ }
143266
+ function isLocalProjectHistoryCatalogItem(value) {
143267
+ return isObjectRecord(value) && typeof value.acpSessionId === "string" && typeof value.title === "string" && (typeof value.updatedAt === "undefined" || typeof value.updatedAt === "string") && (typeof value.importedSessionId === "undefined" || typeof value.importedSessionId === "string") && (typeof value.status === "undefined" || value.status === "available" || value.status === "imported" || value.status === "sync_conflict");
143268
+ }
143269
+ function isLocalProjectHistoryCatalogResult(value) {
143270
+ return isObjectRecord(value) && typeof value.listed === "number" && Number.isInteger(value.listed) && value.listed >= 0 && typeof value.lastListedAt === "number" && Number.isInteger(value.lastListedAt) && value.lastListedAt >= 0 && Array.isArray(value.sessions) && value.sessions.every(isLocalProjectHistoryCatalogItem);
143271
+ }
143272
+ function isLocalProjectHistoryImportResult(value) {
143273
+ return isObjectRecord(value) && isLocalProjectHistorySyncSummary(value.summary) && isLocalProjectHistoryCatalogResult(value.catalog);
143274
+ }
141896
143275
  function isWorkspaceIds(value) {
141897
143276
  return isStringArray(value);
141898
143277
  }
@@ -141924,6 +143303,12 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
141924
143303
  if (value.type === "local-project/checkout-branch") {
141925
143304
  return isLocalProjectCheckoutBranchResult(value.result);
141926
143305
  }
143306
+ if (value.type === "local-project/sync-history") {
143307
+ return isLocalProjectHistoryCatalogResult(value.result);
143308
+ }
143309
+ if (value.type === "local-project/import-history") {
143310
+ return isLocalProjectHistoryImportResult(value.result);
143311
+ }
141927
143312
  return false;
141928
143313
  }
141929
143314
  const SESSION_CONTROL_PATH$2 = "/session-control";
@@ -141938,6 +143323,8 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
141938
143323
  "local-project/list-files",
141939
143324
  "local-project/read-file",
141940
143325
  "local-project/checkout-branch",
143326
+ "local-project/sync-history",
143327
+ "local-project/import-history",
141941
143328
  "worktree/list-files",
141942
143329
  "worktree/read-file"
141943
143330
  ];
@@ -143215,6 +144602,7 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
143215
144602
  localProjects: {
143216
144603
  ...existing,
143217
144604
  [entry2.localProjectId]: {
144605
+ ...previous ?? {},
143218
144606
  id: entry2.localProjectId,
143219
144607
  name: entry2.name,
143220
144608
  rootPath: entry2.rootPath,
@@ -143396,6 +144784,43 @@ export PATH=${toSingleQuotedShellString(ghShimBinDir)}:"$PATH"
143396
144784
  result: this.localProjectControlService.checkoutProjectBranch(rootPath, message.branchName)
143397
144785
  };
143398
144786
  }
144787
+ if (message.type === "local-project/sync-history") {
144788
+ const runtime = await this.resolveWorkspaceRuntime(message.workspaceId);
144789
+ const rootPath = await this.resolveWorkspaceProjectRootPath(runtime, message.localProjectId);
144790
+ const service = new LocalProjectHistorySyncService(runtime.lody.documentManager, this.logger, {
144791
+ workspaceId: message.workspaceId,
144792
+ machineId: this.machineId,
144793
+ userId: this.userId
144794
+ }, message.provider);
144795
+ const result = await service.syncLocalProject({
144796
+ localProjectId: message.localProjectId,
144797
+ rootPath
144798
+ });
144799
+ return {
144800
+ ok: true,
144801
+ type: "local-project/sync-history",
144802
+ result
144803
+ };
144804
+ }
144805
+ if (message.type === "local-project/import-history") {
144806
+ const runtime = await this.resolveWorkspaceRuntime(message.workspaceId);
144807
+ const rootPath = await this.resolveWorkspaceProjectRootPath(runtime, message.localProjectId);
144808
+ const service = new LocalProjectHistorySyncService(runtime.lody.documentManager, this.logger, {
144809
+ workspaceId: message.workspaceId,
144810
+ machineId: this.machineId,
144811
+ userId: this.userId
144812
+ }, message.provider);
144813
+ const result = await service.importLocalProjectSessions({
144814
+ localProjectId: message.localProjectId,
144815
+ rootPath,
144816
+ acpSessionIds: message.acpSessionIds
144817
+ });
144818
+ return {
144819
+ ok: true,
144820
+ type: "local-project/import-history",
144821
+ result
144822
+ };
144823
+ }
143399
144824
  if (message.type === "worktree/list-files") {
143400
144825
  return {
143401
144826
  ok: true,
@@ -143737,13 +145162,14 @@ Received ${signal}, shutting down gracefully...` : "\nShutting down gracefully..
143737
145162
  runtimeStateReporter.setStartupStage("bootstrap");
143738
145163
  const electronBootstrapEnabled = process.env[ELECTRON_BOOTSTRAP_ENV] === "1";
143739
145164
  const electronSessionToken = process.env[ELECTRON_SESSION_TOKEN_ENV]?.trim();
143740
- const electronSessionUserId = process.env[ELECTRON_SESSION_USER_ID_ENV]?.trim() || null;
143741
145165
  const providedAuth = options.auth?.trim();
143742
145166
  const cliAvailability = {
143743
145167
  claude: checkClaude(),
143744
145168
  codex: checkCodex()
143745
145169
  };
143746
145170
  logCliDetectionResults(logger2, cliAvailability);
145171
+ const electronSessionUser = electronSessionToken && !process.env[ELECTRON_SESSION_USER_ID_ENV]?.trim() ? await authClient.getSessionUserFromSessionToken(electronSessionToken) : null;
145172
+ const electronSessionUserId = process.env[ELECTRON_SESSION_USER_ID_ENV]?.trim() || electronSessionUser?.id || null;
143747
145173
  const cliSelection = resolveCliTypesSelection({
143748
145174
  requestedCliTypes: options.cliTypes,
143749
145175
  availability: cliAvailability
@@ -175606,6 +177032,7 @@ ${result.stderr}`;
175606
177032
  this.lastExitCode = result.code;
175607
177033
  this.lastExitAtMs = Date.now();
175608
177034
  this.publishState();
177035
+ if (!this.triggered) return;
175609
177036
  if (this.restartPending) return;
175610
177037
  if (isAlreadyRunningOutcome(result)) {
175611
177038
  if (this.alreadyRunningIsFatal) {