@alas-ide/codex-acp 2.2.2-alas.3 → 2.2.2-alas.4

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 +279 -58
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -34392,7 +34392,7 @@ var package_default = {
34392
34392
  publishConfig: {
34393
34393
  access: "public"
34394
34394
  },
34395
- version: "2.2.2-alas.3",
34395
+ version: "2.2.2-alas.4",
34396
34396
  description: "",
34397
34397
  main: "dist/index.js",
34398
34398
  bin: {
@@ -34469,7 +34469,7 @@ var package_default = {
34469
34469
  alasDownstream: {
34470
34470
  upstreamVersion: "2.2.2",
34471
34471
  upstreamCommit: "202e66e63343ec4f5a5e35ab590251d813d129ca",
34472
- sourceCommit: "2be0ccf63ad9c18131815207511597bb34d6adc0"
34472
+ sourceCommit: "4b517e7e77ece05ef63f34c890bc853b7fb96a43"
34473
34473
  }
34474
34474
  };
34475
34475
 
@@ -35186,6 +35186,19 @@ var CodexAcpClient = class _CodexAcpClient {
35186
35186
  isClientConfiguredProvider(providerId) {
35187
35187
  return providerId === CUSTOM_GATEWAY_PROVIDER_ID && this.gatewayConfigSource === "acpProviders";
35188
35188
  }
35189
+ /** Provider inputs that can change the routing of a subsequent open or turn. Never returned over ACP. */
35190
+ async hookReviewProviderInputs() {
35191
+ const persisted = (await this.codexClient.configRead({ includeLayers: false })).config;
35192
+ const modelProvider = this.getModelProvider() ?? persisted.model_provider ?? "openai";
35193
+ const selected = (providers) => {
35194
+ if (providers === null || typeof providers !== "object" || Array.isArray(providers)) return {};
35195
+ const value = providers[modelProvider];
35196
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
35197
+ };
35198
+ const merged = this.gatewayConfig?.modelProvider === modelProvider ? this.gatewayConfig.config : { ...selected(persisted["model_providers"]), ...selected(this.config["model_providers"]) };
35199
+ const { name: _name, env_key_instructions: _instructions, ...config2 } = merged;
35200
+ return { modelProvider, config: config2 };
35201
+ }
35189
35202
  /**
35190
35203
  * The model provider the agent itself is configured with (launch option or
35191
35204
  * Codex config), ignoring any ACP-configured gateway routing. The
@@ -37141,6 +37154,21 @@ var CodexAppServerClient = class {
37141
37154
  threadLoadsInFlight() {
37142
37155
  return [...this.threadLoads.keys()];
37143
37156
  }
37157
+ /** Validates a reviewed process/configuration and returns the final synchronous dispatch fence. */
37158
+ hookExecutionGate;
37159
+ dispatchRequest(request) {
37160
+ const activeGoal = request.method === "thread/goal/set" && request.params.status !== "paused";
37161
+ if (this.hookExecutionGate !== void 0 && (activeGoal || ["thread/start", "thread/resume", "thread/fork", "turn/start", "review/start"].includes(request.method))) {
37162
+ return this.dispatchReviewedRequest(request, this.hookExecutionGate);
37163
+ }
37164
+ return request.params ? this.connection.sendRequest(request.method, request.params) : this.connection.sendRequest(request.method);
37165
+ }
37166
+ async dispatchReviewedRequest(request, gate) {
37167
+ const params = request.params;
37168
+ const fence = await gate(params.cwd ?? void 0, params.modelProvider);
37169
+ fence();
37170
+ return await this.connection.sendRequest(request.method, request.params);
37171
+ }
37144
37172
  async sendRequest(request) {
37145
37173
  for (const callback of this.codexEventHandlers) {
37146
37174
  callback({ eventType: "request", ...request });
@@ -37148,11 +37176,7 @@ var CodexAppServerClient = class {
37148
37176
  const loadedThreadId = threadLoadOf(request);
37149
37177
  let result;
37150
37178
  if (loadedThreadId === null) {
37151
- if (request.params) {
37152
- result = await this.connection.sendRequest(request.method, request.params);
37153
- } else {
37154
- result = await this.connection.sendRequest(request.method);
37155
- }
37179
+ result = await this.dispatchRequest(request);
37156
37180
  } else {
37157
37181
  result = await this.threadLoadGate(loadedThreadId, () => this.sendThreadLoad(loadedThreadId, request));
37158
37182
  }
@@ -37164,7 +37188,7 @@ var CodexAppServerClient = class {
37164
37188
  async sendThreadLoad(threadId, request) {
37165
37189
  this.threadLoads.set(threadId, (this.threadLoads.get(threadId) ?? 0) + 1);
37166
37190
  try {
37167
- return await this.connection.sendRequest(request.method, request.params);
37191
+ return await this.dispatchRequest(request);
37168
37192
  } finally {
37169
37193
  const count = (this.threadLoads.get(threadId) ?? 1) - 1;
37170
37194
  if (count > 0) this.threadLoads.set(threadId, count);
@@ -37268,6 +37292,183 @@ async function withHistoryTimeout(page, method, threadId) {
37268
37292
  }
37269
37293
  }
37270
37294
 
37295
+ // src/CodexHookReview.ts
37296
+ import { AsyncLocalStorage } from "node:async_hooks";
37297
+ import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
37298
+
37299
+ // src/CodexHookTrust.ts
37300
+ var CODEX_HOOKS_LIST_METHOD = "_codex/hooks/list";
37301
+ var CODEX_HOOKS_TRUST_METHOD = "_codex/hooks/trust";
37302
+ async function listCodexHooks(client, cwd) {
37303
+ const [entry] = (await client.hooksList({ cwds: [cwd] })).data;
37304
+ if (entry === void 0) throw new Error("Codex could not list hooks for the requested directory");
37305
+ return { ...entry, hooks: entry.hooks.filter((hook) => hook.source === "sessionFlags") };
37306
+ }
37307
+ async function trustCodexHooks(client, cwd, requested, beforeWrite, afterReadback) {
37308
+ const isListed = (hooks, request) => hooks.find((hook) => hook.key === request.key && hook.currentHash === request.currentHash);
37309
+ const entry = await listCodexHooks(client, cwd);
37310
+ beforeWrite?.(entry);
37311
+ const current = entry.hooks;
37312
+ const edits = requested.map((request) => {
37313
+ const hook = isListed(current, request);
37314
+ if (hook === void 0) throw new Error("A Codex hook changed before trust was recorded");
37315
+ return hook;
37316
+ }).filter((hook) => hook.trustStatus !== "trusted").map((hook) => ({
37317
+ keyPath: `hooks.state.${JSON.stringify(hook.key)}.trusted_hash`,
37318
+ value: hook.currentHash,
37319
+ mergeStrategy: "replace"
37320
+ }));
37321
+ if (edits.length > 0) {
37322
+ await client.configBatchWrite({ edits, reloadUserConfig: true });
37323
+ }
37324
+ const verified = (await listCodexHooks(client, cwd)).hooks;
37325
+ afterReadback?.();
37326
+ return requested.every((request) => isListed(verified, request)?.trustStatus === "trusted");
37327
+ }
37328
+
37329
+ // src/CodexHookReview.ts
37330
+ var CODEX_HOOK_REVIEW_ERROR = -32044;
37331
+ var CodexHookReviewError = class extends RequestError {
37332
+ constructor(kind) {
37333
+ super(CODEX_HOOK_REVIEW_ERROR, kind === "codex_hook_changed" ? "A Codex hook changed; review the startup hooks again" : "Review the startup hooks for the current Codex process before continuing", { kind });
37334
+ }
37335
+ };
37336
+ var CodexHookReview = class {
37337
+ constructor(client) {
37338
+ this.client = client;
37339
+ }
37340
+ client;
37341
+ enabled = false;
37342
+ revision = 0;
37343
+ reviews = /* @__PURE__ */ new Map();
37344
+ context = new AsyncLocalStorage();
37345
+ configure(capability, air) {
37346
+ this.enabled = !air && typeof capability === "object" && capability !== null && !Array.isArray(capability) && capability.version === 1;
37347
+ this.invalidate();
37348
+ this.bind(this.client());
37349
+ }
37350
+ invalidate() {
37351
+ this.revision++;
37352
+ this.reviews.clear();
37353
+ }
37354
+ bind(client) {
37355
+ client.appServerClient.hookExecutionGate = this.enabled ? async (cwd, provider) => {
37356
+ const context = this.context.getStore();
37357
+ const fail = (error62) => {
37358
+ if (context?.dispatched && error62 instanceof CodexHookReviewError) {
37359
+ throw RequestError.internalError(
37360
+ void 0,
37361
+ "Codex hook review changed after this operation began executing. Review again before sending new work; do not retry the completed work automatically."
37362
+ );
37363
+ }
37364
+ throw error62;
37365
+ };
37366
+ try {
37367
+ if (context === void 0 || cwd !== void 0 && cwd !== context.cwd) throw new CodexHookReviewError("codex_hook_review_required");
37368
+ const fence = await this.validate(client, context.cwd, context.token, provider);
37369
+ return () => {
37370
+ try {
37371
+ fence();
37372
+ } catch (error62) {
37373
+ fail(error62);
37374
+ }
37375
+ context.dispatched = true;
37376
+ };
37377
+ } catch (error62) {
37378
+ return fail(error62);
37379
+ }
37380
+ } : void 0;
37381
+ }
37382
+ run(cwd, meta3, operation) {
37383
+ if (!this.enabled) return operation();
37384
+ const hooks = meta3?.codexHooks;
37385
+ return this.context.run({ cwd, token: hooks?.reviewToken, dispatched: false }, operation);
37386
+ }
37387
+ continueOperation(cwd, meta3, operation) {
37388
+ return this.context.getStore() === void 0 ? this.run(cwd, meta3, operation) : operation();
37389
+ }
37390
+ async list(cwd) {
37391
+ const client = this.client();
37392
+ const revision = this.revision;
37393
+ if (!this.enabled) return await listCodexHooks(client.appServerClient, cwd);
37394
+ const [entry, provider] = await Promise.all([
37395
+ listCodexHooks(client.appServerClient, cwd),
37396
+ client.hookReviewProviderInputs()
37397
+ ]);
37398
+ this.assertCurrent(client.appServerClient, revision);
37399
+ const definitions = definitionFingerprint(entry);
37400
+ const providerFingerprint = fingerprint(provider);
37401
+ const previous = this.reviews.get(cwd);
37402
+ const review = previous?.client === client.appServerClient && previous.revision === revision && previous.fingerprint === definitions && previous.providerFingerprint === providerFingerprint ? previous : { cwd, token: randomUUID3(), fingerprint: definitions, revision, client: client.appServerClient, provider: provider.modelProvider, providerFingerprint };
37403
+ this.reviews.set(cwd, review);
37404
+ const home = client.getHomePath();
37405
+ return { ...entry, reviewToken: review.token, ...home === null ? {} : { codexHome: home } };
37406
+ }
37407
+ async trust(cwd, requested, token) {
37408
+ const client = this.client().appServerClient;
37409
+ if (!this.enabled) return await trustCodexHooks(client, cwd, requested);
37410
+ const review = this.requireReview(client, cwd, token);
37411
+ const fence = await this.validate(this.client(), cwd, token);
37412
+ fence();
37413
+ if (requested.length === 0 || new Set(requested.map((hook) => hook.key)).size !== requested.length || requested.some((hook) => !hook.key || !hook.currentHash)) throw new CodexHookReviewError("codex_hook_changed");
37414
+ return await trustCodexHooks(client, cwd, requested, (entry) => {
37415
+ this.checkDefinition(client, review, entry);
37416
+ for (const request of requested) {
37417
+ const matches = entry.hooks.filter((hook) => hook.key === request.key);
37418
+ if (matches.length !== 1 || matches[0].currentHash !== request.currentHash) {
37419
+ throw new CodexHookReviewError("codex_hook_changed");
37420
+ }
37421
+ }
37422
+ }, () => {
37423
+ this.requireReview(client, cwd, token);
37424
+ });
37425
+ }
37426
+ async validate(client, cwd, token, requestedProvider) {
37427
+ const review = this.requireReview(client.appServerClient, cwd, token);
37428
+ const [entry, provider] = await Promise.all([
37429
+ listCodexHooks(client.appServerClient, cwd),
37430
+ client.hookReviewProviderInputs()
37431
+ ]);
37432
+ this.checkDefinition(client.appServerClient, review, entry);
37433
+ if (fingerprint(provider) !== review.providerFingerprint || requestedProvider != null && requestedProvider !== review.provider) {
37434
+ throw new CodexHookReviewError("codex_hook_changed");
37435
+ }
37436
+ return () => {
37437
+ this.requireReview(client.appServerClient, cwd, token);
37438
+ };
37439
+ }
37440
+ requireReview(client, cwd, token) {
37441
+ const review = this.reviews.get(cwd);
37442
+ if (review === void 0 || typeof token !== "string" || token !== review.token || review.client !== client) {
37443
+ throw new CodexHookReviewError("codex_hook_review_required");
37444
+ }
37445
+ this.assertCurrent(client, review.revision);
37446
+ return review;
37447
+ }
37448
+ checkDefinition(client, review, entry) {
37449
+ this.requireReview(client, review.cwd, review.token);
37450
+ if (definitionFingerprint(entry) !== review.fingerprint) throw new CodexHookReviewError("codex_hook_changed");
37451
+ }
37452
+ assertCurrent(client, revision) {
37453
+ if (client !== this.client().appServerClient || revision !== this.revision || client.connectionLoss.lost) {
37454
+ throw new CodexHookReviewError("codex_hook_review_required");
37455
+ }
37456
+ }
37457
+ };
37458
+ function definitionFingerprint(entry) {
37459
+ const value = { ...entry, hooks: entry.hooks.map(({ trustStatus: _trust, ...definition }) => definition) };
37460
+ return fingerprint(value);
37461
+ }
37462
+ function fingerprint(value) {
37463
+ return createHash2("sha256").update(JSON.stringify(value, (_key, value2) => {
37464
+ if (typeof value2 === "bigint") return value2.toString();
37465
+ if (value2 !== null && typeof value2 === "object" && !Array.isArray(value2)) {
37466
+ return Object.fromEntries(Object.entries(value2).sort(([a], [b]) => a.localeCompare(b)));
37467
+ }
37468
+ return value2;
37469
+ })).digest("hex");
37470
+ }
37471
+
37271
37472
  // src/app-server-recovery/AppServerRecovery.ts
37272
37473
  var import_node3 = __toESM(require_main(), 1);
37273
37474
 
@@ -37907,7 +38108,7 @@ var AppServerRecovery = class {
37907
38108
  });
37908
38109
  }
37909
38110
  resumeError(sessionId, error62, client) {
37910
- if (isAppServerUnavailableError(error62)) return error62;
38111
+ if (isAppServerUnavailableError(error62) || error62 instanceof CodexHookReviewError) return error62;
37911
38112
  const mapped = this.mapError(error62, client);
37912
38113
  if (mapped !== error62) return mapped;
37913
38114
  if (isMissingRolloutError(error62)) {
@@ -40020,35 +40221,8 @@ function clientSupportsSubagents(capabilities) {
40020
40221
  return clientSupportsAirCapability(capabilities, AIR_NATIVE_SUBAGENT_SESSIONS_KEY);
40021
40222
  }
40022
40223
 
40023
- // src/CodexHookTrust.ts
40024
- var CODEX_HOOKS_LIST_METHOD = "_codex/hooks/list";
40025
- var CODEX_HOOKS_TRUST_METHOD = "_codex/hooks/trust";
40026
- async function listCodexHooks(client, cwd) {
40027
- const [entry] = (await client.hooksList({ cwds: [cwd] })).data;
40028
- if (entry === void 0) throw new Error("Codex could not list hooks for the requested directory");
40029
- return { ...entry, hooks: entry.hooks.filter((hook) => hook.source === "sessionFlags") };
40030
- }
40031
- async function trustCodexHooks(client, cwd, requested) {
40032
- const isListed = (hooks, request) => hooks.find((hook) => hook.key === request.key && hook.currentHash === request.currentHash);
40033
- const current = (await listCodexHooks(client, cwd)).hooks;
40034
- const edits = requested.map((request) => {
40035
- const hook = isListed(current, request);
40036
- if (hook === void 0) throw new Error("A Codex hook changed before trust was recorded");
40037
- return hook;
40038
- }).filter((hook) => hook.trustStatus !== "trusted").map((hook) => ({
40039
- keyPath: `hooks.state.${JSON.stringify(hook.key)}.trusted_hash`,
40040
- value: hook.currentHash,
40041
- mergeStrategy: "replace"
40042
- }));
40043
- if (edits.length > 0) {
40044
- await client.configBatchWrite({ edits, reloadUserConfig: true });
40045
- }
40046
- const verified = (await listCodexHooks(client, cwd)).hooks;
40047
- return requested.every((request) => isListed(verified, request)?.trustStatus === "trusted");
40048
- }
40049
-
40050
40224
  // src/CodexAcpServer.ts
40051
- import { randomUUID as randomUUID4 } from "node:crypto";
40225
+ import { randomUUID as randomUUID5 } from "node:crypto";
40052
40226
 
40053
40227
  // src/TitleGenerator.ts
40054
40228
  var TITLE_MODEL = "gpt-5.6-luna";
@@ -40871,7 +41045,7 @@ function remember(cache, key, value) {
40871
41045
  }
40872
41046
 
40873
41047
  // src/SessionListSubscriptions.ts
40874
- import { randomUUID as randomUUID3 } from "node:crypto";
41048
+ import { randomUUID as randomUUID4 } from "node:crypto";
40875
41049
  import path12 from "node:path";
40876
41050
 
40877
41051
  // src/CodexHomeWatcher.ts
@@ -41227,7 +41401,7 @@ var SessionListSubscriptions = class {
41227
41401
  this.startWatching();
41228
41402
  created.ready = this.makeReady(created);
41229
41403
  }
41230
- const subscription = { id: randomUUID3(), group, sent: /* @__PURE__ */ new Map(), ready: false, held: null, readyTimer: null, startedSecond: null, epoch: ++this.epoch };
41404
+ const subscription = { id: randomUUID4(), group, sent: /* @__PURE__ */ new Map(), ready: false, held: null, readyTimer: null, startedSecond: null, epoch: ++this.epoch };
41231
41405
  this.subscriptions.set(subscription.id, subscription);
41232
41406
  group.subscriptions.add(subscription);
41233
41407
  this.startWatching();
@@ -42486,6 +42660,7 @@ var CodexAcpServer = class {
42486
42660
  recovery;
42487
42661
  initializeRequest = null;
42488
42662
  providerUpdate = null;
42663
+ hookReview = new CodexHookReview(() => this.codexAcpClient);
42489
42664
  /** The `sessionIndex` extension, for AIR or a client-neutral opt-in, see `SessionIndexService.ts`. */
42490
42665
  sessionIndex = new SessionIndexService({
42491
42666
  connection: () => this.connection,
@@ -42533,7 +42708,7 @@ var CodexAcpServer = class {
42533
42708
  this.captureStderr();
42534
42709
  this.getExitCode = getExitCode ?? (() => this.codexProcessState?.connection.process.exitCode ?? null);
42535
42710
  this.getRecentStderr = getRecentStderr ?? (() => this.codexProcessState?.stderr ?? "");
42536
- this.sessionFailureEpoch = randomUUID4();
42711
+ this.sessionFailureEpoch = randomUUID5();
42537
42712
  this.clientInfo = null;
42538
42713
  this.clientCapabilities = null;
42539
42714
  this.capabilities = ClientCapabilities.DEFAULT;
@@ -42562,6 +42737,7 @@ var CodexAcpServer = class {
42562
42737
  this.reportingConnection.reports.compareMeta = this.capabilities.airClient;
42563
42738
  this.booleanConfigOptionsSupported = clientSupportsBooleanConfigOptions(_params.clientCapabilities);
42564
42739
  this.sessionIndex.configure(_params.clientCapabilities);
42740
+ this.hookReview.configure(_params.clientCapabilities?._meta?.["codexHooks"], this.capabilities.airClient);
42565
42741
  await this.runWithProcessCheck(() => this.codexAcpClient.initialize(_params));
42566
42742
  this.sessionIndex.observe(this.codexAcpClient);
42567
42743
  this.publishFirstAuthStatusAfterResponse();
@@ -42621,6 +42797,7 @@ var CodexAcpServer = class {
42621
42797
  ...this.capabilities.asyncTasks && !this.capabilities.airClient ? {
42622
42798
  "async-tasks": asyncTaskCapability()
42623
42799
  } : {},
42800
+ ...this.hookReview.enabled ? { codexHooks: { version: 1, reviewToken: true } } : {},
42624
42801
  ...neutralSessionIndex ? {
42625
42802
  [NEUTRAL_SESSION_INDEX_META_KEY]: neutralSessionIndex
42626
42803
  } : {},
@@ -42653,15 +42830,22 @@ var CodexAcpServer = class {
42653
42830
  if (this.providerUpdate !== null) await this.waitForProviderUpdate();
42654
42831
  const appServer = this.ensureAppServer();
42655
42832
  if (appServer) await appServer;
42656
- return await this.runWithProcessCheck(() => listCodexHooks(this.codexAcpClient.appServerClient, cwd));
42833
+ return await this.runWithProcessCheck(() => this.hookReview.list(cwd));
42657
42834
  }
42658
- async trustHooks(cwd, hooks) {
42835
+ async trustHooks(cwd, hooks, reviewToken) {
42659
42836
  if (this.providerUpdate !== null) await this.waitForProviderUpdate();
42660
42837
  const appServer = this.ensureAppServer();
42661
42838
  if (appServer) await appServer;
42662
- return { trusted: await this.runWithProcessCheck(() => trustCodexHooks(this.codexAcpClient.appServerClient, cwd, hooks)) };
42839
+ return { trusted: await this.runWithProcessCheck(() => this.hookReview.trust(cwd, hooks, reviewToken)) };
42663
42840
  }
42664
42841
  async extMethod(method, params) {
42842
+ if ((method === GOAL_CONTROL_METHOD || method === LEGACY_GOAL_CONTROL_METHOD) && (params["action"] === "set" || params["action"] === "resume")) {
42843
+ const session = this.sessions.get(params["sessionId"]);
42844
+ if (session !== void 0) return this.hookReview.run(session.cwd, params["_meta"], () => this.extMethodReviewed(method, params));
42845
+ }
42846
+ return this.extMethodReviewed(method, params);
42847
+ }
42848
+ async extMethodReviewed(method, params) {
42665
42849
  const methodRequest = { method, params };
42666
42850
  if (!isExtMethodRequest(methodRequest)) {
42667
42851
  return {};
@@ -43198,6 +43382,9 @@ Check ${configPath} and project .codex directories, especially their config.toml
43198
43382
  return null;
43199
43383
  }
43200
43384
  async loadSession(params) {
43385
+ return this.hookReview.run(params.cwd, params._meta, () => this.loadSessionReviewed(params));
43386
+ }
43387
+ async loadSessionReviewed(params) {
43201
43388
  readSessionInstructionAppend(params._meta);
43202
43389
  return this.recovery === null ? await this.loadSessionOnce(params) : await this.recovery.trackOpen(params.sessionId, () => this.loadSessionOnce(params));
43203
43390
  }
@@ -43241,6 +43428,9 @@ Check ${configPath} and project .codex directories, especially their config.toml
43241
43428
  };
43242
43429
  }
43243
43430
  async resumeSession(params) {
43431
+ return this.hookReview.run(params.cwd, params._meta, () => this.resumeSessionReviewed(params));
43432
+ }
43433
+ async resumeSessionReviewed(params) {
43244
43434
  readSessionInstructionAppend(params._meta);
43245
43435
  return this.recovery === null ? await this.resumeSessionOnce(params) : await this.recovery.trackOpen(params.sessionId, () => this.resumeSessionOnce(params));
43246
43436
  }
@@ -43264,6 +43454,9 @@ Check ${configPath} and project .codex directories, especially their config.toml
43264
43454
  };
43265
43455
  }
43266
43456
  async forkSession(params) {
43457
+ return this.hookReview.run(params.cwd, params._meta, () => this.forkSessionReviewed(params));
43458
+ }
43459
+ async forkSessionReviewed(params) {
43267
43460
  readSessionInstructionAppend(params._meta);
43268
43461
  if (this.providerUpdate !== null) {
43269
43462
  await this.waitForProviderUpdate();
@@ -43376,6 +43569,9 @@ Check ${configPath} and project .codex directories, especially their config.toml
43376
43569
  return this.sessionOpenGenerations.get(sessionId) === this.getSessionGeneration(sessionId);
43377
43570
  }
43378
43571
  async newSession(params) {
43572
+ return this.hookReview.run(params.cwd, params._meta, () => this.newSessionReviewed(params));
43573
+ }
43574
+ async newSessionReviewed(params) {
43379
43575
  readSessionInstructionAppend(params._meta);
43380
43576
  if (this.providerUpdate !== null) {
43381
43577
  await this.waitForProviderUpdate();
@@ -43401,14 +43597,19 @@ Check ${configPath} and project .codex directories, especially their config.toml
43401
43597
  const appServer = this.ensureAppServer();
43402
43598
  if (appServer) await appServer;
43403
43599
  const elicitationRequester = this.createUrlElicitationRequester(requestId);
43404
- const isAuthenticated = await this.runWithProcessCheck(() => this.codexAcpClient.authenticate(_params, elicitationRequester));
43405
- if (!isAuthenticated) {
43406
- logger.log("Authenticate request failed");
43407
- throw RequestError.authRequired({ methodId: _params.methodId }, "Sign-in did not complete");
43600
+ this.hookReview.invalidate();
43601
+ try {
43602
+ const isAuthenticated = await this.runWithProcessCheck(() => this.codexAcpClient.authenticate(_params, elicitationRequester));
43603
+ if (!isAuthenticated) {
43604
+ logger.log("Authenticate request failed");
43605
+ throw RequestError.authRequired({ methodId: _params.methodId }, "Sign-in did not complete");
43606
+ }
43607
+ await this.refreshAuthState(this.getAuthProviderForAuthenticateRequest(_params));
43608
+ logger.log("Authenticate request completed");
43609
+ return {};
43610
+ } finally {
43611
+ this.hookReview.invalidate();
43408
43612
  }
43409
- await this.refreshAuthState(this.getAuthProviderForAuthenticateRequest(_params));
43410
- logger.log("Authenticate request completed");
43411
- return {};
43412
43613
  }
43413
43614
  createUrlElicitationRequester(requestId) {
43414
43615
  if (requestId == null || !clientSupportsUrlElicitation(this.clientCapabilities)) {
@@ -43447,11 +43648,13 @@ Check ${configPath} and project .codex directories, especially their config.toml
43447
43648
  }
43448
43649
  async setProvider(params) {
43449
43650
  this.codexAcpClient.setProvider(params);
43651
+ this.hookReview.invalidate();
43450
43652
  await this.enqueueProviderUpdate((client) => client.setProvider(params));
43451
43653
  return {};
43452
43654
  }
43453
43655
  async disableProvider(params) {
43454
43656
  this.codexAcpClient.disableProvider(params);
43657
+ if (params.providerId === OPENAI_PROVIDER_ID) this.hookReview.invalidate();
43455
43658
  if (params.providerId !== OPENAI_PROVIDER_ID) {
43456
43659
  return {};
43457
43660
  }
@@ -43480,6 +43683,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
43480
43683
  throw new Error("Cannot restart Codex app-server before ACP initialization");
43481
43684
  }
43482
43685
  await replacement.initialize(this.initializeRequest);
43686
+ this.hookReview.bind(replacement);
43483
43687
  this.codexAcpClient = replacement;
43484
43688
  this.sessionIndex.observe(replacement);
43485
43689
  this.availableCommands = this.createAvailableCommands(replacement);
@@ -43492,6 +43696,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
43492
43696
  if (replacement.appServerClient.connectionLoss.lost) {
43493
43697
  break;
43494
43698
  }
43699
+ if (this.hookReview.enabled) continue;
43495
43700
  try {
43496
43701
  const request = {
43497
43702
  sessionId: session.sessionId,
@@ -43594,7 +43799,9 @@ Check ${configPath} and project .codex directories, especially their config.toml
43594
43799
  currentClient: () => this.codexAcpClient,
43595
43800
  createClient: (connection) => {
43596
43801
  this.captureStderr();
43597
- return this.codexAcpClient.withAppServer(new CodexAppServerClient(connection.connection));
43802
+ const client = this.codexAcpClient.withAppServer(new CodexAppServerClient(connection.connection));
43803
+ this.hookReview.bind(client);
43804
+ return client;
43598
43805
  },
43599
43806
  handshake: (client) => this.initializeRequest !== null && this.codexAcpClient.initialized ? client.initialize(this.initializeRequest) : null,
43600
43807
  install: (client) => this.installCodexClient(client),
@@ -43626,6 +43833,7 @@ Check ${configPath} and project .codex directories, especially their config.toml
43626
43833
  installCodexClient(client) {
43627
43834
  client.adoptRoutingFrom(this.codexAcpClient);
43628
43835
  this.codexAcpClient = client;
43836
+ this.hookReview.bind(client);
43629
43837
  this.sessionIndex.observe(client);
43630
43838
  this.availableCommands = this.createAvailableCommands(client);
43631
43839
  for (const session of this.sessions.values()) {
@@ -44161,11 +44369,15 @@ Check ${configPath} and project .codex directories, especially their config.toml
44161
44369
  }
44162
44370
  return await new Promise((resolve, reject) => {
44163
44371
  let turnStarted = false;
44164
- const promptDone = this.prompt(params, void 0, () => {
44165
- turnStarted = true;
44166
- logger.log(`${source} started a new turn`, { sessionId: params.sessionId });
44167
- resolve(true);
44168
- });
44372
+ const promptDone = this.hookReview.continueOperation(
44373
+ this.getSessionState(params.sessionId).cwd,
44374
+ params._meta,
44375
+ () => this.promptReviewed(params, void 0, () => {
44376
+ turnStarted = true;
44377
+ logger.log(`${source} started a new turn`, { sessionId: params.sessionId });
44378
+ resolve(true);
44379
+ })
44380
+ );
44169
44381
  promptDone.then(
44170
44382
  (response) => {
44171
44383
  if (!turnStarted && response.stopReason === "cancelled") {
@@ -44220,7 +44432,8 @@ Check ${configPath} and project .codex directories, especially their config.toml
44220
44432
  }
44221
44433
  return {
44222
44434
  sessionId,
44223
- prompt
44435
+ prompt,
44436
+ ...params["_meta"] === void 0 ? {} : { _meta: params["_meta"] }
44224
44437
  };
44225
44438
  }
44226
44439
  createSessionConfigOptions(sessionState) {
@@ -45016,6 +45229,13 @@ Check ${configPath} and project .codex directories, especially their config.toml
45016
45229
  return turnId;
45017
45230
  }
45018
45231
  async prompt(params, signal, onTurnStarted) {
45232
+ return this.hookReview.run(
45233
+ this.getSessionState(params.sessionId).cwd,
45234
+ params._meta,
45235
+ () => this.promptReviewed(params, signal, onTurnStarted)
45236
+ );
45237
+ }
45238
+ async promptReviewed(params, signal, onTurnStarted) {
45019
45239
  if (this.providerUpdate !== null) {
45020
45240
  await this.waitForProviderUpdate();
45021
45241
  }
@@ -45857,6 +46077,7 @@ var asyncTaskStopParamsParser = external_exports.object({
45857
46077
  }).passthrough();
45858
46078
  var hooksListParamsParser = external_exports.object({ cwd: external_exports.string().trim().min(1) });
45859
46079
  var hooksTrustParamsParser = external_exports.object({
46080
+ reviewToken: external_exports.string().min(1).optional(),
45860
46081
  cwd: external_exports.string().trim().min(1),
45861
46082
  hooks: external_exports.array(external_exports.object({ key: external_exports.string().min(1), currentHash: external_exports.string().min(1) })).min(1)
45862
46083
  });
@@ -45940,5 +46161,5 @@ function startAcpServer() {
45940
46161
  codexAcpServer = null;
45941
46162
  }
45942
46163
  });
45943
- }).onRequest(methods.agent.initialize, (ctx) => getAgent().initialize(ctx.params)).onRequest(methods.agent.session.new, (ctx) => getAgent().newSession(ctx.params)).onRequest(methods.agent.session.load, (ctx) => getAgent().loadSession(ctx.params)).onRequest(methods.agent.session.fork, (ctx) => getAgent().forkSession(ctx.params)).onRequest(methods.agent.session.list, (ctx) => getAgent().listSessions(ctx.params)).onRequest(methods.agent.session.delete, (ctx) => getAgent().deleteSession(ctx.params)).onRequest(methods.agent.session.resume, (ctx) => getAgent().resumeSession(ctx.params)).onRequest(methods.agent.session.close, (ctx) => getAgent().closeSession(ctx.params)).onRequest(methods.agent.session.setMode, (ctx) => getAgent().setSessionMode(ctx.params)).onRequest(methods.agent.session.setConfigOption, (ctx) => getAgent().setSessionConfigOption(ctx.params)).onRequest(methods.agent.authenticate, (ctx) => getAgent().authenticate(ctx.params, ctx.requestId)).onRequest(methods.agent.logout, (ctx) => getAgent().logout(ctx.params)).onRequest(methods.agent.providers.list, (ctx) => getAgent().listProviders(ctx.params)).onRequest(methods.agent.providers.set, (ctx) => getAgent().setProvider(ctx.params)).onRequest(methods.agent.providers.disable, (ctx) => getAgent().disableProvider(ctx.params)).onRequest(methods.agent.session.prompt, (ctx) => getAgent().prompt(ctx.params, ctx.signal)).onNotification(methods.agent.session.cancel, (ctx) => getAgent().cancel(ctx.params)).onRequest("authentication/status", emptyExtensionParamsParser, (ctx) => getAgent().extMethod("authentication/status", ctx.params)).onRequest("authentication/logout", emptyExtensionParamsParser, (ctx) => getAgent().extMethod("authentication/logout", ctx.params)).onRequest(LEGACY_SET_SESSION_MODEL_METHOD, legacySetSessionModelParamsParser, (ctx) => getAgent().extMethod(LEGACY_SET_SESSION_MODEL_METHOD, ctx.params)).onRequest(SESSION_STEERING_METHOD, sessionSteerParamsParser, (ctx) => getAgent().extMethod(SESSION_STEERING_METHOD, ctx.params)).onRequest(ASYNC_TASK_STOP_METHOD, asyncTaskStopParamsParser, (ctx) => getAgent().extMethod(ASYNC_TASK_STOP_METHOD, ctx.params)).onRequest(CODEX_HOOKS_LIST_METHOD, hooksListParamsParser, (ctx) => getAgent().listHooks(ctx.params.cwd)).onRequest(CODEX_HOOKS_TRUST_METHOD, hooksTrustParamsParser, (ctx) => getAgent().trustHooks(ctx.params.cwd, ctx.params.hooks)).onRequest(GOAL_CONTROL_METHOD, goalControlParamsParser, (ctx) => getAgent().extMethod(GOAL_CONTROL_METHOD, ctx.params)).onRequest(SESSION_RENAME_METHOD, sessionRenameParamsParser, (ctx) => getAgent().sessionIndex.rename(ctx.params)).onRequest(SESSION_ARCHIVE_METHOD, sessionArchiveParamsParser, (ctx) => getAgent().sessionIndex.setArchived(ctx.params, true)).onRequest(SESSION_UNARCHIVE_METHOD, sessionArchiveParamsParser, (ctx) => getAgent().sessionIndex.setArchived(ctx.params, false)).onRequest(SESSION_LIST_SUBSCRIBE_METHOD, sessionListSubscriptionParamsParser, (ctx) => getAgent().sessionIndex.subscribeList(ctx.params)).onRequest(SESSION_LIST_UNSUBSCRIBE_METHOD, sessionListSubscriptionParamsParser, (ctx) => getAgent().sessionIndex.unsubscribeList(ctx.params)).connect(acpJsonStream);
46164
+ }).onRequest(methods.agent.initialize, (ctx) => getAgent().initialize(ctx.params)).onRequest(methods.agent.session.new, (ctx) => getAgent().newSession(ctx.params)).onRequest(methods.agent.session.load, (ctx) => getAgent().loadSession(ctx.params)).onRequest(methods.agent.session.fork, (ctx) => getAgent().forkSession(ctx.params)).onRequest(methods.agent.session.list, (ctx) => getAgent().listSessions(ctx.params)).onRequest(methods.agent.session.delete, (ctx) => getAgent().deleteSession(ctx.params)).onRequest(methods.agent.session.resume, (ctx) => getAgent().resumeSession(ctx.params)).onRequest(methods.agent.session.close, (ctx) => getAgent().closeSession(ctx.params)).onRequest(methods.agent.session.setMode, (ctx) => getAgent().setSessionMode(ctx.params)).onRequest(methods.agent.session.setConfigOption, (ctx) => getAgent().setSessionConfigOption(ctx.params)).onRequest(methods.agent.authenticate, (ctx) => getAgent().authenticate(ctx.params, ctx.requestId)).onRequest(methods.agent.logout, (ctx) => getAgent().logout(ctx.params)).onRequest(methods.agent.providers.list, (ctx) => getAgent().listProviders(ctx.params)).onRequest(methods.agent.providers.set, (ctx) => getAgent().setProvider(ctx.params)).onRequest(methods.agent.providers.disable, (ctx) => getAgent().disableProvider(ctx.params)).onRequest(methods.agent.session.prompt, (ctx) => getAgent().prompt(ctx.params, ctx.signal)).onNotification(methods.agent.session.cancel, (ctx) => getAgent().cancel(ctx.params)).onRequest("authentication/status", emptyExtensionParamsParser, (ctx) => getAgent().extMethod("authentication/status", ctx.params)).onRequest("authentication/logout", emptyExtensionParamsParser, (ctx) => getAgent().extMethod("authentication/logout", ctx.params)).onRequest(LEGACY_SET_SESSION_MODEL_METHOD, legacySetSessionModelParamsParser, (ctx) => getAgent().extMethod(LEGACY_SET_SESSION_MODEL_METHOD, ctx.params)).onRequest(SESSION_STEERING_METHOD, sessionSteerParamsParser, (ctx) => getAgent().extMethod(SESSION_STEERING_METHOD, ctx.params)).onRequest(ASYNC_TASK_STOP_METHOD, asyncTaskStopParamsParser, (ctx) => getAgent().extMethod(ASYNC_TASK_STOP_METHOD, ctx.params)).onRequest(CODEX_HOOKS_LIST_METHOD, hooksListParamsParser, (ctx) => getAgent().listHooks(ctx.params.cwd)).onRequest(CODEX_HOOKS_TRUST_METHOD, hooksTrustParamsParser, (ctx) => getAgent().trustHooks(ctx.params.cwd, ctx.params.hooks, ctx.params.reviewToken)).onRequest(GOAL_CONTROL_METHOD, goalControlParamsParser, (ctx) => getAgent().extMethod(GOAL_CONTROL_METHOD, ctx.params)).onRequest(SESSION_RENAME_METHOD, sessionRenameParamsParser, (ctx) => getAgent().sessionIndex.rename(ctx.params)).onRequest(SESSION_ARCHIVE_METHOD, sessionArchiveParamsParser, (ctx) => getAgent().sessionIndex.setArchived(ctx.params, true)).onRequest(SESSION_UNARCHIVE_METHOD, sessionArchiveParamsParser, (ctx) => getAgent().sessionIndex.setArchived(ctx.params, false)).onRequest(SESSION_LIST_SUBSCRIBE_METHOD, sessionListSubscriptionParamsParser, (ctx) => getAgent().sessionIndex.subscribeList(ctx.params)).onRequest(SESSION_LIST_UNSUBSCRIBE_METHOD, sessionListSubscriptionParamsParser, (ctx) => getAgent().sessionIndex.unsubscribeList(ctx.params)).connect(acpJsonStream);
45944
46165
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "2.2.2-alas.3",
6
+ "version": "2.2.2-alas.4",
7
7
  "description": "",
8
8
  "main": "dist/index.js",
9
9
  "bin": {
@@ -80,6 +80,6 @@
80
80
  "alasDownstream": {
81
81
  "upstreamVersion": "2.2.2",
82
82
  "upstreamCommit": "202e66e63343ec4f5a5e35ab590251d813d129ca",
83
- "sourceCommit": "2be0ccf63ad9c18131815207511597bb34d6adc0"
83
+ "sourceCommit": "4b517e7e77ece05ef63f34c890bc853b7fb96a43"
84
84
  }
85
85
  }