@alas-ide/codex-acp 2.5.0-alas.2 → 2.5.0-alas.3

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 +115 -19
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -40611,7 +40611,7 @@ var package_default = {
40611
40611
  publishConfig: {
40612
40612
  access: "public"
40613
40613
  },
40614
- version: "2.5.0-alas.2",
40614
+ version: "2.5.0-alas.3",
40615
40615
  description: "",
40616
40616
  main: "dist/index.js",
40617
40617
  bin: {
@@ -40689,7 +40689,7 @@ var package_default = {
40689
40689
  alasDownstream: {
40690
40690
  upstreamVersion: "2.5.0",
40691
40691
  upstreamCommit: "34e08970aa324a2be985f5080674d5aa91d7666f",
40692
- sourceCommit: "0f50c0b0345fd0e67b91ffc3c49d6b998d27da0c"
40692
+ sourceCommit: "de8d4c961871fc0584ca1bd89f31a8de95066c65"
40693
40693
  }
40694
40694
  };
40695
40695
 
@@ -40919,9 +40919,31 @@ var CodexSubagentSubscriptions = class {
40919
40919
 
40920
40920
  // src/SessionFork.ts
40921
40921
  import { createHash } from "node:crypto";
40922
+ var NEUTRAL_FORK_POINT_META_KEY = "forkPoint";
40923
+ var NEUTRAL_FORK_POINT_VERSION = 1;
40924
+ function neutralForkPointCapability() {
40925
+ return { version: NEUTRAL_FORK_POINT_VERSION, boundary: "turn" };
40926
+ }
40927
+ function clientRequestsNeutralForkPoint(capabilities) {
40928
+ const declared = capabilities?._meta?.[NEUTRAL_FORK_POINT_META_KEY];
40929
+ if (!isUnknownRecord(declared)) return false;
40930
+ const version2 = declared["version"];
40931
+ return typeof version2 === "number" && Number.isInteger(version2) && version2 >= 1;
40932
+ }
40933
+ function hasNeutralForkPoint(meta3) {
40934
+ return meta3 != null && meta3[NEUTRAL_FORK_POINT_META_KEY] !== void 0;
40935
+ }
40936
+ function forkPointNotNegotiatedError() {
40937
+ return RequestError.invalidParams(
40938
+ { [NEUTRAL_FORK_POINT_META_KEY]: { reason: "notNegotiated" } },
40939
+ "session/fork _meta.forkPoint needs initialize clientCapabilities._meta.forkPoint"
40940
+ );
40941
+ }
40922
40942
  async function forkSession(request, additionalDirectories, dependencies, instructions) {
40943
+ const forkPoint = readForkPoint(request._meta);
40923
40944
  await dependencies.refreshSkills(request.cwd, additionalDirectories);
40924
- const lastTurnId = await resolveForkTurnId(request, dependencies.codexClient);
40945
+ const match = forkPoint ? await resolveForkPoint(request, forkPoint, dependencies.codexClient) : void 0;
40946
+ const lastTurnId = match?.turnId;
40925
40947
  const sessionConfig = await dependencies.createSessionConfig(
40926
40948
  request.cwd,
40927
40949
  additionalDirectories,
@@ -40953,26 +40975,83 @@ async function forkSession(request, additionalDirectories, dependencies, instruc
40953
40975
  currentServiceTier: response.serviceTier ?? null,
40954
40976
  additionalDirectories,
40955
40977
  skippedMcpServers: sessionConfig.skippedMcpServers,
40956
- ...instructions ? { instructions } : {}
40978
+ ...instructions ? { instructions } : {},
40979
+ ...forkPoint?.kind === "neutral" && match ? {
40980
+ forkPoint: {
40981
+ boundary: "turn",
40982
+ messageId: match.messageId,
40983
+ turnId: match.turnId,
40984
+ lastItemId: match.lastItemId,
40985
+ matchedBy: match.matchedBy
40986
+ }
40987
+ } : {}
40957
40988
  };
40958
40989
  }
40959
- async function resolveForkTurnId(request, codexClient) {
40960
- const forkPoint = readAirForkPoint(request._meta);
40961
- if (!forkPoint) return void 0;
40990
+ function readForkPoint(meta3) {
40991
+ if (hasNeutralForkPoint(meta3)) return { kind: "neutral", ...readNeutralForkPoint(meta3[NEUTRAL_FORK_POINT_META_KEY]) };
40992
+ const air = readAirForkPoint(meta3);
40993
+ return air ? { kind: "air", ...air } : void 0;
40994
+ }
40995
+ async function resolveForkPoint(request, forkPoint, codexClient) {
40962
40996
  const history = await codexClient.threadReadWithHistory(request.sessionId);
40963
- const candidateIds = airForkMessageIdCandidates(forkPoint.messageId);
40964
- const itemTurnId = candidateIds.map((candidateId) => history.thread.turns.find((turn) => turn.items.some((item) => item.id === candidateId))?.id).find((turnId) => turnId !== void 0);
40965
- if (itemTurnId) return itemTurnId;
40997
+ const turns = history.thread.turns;
40998
+ const matchById = (candidateId) => {
40999
+ const turn = turns.find((turn2) => turn2.items.some((item) => item.id === candidateId));
41000
+ return turn ? toMatch(turn, candidateId, "id") : void 0;
41001
+ };
41002
+ const candidateIds = forkPoint.kind === "air" ? airForkMessageIdCandidates(forkPoint.messageId) : [forkPoint.messageId];
41003
+ const idMatch = candidateIds.map(matchById).find((match) => match !== void 0);
41004
+ if (idMatch) return idMatch;
40966
41005
  if (forkPoint.messageFingerprint) {
40967
- const matchingTurns = history.thread.turns.flatMap((turn) => turn.items.filter((item) => item.type === "agentMessage" && fingerprintAgentMessage(item.text) === forkPoint.messageFingerprint).map(() => turn.id));
40968
- const fingerprintTurnId = matchingTurns[forkPoint.messageOccurrence - 1];
40969
- if (fingerprintTurnId) return fingerprintTurnId;
41006
+ const renderCwd = history.thread.cwd ?? request.cwd;
41007
+ const clientText = forkPoint.kind === "neutral" ? (text) => renderFileCitations(text, renderCwd) : (text) => text;
41008
+ const matches = turns.flatMap((turn) => turn.items.filter((item) => item.type === "agentMessage" && fingerprintAgentMessage(clientText(item.text)) === forkPoint.messageFingerprint).map((item) => toMatch(turn, item.id, "fingerprint")));
41009
+ const fingerprintMatch = matches[forkPoint.messageOccurrence - 1];
41010
+ if (fingerprintMatch) return fingerprintMatch;
41011
+ }
41012
+ if (forkPoint.kind === "neutral") {
41013
+ throw RequestError.invalidParams(
41014
+ { [NEUTRAL_FORK_POINT_META_KEY]: { messageId: forkPoint.messageId, reason: "notFound" } },
41015
+ `Fork point message ${forkPoint.messageId} was not found in session ${request.sessionId}`
41016
+ );
40970
41017
  }
40971
41018
  throw RequestError.invalidParams(
40972
41019
  { messageId: forkPoint.messageId },
40973
41020
  `Fork point message ${forkPoint.messageId} was not found in session ${request.sessionId}`
40974
41021
  );
40975
41022
  }
41023
+ function toMatch(turn, messageId, matchedBy) {
41024
+ return { messageId, turnId: turn.id, lastItemId: turn.items.at(-1).id, matchedBy };
41025
+ }
41026
+ function readNeutralForkPoint(forkPoint) {
41027
+ const invalid = (field, message) => RequestError.invalidParams(
41028
+ { [NEUTRAL_FORK_POINT_META_KEY]: { field } },
41029
+ `session/fork _meta.forkPoint: ${message}`
41030
+ );
41031
+ if (!isUnknownRecord(forkPoint)) throw invalid(NEUTRAL_FORK_POINT_META_KEY, "must be an object");
41032
+ const version2 = forkPoint["version"];
41033
+ if (version2 !== void 0 && version2 !== NEUTRAL_FORK_POINT_VERSION) {
41034
+ throw invalid("version", `version must be ${NEUTRAL_FORK_POINT_VERSION} when present`);
41035
+ }
41036
+ const messageId = forkPoint["messageId"];
41037
+ if (typeof messageId !== "string" || messageId.trim().length === 0) {
41038
+ throw invalid("messageId", "messageId must be a non-empty string");
41039
+ }
41040
+ const messageFingerprint = forkPoint["messageFingerprint"];
41041
+ if (messageFingerprint !== void 0 && (typeof messageFingerprint !== "string" || !SHA256_FINGERPRINT.test(messageFingerprint))) {
41042
+ throw invalid("messageFingerprint", "messageFingerprint must be sha256: and 64 lowercase hex digits");
41043
+ }
41044
+ const messageOccurrence = forkPoint["messageOccurrence"] ?? 1;
41045
+ if (!Number.isSafeInteger(messageOccurrence) || messageOccurrence < 1) {
41046
+ throw invalid("messageOccurrence", "messageOccurrence must be a positive integer");
41047
+ }
41048
+ return {
41049
+ messageId: messageId.trim(),
41050
+ ...typeof messageFingerprint === "string" && { messageFingerprint },
41051
+ messageOccurrence
41052
+ };
41053
+ }
41054
+ var SHA256_FINGERPRINT = /^sha256:[0-9a-f]{64}$/;
40976
41055
  function readAirForkPoint(meta3) {
40977
41056
  const jetbrains = meta3?.["jetbrains"];
40978
41057
  if (!isUnknownRecord(jetbrains)) return void 0;
@@ -40985,7 +41064,7 @@ function readAirForkPoint(meta3) {
40985
41064
  throw RequestError.invalidParams(void 0, "AIR fork messageId must be a non-empty string");
40986
41065
  }
40987
41066
  const messageFingerprint = fork["messageFingerprint"];
40988
- if (messageFingerprint !== void 0 && (typeof messageFingerprint !== "string" || !/^sha256:[0-9a-f]{64}$/.test(messageFingerprint))) {
41067
+ if (messageFingerprint !== void 0 && (typeof messageFingerprint !== "string" || !SHA256_FINGERPRINT.test(messageFingerprint))) {
40989
41068
  throw RequestError.invalidParams(void 0, "AIR fork messageFingerprint must be a SHA-256 fingerprint");
40990
41069
  }
40991
41070
  const messageOccurrence = fork["messageOccurrence"] ?? 1;
@@ -52061,6 +52140,8 @@ var CodexAcpServer = class {
52061
52140
  /** The capability choices of the client for tool call and plan reports. */
52062
52141
  capabilities;
52063
52142
  booleanConfigOptionsSupported;
52143
+ /** Whether the client opted into the client-neutral `session/fork` `_meta.forkPoint`. */
52144
+ neutralForkPointSupported = false;
52064
52145
  /** Last `authStatus` pushed to the client; used to suppress duplicates. */
52065
52146
  currentAuthStatus;
52066
52147
  /**
@@ -52323,6 +52404,7 @@ var CodexAcpServer = class {
52323
52404
  if (appServer) await appServer;
52324
52405
  this.reportingConnection.reports.compareMeta = this.capabilities.airToolCallContract;
52325
52406
  this.booleanConfigOptionsSupported = clientSupportsBooleanConfigOptions(_params.clientCapabilities);
52407
+ this.neutralForkPointSupported = clientRequestsNeutralForkPoint(_params.clientCapabilities);
52326
52408
  this.sessionIndex.configure(_params.clientCapabilities);
52327
52409
  this.hookReview.configure(_params.clientCapabilities?._meta?.["codexHooks"], this.capabilities.airClient);
52328
52410
  await this.runWithProcessCheck(() => this.codexAcpClient.initialize(_params));
@@ -52382,6 +52464,7 @@ var CodexAcpServer = class {
52382
52464
  if (appServer) await appServer;
52383
52465
  this.reportingConnection.reports.compareMeta = this.capabilities.airToolCallContract;
52384
52466
  this.booleanConfigOptionsSupported = true;
52467
+ this.neutralForkPointSupported = clientRequestsNeutralForkPoint(clientCapabilities);
52385
52468
  this.sessionIndex.configure(clientCapabilities, { neutral: false });
52386
52469
  await this.runWithProcessCheck(() => this.codexAcpClient.initialize({ clientInfo: params.info }));
52387
52470
  this.sessionIndex.observe(this.codexAcpClient);
@@ -52466,6 +52549,10 @@ var CodexAcpServer = class {
52466
52549
  "async-tasks": asyncTaskCapability()
52467
52550
  } : {},
52468
52551
  ...this.hookReview.enabled ? { codexHooks: { version: 1, reviewToken: true } } : {},
52552
+ // The client-neutral fork point, see `docs/air-extensions.md`.
52553
+ ...this.neutralForkPointSupported ? {
52554
+ [NEUTRAL_FORK_POINT_META_KEY]: neutralForkPointCapability()
52555
+ } : {},
52469
52556
  // The session index for a client that opted in without AIR, see `docs/air-extensions.md`.
52470
52557
  ...neutralSessionIndex ? {
52471
52558
  [NEUTRAL_SESSION_INDEX_META_KEY]: neutralSessionIndex
@@ -52759,7 +52846,7 @@ You have been logged out. Please try again.`);
52759
52846
  this.sessionGenerations.set(sessionId, generation);
52760
52847
  return generation;
52761
52848
  }
52762
- async tryCreateSession(request, operation = "sessionId" in request ? "resume" : "new") {
52849
+ async tryCreateSession(request, operation = "sessionId" in request ? "resume" : "new", onForked) {
52763
52850
  clientSystemPromptText(request.systemPrompt);
52764
52851
  const existingSessionRequest = request;
52765
52852
  const previousInstructions = "sessionId" in request ? this.sessions.get(request.sessionId)?.instructions : void 0;
@@ -52810,6 +52897,7 @@ You have been logged out. Please try again.`);
52810
52897
  previousInstructions
52811
52898
  );
52812
52899
  });
52900
+ onForked?.(sessionMetadata);
52813
52901
  } else {
52814
52902
  logger.log(`Create new session...`);
52815
52903
  sessionMetadata = await this.runWithProcessCheck(() => {
@@ -53378,6 +53466,9 @@ You have been logged out. Please try again.`);
53378
53466
  async forkSession(params) {
53379
53467
  params = this.withV1SessionInstructionAppend(params);
53380
53468
  clientSystemPromptText(params.systemPrompt);
53469
+ if (hasNeutralForkPoint(params._meta) && !this.neutralForkPointSupported) {
53470
+ throw forkPointNotNegotiatedError();
53471
+ }
53381
53472
  this.assertInstructionsReady(params.sessionId);
53382
53473
  return this.hookReview.run(
53383
53474
  params.cwd,
@@ -53393,12 +53484,16 @@ You have been logged out. Please try again.`);
53393
53484
  if (appServer) await appServer;
53394
53485
  logger.log("Forking session...", { sessionId: params.sessionId });
53395
53486
  try {
53396
- const [sessionId, , modeState] = await this.tryCreateSession(params, "fork");
53487
+ let forkPoint;
53488
+ const [sessionId, , modeState] = await this.tryCreateSession(params, "fork", (metadata) => {
53489
+ forkPoint = metadata.forkPoint;
53490
+ });
53397
53491
  logger.log("Session forked", { sourceSessionId: params.sessionId, sessionId });
53398
53492
  return {
53399
53493
  sessionId,
53400
53494
  modes: modeState,
53401
- ...this.createSessionConfigOptionsResponse(this.getSessionState(sessionId))
53495
+ ...this.createSessionConfigOptionsResponse(this.getSessionState(sessionId)),
53496
+ ...forkPoint ? { _meta: { [NEUTRAL_FORK_POINT_META_KEY]: forkPoint } } : {}
53402
53497
  };
53403
53498
  } catch (e) {
53404
53499
  const error62 = e instanceof Error ? e : new Error(String(e));
@@ -53409,10 +53504,11 @@ You have been logged out. Please try again.`);
53409
53504
  async forkSessionV2(params) {
53410
53505
  const { systemPrompt, ...rest } = params;
53411
53506
  const request = { ...rest, systemPrompt: toV1SystemPrompt(systemPrompt) };
53412
- const { sessionId } = await this.forkSession(request);
53507
+ const { sessionId, _meta } = await this.forkSession(request);
53413
53508
  return {
53414
53509
  sessionId,
53415
- ...this.createSessionSetupResponseV2(this.getSessionState(sessionId))
53510
+ ...this.createSessionSetupResponseV2(this.getSessionState(sessionId)),
53511
+ ..._meta ? { _meta } : {}
53416
53512
  };
53417
53513
  }
53418
53514
  async listSessions(params) {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "2.5.0-alas.2",
6
+ "version": "2.5.0-alas.3",
7
7
  "description": "",
8
8
  "main": "dist/index.js",
9
9
  "bin": {
@@ -81,6 +81,6 @@
81
81
  "alasDownstream": {
82
82
  "upstreamVersion": "2.5.0",
83
83
  "upstreamCommit": "34e08970aa324a2be985f5080674d5aa91d7666f",
84
- "sourceCommit": "0f50c0b0345fd0e67b91ffc3c49d6b998d27da0c"
84
+ "sourceCommit": "de8d4c961871fc0584ca1bd89f31a8de95066c65"
85
85
  }
86
86
  }