@parall/daemon 1.43.0 → 1.45.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.
@@ -17718,9 +17718,9 @@ var require_getMachineId_linux = __commonJS({
17718
17718
  var api_1 = (init_esm(), __toCommonJS(esm_exports));
17719
17719
  async function getMachineId() {
17720
17720
  const paths = ["/etc/machine-id", "/var/lib/dbus/machine-id"];
17721
- for (const path11 of paths) {
17721
+ for (const path12 of paths) {
17722
17722
  try {
17723
- const result = await fs_1.promises.readFile(path11, { encoding: "utf8" });
17723
+ const result = await fs_1.promises.readFile(path12, { encoding: "utf8" });
17724
17724
  return result.trim();
17725
17725
  } catch (e) {
17726
17726
  api_1.diag.debug(`error reading machine id: ${e}`);
@@ -21123,7 +21123,7 @@ function appendRootPathToUrlIfNeeded(url) {
21123
21123
  return void 0;
21124
21124
  }
21125
21125
  }
21126
- function appendResourcePathToUrl(url, path11) {
21126
+ function appendResourcePathToUrl(url, path12) {
21127
21127
  try {
21128
21128
  new URL(url);
21129
21129
  } catch (_a) {
@@ -21133,11 +21133,11 @@ function appendResourcePathToUrl(url, path11) {
21133
21133
  if (!url.endsWith("/")) {
21134
21134
  url = url + "/";
21135
21135
  }
21136
- url += path11;
21136
+ url += path12;
21137
21137
  try {
21138
21138
  new URL(url);
21139
21139
  } catch (_b) {
21140
- diag2.warn("Configuration: Provided URL appended with '" + path11 + "' is not a valid URL, using 'undefined' instead of '" + url + "'");
21140
+ diag2.warn("Configuration: Provided URL appended with '" + path12 + "' is not a valid URL, using 'undefined' instead of '" + url + "'");
21141
21141
  return void 0;
21142
21142
  }
21143
21143
  return url;
@@ -24050,7 +24050,7 @@ var require_parse = __commonJS({
24050
24050
  "ts/node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/parse.js"(exports2, module2) {
24051
24051
  "use strict";
24052
24052
  var SemVer = require_semver();
24053
- var parse2 = (version, options, throwErrors = false) => {
24053
+ var parse = (version, options, throwErrors = false) => {
24054
24054
  if (version instanceof SemVer) {
24055
24055
  return version;
24056
24056
  }
@@ -24063,7 +24063,7 @@ var require_parse = __commonJS({
24063
24063
  throw er;
24064
24064
  }
24065
24065
  };
24066
- module2.exports = parse2;
24066
+ module2.exports = parse;
24067
24067
  }
24068
24068
  });
24069
24069
 
@@ -24071,9 +24071,9 @@ var require_parse = __commonJS({
24071
24071
  var require_valid = __commonJS({
24072
24072
  "ts/node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/valid.js"(exports2, module2) {
24073
24073
  "use strict";
24074
- var parse2 = require_parse();
24074
+ var parse = require_parse();
24075
24075
  var valid = (version, options) => {
24076
- const v = parse2(version, options);
24076
+ const v = parse(version, options);
24077
24077
  return v ? v.version : null;
24078
24078
  };
24079
24079
  module2.exports = valid;
@@ -24084,9 +24084,9 @@ var require_valid = __commonJS({
24084
24084
  var require_clean = __commonJS({
24085
24085
  "ts/node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/clean.js"(exports2, module2) {
24086
24086
  "use strict";
24087
- var parse2 = require_parse();
24087
+ var parse = require_parse();
24088
24088
  var clean = (version, options) => {
24089
- const s = parse2(version.trim().replace(/^[=v]+/, ""), options);
24089
+ const s = parse(version.trim().replace(/^[=v]+/, ""), options);
24090
24090
  return s ? s.version : null;
24091
24091
  };
24092
24092
  module2.exports = clean;
@@ -24121,10 +24121,10 @@ var require_inc = __commonJS({
24121
24121
  var require_diff = __commonJS({
24122
24122
  "ts/node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/diff.js"(exports2, module2) {
24123
24123
  "use strict";
24124
- var parse2 = require_parse();
24124
+ var parse = require_parse();
24125
24125
  var diff = (version1, version2) => {
24126
- const v1 = parse2(version1, null, true);
24127
- const v2 = parse2(version2, null, true);
24126
+ const v1 = parse(version1, null, true);
24127
+ const v2 = parse(version2, null, true);
24128
24128
  const comparison = v1.compare(v2);
24129
24129
  if (comparison === 0) {
24130
24130
  return null;
@@ -24195,9 +24195,9 @@ var require_patch = __commonJS({
24195
24195
  var require_prerelease = __commonJS({
24196
24196
  "ts/node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/prerelease.js"(exports2, module2) {
24197
24197
  "use strict";
24198
- var parse2 = require_parse();
24198
+ var parse = require_parse();
24199
24199
  var prerelease = (version, options) => {
24200
- const parsed = parse2(version, options);
24200
+ const parsed = parse(version, options);
24201
24201
  return parsed && parsed.prerelease.length ? parsed.prerelease : null;
24202
24202
  };
24203
24203
  module2.exports = prerelease;
@@ -24383,7 +24383,7 @@ var require_coerce = __commonJS({
24383
24383
  "ts/node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/coerce.js"(exports2, module2) {
24384
24384
  "use strict";
24385
24385
  var SemVer = require_semver();
24386
- var parse2 = require_parse();
24386
+ var parse = require_parse();
24387
24387
  var { safeRe: re2, t } = require_re();
24388
24388
  var coerce = (version, options) => {
24389
24389
  if (version instanceof SemVer) {
@@ -24418,7 +24418,7 @@ var require_coerce = __commonJS({
24418
24418
  const patch = match[4] || "0";
24419
24419
  const prerelease = options.includePrerelease && match[5] ? `-${match[5]}` : "";
24420
24420
  const build = options.includePrerelease && match[6] ? `+${match[6]}` : "";
24421
- return parse2(`${major2}.${minor}.${patch}${prerelease}${build}`, options);
24421
+ return parse(`${major2}.${minor}.${patch}${prerelease}${build}`, options);
24422
24422
  };
24423
24423
  module2.exports = coerce;
24424
24424
  }
@@ -25435,7 +25435,7 @@ var require_semver2 = __commonJS({
25435
25435
  var constants2 = require_constants3();
25436
25436
  var SemVer = require_semver();
25437
25437
  var identifiers = require_identifiers();
25438
- var parse2 = require_parse();
25438
+ var parse = require_parse();
25439
25439
  var valid = require_valid();
25440
25440
  var clean = require_clean();
25441
25441
  var inc = require_inc();
@@ -25473,7 +25473,7 @@ var require_semver2 = __commonJS({
25473
25473
  var simplifyRange = require_simplify();
25474
25474
  var subset = require_subset();
25475
25475
  module2.exports = {
25476
- parse: parse2,
25476
+ parse,
25477
25477
  valid,
25478
25478
  clean,
25479
25479
  inc,
@@ -26487,9 +26487,6 @@ function laneContextFilePath(contextDir, targetUri, threadRootId) {
26487
26487
  return path.join(contextDir, `${laneKeyForTarget(targetUri, threadRootId)}.json`);
26488
26488
  }
26489
26489
 
26490
- // ts/agent-core/dist/lane-ledger.js
26491
- import * as fs from "node:fs";
26492
-
26493
26490
  // ts/sdk/dist/types.js
26494
26491
  var MENTION_ALL_USER_ID = "all";
26495
26492
 
@@ -26965,7 +26962,7 @@ var ParallClient = class _ParallClient {
26965
26962
  return apiError;
26966
26963
  }
26967
26964
  /** Build headers common to all requests (auth, swimlane). */
26968
- buildHeaders(path11, extra) {
26965
+ buildHeaders(path12, extra) {
26969
26966
  const headers = {
26970
26967
  "Content-Type": "application/json",
26971
26968
  ...extra
@@ -26976,7 +26973,7 @@ var ParallClient = class _ParallClient {
26976
26973
  if (this.swimlaneName) {
26977
26974
  headers["X-Prll-Swimlane"] = this.swimlaneName;
26978
26975
  }
26979
- if (path11.startsWith(API_BASE)) {
26976
+ if (path12.startsWith(API_BASE)) {
26980
26977
  const overrides = this.getFeatureFlagOverrides?.();
26981
26978
  if (overrides)
26982
26979
  headers["X-Prll-FF-Override"] = overrides;
@@ -26999,8 +26996,8 @@ var ParallClient = class _ParallClient {
26999
26996
  * is authoritative, so wiki vs api routing can't drift from how a caller
27000
26997
  * happens to invoke the client.
27001
26998
  */
27002
- baseUrlFor(path11) {
27003
- return path11.startsWith(WIKI_BASE) ? this.wikiBaseUrl : this.baseUrl;
26999
+ baseUrlFor(path12) {
27000
+ return path12.startsWith(WIKI_BASE) ? this.wikiBaseUrl : this.baseUrl;
27004
27001
  }
27005
27002
  setToken(token) {
27006
27003
  this.token = token;
@@ -27027,10 +27024,10 @@ var ParallClient = class _ParallClient {
27027
27024
  * REFRESH_THRESHOLD_S, refresh it **before** sending the request.
27028
27025
  * No-op when the token is still fresh, missing, or un-parseable.
27029
27026
  */
27030
- async ensureFreshToken(path11) {
27027
+ async ensureFreshToken(path12) {
27031
27028
  if (!this.token || !this.getRefreshToken)
27032
27029
  return;
27033
- const pathSuffix = path11.replace(/^\/api\/v1/, "");
27030
+ const pathSuffix = path12.replace(/^\/api\/v1/, "");
27034
27031
  if (_ParallClient.AUTH_PATHS.has(pathSuffix))
27035
27032
  return;
27036
27033
  const exp = _ParallClient.decodeJwtExp(this.token);
@@ -27062,11 +27059,11 @@ var ParallClient = class _ParallClient {
27062
27059
  this.refreshPromise = null;
27063
27060
  }
27064
27061
  }
27065
- async request(method, path11, body, query, retried = false, opts) {
27062
+ async request(method, path12, body, query, retried = false, opts) {
27066
27063
  if (!retried) {
27067
- await this.ensureFreshToken(path11);
27064
+ await this.ensureFreshToken(path12);
27068
27065
  }
27069
- let url = `${this.baseUrlFor(path11)}${path11}`;
27066
+ let url = `${this.baseUrlFor(path12)}${path12}`;
27070
27067
  if (query) {
27071
27068
  const params = new URLSearchParams();
27072
27069
  for (const [key, value] of Object.entries(query)) {
@@ -27078,7 +27075,7 @@ var ParallClient = class _ParallClient {
27078
27075
  if (qs)
27079
27076
  url += `?${qs}`;
27080
27077
  }
27081
- const headers = this.buildHeaders(path11);
27078
+ const headers = this.buildHeaders(path12);
27082
27079
  const timeoutSignal = AbortSignal.timeout(opts?.timeoutMs ?? 15e3);
27083
27080
  const signal = opts?.signal ? AbortSignal.any([opts.signal, timeoutSignal]) : timeoutSignal;
27084
27081
  let res;
@@ -27096,12 +27093,12 @@ var ParallClient = class _ParallClient {
27096
27093
  throw _ParallClient.normalizeFetchError(err);
27097
27094
  }
27098
27095
  if (res.status === 401) {
27099
- const pathSuffix = path11.replace(/^\/api\/v1/, "");
27096
+ const pathSuffix = path12.replace(/^\/api\/v1/, "");
27100
27097
  const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
27101
27098
  if (!retried && !isAuthPath && this.getRefreshToken) {
27102
27099
  const refreshed = await this.tryRefresh();
27103
27100
  if (refreshed) {
27104
- return this.request(method, path11, body, query, true, opts);
27101
+ return this.request(method, path12, body, query, true, opts);
27105
27102
  }
27106
27103
  }
27107
27104
  if (this.onTokenExpired && !isAuthPath) {
@@ -27131,15 +27128,15 @@ var ParallClient = class _ParallClient {
27131
27128
  * hit the 100 MiB cap, so a longer 5-minute timeout is used so a
27132
27129
  * 50 MiB blob on a slow connection doesn't get chopped at 15 s.
27133
27130
  */
27134
- async multipartRequest(method, path11, body, retried = false) {
27131
+ async multipartRequest(method, path12, body, retried = false) {
27135
27132
  if (!retried) {
27136
- await this.ensureFreshToken(path11);
27133
+ await this.ensureFreshToken(path12);
27137
27134
  }
27138
- const { "Content-Type": _drop, ...headers } = this.buildHeaders(path11);
27135
+ const { "Content-Type": _drop, ...headers } = this.buildHeaders(path12);
27139
27136
  void _drop;
27140
27137
  let res;
27141
27138
  try {
27142
- res = await fetch(`${this.baseUrlFor(path11)}${path11}`, {
27139
+ res = await fetch(`${this.baseUrlFor(path12)}${path12}`, {
27143
27140
  method,
27144
27141
  headers,
27145
27142
  body,
@@ -27149,12 +27146,12 @@ var ParallClient = class _ParallClient {
27149
27146
  throw _ParallClient.normalizeFetchError(err);
27150
27147
  }
27151
27148
  if (res.status === 401) {
27152
- const pathSuffix = path11.replace(/^\/api\/v1/, "");
27149
+ const pathSuffix = path12.replace(/^\/api\/v1/, "");
27153
27150
  const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
27154
27151
  if (!retried && !isAuthPath && this.getRefreshToken) {
27155
27152
  const refreshed = await this.tryRefresh();
27156
27153
  if (refreshed) {
27157
- return this.multipartRequest(method, path11, body, true);
27154
+ return this.multipartRequest(method, path12, body, true);
27158
27155
  }
27159
27156
  }
27160
27157
  if (this.onTokenExpired && !isAuthPath) {
@@ -27858,8 +27855,8 @@ var ParallClient = class _ParallClient {
27858
27855
  async requestMachineUpdate(orgId, machineId, mandatory = false) {
27859
27856
  await this.request("POST", ENDPOINTS.MACHINE_REQUEST_UPDATE(orgId, machineId), { mandatory });
27860
27857
  }
27861
- async browseMachineFilesystem(orgId, machineId, path11) {
27862
- return this.request("POST", ENDPOINTS.MACHINE_BROWSE(orgId, machineId), { path: path11 }, void 0, false, { timeoutMs: 15e3 });
27858
+ async browseMachineFilesystem(orgId, machineId, path12) {
27859
+ return this.request("POST", ENDPOINTS.MACHINE_BROWSE(orgId, machineId), { path: path12 }, void 0, false, { timeoutMs: 15e3 });
27863
27860
  }
27864
27861
  /** Create a new machine key. Returns the raw key string (shown once) + metadata. */
27865
27862
  async createMachineKey(orgId, machineId, name) {
@@ -27944,7 +27941,6 @@ var ParallClient = class _ParallClient {
27944
27941
  async steerDispatch(orgId, req) {
27945
27942
  return this.request("POST", ENDPOINTS.DISPATCH_STEER(orgId), req);
27946
27943
  }
27947
- /** End a turn: no_action sweep of the lane's members + lane release + re-drive check. */
27948
27944
  async completeDispatch(orgId, req) {
27949
27945
  return this.request("POST", ENDPOINTS.DISPATCH_COMPLETE(orgId), req);
27950
27946
  }
@@ -27952,6 +27948,10 @@ var ParallClient = class _ParallClient {
27952
27948
  * End a turn for a lane-less runtime: resolve the turn's folded WorkItems
27953
27949
  * by source — broad-cover to the turn's reply Effect when one exists,
27954
27950
  * no_action sweep otherwise. Idempotent.
27951
+ *
27952
+ * @deprecated Legacy ok-only alias — use {@link completeDispatch} with the
27953
+ * `sources` form, which also carries `turn_outcome`. The endpoint retires
27954
+ * at S3b (dispatch-convergence-design.md §3).
27955
27955
  */
27956
27956
  async completeDispatchSources(orgId, req) {
27957
27957
  return this.request("POST", ENDPOINTS.DISPATCH_COMPLETE_SOURCES(orgId), req);
@@ -28428,8 +28428,8 @@ var ParallClient = class _ParallClient {
28428
28428
  async deleteWikiRestriction(orgId, wikiId, restrictionId) {
28429
28429
  await this.request("DELETE", ENDPOINTS.WIKI_RESTRICTION(orgId, wikiId, restrictionId));
28430
28430
  }
28431
- async getWikiAccessStatus(orgId, wikiId, path11) {
28432
- return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0, path11 ? { path: path11 } : void 0);
28431
+ async getWikiAccessStatus(orgId, wikiId, path12) {
28432
+ return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0, path12 ? { path: path12 } : void 0);
28433
28433
  }
28434
28434
  async createWikiAccessRequest(orgId, wikiId, data) {
28435
28435
  await this.request("POST", ENDPOINTS.WIKI_ACCESS_REQUESTS(orgId, wikiId), data);
@@ -28438,14 +28438,14 @@ var ParallClient = class _ParallClient {
28438
28438
  async getWikiCommits(orgId, wikiId, params) {
28439
28439
  return this.request("GET", ENDPOINTS.WIKI_COMMITS(orgId, wikiId), void 0, params);
28440
28440
  }
28441
- async getWikiFileCommits(orgId, wikiId, path11, params) {
28441
+ async getWikiFileCommits(orgId, wikiId, path12, params) {
28442
28442
  return this.request("GET", ENDPOINTS.WIKI_FILE_COMMITS(orgId, wikiId), void 0, {
28443
- path: path11,
28443
+ path: path12,
28444
28444
  ...params
28445
28445
  });
28446
28446
  }
28447
- async getWikiBlame(orgId, wikiId, path11, ref) {
28448
- return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path: path11, ref });
28447
+ async getWikiBlame(orgId, wikiId, path12, ref) {
28448
+ return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path: path12, ref });
28449
28449
  }
28450
28450
  // ---- Wiki Operations (audit log) ----
28451
28451
  async getWikiOperations(orgId, wikiId, params) {
@@ -29098,6 +29098,7 @@ var ParallWs = class {
29098
29098
  };
29099
29099
 
29100
29100
  // ts/agent-core/dist/lane-ledger.js
29101
+ import * as fs from "node:fs";
29101
29102
  var LedgerUnsupportedError = class extends Error {
29102
29103
  };
29103
29104
  function isStaleLane(err) {
@@ -29157,7 +29158,11 @@ var LaneLedger = class {
29157
29158
  throw err;
29158
29159
  }
29159
29160
  if (!res.claimed || !res.lane) {
29160
- this.opts.log?.info(`lane for ${targetUri} held by a healthy incumbent \u2014 leaving events pending for re-drive`);
29161
+ if (res.reason === "empty") {
29162
+ this.opts.log?.warn(`claim for ${targetUri} came back empty \u2014 nothing foldable; leaving to the reconciler`);
29163
+ } else {
29164
+ this.opts.log?.info(`lane for ${targetUri} held by a healthy incumbent \u2014 leaving events pending for re-drive`);
29165
+ }
29161
29166
  return null;
29162
29167
  }
29163
29168
  const leaseUntilMs = Date.parse(res.lease_until ?? "");
@@ -29234,6 +29239,18 @@ var LaneLedger = class {
29234
29239
  * re-drives any same-target pending work. A STALE_LANE answer means a
29235
29240
  * takeover already owns the resource — local state is dropped either way.
29236
29241
  */
29242
+ /**
29243
+ * Record that the turn on this lane surfaced a runtime error. The flow
29244
+ * settles an errored lane immediately (dispatchLaneGroup returns 'failed'
29245
+ * after a forced complete), so the bit normally lives for one turn only —
29246
+ * it is the transport between the gateway's per-session error signal and
29247
+ * this lane's complete request.
29248
+ */
29249
+ markTurnError(laneKey) {
29250
+ const lane = this.lanes.get(laneKey);
29251
+ if (lane)
29252
+ lane.turnError = true;
29253
+ }
29237
29254
  async completeIfIdle(laneKey, hasMoreLocal) {
29238
29255
  const lane = this.lanes.get(laneKey);
29239
29256
  if (!lane || hasMoreLocal)
@@ -29244,7 +29261,10 @@ var LaneLedger = class {
29244
29261
  const res = await this.opts.client.completeDispatch(this.opts.orgId, {
29245
29262
  lane: lane.lane,
29246
29263
  target_uri: lane.targetUri,
29247
- thread_root_id: lane.threadRootId
29264
+ thread_root_id: lane.threadRootId,
29265
+ // An error turn releases its members for retry instead of sweeping
29266
+ // them as handled (ignored by older servers).
29267
+ turn_outcome: lane.turnError ? "error" : "ok"
29248
29268
  });
29249
29269
  if (res.swept_no_action > 0 || res.redriven) {
29250
29270
  this.opts.log?.info(`lane complete for ${lane.targetUri}: swept ${res.swept_no_action} no_action, redriven=${res.redriven}`);
@@ -29257,6 +29277,17 @@ var LaneLedger = class {
29257
29277
  this.opts.log?.warn(`lane complete failed for ${lane.targetUri}: ${String(err)}`);
29258
29278
  }
29259
29279
  }
29280
+ /**
29281
+ * Renew one lane by its key — the external runtime-activity hook for
29282
+ * adapters whose tool traffic bypasses the RuntimeEvent stream (openclaw
29283
+ * hooks). Scoped to the session's own lane: renewing every lane would let
29284
+ * one busy fork keep an unrelated stalled fork's lane leased forever.
29285
+ */
29286
+ renewByKey(laneKey) {
29287
+ const lane = this.lanes.get(laneKey);
29288
+ if (lane)
29289
+ this.maybeRenew(lane);
29290
+ }
29260
29291
  /**
29261
29292
  * Long-turn keepalive: renew the lane's lease on runtime activity, throttled
29262
29293
  * so a chatty turn doesn't spam the server. Without this, a legitimately
@@ -29351,6 +29382,20 @@ var LaneLedger = class {
29351
29382
  this.lanes.set(lane.laneKey, lane);
29352
29383
  return lane;
29353
29384
  }
29385
+ /**
29386
+ * Drop a lane's local record without any server call — for a typed lane
29387
+ * whose member the by-id complete just resolved (the server dropped the
29388
+ * vacated lane row in the same transaction). Calling completeIfIdle
29389
+ * instead would fire a lane-form Complete at a lane that no longer exists
29390
+ * and burn an RPC on the guaranteed STALE_LANE answer.
29391
+ */
29392
+ dropLocal(laneKey) {
29393
+ const lane = this.lanes.get(laneKey);
29394
+ if (!lane)
29395
+ return;
29396
+ this.lanes.delete(laneKey);
29397
+ this.removeLaneContext(lane);
29398
+ }
29354
29399
  /**
29355
29400
  * Remove the per-lane context file (and its CLI sidecar) when the lane
29356
29401
  * ends. A leftover file would make a later cross-context send to the same
@@ -29398,25 +29443,136 @@ async function dispatchLaneGroup(host, opts) {
29398
29443
  }
29399
29444
  return "foreign";
29400
29445
  }
29446
+ host.noteSessionLane(opts.sessionKey, lane.laneKey);
29401
29447
  let dispatched = false;
29402
29448
  try {
29403
29449
  dispatched = await host.runDispatch(event, opts.sessionKey, opts.body, opts.earlier, opts.captureText);
29404
29450
  } catch (err) {
29451
+ host.noteSessionLane(opts.sessionKey, null);
29405
29452
  await ledger.release(lane.laneKey).catch(() => {
29406
29453
  });
29407
29454
  throw err;
29455
+ } finally {
29456
+ if (dispatched)
29457
+ host.noteSessionLane(opts.sessionKey, null);
29408
29458
  }
29409
29459
  if (!dispatched) {
29410
29460
  return "shutdown";
29411
29461
  }
29462
+ if (host.consumeTurnError(opts.sessionKey)) {
29463
+ ledger.markTurnError(lane.laneKey);
29464
+ for (const msgId of lane.folded.keys()) {
29465
+ host.dispatchedMessages.delete(msgId);
29466
+ }
29467
+ try {
29468
+ host.opts.dispatchAdapter.abortDispatch?.(opts.sessionKey);
29469
+ } catch {
29470
+ }
29471
+ await ledger.completeIfIdle(lane.laneKey, false);
29472
+ return "failed";
29473
+ }
29412
29474
  const pendingInjections = host.opts.dispatchAdapter.hasPendingInjections?.(opts.sessionKey) ?? false;
29413
29475
  await ledger.completeIfIdle(lane.laneKey, pendingInjections || opts.hasMoreLocal());
29414
29476
  return "dispatched";
29415
29477
  }
29478
+ function typedLedgerEventIds(host, events) {
29479
+ if (!host.laneLedger || host.ledgerDisabled)
29480
+ return null;
29481
+ const ids = [];
29482
+ for (const ev of events) {
29483
+ if (!ev.dispatchEventId || host.usesLaneLedger(ev))
29484
+ return null;
29485
+ ids.push(ev.dispatchEventId);
29486
+ }
29487
+ return ids.length > 0 ? ids : null;
29488
+ }
29489
+ function isByIDCompleteUnsupported(err) {
29490
+ return err instanceof ApiError && err.status === 400;
29491
+ }
29492
+ async function resolveDispatchByID(host, dispatchEventId, lane) {
29493
+ try {
29494
+ await host.opts.client.completeDispatch(host.opts.config.org_id, {
29495
+ dispatch_event_id: dispatchEventId,
29496
+ ...lane ? { lane } : {},
29497
+ turn_outcome: "ok"
29498
+ });
29499
+ return "ok";
29500
+ } catch (err) {
29501
+ if (err instanceof ApiError && err.status === 409)
29502
+ return "stale";
29503
+ if (isByIDCompleteUnsupported(err))
29504
+ return "unsupported";
29505
+ host.opts.log?.warn(`by-id complete failed for ${dispatchEventId} \u2014 leaving for re-drive: ${String(err)}`);
29506
+ return "failed";
29507
+ }
29508
+ }
29509
+ function clearTypedDedupeForEvent(host, event) {
29510
+ const sourceId = event.ackSourceId;
29511
+ if (!sourceId)
29512
+ return;
29513
+ switch (event.ackSourceType) {
29514
+ case "task_activity": {
29515
+ const prefix = `${event.targetId}:`;
29516
+ for (const key of host.dispatchedTasks) {
29517
+ if (key.startsWith(prefix))
29518
+ host.dispatchedTasks.delete(key);
29519
+ }
29520
+ break;
29521
+ }
29522
+ case "comment":
29523
+ host.dispatchedTasks.delete(`comment:${sourceId}`);
29524
+ break;
29525
+ case "schedule_run":
29526
+ host.dispatchedTasks.delete(`schedule_run:${sourceId}`);
29527
+ break;
29528
+ case "external_trigger_run":
29529
+ host.dispatchedTasks.delete(`external_trigger_run:${sourceId}`);
29530
+ break;
29531
+ case "channel_message":
29532
+ host.dispatchedMessages.delete(`channel_message:${sourceId}`);
29533
+ break;
29534
+ case "approval":
29535
+ host.dispatchedTasks.delete(`approval:${sourceId}`);
29536
+ break;
29537
+ }
29538
+ }
29539
+ async function settleDrainedTypedGroup(host, events, ids, turnErrored) {
29540
+ if (turnErrored) {
29541
+ host.opts.log?.info(`buffered typed turn for ${events[events.length - 1]?.messageId} surfaced a runtime error \u2014 leaving for re-drive`);
29542
+ for (const event of events)
29543
+ clearTypedDedupeForEvent(host, event);
29544
+ return;
29545
+ }
29546
+ const legacyAckFrom = async (start) => {
29547
+ for (const [j, id] of ids.slice(start).entries()) {
29548
+ try {
29549
+ await host.opts.client.ackDispatchByID(host.opts.config.org_id, id);
29550
+ } catch {
29551
+ clearTypedDedupeForEvent(host, events[start + j]);
29552
+ }
29553
+ }
29554
+ };
29555
+ if (host.typedByIdCompleteUnsupported) {
29556
+ await legacyAckFrom(0);
29557
+ return;
29558
+ }
29559
+ for (const [i, id] of ids.entries()) {
29560
+ const outcome = await resolveDispatchByID(host, id);
29561
+ if (outcome === "unsupported") {
29562
+ host.typedByIdCompleteUnsupported = true;
29563
+ host.opts.log?.warn("server predates the by-id dispatch complete \u2014 falling back to legacy typed acks");
29564
+ await legacyAckFrom(i);
29565
+ return;
29566
+ }
29567
+ if (outcome !== "ok") {
29568
+ clearTypedDedupeForEvent(host, events[i]);
29569
+ }
29570
+ }
29571
+ }
29416
29572
  var TYPED_BACKOFF_BASE_MS = 2e3;
29417
29573
  var TYPED_BACKOFF_CAP_MS = 5 * 6e4;
29418
29574
  var TYPED_BACKOFF_MAP_CAP = 512;
29419
- async function consumeTypedDispatch(host, ref, run, ack) {
29575
+ async function consumeTypedDispatch(host, ref, run, hooks) {
29420
29576
  const backoffKey = ref.dispatchEventId ?? `${ref.sourceType}:${ref.sourceId}`;
29421
29577
  const armed = host.typedRedriveBackoff.get(backoffKey);
29422
29578
  if (armed) {
@@ -29432,8 +29588,8 @@ async function consumeTypedDispatch(host, ref, run, ack) {
29432
29588
  return;
29433
29589
  }
29434
29590
  const settleAck = (ackResult) => ackResult !== false;
29435
- const settle = (acked2) => {
29436
- if (acked2) {
29591
+ const settle = (resolved2) => {
29592
+ if (resolved2) {
29437
29593
  host.typedRedriveBackoff.delete(backoffKey);
29438
29594
  return;
29439
29595
  }
@@ -29447,13 +29603,13 @@ async function consumeTypedDispatch(host, ref, run, ack) {
29447
29603
  host.typedRedriveBackoff.set(backoffKey, { failures, until: Date.now() + backoffMs });
29448
29604
  };
29449
29605
  const runLegacy = async () => {
29450
- let acked2 = false;
29606
+ let acked = false;
29451
29607
  try {
29452
29608
  if (await run(ref.dispatchEventId)) {
29453
- acked2 = settleAck(await ack(ref.dispatchEventId));
29609
+ acked = settleAck(await hooks.legacyAck(ref.dispatchEventId));
29454
29610
  }
29455
29611
  } finally {
29456
- settle(acked2);
29612
+ settle(acked);
29457
29613
  }
29458
29614
  };
29459
29615
  if (!host.laneLedger || host.ledgerDisabled) {
@@ -29475,15 +29631,39 @@ async function consumeTypedDispatch(host, ref, run, ack) {
29475
29631
  host.opts.log?.info(`typed dispatch ${ref.dispatchEventId ?? `${ref.sourceType}:${ref.sourceId}`} not claimable (held elsewhere or already resolved) \u2014 skipping`);
29476
29632
  return;
29477
29633
  }
29478
- let acked = false;
29634
+ let resolved = false;
29635
+ let viaLegacyAck = false;
29479
29636
  try {
29480
29637
  if (await run(lane.typedDispatchEventId)) {
29481
- acked = settleAck(await ack(lane.typedDispatchEventId));
29638
+ if (host.typedByIdCompleteUnsupported || !lane.typedDispatchEventId) {
29639
+ viaLegacyAck = true;
29640
+ resolved = settleAck(await hooks.legacyAck(lane.typedDispatchEventId));
29641
+ } else {
29642
+ const outcome = await resolveDispatchByID(host, lane.typedDispatchEventId, lane.lane);
29643
+ if (outcome === "unsupported") {
29644
+ host.typedByIdCompleteUnsupported = true;
29645
+ host.opts.log?.warn("server predates the by-id dispatch complete \u2014 falling back to the legacy typed ack");
29646
+ viaLegacyAck = true;
29647
+ resolved = settleAck(await hooks.legacyAck(lane.typedDispatchEventId));
29648
+ } else if (outcome === "stale") {
29649
+ hooks.clearDedupe?.();
29650
+ resolved = true;
29651
+ } else {
29652
+ resolved = outcome === "ok";
29653
+ if (!resolved) {
29654
+ hooks.clearDedupe?.();
29655
+ }
29656
+ }
29657
+ }
29482
29658
  }
29483
29659
  } finally {
29484
- settle(acked);
29485
- await host.laneLedger.completeIfIdle(lane.laneKey, false).catch(() => {
29486
- });
29660
+ settle(resolved);
29661
+ if (resolved && !viaLegacyAck) {
29662
+ host.laneLedger.dropLocal(lane.laneKey);
29663
+ } else {
29664
+ await host.laneLedger.completeIfIdle(lane.laneKey, false).catch(() => {
29665
+ });
29666
+ }
29487
29667
  }
29488
29668
  }
29489
29669
  async function consumeMessageWorkItem(host, item) {
@@ -29492,6 +29672,16 @@ async function consumeMessageWorkItem(host, item) {
29492
29672
  if (!host.tryClaimMessage(item.source_id))
29493
29673
  return;
29494
29674
  const ackItem = () => {
29675
+ if (host.laneLedger && !host.ledgerDisabled && !host.typedByIdCompleteUnsupported) {
29676
+ void resolveDispatchByID(host, item.id).then((outcome) => {
29677
+ if (outcome === "unsupported") {
29678
+ host.typedByIdCompleteUnsupported = true;
29679
+ host.opts.client.ackDispatchByID(host.opts.config.org_id, item.id).catch(() => {
29680
+ });
29681
+ }
29682
+ });
29683
+ return;
29684
+ }
29495
29685
  host.opts.client.ackDispatchByID(host.opts.config.org_id, item.id).catch(() => {
29496
29686
  });
29497
29687
  };
@@ -30671,20 +30861,23 @@ var ParallAgentGateway = class {
30671
30861
  if (data.status !== "todo" && data.status !== "in_progress")
30672
30862
  return;
30673
30863
  try {
30674
- await this.consumeTypedDispatch(data.dispatch_event_id ? { dispatchEventId: data.dispatch_event_id } : { sourceType: "task_activity", sourceId: data.id }, (dispatchEventId) => this.handleTaskAssignment(data, data.id, dispatchEventId), (dispatchEventId) => {
30675
- if (dispatchEventId) {
30676
- return this.ackDispatchEvent(dispatchEventId, () => {
30864
+ await this.consumeTypedDispatch(data.dispatch_event_id ? { dispatchEventId: data.dispatch_event_id } : { sourceType: "task_activity", sourceId: data.id }, (dispatchEventId) => this.handleTaskAssignment(data, data.id, dispatchEventId), {
30865
+ legacyAck: (dispatchEventId) => {
30866
+ if (dispatchEventId) {
30867
+ return this.ackDispatchEvent(dispatchEventId, () => {
30868
+ this.dispatchedTasks.delete(`${data.id}:${data.updated_at}`);
30869
+ });
30870
+ }
30871
+ return this.opts.client.ackDispatch(this.opts.config.org_id, {
30872
+ source_type: "task_activity",
30873
+ source_id: data.id
30874
+ }).then(() => true, (err) => {
30677
30875
  this.dispatchedTasks.delete(`${data.id}:${data.updated_at}`);
30876
+ this.opts.log?.warn(`dispatch ack failed for task ${data.id}, releasing for re-drive: ${String(err)}`);
30877
+ return false;
30678
30878
  });
30679
- }
30680
- return this.opts.client.ackDispatch(this.opts.config.org_id, {
30681
- source_type: "task_activity",
30682
- source_id: data.id
30683
- }).then(() => true, (err) => {
30684
- this.dispatchedTasks.delete(`${data.id}:${data.updated_at}`);
30685
- this.opts.log?.warn(`dispatch ack failed for task ${data.id}, releasing for re-drive: ${String(err)}`);
30686
- return false;
30687
- });
30879
+ },
30880
+ clearDedupe: () => this.dispatchedTasks.delete(`${data.id}:${data.updated_at}`)
30688
30881
  });
30689
30882
  } catch (err) {
30690
30883
  this.opts.log?.error(`task dispatch failed for ${data.id}: ${String(err)}`);
@@ -30703,7 +30896,10 @@ var ParallAgentGateway = class {
30703
30896
  if (!data.source_id || !data.task_id)
30704
30897
  return;
30705
30898
  try {
30706
- await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleTaskComment(data.source_id, data.task_id ?? "", data.actor_id, data.delivery_reason), () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)));
30899
+ await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.handleTaskComment(data.source_id, data.task_id ?? "", data.actor_id, data.delivery_reason, dispatchEventId), {
30900
+ legacyAck: () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)),
30901
+ clearDedupe: () => this.clearTypedDispatchDedupe(data)
30902
+ });
30707
30903
  } catch (err) {
30708
30904
  this.opts.log?.error(`task comment dispatch failed for ${data.source_id}: ${String(err)}`);
30709
30905
  }
@@ -30711,7 +30907,10 @@ var ParallAgentGateway = class {
30711
30907
  if (!data.source_id)
30712
30908
  return;
30713
30909
  try {
30714
- await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleWikiComment(data.source_id, data.actor_id, data.delivery_reason), () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)));
30910
+ await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.handleWikiComment(data.source_id, data.actor_id, data.delivery_reason, dispatchEventId), {
30911
+ legacyAck: () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)),
30912
+ clearDedupe: () => this.clearTypedDispatchDedupe(data)
30913
+ });
30715
30914
  } catch (err) {
30716
30915
  this.opts.log?.error(`wiki comment dispatch failed for ${data.source_id}: ${String(err)}`);
30717
30916
  }
@@ -30722,7 +30921,10 @@ var ParallAgentGateway = class {
30722
30921
  await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.handleTaskDispatch(data.task_id ?? "", data.source_id ?? data.task_id ?? "", {
30723
30922
  allowCreator: true,
30724
30923
  dispatchEventId
30725
- }), () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)));
30924
+ }), {
30925
+ legacyAck: () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)),
30926
+ clearDedupe: () => this.clearTypedDispatchDedupe(data)
30927
+ });
30726
30928
  } catch (err) {
30727
30929
  this.opts.log?.error(`task update dispatch failed for ${data.task_id}: ${String(err)}`);
30728
30930
  }
@@ -30730,7 +30932,10 @@ var ParallAgentGateway = class {
30730
30932
  if (!data.source_id)
30731
30933
  return;
30732
30934
  try {
30733
- await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleScheduleFire(data.source_id, data.actor_id), () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)));
30935
+ await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.fetchAndHandleScheduleFire(data.source_id, data.actor_id, dispatchEventId), {
30936
+ legacyAck: () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)),
30937
+ clearDedupe: () => this.clearTypedDispatchDedupe(data)
30938
+ });
30734
30939
  } catch (err) {
30735
30940
  this.opts.log?.error(`schedule fire dispatch failed for ${data.source_id}: ${String(err)}`);
30736
30941
  }
@@ -30738,7 +30943,10 @@ var ParallAgentGateway = class {
30738
30943
  if (!data.source_id)
30739
30944
  return;
30740
30945
  try {
30741
- await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleExternalTriggerRun(data.source_id), () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)));
30946
+ await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.fetchAndHandleExternalTriggerRun(data.source_id, dispatchEventId), {
30947
+ legacyAck: () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)),
30948
+ clearDedupe: () => this.clearTypedDispatchDedupe(data)
30949
+ });
30742
30950
  } catch (err) {
30743
30951
  this.opts.log?.error(`external trigger dispatch failed for ${data.source_id}: ${String(err)}`);
30744
30952
  }
@@ -30746,7 +30954,10 @@ var ParallAgentGateway = class {
30746
30954
  if (!data.source_id)
30747
30955
  return;
30748
30956
  try {
30749
- await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleChannelMessage(data.source_id), () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)));
30957
+ await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.fetchAndHandleChannelMessage(data.source_id, dispatchEventId), {
30958
+ legacyAck: () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)),
30959
+ clearDedupe: () => this.clearTypedDispatchDedupe(data)
30960
+ });
30750
30961
  } catch (err) {
30751
30962
  this.opts.log?.error(`channel message dispatch failed for ${data.source_id}: ${String(err)}`);
30752
30963
  }
@@ -30754,7 +30965,10 @@ var ParallAgentGateway = class {
30754
30965
  if (!data.source_id)
30755
30966
  return;
30756
30967
  try {
30757
- await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleApprovalDecided(data.source_id, data.actor_id, data.chat_id ?? null), () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)));
30968
+ await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.fetchAndHandleApprovalDecided(data.source_id, data.actor_id, data.chat_id ?? null, dispatchEventId), {
30969
+ legacyAck: () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)),
30970
+ clearDedupe: () => this.clearTypedDispatchDedupe(data)
30971
+ });
30758
30972
  } catch (err) {
30759
30973
  this.opts.log?.error(`approval decided dispatch failed for ${data.source_id}: ${String(err)}`);
30760
30974
  }
@@ -30791,6 +31005,44 @@ var ParallAgentGateway = class {
30791
31005
  this.dispatchedMessages.add(id);
30792
31006
  return true;
30793
31007
  }
31008
+ /**
31009
+ * Lane currently being dispatched per session — lets external activity
31010
+ * signals renew exactly the caller's lane (renewing all lanes would keep
31011
+ * an unrelated stalled fork's lane leased forever).
31012
+ */
31013
+ sessionActiveLanes = /* @__PURE__ */ new Map();
31014
+ noteSessionLane(sessionKey, laneKey) {
31015
+ if (laneKey == null)
31016
+ this.sessionActiveLanes.delete(sessionKey);
31017
+ else
31018
+ this.sessionActiveLanes.set(sessionKey, laneKey);
31019
+ }
31020
+ /**
31021
+ * External runtime-activity signal for adapters whose tool activity does
31022
+ * not flow through the RuntimeEvent stream (openclaw hooks call this from
31023
+ * the tool-call lifecycle): renews the session's OWN active ledger lane so
31024
+ * a long tool call cannot outlive the lease and get dethroned mid-turn.
31025
+ * No-op without an active ledger lane for the session.
31026
+ */
31027
+ touchRuntimeActivity(sessionKey) {
31028
+ if (this.ledgerDisabled)
31029
+ return;
31030
+ const laneKey = this.sessionActiveLanes.get(sessionKey);
31031
+ if (laneKey)
31032
+ this.laneLedger?.renewByKey(laneKey);
31033
+ }
31034
+ /** Sessions whose in-flight turn surfaced a runtime error event. */
31035
+ turnErrorSessions = /* @__PURE__ */ new Set();
31036
+ /**
31037
+ * Consume (read-and-clear) the error marker for sessionKey's last turn.
31038
+ * Feeds complete's turn_outcome so an error turn's lane members are
31039
+ * released for retry instead of no_action-swept (design §3). Consuming
31040
+ * (rather than peeking) keeps one-shot fork session keys from accumulating
31041
+ * in the set forever.
31042
+ */
31043
+ consumeTurnError(sessionKey) {
31044
+ return this.turnErrorSessions.delete(sessionKey);
31045
+ }
30794
31046
  async emitDispatchReceived(event) {
30795
31047
  const sourceType = event.ackSourceType ?? (event.type === "task" ? "task_activity" : "message");
30796
31048
  const sourceId = event.ackSourceId ?? event.messageId;
@@ -30799,6 +31051,20 @@ var ParallAgentGateway = class {
30799
31051
  source_id: sourceId
30800
31052
  });
30801
31053
  }
31054
+ /**
31055
+ * Sticky: the server answered a by-id complete with 400 (predates the
31056
+ * form). Typed resolution falls back to the legacy ack for the rest of
31057
+ * the process lifetime.
31058
+ */
31059
+ typedByIdCompleteUnsupported = false;
31060
+ // Typed-face ledger helpers live in gateway-lane-flow.ts; thin delegates
31061
+ // keep the site code and tests on the class surface.
31062
+ typedLedgerEventIds(events) {
31063
+ return typedLedgerEventIds(this.laneFlowHost(), events);
31064
+ }
31065
+ resolveDispatchByID(dispatchEventId, lane) {
31066
+ return resolveDispatchByID(this.laneFlowHost(), dispatchEventId, lane);
31067
+ }
30802
31068
  /** True when this event's lifecycle is owned by the dispatch lane ledger. */
30803
31069
  usesLaneLedger(event) {
30804
31070
  return this.laneLedger != null && !this.ledgerDisabled && this.laneLedger.handles(event);
@@ -30829,15 +31095,16 @@ var ParallAgentGateway = class {
30829
31095
  dispatchLaneGroup(opts) {
30830
31096
  return dispatchLaneGroup(this.laneFlowHost(), opts);
30831
31097
  }
30832
- consumeTypedDispatch(ref, run, ack) {
30833
- return consumeTypedDispatch(this.laneFlowHost(), ref, run, ack);
31098
+ consumeTypedDispatch(ref, run, hooks) {
31099
+ return consumeTypedDispatch(this.laneFlowHost(), ref, run, hooks);
30834
31100
  }
30835
- // Typed completion must wait until the administrative ack has either
30836
- // committed or failed. Errors stay best-effort: a failed ack leaves the row
30837
- // received, so Complete releases and re-drives it safely. The boolean
30838
- // outcome feeds the typed-consume backoff an ack that failed must count
30839
- // as a failed consume, or an ack outage would clear the backoff entry and
30840
- // let the release re-drive spin at wire speed.
31101
+ // Legacy administrative ack (ledger-disabled fallback only). Typed
31102
+ // completion must wait until the ack has either committed or failed.
31103
+ // Errors stay best-effort: a failed ack leaves the row received, so
31104
+ // Complete releases and re-drives it safely. The boolean outcome feeds the
31105
+ // typed-consume backoff an ack that failed must count as a failed
31106
+ // consume, or an ack outage would clear the backoff entry and let the
31107
+ // release re-drive spin at wire speed.
30841
31108
  ackDispatchEvent(dispatchEventId, onFailure) {
30842
31109
  return this.opts.client.ackDispatchByID(this.opts.config.org_id, dispatchEventId).then(() => true, (err) => {
30843
31110
  onFailure?.();
@@ -30845,6 +31112,9 @@ var ParallAgentGateway = class {
30845
31112
  return false;
30846
31113
  });
30847
31114
  }
31115
+ // PARITY: this switch and gateway-lane-flow's clearTypedDedupeForEvent must
31116
+ // handle the same typed source families — extend BOTH when adding a typed
31117
+ // event type (same dedupe entries, keyed from different event shapes).
30848
31118
  clearTypedDispatchDedupe(item) {
30849
31119
  switch (item.event_type) {
30850
31120
  case "task_assign":
@@ -31140,6 +31410,7 @@ var ParallAgentGateway = class {
31140
31410
  this.pendingRestartNotification = null;
31141
31411
  }
31142
31412
  resetDispatchMetrics(sessionKey);
31413
+ this.turnErrorSessions.delete(sessionKey);
31143
31414
  return runWithSessionKey(sessionKey, async () => {
31144
31415
  let dispatchSpan = null;
31145
31416
  setSessionChatId(sessionKey, event.targetId);
@@ -31247,6 +31518,9 @@ var ParallAgentGateway = class {
31247
31518
  } else if (runtimeEvent.type === "tool_result" && pendingSendCallIds.delete(runtimeEvent.callId)) {
31248
31519
  recordMessageSend(sessionKey, !runtimeEvent.error);
31249
31520
  }
31521
+ if (runtimeEvent.type === "error") {
31522
+ this.turnErrorSessions.add(sessionKey);
31523
+ }
31250
31524
  await this.createRuntimeStep(binding.agentSessionId, event, runtimeEvent, stepIdFilePath, contextFilePath, laneContextFilePath2);
31251
31525
  }
31252
31526
  if (!binding) {
@@ -31394,16 +31668,32 @@ var ParallAgentGateway = class {
31394
31668
  body: buildForkScopePrefix(last) + buildEventBody(last),
31395
31669
  earlier,
31396
31670
  captureText: batchText,
31397
- hasMoreLocal: () => fork.queue.length > 0
31671
+ // Per-LANE residue check (parity with the main-buffer path):
31672
+ // the fork queue can hold several lanes (channel + thread of
31673
+ // the same chat). A whole-queue check would defer THIS lane's
31674
+ // complete behind another lane's items and never revisit it —
31675
+ // its members would sit received until lease expiry.
31676
+ hasMoreLocal: () => fork.queue.some((it) => this.dispatchGroupKey(it.event) === this.dispatchGroupKey(last))
31398
31677
  });
31399
31678
  if (outcome === "foreign") {
31400
31679
  for (const item of items)
31401
31680
  item.resolve(false);
31402
31681
  break;
31403
31682
  }
31683
+ if (outcome === "failed") {
31684
+ for (const item of items)
31685
+ item.resolve(false);
31686
+ continue;
31687
+ }
31404
31688
  dispatched = outcome === "dispatched";
31405
31689
  } else {
31406
31690
  dispatched = await this.runDispatch(last, fork.fork.sessionKey, buildForkScopePrefix(last) + buildEventBody(last), earlier, batchText);
31691
+ if (dispatched && this.typedLedgerEventIds(events) && this.consumeTurnError(fork.fork.sessionKey)) {
31692
+ this.opts.log?.info(`typed fork turn for ${last.messageId} surfaced a runtime error \u2014 releasing for retry`);
31693
+ for (const item of items)
31694
+ item.resolve(false);
31695
+ continue;
31696
+ }
31407
31697
  }
31408
31698
  if (!dispatched) {
31409
31699
  for (const item of items) {
@@ -31434,6 +31724,7 @@ var ParallAgentGateway = class {
31434
31724
  remaining.resolve(false);
31435
31725
  }
31436
31726
  } finally {
31727
+ this.turnErrorSessions.delete(fork.fork.sessionKey);
31437
31728
  if (fork.deadlineTimer) {
31438
31729
  clearTimeout(fork.deadlineTimer);
31439
31730
  fork.deadlineTimer = null;
@@ -31546,15 +31837,22 @@ var ParallAgentGateway = class {
31546
31837
  this.dispatchState.pendingForkResults.unshift(...pendingFork);
31547
31838
  continue;
31548
31839
  }
31840
+ if (outcome === "failed") {
31841
+ this.dispatchState.pendingForkResults.unshift(...pendingFork);
31842
+ continue;
31843
+ }
31549
31844
  continue;
31550
31845
  }
31551
- try {
31552
- await this.emitDispatchReceived(event);
31553
- } catch (err) {
31554
- this.opts.log?.warn?.(`mark-received failed for buffered dispatch, leaving unacked for retry: ${String(err)}`);
31555
- this.dispatchState.mainBuffer.unshift(...events);
31556
- this.dispatchState.pendingForkResults.unshift(...pendingFork);
31557
- break;
31846
+ const typedRefs = this.typedLedgerEventIds(events);
31847
+ if (!typedRefs) {
31848
+ try {
31849
+ await this.emitDispatchReceived(event);
31850
+ } catch (err) {
31851
+ this.opts.log?.warn?.(`mark-received failed for buffered dispatch, leaving unacked for retry: ${String(err)}`);
31852
+ this.dispatchState.mainBuffer.unshift(...events);
31853
+ this.dispatchState.pendingForkResults.unshift(...pendingFork);
31854
+ break;
31855
+ }
31558
31856
  }
31559
31857
  const dispatched = await this.runDispatch(event, this.opts.runtimeKey, forkPrefix + buildEventBody(event), earlier);
31560
31858
  if (!dispatched) {
@@ -31562,6 +31860,10 @@ var ParallAgentGateway = class {
31562
31860
  this.dispatchState.pendingForkResults.unshift(...pendingFork);
31563
31861
  break;
31564
31862
  }
31863
+ if (typedRefs) {
31864
+ await settleDrainedTypedGroup(this.laneFlowHost(), events, typedRefs, this.consumeTurnError(this.opts.runtimeKey));
31865
+ continue;
31866
+ }
31565
31867
  for (const bufferedEvent of events) {
31566
31868
  const sourceType = bufferedEvent.ackSourceType ?? (bufferedEvent.type === "task" ? "task_activity" : "message");
31567
31869
  const sourceId = bufferedEvent.ackSourceId ?? bufferedEvent.messageId;
@@ -31621,20 +31923,27 @@ var ParallAgentGateway = class {
31621
31923
  }
31622
31924
  return outcome === "dispatched";
31623
31925
  }
31624
- try {
31625
- await this.emitDispatchReceived(event);
31626
- } catch (err) {
31627
- this.opts.log?.warn?.(`mark-received failed, leaving unacked for retry: ${String(err)}`);
31628
- this.dispatchState.mainDispatching = false;
31629
- this.dispatchState.mainCurrentTargetId = void 0;
31630
- this.mainCurrentGroupKey = void 0;
31631
- this.dispatchState.mainPreDispatchBranchPoint = void 0;
31632
- this.dispatchState.pendingForkResults.unshift(...pendingFork);
31633
- return false;
31926
+ const typedRefs = this.typedLedgerEventIds([event]);
31927
+ if (!typedRefs) {
31928
+ try {
31929
+ await this.emitDispatchReceived(event);
31930
+ } catch (err) {
31931
+ this.opts.log?.warn?.(`mark-received failed, leaving unacked for retry: ${String(err)}`);
31932
+ this.dispatchState.mainDispatching = false;
31933
+ this.dispatchState.mainCurrentTargetId = void 0;
31934
+ this.mainCurrentGroupKey = void 0;
31935
+ this.dispatchState.mainPreDispatchBranchPoint = void 0;
31936
+ this.dispatchState.pendingForkResults.unshift(...pendingFork);
31937
+ return false;
31938
+ }
31634
31939
  }
31635
31940
  let dispatched = false;
31636
31941
  try {
31637
31942
  dispatched = await this.runDispatch(event, this.opts.runtimeKey, forkPrefix + buildEventBody(event));
31943
+ if (dispatched && typedRefs && this.consumeTurnError(this.opts.runtimeKey)) {
31944
+ this.opts.log?.info(`typed dispatch turn for ${event.messageId} surfaced a runtime error \u2014 releasing for retry`);
31945
+ dispatched = false;
31946
+ }
31638
31947
  if (!dispatched) {
31639
31948
  this.dispatchState.pendingForkResults.unshift(...pendingFork);
31640
31949
  }
@@ -31649,7 +31958,7 @@ var ParallAgentGateway = class {
31649
31958
  }
31650
31959
  this.dispatchState.mainBuffer.push(event);
31651
31960
  if (this.usesLaneLedger(event)) {
31652
- if (this.mainCurrentGroupKey === this.dispatchGroupKey(event) && await this.laneLedger?.steerLive(event) && await this.opts.dispatchAdapter.enqueueDuringDispatch?.(this.opts.runtimeKey, buildEventBody(event))) {
31961
+ if (this.mainCurrentGroupKey === this.dispatchGroupKey(event) && this.opts.dispatchAdapter.enqueueDuringDispatch != null && await this.laneLedger?.steerLive(event) && await this.opts.dispatchAdapter.enqueueDuringDispatch(this.opts.runtimeKey, buildEventBody(event))) {
31653
31962
  this.opts.log?.info(`steer folded+injected for ${event.messageId} (will drain for bookkeeping)`);
31654
31963
  }
31655
31964
  } else if (this.dispatchState.mainCurrentTargetId === event.targetId && await this.opts.dispatchAdapter.enqueueDuringDispatch?.(this.opts.runtimeKey, buildEventBody(event))) {
@@ -31676,12 +31985,14 @@ var ParallAgentGateway = class {
31676
31985
  this.dispatchState.mainBuffer.push(event);
31677
31986
  return false;
31678
31987
  }
31679
- try {
31680
- await this.emitDispatchReceived(event);
31681
- } catch (err) {
31682
- this.opts.log?.warn?.(`mark-received failed for fork dispatch, leaving unacked for retry: ${String(err)}`);
31683
- this.dispatchState.mainBuffer.push(event);
31684
- return false;
31988
+ if (!this.usesLaneLedger(event) && !this.typedLedgerEventIds([event])) {
31989
+ try {
31990
+ await this.emitDispatchReceived(event);
31991
+ } catch (err) {
31992
+ this.opts.log?.warn?.(`mark-received failed for fork dispatch, leaving unacked for retry: ${String(err)}`);
31993
+ this.dispatchState.mainBuffer.push(event);
31994
+ return false;
31995
+ }
31685
31996
  }
31686
31997
  const fork = await this.opts.dispatchAdapter.forkSession({
31687
31998
  sessionKey: this.opts.runtimeKey,
@@ -31855,7 +32166,10 @@ var ParallAgentGateway = class {
31855
32166
  return;
31856
32167
  await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskDispatch(item.task_id ?? "", item.source_id ?? item.task_id ?? "", {
31857
32168
  dispatchEventId
31858
- }), (dispatchEventId) => this.ackDispatchEvent(dispatchEventId ?? item.id, () => this.clearTypedDispatchDedupe(item)));
32169
+ }), {
32170
+ legacyAck: (dispatchEventId) => this.ackDispatchEvent(dispatchEventId ?? item.id, () => this.clearTypedDispatchDedupe(item)),
32171
+ clearDedupe: () => this.clearTypedDispatchDedupe(item)
32172
+ });
31859
32173
  }
31860
32174
  consumeMessageWorkItem(item) {
31861
32175
  return consumeMessageWorkItem(this.laneFlowHost(), item);
@@ -31916,7 +32230,7 @@ var ParallAgentGateway = class {
31916
32230
  }
31917
32231
  return this.handleTaskAssignment(task, ackSourceId, opts.dispatchEventId);
31918
32232
  }
31919
- async handleTaskComment(commentId, taskId, actorId, deliveryReason) {
32233
+ async handleTaskComment(commentId, taskId, actorId, deliveryReason, dispatchEventId) {
31920
32234
  if (this.shuttingDown)
31921
32235
  return false;
31922
32236
  const dedupeKey = `comment:${commentId}`;
@@ -31972,7 +32286,8 @@ var ParallAgentGateway = class {
31972
32286
  body: parts.join("\n"),
31973
32287
  deliveryReason: deliveryReason ?? void 0,
31974
32288
  ackSourceType: "comment",
31975
- ackSourceId: commentId
32289
+ ackSourceId: commentId,
32290
+ dispatchEventId
31976
32291
  };
31977
32292
  let dispatched;
31978
32293
  try {
@@ -31986,7 +32301,7 @@ var ParallAgentGateway = class {
31986
32301
  }
31987
32302
  return dispatched;
31988
32303
  }
31989
- async handleWikiComment(commentId, actorId, deliveryReason) {
32304
+ async handleWikiComment(commentId, actorId, deliveryReason, dispatchEventId) {
31990
32305
  if (this.shuttingDown)
31991
32306
  return false;
31992
32307
  const dedupeKey = `comment:${commentId}`;
@@ -32032,7 +32347,8 @@ var ParallAgentGateway = class {
32032
32347
  deliveryReason: deliveryReason ?? void 0,
32033
32348
  replyTargetUri: comment.target_uri,
32034
32349
  ackSourceType: "comment",
32035
- ackSourceId: commentId
32350
+ ackSourceId: commentId,
32351
+ dispatchEventId
32036
32352
  };
32037
32353
  let dispatched;
32038
32354
  try {
@@ -32054,7 +32370,7 @@ var ParallAgentGateway = class {
32054
32370
  * access to already-delivered run snapshots, and the runtime must not crash
32055
32371
  * or retry forever in that case.
32056
32372
  */
32057
- async fetchAndHandleScheduleFire(runId, actorId) {
32373
+ async fetchAndHandleScheduleFire(runId, actorId, dispatchEventId) {
32058
32374
  let run = null;
32059
32375
  try {
32060
32376
  run = await this.opts.client.getScheduleRun(this.opts.config.org_id, runId);
@@ -32069,9 +32385,9 @@ var ParallAgentGateway = class {
32069
32385
  }
32070
32386
  if (!run)
32071
32387
  return true;
32072
- return this.handleScheduleFire(run, actorId);
32388
+ return this.handleScheduleFire(run, actorId, dispatchEventId);
32073
32389
  }
32074
- async handleScheduleFire(run, actorId) {
32390
+ async handleScheduleFire(run, actorId, dispatchEventId) {
32075
32391
  if (this.shuttingDown)
32076
32392
  return false;
32077
32393
  const dedupeKey = `schedule_run:${run.id}`;
@@ -32094,7 +32410,8 @@ var ParallAgentGateway = class {
32094
32410
  scheduledFireAt: run.scheduled_fire_at,
32095
32411
  attachedUri: run.fired_attached_uri ?? void 0,
32096
32412
  ackSourceType: "schedule_run",
32097
- ackSourceId: run.id
32413
+ ackSourceId: run.id,
32414
+ dispatchEventId
32098
32415
  };
32099
32416
  let dispatched;
32100
32417
  try {
@@ -32108,7 +32425,7 @@ var ParallAgentGateway = class {
32108
32425
  }
32109
32426
  return dispatched;
32110
32427
  }
32111
- async fetchAndHandleExternalTriggerRun(runId) {
32428
+ async fetchAndHandleExternalTriggerRun(runId, dispatchEventId) {
32112
32429
  let run = null;
32113
32430
  try {
32114
32431
  run = await this.opts.client.getExternalTriggerRun(this.opts.config.org_id, runId);
@@ -32123,14 +32440,14 @@ var ParallAgentGateway = class {
32123
32440
  }
32124
32441
  if (!run)
32125
32442
  return true;
32126
- return this.handleExternalTriggerRun(run);
32443
+ return this.handleExternalTriggerRun(run, dispatchEventId);
32127
32444
  }
32128
32445
  // fetchAndHandleChannelMessage resolves a channel_message dispatch to its
32129
32446
  // durable ChannelMessage + conversation and hands it to the inbound
32130
32447
  // pipeline. targetId = the ChannelConversation id, so per-conversation
32131
32448
  // multi-turn continuity rides the same per-target session mechanics as
32132
32449
  // chats. Design: docs/engineering-design/external-im-channel-design.md.
32133
- async fetchAndHandleChannelMessage(messageId) {
32450
+ async fetchAndHandleChannelMessage(messageId, dispatchEventId) {
32134
32451
  if (this.shuttingDown)
32135
32452
  return false;
32136
32453
  const claimKey = `channel_message:${messageId}`;
@@ -32183,7 +32500,8 @@ var ParallAgentGateway = class {
32183
32500
  channelExternalMessageId: msg.external_message_id,
32184
32501
  channelCliCapable: cliCapable,
32185
32502
  ackSourceType: "channel_message",
32186
- ackSourceId: msg.id
32503
+ ackSourceId: msg.id,
32504
+ dispatchEventId
32187
32505
  };
32188
32506
  let dispatched;
32189
32507
  try {
@@ -32197,7 +32515,7 @@ var ParallAgentGateway = class {
32197
32515
  }
32198
32516
  return dispatched;
32199
32517
  }
32200
- async handleExternalTriggerRun(run) {
32518
+ async handleExternalTriggerRun(run, dispatchEventId) {
32201
32519
  if (this.shuttingDown)
32202
32520
  return false;
32203
32521
  const dedupeKey = `external_trigger_run:${run.id}`;
@@ -32222,7 +32540,8 @@ var ParallAgentGateway = class {
32222
32540
  externalIngressEventType: run.ingress_event_type || void 0,
32223
32541
  attachedUri,
32224
32542
  ackSourceType: "external_trigger_run",
32225
- ackSourceId: run.id
32543
+ ackSourceId: run.id,
32544
+ dispatchEventId
32226
32545
  };
32227
32546
  let dispatched;
32228
32547
  try {
@@ -32236,7 +32555,7 @@ var ParallAgentGateway = class {
32236
32555
  }
32237
32556
  return dispatched;
32238
32557
  }
32239
- async fetchAndHandleApprovalDecided(approvalId, actorId, chatId) {
32558
+ async fetchAndHandleApprovalDecided(approvalId, actorId, chatId, dispatchEventId) {
32240
32559
  let approval = null;
32241
32560
  try {
32242
32561
  approval = await this.opts.client.getApproval(approvalId);
@@ -32267,7 +32586,12 @@ var ParallAgentGateway = class {
32267
32586
  senderId: actorId ?? approval.decided_by ?? "system",
32268
32587
  senderName: "approver",
32269
32588
  messageId: approval.id,
32270
- body
32589
+ body,
32590
+ // The WorkItem's real source pair. Without it the legacy fallback
32591
+ // guessed ('message', approval_id) — a pair no row matches.
32592
+ ackSourceType: "approval",
32593
+ ackSourceId: approval.id,
32594
+ dispatchEventId
32271
32595
  };
32272
32596
  let dispatched;
32273
32597
  try {
@@ -32314,7 +32638,17 @@ var ParallAgentGateway = class {
32314
32638
  break;
32315
32639
  if (overflowMode && processed >= CATCHUP_MAX) {
32316
32640
  try {
32317
- await this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id);
32641
+ if (this.laneLedger && !this.ledgerDisabled && !this.typedByIdCompleteUnsupported) {
32642
+ const outcome = await this.resolveDispatchByID(item.id);
32643
+ if (outcome === "unsupported") {
32644
+ this.typedByIdCompleteUnsupported = true;
32645
+ await this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id);
32646
+ } else if (outcome === "failed") {
32647
+ throw new Error("by-id complete failed");
32648
+ }
32649
+ } else {
32650
+ await this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id);
32651
+ }
32318
32652
  skipped++;
32319
32653
  const key = item.event_type;
32320
32654
  skippedByType.set(key, (skippedByType.get(key) ?? 0) + 1);
@@ -32325,12 +32659,15 @@ var ParallAgentGateway = class {
32325
32659
  }
32326
32660
  processed++;
32327
32661
  try {
32328
- const ackItem = () => this.ackDispatchEvent(item.id, () => this.clearTypedDispatchDedupe(item));
32662
+ const typedHooks = {
32663
+ legacyAck: () => this.ackDispatchEvent(item.id, () => this.clearTypedDispatchDedupe(item)),
32664
+ clearDedupe: () => this.clearTypedDispatchDedupe(item)
32665
+ };
32329
32666
  if (item.event_type === "task_assign" && item.task_id) {
32330
32667
  try {
32331
32668
  await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskDispatch(item.task_id ?? "", item.source_id ?? item.task_id ?? "", {
32332
32669
  dispatchEventId
32333
- }), ackItem);
32670
+ }), typedHooks);
32334
32671
  } catch (err) {
32335
32672
  this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
32336
32673
  continue;
@@ -32340,23 +32677,23 @@ var ParallAgentGateway = class {
32340
32677
  await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskDispatch(item.task_id ?? "", item.source_id ?? item.task_id ?? "", {
32341
32678
  allowCreator: true,
32342
32679
  dispatchEventId
32343
- }), ackItem);
32680
+ }), typedHooks);
32344
32681
  } catch (err) {
32345
32682
  this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
32346
32683
  continue;
32347
32684
  }
32348
32685
  } else if (item.event_type === "task_comment" && item.source_id && item.task_id) {
32349
- await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.handleTaskComment(item.source_id, item.task_id ?? "", item.actor_id, item.delivery_reason), ackItem);
32686
+ await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskComment(item.source_id, item.task_id ?? "", item.actor_id, item.delivery_reason, dispatchEventId), typedHooks);
32350
32687
  } else if (item.event_type === "wiki_comment" && item.source_id) {
32351
- await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.handleWikiComment(item.source_id, item.actor_id, item.delivery_reason), ackItem);
32688
+ await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleWikiComment(item.source_id, item.actor_id, item.delivery_reason, dispatchEventId), typedHooks);
32352
32689
  } else if (item.event_type === "schedule.fire" && item.source_id) {
32353
- await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleScheduleFire(item.source_id, item.actor_id), ackItem);
32690
+ await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.fetchAndHandleScheduleFire(item.source_id, item.actor_id, dispatchEventId), typedHooks);
32354
32691
  } else if (item.event_type === "external_trigger" && item.source_id) {
32355
- await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleExternalTriggerRun(item.source_id), ackItem);
32692
+ await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.fetchAndHandleExternalTriggerRun(item.source_id, dispatchEventId), typedHooks);
32356
32693
  } else if (item.event_type === "channel_message" && item.source_id) {
32357
- await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleChannelMessage(item.source_id), ackItem);
32694
+ await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.fetchAndHandleChannelMessage(item.source_id, dispatchEventId), typedHooks);
32358
32695
  } else if (item.event_type === "approval_decided" && item.source_id) {
32359
- await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleApprovalDecided(item.source_id, item.actor_id, item.chat_id ?? null), ackItem);
32696
+ await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.fetchAndHandleApprovalDecided(item.source_id, item.actor_id, item.chat_id ?? null, dispatchEventId), typedHooks);
32360
32697
  } else if (item.event_type === "message" && item.source_id && item.chat_id) {
32361
32698
  await this.consumeMessageWorkItem({
32362
32699
  id: item.id,
@@ -33054,16 +33391,32 @@ parall tasks list --assignee-id prll://usr_xxx # first page only (default 20)
33054
33391
  parall tasks subtasks prll://tsk_xxx # children of a single parent task
33055
33392
 
33056
33393
  # Create a task (add --parent-id to make it a SUBTASK of another task)
33057
- parall tasks create --title "Task title" [--assignee-id prll://usr_xxx] [--parent-id prll://tsk_xxx] [--project-id prll://prj_xxx]
33394
+ parall tasks create --title "Task title" [--assignee-id prll://usr_xxx] [--parent-id prll://tsk_xxx] [--project-id prll://prj_xxx] [--due-date 2026-08-01]
33395
+
33396
+ # Update task status \u2014 add --placement end so the task lands at the end of
33397
+ # its NEW status column (a bare --status keeps the old column's sort_order)
33398
+ parall tasks update prll://tsk_xxx --status in_progress --placement end
33399
+ parall tasks update prll://tsk_xxx --status done --placement end
33400
+
33401
+ # Due date \u2014 a plain YYYY-MM-DD date (no timestamps); "none" clears it
33402
+ parall tasks update prll://tsk_xxx --due-date 2026-08-01
33403
+ parall tasks update prll://tsk_xxx --due-date none
33058
33404
 
33059
- # Update task status
33060
- parall tasks update prll://tsk_xxx --status in_progress
33061
- parall tasks update prll://tsk_xxx --status done
33405
+ # Move a task to the end of its status column
33406
+ parall tasks update prll://tsk_xxx --placement end
33062
33407
 
33063
33408
  # Add a comment
33064
33409
  parall tasks comments add prll://tsk_xxx --body "Progress update..."
33065
33410
  \`\`\`
33066
33411
 
33412
+ Ordering: to append a task to the end of a status column, always use
33413
+ \`--placement end\` \u2014 the server resolves the position atomically. This
33414
+ includes status changes: a bare \`--status\` keeps the task's old
33415
+ \`sort_order\`, which may collide inside the new column. Do NOT compute a
33416
+ \`sort_order\` value yourself from listed tasks (your view may be stale or
33417
+ partial). \`--sort-order\` is only for pinpoint insertion between two cards
33418
+ you just listed, and it cannot be combined with \`--placement\`.
33419
+
33067
33420
  Subtasks are just tasks with a parent: create one with \`tasks create --parent-id\`,
33068
33421
  re-parent with \`tasks update --parent-id\`, list a parent's children with
33069
33422
  \`tasks subtasks\`. \`tasks list\` without \`--parent-id\` already returns both
@@ -33098,7 +33451,7 @@ watcher.
33098
33451
  When you receive \`[Event: task.assigned]\`:
33099
33452
 
33100
33453
  1. Acknowledge with a comment: \`tasks comments add prll://tsk_xxx --body "On it"\`
33101
- 2. Update status: \`tasks update prll://tsk_xxx --status in_progress\`
33454
+ 2. Update status: \`tasks update prll://tsk_xxx --status in_progress --placement end\`
33102
33455
  3. Do the work
33103
33456
  4. Report results in a comment. If a gate remains \u2014 review, merge, deploy,
33104
33457
  requester acceptance \u2014 set \`in_review\` and name the gate; set \`done\`
@@ -33344,7 +33697,7 @@ parall schedules create \\
33344
33697
  --run-at <FUTURE_RFC3339_TIME>
33345
33698
  \`\`\`
33346
33699
 
33347
- \`--target-ids\` is who receives the fire (usually yourself when you're self-scheduling; another agent or human when delegating). \`--attached-to-uri\` optionally anchors the schedule to a task / chat / project / wiki page \u2014 when that resource is archived or deleted, the schedule auto-cancels (\`cancel_reason=attached_gone\`).
33700
+ \`--target-ids\` is who receives the fire (usually yourself when you're self-scheduling; another agent or human when delegating). \`--attached-to-uri\` optionally anchors the schedule to a task / chat / project / wiki page \u2014 when that resource is archived or deleted, the schedule auto-cancels (\`status_reason=attached_gone\`). A schedule whose agent targets all sit on a terminated machine is auto-paused by the platform (\`status_reason=attendee_unreachable\`) instead of firing into a void; resuming a recurring schedule while the machine is still terminated just pauses it again on the next slot (a one-shot resumed past its catch-up window instead follows the normal missed semantics and completes).
33348
33701
 
33349
33702
  ### Reminders for someone else
33350
33703
 
@@ -33660,9 +34013,8 @@ function stepIdFilePathForSession(stateDir, sessionKey) {
33660
34013
  }
33661
34014
 
33662
34015
  // ts/codex-agent/dist/dispatch.js
33663
- import { execSync as execSync2, spawn } from "node:child_process";
34016
+ import { spawn } from "node:child_process";
33664
34017
  import { randomUUID } from "node:crypto";
33665
- import * as fs7 from "node:fs";
33666
34018
  import * as path8 from "node:path";
33667
34019
 
33668
34020
  // ts/agent-core/dist/internal/attachment-input.js
@@ -34237,84 +34589,292 @@ function parseContentLength(value) {
34237
34589
  return n;
34238
34590
  }
34239
34591
 
34240
- // ts/codex-agent/dist/event-mapping.js
34241
- var EventMapper = class {
34242
- toolCallStart = /* @__PURE__ */ new Map();
34243
- /**
34244
- * Map a single server notification to zero or more RuntimeEvents.
34245
- * Return `null` for notifications that have no chat surface.
34246
- */
34247
- map(method, params) {
34248
- const events = [];
34249
- const p = params ?? {};
34250
- switch (method) {
34251
- // Streaming deltas are intentionally dropped see the class comment.
34252
- case "item/agentMessage/delta":
34253
- case "item/reasoning/textDelta":
34254
- case "item/reasoning/summaryTextDelta":
34255
- break;
34256
- case "item/started": {
34257
- const item = p.item;
34258
- if (!item || typeof item.type !== "string")
34259
- break;
34260
- events.push(...this.mapItem(item, "started"));
34261
- break;
34262
- }
34263
- case "item/completed": {
34264
- const item = p.item;
34265
- if (!item || typeof item.type !== "string")
34266
- break;
34267
- events.push(...this.mapItem(item, "completed"));
34268
- break;
34269
- }
34270
- case "turn/completed": {
34271
- const turn = p.turn;
34272
- const status = turn ? asString(turn.status) : void 0;
34273
- const error = turn?.error;
34274
- if (status === "failed") {
34275
- const message = asString(error?.message) ?? "Codex turn failed";
34276
- events.push({ type: "error", message });
34277
- }
34278
- break;
34279
- }
34280
- case "error": {
34281
- const message = asString(p.message) ?? "Codex error";
34282
- events.push({ type: "error", message });
34283
- break;
34284
- }
34285
- }
34286
- return events;
34592
+ // ts/codex-agent/dist/app-server-process.js
34593
+ import { execSync as execSync2 } from "node:child_process";
34594
+ import * as fs7 from "node:fs";
34595
+ var IS_WIN32 = process.platform === "win32";
34596
+ function quoteWin32Arg(arg) {
34597
+ if (!/[\s"&|^<>()]/.test(arg))
34598
+ return arg;
34599
+ return `"${arg.replace(/"/g, '""')}"`;
34600
+ }
34601
+ function killWin32Tree(pid) {
34602
+ try {
34603
+ execSync2(`taskkill /T /F /PID ${pid}`, { windowsHide: true, stdio: "ignore" });
34604
+ return true;
34605
+ } catch {
34606
+ return false;
34287
34607
  }
34288
- mapItem(item, phase) {
34289
- const type = asString(item.type);
34290
- const id = asString(item.id);
34291
- const now = Date.now();
34292
- if (type === "agentMessage") {
34293
- if (phase !== "completed")
34294
- return [];
34295
- const text = asString(item.text);
34296
- if (!text)
34297
- return [];
34298
- return [{ type: "text", text, project: false }];
34299
- }
34300
- if (type === "reasoning") {
34301
- if (phase !== "completed")
34302
- return [];
34303
- const text = joinReasoningText(item);
34304
- if (!text)
34305
- return [];
34306
- return [{ type: "thinking", text }];
34307
- }
34308
- if (type === "commandExecution") {
34309
- const command = formatCommand(item.command);
34310
- const callId = id ?? `shell-${now}`;
34311
- if (phase === "started") {
34312
- this.toolCallStart.set(callId, now);
34313
- return [
34314
- {
34315
- type: "tool_call",
34316
- callId,
34317
- toolName: "shell",
34608
+ }
34609
+ function ensureGitRepo(workingDirectory) {
34610
+ fs7.mkdirSync(workingDirectory, { recursive: true });
34611
+ try {
34612
+ execSync2("git rev-parse --is-inside-work-tree", { cwd: workingDirectory, stdio: "pipe" });
34613
+ ensureLocalAttachmentGitExclude(workingDirectory);
34614
+ return;
34615
+ } catch {
34616
+ }
34617
+ const env = {
34618
+ ...process.env,
34619
+ GIT_AUTHOR_NAME: "parall-codex-agent",
34620
+ GIT_AUTHOR_EMAIL: "agent@parall.local",
34621
+ GIT_COMMITTER_NAME: "parall-codex-agent",
34622
+ GIT_COMMITTER_EMAIL: "agent@parall.local"
34623
+ };
34624
+ try {
34625
+ execSync2("git init", { cwd: workingDirectory, stdio: "pipe", env });
34626
+ execSync2("git commit --allow-empty -m init", { cwd: workingDirectory, stdio: "pipe", env });
34627
+ ensureLocalAttachmentGitExclude(workingDirectory);
34628
+ } catch {
34629
+ }
34630
+ }
34631
+
34632
+ // ts/codex-agent/dist/app-server-protocol.js
34633
+ function buildTurnInput(body, images) {
34634
+ return [
34635
+ { type: "text", text: body },
34636
+ ...images.map((image) => ({ type: "localImage", path: image.localPath }))
34637
+ ];
34638
+ }
34639
+ function extractThreadId(result) {
34640
+ if (!result || typeof result !== "object")
34641
+ return void 0;
34642
+ const r = result;
34643
+ if (typeof r.threadId === "string")
34644
+ return r.threadId;
34645
+ const thread = r.thread;
34646
+ if (thread && typeof thread.id === "string")
34647
+ return thread.id;
34648
+ return void 0;
34649
+ }
34650
+ function extractTurnId(result) {
34651
+ if (!result || typeof result !== "object")
34652
+ return void 0;
34653
+ const r = result;
34654
+ if (typeof r.turnId === "string")
34655
+ return r.turnId;
34656
+ const turn = r.turn;
34657
+ if (turn && typeof turn.id === "string")
34658
+ return turn.id;
34659
+ return void 0;
34660
+ }
34661
+ function extractThreadIdFromNotification(params) {
34662
+ if (!params || typeof params !== "object")
34663
+ return void 0;
34664
+ const p = params;
34665
+ if (typeof p.threadId === "string")
34666
+ return p.threadId;
34667
+ const thread = p.thread;
34668
+ if (thread && typeof thread.id === "string")
34669
+ return thread.id;
34670
+ const meta = p._meta ?? p.meta;
34671
+ if (meta && typeof meta.threadId === "string")
34672
+ return meta.threadId;
34673
+ return void 0;
34674
+ }
34675
+
34676
+ // ts/codex-agent/dist/jsonrpc-client.js
34677
+ var DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
34678
+ var JsonRpcStdioClient = class {
34679
+ proc;
34680
+ requestTimeoutMs;
34681
+ killProcess;
34682
+ nextId = 1;
34683
+ pending = /* @__PURE__ */ new Map();
34684
+ buffer = "";
34685
+ onNotification = null;
34686
+ disposed = false;
34687
+ constructor(proc, requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS, killProcess) {
34688
+ this.proc = proc;
34689
+ this.requestTimeoutMs = requestTimeoutMs;
34690
+ this.killProcess = killProcess;
34691
+ proc.stdout.setEncoding("utf8");
34692
+ proc.stdout.on("data", (chunk) => this.ingest(chunk));
34693
+ proc.once("close", () => this.dispose(new Error("app-server subprocess closed")));
34694
+ proc.once("error", (err) => this.dispose(err));
34695
+ proc.stdin.on("error", (err) => this.killUnhealthy(err));
34696
+ }
34697
+ setNotificationHandler(handler) {
34698
+ this.onNotification = handler;
34699
+ }
34700
+ sendRequest(method, params) {
34701
+ if (this.disposed) {
34702
+ return Promise.reject(new Error("JSON-RPC client disposed"));
34703
+ }
34704
+ const id = this.nextId++;
34705
+ const request3 = { jsonrpc: "2.0", id, method, params };
34706
+ const promise = new Promise((resolve4, reject) => {
34707
+ const timer = setTimeout(() => {
34708
+ const pending = this.pending.get(id);
34709
+ if (!pending)
34710
+ return;
34711
+ this.pending.delete(id);
34712
+ reject(new Error(`JSON-RPC request "${method}" timed out after ${this.requestTimeoutMs}ms`));
34713
+ this.killUnhealthy(new Error(`request "${method}" timed out; subprocess assumed hung`));
34714
+ }, this.requestTimeoutMs);
34715
+ this.pending.set(id, { resolve: resolve4, reject, timer });
34716
+ });
34717
+ this.writeFrame(request3);
34718
+ return promise;
34719
+ }
34720
+ sendNotification(method, params) {
34721
+ if (this.disposed)
34722
+ return;
34723
+ const notification = { jsonrpc: "2.0", method, params };
34724
+ this.writeFrame(notification);
34725
+ }
34726
+ isDisposed() {
34727
+ return this.disposed;
34728
+ }
34729
+ dispose(err) {
34730
+ if (this.disposed)
34731
+ return;
34732
+ this.disposed = true;
34733
+ for (const pending of this.pending.values()) {
34734
+ clearTimeout(pending.timer);
34735
+ pending.reject(err);
34736
+ }
34737
+ this.pending.clear();
34738
+ }
34739
+ writeFrame(message) {
34740
+ try {
34741
+ this.proc.stdin.write(`${JSON.stringify(message)}
34742
+ `);
34743
+ } catch (err) {
34744
+ this.killUnhealthy(err instanceof Error ? err : new Error(String(err)));
34745
+ }
34746
+ }
34747
+ killUnhealthy(err) {
34748
+ this.dispose(err);
34749
+ if (this.proc.exitCode === null && this.proc.signalCode === null) {
34750
+ if (this.killProcess) {
34751
+ this.killProcess(this.proc);
34752
+ } else {
34753
+ this.proc.kill("SIGTERM");
34754
+ }
34755
+ }
34756
+ }
34757
+ ingest(chunk) {
34758
+ this.buffer += chunk;
34759
+ let newlineIndex = this.buffer.indexOf("\n");
34760
+ while (newlineIndex >= 0) {
34761
+ const line = this.buffer.slice(0, newlineIndex).trim();
34762
+ this.buffer = this.buffer.slice(newlineIndex + 1);
34763
+ if (line)
34764
+ this.handleLine(line);
34765
+ newlineIndex = this.buffer.indexOf("\n");
34766
+ }
34767
+ }
34768
+ handleLine(line) {
34769
+ let message;
34770
+ try {
34771
+ message = JSON.parse(line);
34772
+ } catch {
34773
+ return;
34774
+ }
34775
+ if (isResponse(message)) {
34776
+ const pending = this.pending.get(message.id);
34777
+ if (!pending)
34778
+ return;
34779
+ this.pending.delete(message.id);
34780
+ clearTimeout(pending.timer);
34781
+ if (message.error) {
34782
+ pending.reject(new Error(message.error.message || "JSON-RPC error"));
34783
+ } else {
34784
+ pending.resolve(message.result);
34785
+ }
34786
+ return;
34787
+ }
34788
+ if (isNotification(message)) {
34789
+ this.onNotification?.(message.method, message.params);
34790
+ }
34791
+ }
34792
+ };
34793
+ function isResponse(m) {
34794
+ return !!m && typeof m === "object" && "id" in m && ("result" in m || "error" in m);
34795
+ }
34796
+ function isNotification(m) {
34797
+ return !!m && typeof m === "object" && "method" in m && !("id" in m);
34798
+ }
34799
+
34800
+ // ts/codex-agent/dist/event-mapping.js
34801
+ var EventMapper = class {
34802
+ toolCallStart = /* @__PURE__ */ new Map();
34803
+ /**
34804
+ * Map a single server notification to zero or more RuntimeEvents.
34805
+ * Return `null` for notifications that have no chat surface.
34806
+ */
34807
+ map(method, params) {
34808
+ const events = [];
34809
+ const p = params ?? {};
34810
+ switch (method) {
34811
+ // Streaming deltas are intentionally dropped — see the class comment.
34812
+ case "item/agentMessage/delta":
34813
+ case "item/reasoning/textDelta":
34814
+ case "item/reasoning/summaryTextDelta":
34815
+ break;
34816
+ case "item/started": {
34817
+ const item = p.item;
34818
+ if (!item || typeof item.type !== "string")
34819
+ break;
34820
+ events.push(...this.mapItem(item, "started"));
34821
+ break;
34822
+ }
34823
+ case "item/completed": {
34824
+ const item = p.item;
34825
+ if (!item || typeof item.type !== "string")
34826
+ break;
34827
+ events.push(...this.mapItem(item, "completed"));
34828
+ break;
34829
+ }
34830
+ case "turn/completed": {
34831
+ const turn = p.turn;
34832
+ const status = turn ? asString(turn.status) : void 0;
34833
+ const error = turn?.error;
34834
+ if (status === "failed") {
34835
+ const message = asString(error?.message) ?? "Codex turn failed";
34836
+ events.push({ type: "error", message });
34837
+ }
34838
+ break;
34839
+ }
34840
+ case "error": {
34841
+ const message = asString(p.message) ?? "Codex error";
34842
+ events.push({ type: "error", message });
34843
+ break;
34844
+ }
34845
+ }
34846
+ return events;
34847
+ }
34848
+ mapItem(item, phase) {
34849
+ const type = asString(item.type);
34850
+ const id = asString(item.id);
34851
+ const now = Date.now();
34852
+ if (type === "agentMessage") {
34853
+ if (phase !== "completed")
34854
+ return [];
34855
+ const text = asString(item.text);
34856
+ if (!text)
34857
+ return [];
34858
+ return [{ type: "text", text, project: false }];
34859
+ }
34860
+ if (type === "reasoning") {
34861
+ if (phase !== "completed")
34862
+ return [];
34863
+ const text = joinReasoningText(item);
34864
+ if (!text)
34865
+ return [];
34866
+ return [{ type: "thinking", text }];
34867
+ }
34868
+ if (type === "commandExecution") {
34869
+ const command = formatCommand(item.command);
34870
+ const callId = id ?? `shell-${now}`;
34871
+ if (phase === "started") {
34872
+ this.toolCallStart.set(callId, now);
34873
+ return [
34874
+ {
34875
+ type: "tool_call",
34876
+ callId,
34877
+ toolName: "shell",
34318
34878
  input: { command, cwd: asString(item.cwd) },
34319
34879
  startedAt: new Date(now).toISOString()
34320
34880
  }
@@ -34468,145 +35028,43 @@ function formatCommand(command) {
34468
35028
  return "";
34469
35029
  }
34470
35030
 
34471
- // ts/codex-agent/dist/jsonrpc-client.js
34472
- var DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
34473
- var JsonRpcStdioClient = class {
34474
- proc;
34475
- requestTimeoutMs;
34476
- killProcess;
34477
- nextId = 1;
34478
- pending = /* @__PURE__ */ new Map();
34479
- buffer = "";
34480
- onNotification = null;
34481
- disposed = false;
34482
- constructor(proc, requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS, killProcess) {
34483
- this.proc = proc;
34484
- this.requestTimeoutMs = requestTimeoutMs;
34485
- this.killProcess = killProcess;
34486
- proc.stdout.setEncoding("utf8");
34487
- proc.stdout.on("data", (chunk) => this.ingest(chunk));
34488
- proc.once("close", () => this.dispose(new Error("app-server subprocess closed")));
34489
- proc.once("error", (err) => this.dispose(err));
34490
- proc.stdin.on("error", (err) => this.killUnhealthy(err));
34491
- }
34492
- setNotificationHandler(handler) {
34493
- this.onNotification = handler;
34494
- }
34495
- sendRequest(method, params) {
34496
- if (this.disposed) {
34497
- return Promise.reject(new Error("JSON-RPC client disposed"));
34498
- }
34499
- const id = this.nextId++;
34500
- const request3 = { jsonrpc: "2.0", id, method, params };
34501
- const promise = new Promise((resolve4, reject) => {
34502
- const timer = setTimeout(() => {
34503
- const pending = this.pending.get(id);
34504
- if (!pending)
34505
- return;
34506
- this.pending.delete(id);
34507
- reject(new Error(`JSON-RPC request "${method}" timed out after ${this.requestTimeoutMs}ms`));
34508
- this.killUnhealthy(new Error(`request "${method}" timed out; subprocess assumed hung`));
34509
- }, this.requestTimeoutMs);
34510
- this.pending.set(id, { resolve: resolve4, reject, timer });
34511
- });
34512
- this.writeFrame(request3);
34513
- return promise;
34514
- }
34515
- sendNotification(method, params) {
34516
- if (this.disposed)
35031
+ // ts/codex-agent/dist/turn-sink.js
35032
+ var TurnSink = class {
35033
+ mapper = new EventMapper();
35034
+ queue = [];
35035
+ resolver = null;
35036
+ closed = false;
35037
+ push(envelope) {
35038
+ if (this.closed)
34517
35039
  return;
34518
- const notification = { jsonrpc: "2.0", method, params };
34519
- this.writeFrame(notification);
34520
- }
34521
- isDisposed() {
34522
- return this.disposed;
34523
- }
34524
- dispose(err) {
34525
- if (this.disposed)
35040
+ if (this.resolver) {
35041
+ const r = this.resolver;
35042
+ this.resolver = null;
35043
+ r(envelope);
34526
35044
  return;
34527
- this.disposed = true;
34528
- for (const pending of this.pending.values()) {
34529
- clearTimeout(pending.timer);
34530
- pending.reject(err);
34531
- }
34532
- this.pending.clear();
34533
- }
34534
- writeFrame(message) {
34535
- try {
34536
- this.proc.stdin.write(`${JSON.stringify(message)}
34537
- `);
34538
- } catch (err) {
34539
- this.killUnhealthy(err instanceof Error ? err : new Error(String(err)));
34540
- }
34541
- }
34542
- killUnhealthy(err) {
34543
- this.dispose(err);
34544
- if (this.proc.exitCode === null && this.proc.signalCode === null) {
34545
- if (this.killProcess) {
34546
- this.killProcess(this.proc);
34547
- } else {
34548
- this.proc.kill("SIGTERM");
34549
- }
34550
35045
  }
35046
+ this.queue.push(envelope);
34551
35047
  }
34552
- ingest(chunk) {
34553
- this.buffer += chunk;
34554
- let newlineIndex = this.buffer.indexOf("\n");
34555
- while (newlineIndex >= 0) {
34556
- const line = this.buffer.slice(0, newlineIndex).trim();
34557
- this.buffer = this.buffer.slice(newlineIndex + 1);
34558
- if (line)
34559
- this.handleLine(line);
34560
- newlineIndex = this.buffer.indexOf("\n");
35048
+ next() {
35049
+ const pending = this.queue.shift();
35050
+ if (pending)
35051
+ return Promise.resolve(pending);
35052
+ if (this.closed) {
35053
+ return Promise.resolve({ kind: "turn_end" });
34561
35054
  }
35055
+ return new Promise((resolve4) => {
35056
+ this.resolver = resolve4;
35057
+ });
34562
35058
  }
34563
- handleLine(line) {
34564
- let message;
34565
- try {
34566
- message = JSON.parse(line);
34567
- } catch {
34568
- return;
34569
- }
34570
- if (isResponse(message)) {
34571
- const pending = this.pending.get(message.id);
34572
- if (!pending)
34573
- return;
34574
- this.pending.delete(message.id);
34575
- clearTimeout(pending.timer);
34576
- if (message.error) {
34577
- pending.reject(new Error(message.error.message || "JSON-RPC error"));
34578
- } else {
34579
- pending.resolve(message.result);
34580
- }
34581
- return;
34582
- }
34583
- if (isNotification(message)) {
34584
- this.onNotification?.(message.method, message.params);
34585
- }
35059
+ close() {
35060
+ this.closed = true;
35061
+ const r = this.resolver;
35062
+ this.resolver = null;
35063
+ r?.({ kind: "turn_end" });
34586
35064
  }
34587
35065
  };
34588
- function isResponse(m) {
34589
- return !!m && typeof m === "object" && "id" in m && ("result" in m || "error" in m);
34590
- }
34591
- function isNotification(m) {
34592
- return !!m && typeof m === "object" && "method" in m && !("id" in m);
34593
- }
34594
35066
 
34595
35067
  // ts/codex-agent/dist/dispatch.js
34596
- var IS_WIN32 = process.platform === "win32";
34597
- function quoteWin32Arg(arg) {
34598
- if (!/[\s"&|^<>()]/.test(arg))
34599
- return arg;
34600
- return `"${arg.replace(/"/g, '""')}"`;
34601
- }
34602
- function killWin32Tree(pid) {
34603
- try {
34604
- execSync2(`taskkill /T /F /PID ${pid}`, { windowsHide: true, stdio: "ignore" });
34605
- return true;
34606
- } catch {
34607
- return false;
34608
- }
34609
- }
34610
35068
  var CodexAppServerAdapter = class {
34611
35069
  opts;
34612
35070
  client = null;
@@ -34643,6 +35101,8 @@ var CodexAppServerAdapter = class {
34643
35101
  this.opts.model = config.model ?? void 0;
34644
35102
  if (config.reasoningEffort !== void 0)
34645
35103
  this.opts.reasoningEffort = config.reasoningEffort ?? void 0;
35104
+ if (config.developerInstructions !== void 0)
35105
+ this.opts.developerInstructions = config.developerInstructions ?? void 0;
34646
35106
  }
34647
35107
  async enqueueDuringDispatch(sessionKey, body) {
34648
35108
  const client = this.client;
@@ -34887,6 +35347,9 @@ var CodexAppServerAdapter = class {
34887
35347
  if (this.opts.useParallProvider) {
34888
35348
  forkParams.modelProvider = "parall";
34889
35349
  }
35350
+ if (this.opts.developerInstructions) {
35351
+ forkParams.developerInstructions = this.opts.developerInstructions;
35352
+ }
34890
35353
  if (this.opts.model)
34891
35354
  forkParams.model = this.opts.model;
34892
35355
  if (this.opts.reasoningEffort) {
@@ -34912,11 +35375,14 @@ var CodexAppServerAdapter = class {
34912
35375
  return null;
34913
35376
  }
34914
35377
  /**
34915
- * Lazily restart the app-server before the NEXT turn: the workspace
34916
- * config.toml (developer_instructions, carrying capability fragments) is
34917
- * loaded at process start, so a changed fragment set needs a respawn to
34918
- * reach the prompt. Deferred to the next dispatch with no active turns —
34919
- * never kills an in-flight turn; thread state survives via thread/resume.
35378
+ * Lazily restart the app-server before the NEXT turn, so a subprocess that
35379
+ * has been running since before a capability change starts clean. Deferred
35380
+ * to the next dispatch with no active turns never kills an in-flight turn;
35381
+ * thread state survives via thread/resume.
35382
+ *
35383
+ * A restart does NOT re-instruct an already-persisted thread — see the
35384
+ * `developerInstructions` option doc. The capability shim dir on PATH is what
35385
+ * makes a grant/revocation effective immediately.
34920
35386
  */
34921
35387
  requestProcessRestart() {
34922
35388
  this.restartRequested = true;
@@ -34926,7 +35392,7 @@ var CodexAppServerAdapter = class {
34926
35392
  if (!this.restartRequested || this.activeTurns.size > 0)
34927
35393
  return;
34928
35394
  this.restartRequested = false;
34929
- (log2 ?? this.opts.log)?.info?.("restarting codex app-server to pick up updated developer_instructions");
35395
+ (log2 ?? this.opts.log)?.info?.("restarting codex app-server after a capability change (new threads pick up the refreshed developerInstructions; an already-persisted thread keeps its own)");
34930
35396
  await this.stop();
34931
35397
  }
34932
35398
  async stop() {
@@ -35065,6 +35531,9 @@ var CodexAppServerAdapter = class {
35065
35531
  commonParams.modelProvider = "parall";
35066
35532
  }
35067
35533
  commonParams.sandbox = normalizeSandbox(this.opts.sandbox);
35534
+ if (this.opts.developerInstructions) {
35535
+ commonParams.developerInstructions = this.opts.developerInstructions;
35536
+ }
35068
35537
  if (this.opts.model)
35069
35538
  commonParams.model = this.opts.model;
35070
35539
  if (this.opts.reasoningEffort) {
@@ -35085,121 +35554,23 @@ var CodexAppServerAdapter = class {
35085
35554
  if (method === "error") {
35086
35555
  const msg = params?.message ?? "Codex app-server error";
35087
35556
  for (const sink2 of this.activeTurns.values()) {
35088
- sink2.push({ kind: "error", message: String(msg) });
35089
- }
35090
- }
35091
- return;
35092
- }
35093
- const sink = this.activeTurns.get(threadId);
35094
- if (!sink)
35095
- return;
35096
- for (const event of sink.mapper.map(method, params)) {
35097
- sink.push({ kind: "runtime", event });
35098
- }
35099
- if (method === "turn/completed") {
35100
- this.activeTurnIds.delete(threadId);
35101
- sink.push({ kind: "turn_end", threadId });
35102
- }
35103
- }
35104
- };
35105
- var TurnSink = class {
35106
- mapper = new EventMapper();
35107
- queue = [];
35108
- resolver = null;
35109
- closed = false;
35110
- push(envelope) {
35111
- if (this.closed)
35112
- return;
35113
- if (this.resolver) {
35114
- const r = this.resolver;
35115
- this.resolver = null;
35116
- r(envelope);
35117
- return;
35118
- }
35119
- this.queue.push(envelope);
35120
- }
35121
- next() {
35122
- const pending = this.queue.shift();
35123
- if (pending)
35124
- return Promise.resolve(pending);
35125
- if (this.closed) {
35126
- return Promise.resolve({ kind: "turn_end" });
35127
- }
35128
- return new Promise((resolve4) => {
35129
- this.resolver = resolve4;
35130
- });
35131
- }
35132
- close() {
35133
- this.closed = true;
35134
- const r = this.resolver;
35135
- this.resolver = null;
35136
- r?.({ kind: "turn_end" });
35137
- }
35138
- };
35139
- function ensureGitRepo(workingDirectory) {
35140
- fs7.mkdirSync(workingDirectory, { recursive: true });
35141
- try {
35142
- execSync2("git rev-parse --is-inside-work-tree", { cwd: workingDirectory, stdio: "pipe" });
35143
- ensureLocalAttachmentGitExclude(workingDirectory);
35144
- return;
35145
- } catch {
35146
- }
35147
- const env = {
35148
- ...process.env,
35149
- GIT_AUTHOR_NAME: "parall-codex-agent",
35150
- GIT_AUTHOR_EMAIL: "agent@parall.local",
35151
- GIT_COMMITTER_NAME: "parall-codex-agent",
35152
- GIT_COMMITTER_EMAIL: "agent@parall.local"
35153
- };
35154
- try {
35155
- execSync2("git init", { cwd: workingDirectory, stdio: "pipe", env });
35156
- execSync2("git commit --allow-empty -m init", { cwd: workingDirectory, stdio: "pipe", env });
35157
- ensureLocalAttachmentGitExclude(workingDirectory);
35158
- } catch {
35557
+ sink2.push({ kind: "error", message: String(msg) });
35558
+ }
35559
+ }
35560
+ return;
35561
+ }
35562
+ const sink = this.activeTurns.get(threadId);
35563
+ if (!sink)
35564
+ return;
35565
+ for (const event of sink.mapper.map(method, params)) {
35566
+ sink.push({ kind: "runtime", event });
35567
+ }
35568
+ if (method === "turn/completed") {
35569
+ this.activeTurnIds.delete(threadId);
35570
+ sink.push({ kind: "turn_end", threadId });
35571
+ }
35159
35572
  }
35160
- }
35161
- function buildTurnInput(body, images) {
35162
- return [
35163
- { type: "text", text: body },
35164
- ...images.map((image) => ({ type: "localImage", path: image.localPath }))
35165
- ];
35166
- }
35167
- function extractThreadId(result) {
35168
- if (!result || typeof result !== "object")
35169
- return void 0;
35170
- const r = result;
35171
- if (typeof r.threadId === "string")
35172
- return r.threadId;
35173
- const thread = r.thread;
35174
- if (thread && typeof thread.id === "string")
35175
- return thread.id;
35176
- return void 0;
35177
- }
35178
- function extractTurnId(result) {
35179
- if (!result || typeof result !== "object")
35180
- return void 0;
35181
- const r = result;
35182
- if (typeof r.turnId === "string")
35183
- return r.turnId;
35184
- const turn = r.turn;
35185
- if (turn && typeof turn.id === "string")
35186
- return turn.id;
35187
- return void 0;
35188
- }
35189
- function extractThreadIdFromNotification(params) {
35190
- if (!params || typeof params !== "object")
35191
- return void 0;
35192
- const p = params;
35193
- if (typeof p.threadId === "string")
35194
- return p.threadId;
35195
- const thread = p.thread;
35196
- if (thread && typeof thread.id === "string")
35197
- return thread.id;
35198
- const meta = p._meta ?? p.meta;
35199
- if (meta && typeof meta.threadId === "string")
35200
- return meta.threadId;
35201
- return void 0;
35202
- }
35573
+ };
35203
35574
  function errToString(err) {
35204
35575
  if (err instanceof Error)
35205
35576
  return err.message;
@@ -35276,697 +35647,123 @@ var CodexSessionManager = class {
35276
35647
  };
35277
35648
 
35278
35649
  // ts/codex-agent/dist/workspace.js
35650
+ import * as fs10 from "node:fs";
35651
+ import * as path11 from "node:path";
35652
+
35653
+ // ts/codex-agent/dist/legacy-workspace-config-migration.js
35279
35654
  import * as fs9 from "node:fs";
35280
35655
  import * as path10 from "node:path";
35281
-
35282
- // ts/node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/error.js
35283
- function getLineColFromPtr(string, ptr) {
35284
- let lines = string.slice(0, ptr).split(/\r\n|\n|\r/g);
35285
- return [lines.length, lines.pop().length + 1];
35286
- }
35287
- function makeCodeBlock(string, line, column) {
35288
- let lines = string.split(/\r\n|\n|\r/g);
35289
- let codeblock = "";
35290
- let numberLen = (Math.log10(line + 1) | 0) + 1;
35291
- for (let i = line - 1; i <= line + 1; i++) {
35292
- let l = lines[i - 1];
35293
- if (!l)
35294
- continue;
35295
- codeblock += i.toString().padEnd(numberLen, " ");
35296
- codeblock += ": ";
35297
- codeblock += l;
35298
- codeblock += "\n";
35299
- if (i === line) {
35300
- codeblock += " ".repeat(numberLen + column + 2);
35301
- codeblock += "^\n";
35302
- }
35303
- }
35304
- return codeblock;
35305
- }
35306
- var TomlError = class extends Error {
35307
- line;
35308
- column;
35309
- codeblock;
35310
- constructor(message, options) {
35311
- const [line, column] = getLineColFromPtr(options.toml, options.ptr);
35312
- const codeblock = makeCodeBlock(options.toml, line, column);
35313
- super(`Invalid TOML document: ${message}
35314
-
35315
- ${codeblock}`, options);
35316
- this.line = line;
35317
- this.column = column;
35318
- this.codeblock = codeblock;
35319
- }
35320
- };
35321
-
35322
- // ts/node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/util.js
35323
- function isEscaped(str, ptr) {
35324
- let i = 0;
35325
- while (str[ptr - ++i] === "\\")
35326
- ;
35327
- return --i && i % 2;
35328
- }
35329
- function indexOfNewline(str, start = 0, end = str.length) {
35330
- let idx = str.indexOf("\n", start);
35331
- if (str[idx - 1] === "\r")
35332
- idx--;
35333
- return idx <= end ? idx : -1;
35334
- }
35335
- function skipComment(str, ptr) {
35336
- for (let i = ptr; i < str.length; i++) {
35337
- let c = str[i];
35338
- if (c === "\n")
35339
- return i;
35340
- if (c === "\r" && str[i + 1] === "\n")
35341
- return i + 1;
35342
- if (c < " " && c !== " " || c === "\x7F") {
35343
- throw new TomlError("control characters are not allowed in comments", {
35344
- toml: str,
35345
- ptr
35346
- });
35347
- }
35348
- }
35349
- return str.length;
35656
+ var LEGACY_CONFIG_RELPATH = [".codex", "config.toml"];
35657
+ var MIGRATION_SENTINEL_RELPATH = [".parall", "legacy-workspace-config-migration.v1"];
35658
+ function legacyWorkspaceConfigPath(workspaceDir) {
35659
+ return path10.join(workspaceDir, ...LEGACY_CONFIG_RELPATH);
35350
35660
  }
35351
- function skipVoid(str, ptr, banNewLines, banComments) {
35352
- let c;
35353
- while (1) {
35354
- while ((c = str[ptr]) === " " || c === " " || !banNewLines && (c === "\n" || c === "\r" && str[ptr + 1] === "\n"))
35355
- ptr++;
35356
- if (banComments || c !== "#")
35357
- break;
35358
- ptr = skipComment(str, ptr);
35359
- }
35360
- return ptr;
35661
+ function migrationSentinelPath(workspaceDir) {
35662
+ return path10.join(workspaceDir, ...MIGRATION_SENTINEL_RELPATH);
35361
35663
  }
35362
- function skipUntil(str, ptr, sep2, end, banNewLines = false) {
35363
- if (!end) {
35364
- ptr = indexOfNewline(str, ptr);
35365
- return ptr < 0 ? str.length : ptr;
35366
- }
35367
- for (let i = ptr; i < str.length; i++) {
35368
- let c = str[i];
35369
- if (c === "#") {
35370
- i = indexOfNewline(str, i);
35371
- } else if (c === sep2) {
35372
- return i + 1;
35373
- } else if (c === end || banNewLines && (c === "\n" || c === "\r" && str[i + 1] === "\n")) {
35374
- return i;
35375
- }
35376
- }
35377
- throw new TomlError("cannot find end of structure", {
35378
- toml: str,
35379
- ptr
35380
- });
35664
+ function legacyWorkspaceConfigToml(prompt) {
35665
+ return `developer_instructions = """
35666
+ ${prompt.replace(/\\/g, "\\\\").replace(/"""/g, '\\"""')}
35667
+ """
35668
+ `;
35381
35669
  }
35382
- function getStringEnd(str, seek) {
35383
- let first = str[seek];
35384
- let target = first === str[seek + 1] && str[seek + 1] === str[seek + 2] ? str.slice(seek, seek + 3) : first;
35385
- seek += target.length - 1;
35386
- do
35387
- seek = str.indexOf(target, ++seek);
35388
- while (seek > -1 && first !== "'" && isEscaped(str, seek));
35389
- if (seek > -1) {
35390
- seek += target.length;
35391
- if (target.length > 1) {
35392
- if (str[seek] === first)
35393
- seek++;
35394
- if (str[seek] === first)
35395
- seek++;
35396
- }
35397
- }
35398
- return seek;
35670
+ function classifyLegacyWorkspaceConfig(facts) {
35671
+ const { entry, rawContent, proof } = facts;
35672
+ if (!entry)
35673
+ return { action: "none" };
35674
+ if (!entry.isPlainFile)
35675
+ return { action: "preserve", reason: "not-a-plain-file" };
35676
+ if (entry.hardLinks !== 1)
35677
+ return { action: "preserve", reason: "extra-hard-links" };
35678
+ if (proof.kind === "absent")
35679
+ return { action: "preserve", reason: "no-authorship-proof" };
35680
+ if (proof.kind === "unreadable") {
35681
+ return { action: "preserve", reason: "authorship-proof-unreadable" };
35682
+ }
35683
+ if (rawContent === null)
35684
+ return { action: "preserve", reason: "config-unreadable" };
35685
+ if (rawContent !== legacyWorkspaceConfigToml(proof.prompt)) {
35686
+ return { action: "preserve", reason: "content-mismatch" };
35687
+ }
35688
+ return { action: "remove" };
35399
35689
  }
35400
-
35401
- // ts/node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/date.js
35402
- var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i;
35403
- var TomlDate = class _TomlDate extends Date {
35404
- #hasDate = false;
35405
- #hasTime = false;
35406
- #offset = null;
35407
- constructor(date) {
35408
- let hasDate = true;
35409
- let hasTime = true;
35410
- let offset = "Z";
35411
- if (typeof date === "string") {
35412
- let match = date.match(DATE_TIME_RE);
35413
- if (match) {
35414
- if (!match[1]) {
35415
- hasDate = false;
35416
- date = `0000-01-01T${date}`;
35417
- }
35418
- hasTime = !!match[2];
35419
- hasTime && date[10] === " " && (date = date.replace(" ", "T"));
35420
- if (match[2] && +match[2] > 23) {
35421
- date = "";
35422
- } else {
35423
- offset = match[3] || null;
35424
- date = date.toUpperCase();
35425
- if (!offset && hasTime)
35426
- date += "Z";
35427
- }
35428
- } else {
35429
- date = "";
35430
- }
35431
- }
35432
- super(date);
35433
- if (!isNaN(this.getTime())) {
35434
- this.#hasDate = hasDate;
35435
- this.#hasTime = hasTime;
35436
- this.#offset = offset;
35437
- }
35438
- }
35439
- isDateTime() {
35440
- return this.#hasDate && this.#hasTime;
35441
- }
35442
- isLocal() {
35443
- return !this.#hasDate || !this.#hasTime || !this.#offset;
35444
- }
35445
- isDate() {
35446
- return this.#hasDate && !this.#hasTime;
35447
- }
35448
- isTime() {
35449
- return this.#hasTime && !this.#hasDate;
35450
- }
35451
- isValid() {
35452
- return this.#hasDate || this.#hasTime;
35453
- }
35454
- toISOString() {
35455
- let iso = super.toISOString();
35456
- if (this.isDate())
35457
- return iso.slice(0, 10);
35458
- if (this.isTime())
35459
- return iso.slice(11, 23);
35460
- if (this.#offset === null)
35461
- return iso.slice(0, -1);
35462
- if (this.#offset === "Z")
35463
- return iso;
35464
- let offset = +this.#offset.slice(1, 3) * 60 + +this.#offset.slice(4, 6);
35465
- offset = this.#offset[0] === "-" ? offset : -offset;
35466
- let offsetDate = new Date(this.getTime() - offset * 6e4);
35467
- return offsetDate.toISOString().slice(0, -1) + this.#offset;
35468
- }
35469
- static wrapAsOffsetDateTime(jsDate, offset = "Z") {
35470
- let date = new _TomlDate(jsDate);
35471
- date.#offset = offset;
35472
- return date;
35473
- }
35474
- static wrapAsLocalDateTime(jsDate) {
35475
- let date = new _TomlDate(jsDate);
35476
- date.#offset = null;
35477
- return date;
35478
- }
35479
- static wrapAsLocalDate(jsDate) {
35480
- let date = new _TomlDate(jsDate);
35481
- date.#hasTime = false;
35482
- date.#offset = null;
35483
- return date;
35484
- }
35485
- static wrapAsLocalTime(jsDate) {
35486
- let date = new _TomlDate(jsDate);
35487
- date.#hasDate = false;
35488
- date.#offset = null;
35489
- return date;
35490
- }
35690
+ var PRESERVE_DETAIL = {
35691
+ "not-a-plain-file": "it is a symlink or directory, not a plain file the bridge could have written",
35692
+ "extra-hard-links": "the file has more than one hard link, so another path shares this inode",
35693
+ "no-authorship-proof": "this workspace has no .parall/system-prompt.md from a previous boot, so nothing here can be proven ours",
35694
+ "authorship-proof-unreadable": "the .parall/system-prompt.md authorship proof exists but could not be read, so ownership cannot be evaluated",
35695
+ "config-unreadable": "its own bytes could not be read, so authorship cannot be proven",
35696
+ "content-mismatch": "its bytes are not what the retired bridge would have written"
35491
35697
  };
35698
+ var SENTINEL_BODY = `Parall codex bridge \u2014 one-shot workspace migration record (v1)
35492
35699
 
35493
- // ts/node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/primitive.js
35494
- var INT_REGEX = /^((0x[0-9a-fA-F](_?[0-9a-fA-F])*)|(([+-]|0[ob])?\d(_?\d)*))$/;
35495
- var FLOAT_REGEX = /^[+-]?\d(_?\d)*(\.\d(_?\d)*)?([eE][+-]?\d(_?\d)*)?$/;
35496
- var LEADING_ZERO = /^[+-]?0[0-9_]/;
35497
- var ESCAPE_REGEX = /^[0-9a-f]{2,8}$/i;
35498
- var ESC_MAP = {
35499
- b: "\b",
35500
- t: " ",
35501
- n: "\n",
35502
- f: "\f",
35503
- r: "\r",
35504
- e: "\x1B",
35505
- '"': '"',
35506
- "\\": "\\"
35507
- };
35508
- function parseString(str, ptr = 0, endPtr = str.length) {
35509
- let isLiteral = str[ptr] === "'";
35510
- let isMultiline = str[ptr++] === str[ptr] && str[ptr] === str[ptr + 1];
35511
- if (isMultiline) {
35512
- endPtr -= 2;
35513
- if (str[ptr += 2] === "\r")
35514
- ptr++;
35515
- if (str[ptr] === "\n")
35516
- ptr++;
35517
- }
35518
- let tmp = 0;
35519
- let isEscape;
35520
- let parsed = "";
35521
- let sliceStart = ptr;
35522
- while (ptr < endPtr - 1) {
35523
- let c = str[ptr++];
35524
- if (c === "\n" || c === "\r" && str[ptr] === "\n") {
35525
- if (!isMultiline) {
35526
- throw new TomlError("newlines are not allowed in strings", {
35527
- toml: str,
35528
- ptr: ptr - 1
35529
- });
35530
- }
35531
- } else if (c < " " && c !== " " || c === "\x7F") {
35532
- throw new TomlError("control characters are not allowed in strings", {
35533
- toml: str,
35534
- ptr: ptr - 1
35535
- });
35536
- }
35537
- if (isEscape) {
35538
- isEscape = false;
35539
- if (c === "x" || c === "u" || c === "U") {
35540
- let code = str.slice(ptr, ptr += c === "x" ? 2 : c === "u" ? 4 : 8);
35541
- if (!ESCAPE_REGEX.test(code)) {
35542
- throw new TomlError("invalid unicode escape", {
35543
- toml: str,
35544
- ptr: tmp
35545
- });
35546
- }
35547
- try {
35548
- parsed += String.fromCodePoint(parseInt(code, 16));
35549
- } catch {
35550
- throw new TomlError("invalid unicode escape", {
35551
- toml: str,
35552
- ptr: tmp
35553
- });
35554
- }
35555
- } else if (isMultiline && (c === "\n" || c === " " || c === " " || c === "\r")) {
35556
- ptr = skipVoid(str, ptr - 1, true);
35557
- if (str[ptr] !== "\n" && str[ptr] !== "\r") {
35558
- throw new TomlError("invalid escape: only line-ending whitespace may be escaped", {
35559
- toml: str,
35560
- ptr: tmp
35561
- });
35562
- }
35563
- ptr = skipVoid(str, ptr);
35564
- } else if (c in ESC_MAP) {
35565
- parsed += ESC_MAP[c];
35566
- } else {
35567
- throw new TomlError("unrecognized escape sequence", {
35568
- toml: str,
35569
- ptr: tmp
35570
- });
35571
- }
35572
- sliceStart = ptr;
35573
- } else if (!isLiteral && c === "\\") {
35574
- tmp = ptr - 1;
35575
- isEscape = true;
35576
- parsed += str.slice(sliceStart, tmp);
35577
- }
35578
- }
35579
- return parsed + str.slice(sliceStart, endPtr - 1);
35580
- }
35581
- function parseValue(value, toml, ptr, integersAsBigInt) {
35582
- if (value === "true")
35583
- return true;
35584
- if (value === "false")
35585
- return false;
35586
- if (value === "-inf")
35587
- return -Infinity;
35588
- if (value === "inf" || value === "+inf")
35589
- return Infinity;
35590
- if (value === "nan" || value === "+nan" || value === "-nan")
35591
- return NaN;
35592
- if (value === "-0")
35593
- return integersAsBigInt ? 0n : 0;
35594
- let isInt = INT_REGEX.test(value);
35595
- if (isInt || FLOAT_REGEX.test(value)) {
35596
- if (LEADING_ZERO.test(value)) {
35597
- throw new TomlError("leading zeroes are not allowed", {
35598
- toml,
35599
- ptr
35600
- });
35601
- }
35602
- value = value.replace(/_/g, "");
35603
- let numeric = +value;
35604
- if (isNaN(numeric)) {
35605
- throw new TomlError("invalid number", {
35606
- toml,
35607
- ptr
35608
- });
35609
- }
35610
- if (isInt) {
35611
- if ((isInt = !Number.isSafeInteger(numeric)) && !integersAsBigInt) {
35612
- throw new TomlError("integer value cannot be represented losslessly", {
35613
- toml,
35614
- ptr
35615
- });
35616
- }
35617
- if (isInt || integersAsBigInt === true)
35618
- numeric = BigInt(value);
35619
- }
35620
- return numeric;
35621
- }
35622
- const date = new TomlDate(value);
35623
- if (!date.isValid()) {
35624
- throw new TomlError("invalid value", {
35625
- toml,
35626
- ptr
35627
- });
35628
- }
35629
- return date;
35630
- }
35700
+ The legacy workspace-config cleanup has been CLAIMED for this workspace. Only the
35701
+ boot that created this file was allowed to remove a bridge-authored
35702
+ .codex/config.toml, and only under a byte-exact authorship proof.
35631
35703
 
35632
- // ts/node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/extract.js
35633
- function sliceAndTrimEndOf(str, startPtr, endPtr) {
35634
- let value = str.slice(startPtr, endPtr);
35635
- let commentIdx = value.indexOf("#");
35636
- if (commentIdx > -1) {
35637
- skipComment(str, commentIdx);
35638
- value = value.slice(0, commentIdx);
35639
- }
35640
- return [value.trimEnd(), commentIdx];
35641
- }
35642
- function extractValue(str, ptr, end, depth, integersAsBigInt) {
35643
- if (depth === 0) {
35644
- throw new TomlError("document contains excessively nested structures. aborting.", {
35645
- toml: str,
35646
- ptr
35647
- });
35648
- }
35649
- let c = str[ptr];
35650
- if (c === "[" || c === "{") {
35651
- let [value, endPtr2] = c === "[" ? parseArray(str, ptr, depth, integersAsBigInt) : parseInlineTable(str, ptr, depth, integersAsBigInt);
35652
- if (end) {
35653
- endPtr2 = skipVoid(str, endPtr2);
35654
- if (str[endPtr2] === ",")
35655
- endPtr2++;
35656
- else if (str[endPtr2] !== end) {
35657
- throw new TomlError("expected comma or end of structure", {
35658
- toml: str,
35659
- ptr: endPtr2
35660
- });
35661
- }
35662
- }
35663
- return [value, endPtr2];
35664
- }
35665
- let endPtr;
35666
- if (c === '"' || c === "'") {
35667
- endPtr = getStringEnd(str, ptr);
35668
- let parsed = parseString(str, ptr, endPtr);
35669
- if (end) {
35670
- endPtr = skipVoid(str, endPtr);
35671
- if (str[endPtr] && str[endPtr] !== "," && str[endPtr] !== end && str[endPtr] !== "\n" && str[endPtr] !== "\r") {
35672
- throw new TomlError("unexpected character encountered", {
35673
- toml: str,
35674
- ptr: endPtr
35675
- });
35676
- }
35677
- endPtr += +(str[endPtr] === ",");
35678
- }
35679
- return [parsed, endPtr];
35680
- }
35681
- endPtr = skipUntil(str, ptr, ",", end);
35682
- let slice = sliceAndTrimEndOf(str, ptr, endPtr - +(str[endPtr - 1] === ","));
35683
- if (!slice[0]) {
35684
- throw new TomlError("incomplete key-value declaration: no value specified", {
35685
- toml: str,
35686
- ptr
35687
- });
35688
- }
35689
- if (end && slice[1] > -1) {
35690
- endPtr = skipVoid(str, ptr + slice[1]);
35691
- endPtr += +(str[endPtr] === ",");
35692
- }
35693
- return [
35694
- parseValue(slice[0], str, ptr, integersAsBigInt),
35695
- endPtr
35696
- ];
35697
- }
35704
+ While this file exists the bridge will NEVER auto-remove .codex/config.toml again.
35705
+ Deleting this file does not safely re-arm the migration: the evidence it relied on
35706
+ (.parall/system-prompt.md as written by a pre-protocol-delivery bridge) has since
35707
+ been overwritten by this bridge, so a re-run could mistake a prompt it wrote itself
35708
+ for legacy evidence and delete a file that is not ours.
35698
35709
 
35699
- // ts/node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/struct.js
35700
- var KEY_PART_RE = /^[a-zA-Z0-9-_]+[ \t]*$/;
35701
- function parseKey(str, ptr, end = "=") {
35702
- let dot = ptr - 1;
35703
- let parsed = [];
35704
- let endPtr = str.indexOf(end, ptr);
35705
- if (endPtr < 0) {
35706
- throw new TomlError("incomplete key-value: cannot find end of key", {
35707
- toml: str,
35708
- ptr
35709
- });
35710
- }
35711
- do {
35712
- let c = str[ptr = ++dot];
35713
- if (c !== " " && c !== " ") {
35714
- if (c === '"' || c === "'") {
35715
- if (c === str[ptr + 1] && c === str[ptr + 2]) {
35716
- throw new TomlError("multiline strings are not allowed in keys", {
35717
- toml: str,
35718
- ptr
35719
- });
35720
- }
35721
- let eos = getStringEnd(str, ptr);
35722
- if (eos < 0) {
35723
- throw new TomlError("unfinished string encountered", {
35724
- toml: str,
35725
- ptr
35726
- });
35727
- }
35728
- dot = str.indexOf(".", eos);
35729
- let strEnd = str.slice(eos, dot < 0 || dot > endPtr ? endPtr : dot);
35730
- let newLine = indexOfNewline(strEnd);
35731
- if (newLine > -1) {
35732
- throw new TomlError("newlines are not allowed in keys", {
35733
- toml: str,
35734
- ptr: ptr + dot + newLine
35735
- });
35736
- }
35737
- if (strEnd.trimStart()) {
35738
- throw new TomlError("found extra tokens after the string part", {
35739
- toml: str,
35740
- ptr: eos
35741
- });
35742
- }
35743
- if (endPtr < eos) {
35744
- endPtr = str.indexOf(end, eos);
35745
- if (endPtr < 0) {
35746
- throw new TomlError("incomplete key-value: cannot find end of key", {
35747
- toml: str,
35748
- ptr
35749
- });
35750
- }
35751
- }
35752
- parsed.push(parseString(str, ptr, eos));
35753
- } else {
35754
- dot = str.indexOf(".", ptr);
35755
- let part = str.slice(ptr, dot < 0 || dot > endPtr ? endPtr : dot);
35756
- if (!KEY_PART_RE.test(part)) {
35757
- throw new TomlError("only letter, numbers, dashes and underscores are allowed in keys", {
35758
- toml: str,
35759
- ptr
35760
- });
35761
- }
35762
- parsed.push(part.trimEnd());
35763
- }
35764
- }
35765
- } while (dot + 1 && dot < endPtr);
35766
- return [parsed, skipVoid(str, endPtr + 1, true, true)];
35767
- }
35768
- function parseInlineTable(str, ptr, depth, integersAsBigInt) {
35769
- let res = {};
35770
- let seen = /* @__PURE__ */ new Set();
35771
- let c;
35772
- ptr++;
35773
- while ((c = str[ptr++]) !== "}" && c) {
35774
- if (c === ",") {
35775
- throw new TomlError("expected value, found comma", {
35776
- toml: str,
35777
- ptr: ptr - 1
35778
- });
35779
- } else if (c === "#")
35780
- ptr = skipComment(str, ptr);
35781
- else if (c !== " " && c !== " " && c !== "\n" && c !== "\r") {
35782
- let k;
35783
- let t = res;
35784
- let hasOwn = false;
35785
- let [key, keyEndPtr] = parseKey(str, ptr - 1);
35786
- for (let i = 0; i < key.length; i++) {
35787
- if (i)
35788
- t = hasOwn ? t[k] : t[k] = {};
35789
- k = key[i];
35790
- if ((hasOwn = Object.hasOwn(t, k)) && (typeof t[k] !== "object" || seen.has(t[k]))) {
35791
- throw new TomlError("trying to redefine an already defined value", {
35792
- toml: str,
35793
- ptr
35794
- });
35795
- }
35796
- if (!hasOwn && k === "__proto__") {
35797
- Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
35798
- }
35799
- }
35800
- if (hasOwn) {
35801
- throw new TomlError("trying to redefine an already defined value", {
35802
- toml: str,
35803
- ptr
35804
- });
35805
- }
35806
- let [value, valueEndPtr] = extractValue(str, keyEndPtr, "}", depth - 1, integersAsBigInt);
35807
- seen.add(value);
35808
- t[k] = value;
35809
- ptr = valueEndPtr;
35810
- }
35811
- }
35812
- if (!c) {
35813
- throw new TomlError("unfinished table encountered", {
35814
- toml: str,
35815
- ptr
35816
- });
35710
+ If a stale .codex/config.toml is still present, remove it by hand.
35711
+ `;
35712
+ function claimLegacyWorkspaceConfigMigration(workspaceDir) {
35713
+ const sentinel = migrationSentinelPath(workspaceDir);
35714
+ fs9.mkdirSync(path10.dirname(sentinel), { recursive: true });
35715
+ try {
35716
+ fs9.writeFileSync(sentinel, SENTINEL_BODY, { flag: "wx" });
35717
+ return "claimed";
35718
+ } catch (err) {
35719
+ if (err.code === "EEXIST")
35720
+ return "already-claimed";
35721
+ throw err;
35817
35722
  }
35818
- return [res, ptr];
35819
35723
  }
35820
- function parseArray(str, ptr, depth, integersAsBigInt) {
35821
- let res = [];
35822
- let c;
35823
- ptr++;
35824
- while ((c = str[ptr++]) !== "]" && c) {
35825
- if (c === ",") {
35826
- throw new TomlError("expected value, found comma", {
35827
- toml: str,
35828
- ptr: ptr - 1
35829
- });
35830
- } else if (c === "#")
35831
- ptr = skipComment(str, ptr);
35832
- else if (c !== " " && c !== " " && c !== "\n" && c !== "\r") {
35833
- let e = extractValue(str, ptr - 1, "]", depth - 1, integersAsBigInt);
35834
- res.push(e[0]);
35835
- ptr = e[1];
35836
- }
35837
- }
35838
- if (!c) {
35839
- throw new TomlError("unfinished array encountered", {
35840
- toml: str,
35841
- ptr
35842
- });
35843
- }
35844
- return [res, ptr];
35724
+ function runLegacyWorkspaceConfigMigration(workspaceDir, readProof, log2) {
35725
+ if (claimLegacyWorkspaceConfigMigration(workspaceDir) === "already-claimed")
35726
+ return;
35727
+ cleanupLegacyWorkspaceConfig(workspaceDir, readProof(), log2);
35845
35728
  }
35846
-
35847
- // ts/node_modules/.pnpm/smol-toml@1.6.1/node_modules/smol-toml/dist/parse.js
35848
- function peekTable(key, table, meta, type) {
35849
- let t = table;
35850
- let m = meta;
35851
- let k;
35852
- let hasOwn = false;
35853
- let state;
35854
- for (let i = 0; i < key.length; i++) {
35855
- if (i) {
35856
- t = hasOwn ? t[k] : t[k] = {};
35857
- m = (state = m[k]).c;
35858
- if (type === 0 && (state.t === 1 || state.t === 2)) {
35859
- return null;
35860
- }
35861
- if (state.t === 2) {
35862
- let l = t.length - 1;
35863
- t = t[l];
35864
- m = m[l].c;
35865
- }
35866
- }
35867
- k = key[i];
35868
- if ((hasOwn = Object.hasOwn(t, k)) && m[k]?.t === 0 && m[k]?.d) {
35869
- return null;
35870
- }
35871
- if (!hasOwn) {
35872
- if (k === "__proto__") {
35873
- Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
35874
- Object.defineProperty(m, k, { enumerable: true, configurable: true, writable: true });
35875
- }
35876
- m[k] = {
35877
- t: i < key.length - 1 && type === 2 ? 3 : type,
35878
- d: false,
35879
- i: 0,
35880
- c: {}
35881
- };
35729
+ function cleanupLegacyWorkspaceConfig(workspaceDir, proof, log2) {
35730
+ const configPath = legacyWorkspaceConfigPath(workspaceDir);
35731
+ let entry;
35732
+ try {
35733
+ entry = fs9.lstatSync(configPath);
35734
+ } catch (err) {
35735
+ if (err.code !== "ENOENT") {
35736
+ log2?.warn(`could not inspect legacy workspace codex config ${configPath}: ${String(err)}`);
35882
35737
  }
35738
+ return;
35883
35739
  }
35884
- state = m[k];
35885
- if (state.t !== type && !(type === 1 && state.t === 3)) {
35886
- return null;
35887
- }
35888
- if (type === 2) {
35889
- if (!state.d) {
35890
- state.d = true;
35891
- t[k] = [];
35740
+ const isPlainFile = entry.isFile();
35741
+ let rawContent = null;
35742
+ if (isPlainFile) {
35743
+ try {
35744
+ rawContent = fs9.readFileSync(configPath, "utf8");
35745
+ } catch (err) {
35746
+ log2?.warn(`could not read legacy workspace codex config ${configPath}: ${String(err)}`);
35892
35747
  }
35893
- t[k].push(t = {});
35894
- state.c[state.i++] = state = { t: 1, d: false, i: 0, c: {} };
35895
- }
35896
- if (state.d) {
35897
- return null;
35898
35748
  }
35899
- state.d = true;
35900
- if (type === 1) {
35901
- t = hasOwn ? t[k] : t[k] = {};
35902
- } else if (type === 0 && hasOwn) {
35903
- return null;
35904
- }
35905
- return [k, t, state.c];
35906
- }
35907
- function parse(toml, { maxDepth = 1e3, integersAsBigInt } = {}) {
35908
- let res = {};
35909
- let meta = {};
35910
- let tbl = res;
35911
- let m = meta;
35912
- for (let ptr = skipVoid(toml, 0); ptr < toml.length; ) {
35913
- if (toml[ptr] === "[") {
35914
- let isTableArray = toml[++ptr] === "[";
35915
- let k = parseKey(toml, ptr += +isTableArray, "]");
35916
- if (isTableArray) {
35917
- if (toml[k[1] - 1] !== "]") {
35918
- throw new TomlError("expected end of table declaration", {
35919
- toml,
35920
- ptr: k[1] - 1
35921
- });
35922
- }
35923
- k[1]++;
35924
- }
35925
- let p = peekTable(
35926
- k[0],
35927
- res,
35928
- meta,
35929
- isTableArray ? 2 : 1
35930
- /* Type.EXPLICIT */
35931
- );
35932
- if (!p) {
35933
- throw new TomlError("trying to redefine an already defined table or value", {
35934
- toml,
35935
- ptr
35936
- });
35937
- }
35938
- m = p[2];
35939
- tbl = p[1];
35940
- ptr = k[1];
35941
- } else {
35942
- let k = parseKey(toml, ptr);
35943
- let p = peekTable(
35944
- k[0],
35945
- tbl,
35946
- m,
35947
- 0
35948
- /* Type.DOTTED */
35949
- );
35950
- if (!p) {
35951
- throw new TomlError("trying to redefine an already defined table or value", {
35952
- toml,
35953
- ptr
35954
- });
35955
- }
35956
- let v = extractValue(toml, k[1], void 0, maxDepth, integersAsBigInt);
35957
- p[1][p[0]] = v[0];
35958
- ptr = v[1];
35959
- }
35960
- ptr = skipVoid(toml, ptr, true);
35961
- if (toml[ptr] && toml[ptr] !== "\n" && toml[ptr] !== "\r") {
35962
- throw new TomlError("each key-value declaration must be followed by an end-of-line", {
35963
- toml,
35964
- ptr
35965
- });
35749
+ const verdict = classifyLegacyWorkspaceConfig({
35750
+ entry: { isPlainFile, hardLinks: entry.nlink },
35751
+ rawContent,
35752
+ proof
35753
+ });
35754
+ if (verdict.action === "none")
35755
+ return;
35756
+ if (verdict.action === "remove") {
35757
+ try {
35758
+ fs9.unlinkSync(configPath);
35759
+ } catch (err) {
35760
+ if (err.code === "ENOENT")
35761
+ return;
35762
+ log2?.warn(`could not remove legacy workspace codex config ${configPath}: ${String(err)}`);
35966
35763
  }
35967
- ptr = skipVoid(toml, ptr);
35764
+ return;
35968
35765
  }
35969
- return res;
35766
+ log2?.warn(`leaving ${configPath} in place \u2014 ${PRESERVE_DETAIL[verdict.reason]}, so it is not provably the retired bridge's own artifact. Codex loads it for trusted workspaces IN ADDITION to the platform instructions; remove it manually if it is a stale artifact.`);
35970
35767
  }
35971
35768
 
35972
35769
  // ts/codex-agent/dist/workspace.js
@@ -35980,16 +35777,16 @@ function sleepSync(ms) {
35980
35777
  Atomics.wait(SLEEP_SIGNAL, 0, 0, ms);
35981
35778
  }
35982
35779
  function withConfigLock(codexHome, log2, fn) {
35983
- const queueDir = path10.join(codexHome, "config.toml.lock.d");
35780
+ const queueDir = path11.join(codexHome, "config.toml.lock.d");
35984
35781
  let ticketName = bakeryEnqueue(queueDir);
35985
- let ticketPath = ticketName ? path10.join(queueDir, ticketName) : "";
35782
+ let ticketPath = ticketName ? path11.join(queueDir, ticketName) : "";
35986
35783
  let acquired = false;
35987
35784
  let heldPath = "";
35988
35785
  const deadline = Date.now() + CONFIG_LOCK_TIMINGS.waitMs;
35989
35786
  while (ticketName && Date.now() < deadline) {
35990
35787
  let names;
35991
35788
  try {
35992
- names = fs9.readdirSync(queueDir).sort();
35789
+ names = fs10.readdirSync(queueDir).sort();
35993
35790
  } catch {
35994
35791
  break;
35995
35792
  }
@@ -35997,7 +35794,7 @@ function withConfigLock(codexHome, log2, fn) {
35997
35794
  ticketName = bakeryEnqueue(queueDir);
35998
35795
  if (!ticketName)
35999
35796
  break;
36000
- ticketPath = path10.join(queueDir, ticketName);
35797
+ ticketPath = path11.join(queueDir, ticketName);
36001
35798
  continue;
36002
35799
  }
36003
35800
  const now = Date.now();
@@ -36005,12 +35802,12 @@ function withConfigLock(codexHome, log2, fn) {
36005
35802
  let blocked = false;
36006
35803
  let head;
36007
35804
  for (const name of names) {
36008
- const entryPath = path10.join(queueDir, name);
35805
+ const entryPath = path11.join(queueDir, name);
36009
35806
  if (name.startsWith("held-")) {
36010
35807
  const enteredAt = Number.parseInt(name.slice(5, 20), 10);
36011
35808
  if (Number.isFinite(enteredAt) && now - enteredAt > CONFIG_LOCK_TIMINGS.staleMs) {
36012
35809
  try {
36013
- fs9.unlinkSync(entryPath);
35810
+ fs10.unlinkSync(entryPath);
36014
35811
  } catch {
36015
35812
  }
36016
35813
  rescan = true;
@@ -36021,14 +35818,14 @@ function withConfigLock(codexHome, log2, fn) {
36021
35818
  }
36022
35819
  let mtimeMs;
36023
35820
  try {
36024
- mtimeMs = fs9.statSync(entryPath).mtimeMs;
35821
+ mtimeMs = fs10.statSync(entryPath).mtimeMs;
36025
35822
  } catch {
36026
35823
  rescan = true;
36027
35824
  break;
36028
35825
  }
36029
35826
  if (now - mtimeMs > CONFIG_LOCK_TIMINGS.staleMs) {
36030
35827
  try {
36031
- fs9.unlinkSync(entryPath);
35828
+ fs10.unlinkSync(entryPath);
36032
35829
  } catch {
36033
35830
  }
36034
35831
  rescan = true;
@@ -36048,9 +35845,9 @@ function withConfigLock(codexHome, log2, fn) {
36048
35845
  if (!blocked && head === ticketName) {
36049
35846
  CONFIG_LOCK_TEST_HOOKS.beforeTicketEntry?.();
36050
35847
  const heldName = `held-${String(Date.now()).padStart(15, "0")}-${ticketName.slice(2)}`;
36051
- const candidateHeldPath = path10.join(queueDir, heldName);
35848
+ const candidateHeldPath = path11.join(queueDir, heldName);
36052
35849
  try {
36053
- fs9.renameSync(ticketPath, candidateHeldPath);
35850
+ fs10.renameSync(ticketPath, candidateHeldPath);
36054
35851
  } catch {
36055
35852
  continue;
36056
35853
  }
@@ -36060,7 +35857,7 @@ function withConfigLock(codexHome, log2, fn) {
36060
35857
  }
36061
35858
  try {
36062
35859
  const t = /* @__PURE__ */ new Date();
36063
- fs9.utimesSync(ticketPath, t, t);
35860
+ fs10.utimesSync(ticketPath, t, t);
36064
35861
  } catch {
36065
35862
  }
36066
35863
  sleepSync(CONFIG_LOCK_TIMINGS.retryMs);
@@ -36069,7 +35866,7 @@ function withConfigLock(codexHome, log2, fn) {
36069
35866
  log2?.warn(`could not acquire ${queueDir}; writing config.toml without lock`);
36070
35867
  if (ticketName) {
36071
35868
  try {
36072
- fs9.unlinkSync(ticketPath);
35869
+ fs10.unlinkSync(ticketPath);
36073
35870
  } catch {
36074
35871
  }
36075
35872
  }
@@ -36079,7 +35876,7 @@ function withConfigLock(codexHome, log2, fn) {
36079
35876
  } finally {
36080
35877
  if (acquired) {
36081
35878
  try {
36082
- fs9.unlinkSync(heldPath);
35879
+ fs10.unlinkSync(heldPath);
36083
35880
  } catch {
36084
35881
  }
36085
35882
  }
@@ -36089,17 +35886,17 @@ var CONFIG_LOCK_TEST_HOOKS = {};
36089
35886
  function bakeryEnqueue(queueDir) {
36090
35887
  for (let attempt = 0; attempt < 3; attempt++) {
36091
35888
  const token = `${process.pid}-${Math.random().toString(36).slice(2, 10)}`;
36092
- const markerPath = path10.join(queueDir, `choosing-${token}`);
35889
+ const markerPath = path11.join(queueDir, `choosing-${token}`);
36093
35890
  try {
36094
- fs9.mkdirSync(queueDir, { recursive: true });
35891
+ fs10.mkdirSync(queueDir, { recursive: true });
36095
35892
  CONFIG_LOCK_TEST_HOOKS.beforeChoosingMarker?.();
36096
- fs9.writeFileSync(markerPath, "", { flag: "wx" });
35893
+ fs10.writeFileSync(markerPath, "", { flag: "wx" });
36097
35894
  } catch {
36098
35895
  return null;
36099
35896
  }
36100
35897
  try {
36101
35898
  let maxSeq = 0;
36102
- for (const name of fs9.readdirSync(queueDir)) {
35899
+ for (const name of fs10.readdirSync(queueDir)) {
36103
35900
  if (!name.startsWith("t-"))
36104
35901
  continue;
36105
35902
  const seq = Number.parseInt(name.slice(2, 12), 10);
@@ -36107,18 +35904,18 @@ function bakeryEnqueue(queueDir) {
36107
35904
  maxSeq = seq;
36108
35905
  }
36109
35906
  const ticketName = `t-${String(maxSeq + 1).padStart(10, "0")}-${token}`;
36110
- const ticketPath = path10.join(queueDir, ticketName);
35907
+ const ticketPath = path11.join(queueDir, ticketName);
36111
35908
  CONFIG_LOCK_TEST_HOOKS.beforeTicketPublish?.();
36112
- fs9.renameSync(markerPath, ticketPath);
35909
+ fs10.renameSync(markerPath, ticketPath);
36113
35910
  try {
36114
35911
  const t = /* @__PURE__ */ new Date();
36115
- fs9.utimesSync(ticketPath, t, t);
35912
+ fs10.utimesSync(ticketPath, t, t);
36116
35913
  } catch {
36117
35914
  }
36118
35915
  return ticketName;
36119
35916
  } catch (err) {
36120
35917
  try {
36121
- fs9.unlinkSync(markerPath);
35918
+ fs10.unlinkSync(markerPath);
36122
35919
  } catch {
36123
35920
  }
36124
35921
  if (err.code === "ENOENT")
@@ -36132,21 +35929,21 @@ function writeConfigAtomic(filePath, content) {
36132
35929
  const realPath = resolveWriteTarget(filePath);
36133
35930
  let mode;
36134
35931
  try {
36135
- mode = fs9.statSync(realPath).mode & 511;
35932
+ mode = fs10.statSync(realPath).mode & 511;
36136
35933
  } catch {
36137
35934
  }
36138
35935
  const tmpPath = `${realPath}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
36139
35936
  let tmpCreated = false;
36140
35937
  try {
36141
- fs9.writeFileSync(tmpPath, content, { encoding: "utf8", mode: 384, flag: "wx" });
35938
+ fs10.writeFileSync(tmpPath, content, { encoding: "utf8", mode: 384, flag: "wx" });
36142
35939
  tmpCreated = true;
36143
35940
  if (mode !== void 0)
36144
- fs9.chmodSync(tmpPath, mode);
36145
- fs9.renameSync(tmpPath, realPath);
35941
+ fs10.chmodSync(tmpPath, mode);
35942
+ fs10.renameSync(tmpPath, realPath);
36146
35943
  } catch (err) {
36147
35944
  if (tmpCreated || err.code !== "EEXIST") {
36148
35945
  try {
36149
- fs9.unlinkSync(tmpPath);
35946
+ fs10.unlinkSync(tmpPath);
36150
35947
  } catch {
36151
35948
  }
36152
35949
  }
@@ -36155,13 +35952,13 @@ function writeConfigAtomic(filePath, content) {
36155
35952
  }
36156
35953
  function resolveWriteTarget(filePath) {
36157
35954
  try {
36158
- return fs9.realpathSync(filePath);
35955
+ return fs10.realpathSync(filePath);
36159
35956
  } catch (err) {
36160
35957
  if (err.code !== "ENOENT")
36161
35958
  throw err;
36162
35959
  }
36163
35960
  const seen = /* @__PURE__ */ new Set();
36164
- let p = path10.resolve(filePath);
35961
+ let p = path11.resolve(filePath);
36165
35962
  for (let depth = 0; depth < 40; depth++) {
36166
35963
  if (seen.has(p)) {
36167
35964
  throw new Error(`symlink cycle at ${p} while resolving ${filePath}`);
@@ -36169,87 +35966,17 @@ function resolveWriteTarget(filePath) {
36169
35966
  seen.add(p);
36170
35967
  let link;
36171
35968
  try {
36172
- link = fs9.readlinkSync(p);
35969
+ link = fs10.readlinkSync(p);
36173
35970
  } catch (err) {
36174
35971
  const code = err.code;
36175
35972
  if (code === "ENOENT" || code === "EINVAL")
36176
35973
  return p;
36177
35974
  throw err;
36178
35975
  }
36179
- p = path10.resolve(path10.dirname(p), link);
35976
+ p = path11.resolve(path11.dirname(p), link);
36180
35977
  }
36181
35978
  throw new Error(`symlink chain deeper than 40 while resolving ${filePath}`);
36182
35979
  }
36183
- function ensureWorkspaceTrusted(codexHome, workspaceDir, log2) {
36184
- withConfigLock(codexHome, log2, () => ensureWorkspaceTrustedLocked(codexHome, workspaceDir, log2));
36185
- }
36186
- function ensureWorkspaceTrustedLocked(codexHome, workspaceDir, log2) {
36187
- const configPath = path10.join(codexHome, "config.toml");
36188
- const normalizedPath = path10.resolve(workspaceDir);
36189
- try {
36190
- let content = "";
36191
- try {
36192
- content = fs9.readFileSync(configPath, "utf8");
36193
- } catch (err) {
36194
- if (err.code !== "ENOENT")
36195
- throw err;
36196
- }
36197
- let parsed;
36198
- if (content) {
36199
- try {
36200
- parsed = parse(content);
36201
- } catch {
36202
- log2?.warn(`Codex config.toml is not valid TOML; skipping trust write for ${normalizedPath}`);
36203
- return;
36204
- }
36205
- }
36206
- const projects = parsed?.projects;
36207
- const existingTrust = projects?.[normalizedPath]?.trust_level;
36208
- if (existingTrust === "trusted")
36209
- return;
36210
- if (existingTrust !== void 0) {
36211
- log2?.warn(`Codex config marks ${normalizedPath} as trust_level=${JSON.stringify(existingTrust)}; respecting the explicit decision \u2014 project-level developer_instructions will not load for this workspace`);
36212
- return;
36213
- }
36214
- const escapedPath = normalizedPath.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
36215
- const sectionHeader = `[projects."${escapedPath}"]`;
36216
- const trustLine = 'trust_level = "trusted"';
36217
- const headerIdx = content.indexOf(sectionHeader);
36218
- if (headerIdx !== -1) {
36219
- const headerLineEnd = content.indexOf("\n", headerIdx);
36220
- if (headerLineEnd === -1) {
36221
- content = `${content}
36222
- ${trustLine}
36223
- `;
36224
- } else {
36225
- const afterHeader = headerLineEnd + 1;
36226
- const sectionEnd = findSectionEnd(content, afterHeader);
36227
- const sectionBody = content.substring(afterHeader, sectionEnd);
36228
- const trustMatch = sectionBody.match(/^[ \t]*trust_level[ \t]*=.*$/m);
36229
- if (trustMatch) {
36230
- const matchStart = afterHeader + trustMatch.index;
36231
- const matchEnd = matchStart + trustMatch[0].length;
36232
- content = content.substring(0, matchStart) + trustLine + content.substring(matchEnd);
36233
- } else {
36234
- content = content.substring(0, afterHeader) + trustLine + "\n" + content.substring(afterHeader);
36235
- }
36236
- }
36237
- writeConfigAtomic(configPath, content);
36238
- } else if (projects?.[normalizedPath] !== void 0) {
36239
- log2?.warn(`Codex config.toml has non-canonical header for ${normalizedPath}; skipping trust write`);
36240
- } else if (projects && !content.includes("[projects.")) {
36241
- log2?.warn(`Codex config.toml uses inline table for projects; skipping trust write for ${normalizedPath}`);
36242
- } else {
36243
- fs9.mkdirSync(codexHome, { recursive: true });
36244
- fs9.appendFileSync(configPath, `
36245
- ${sectionHeader}
36246
- ${trustLine}
36247
- `, "utf8");
36248
- }
36249
- } catch (err) {
36250
- log2?.warn(`failed to write Codex project trust for ${normalizedPath}: ${String(err)}`);
36251
- }
36252
- }
36253
35980
  function isParallProxyMode(env = process.env) {
36254
35981
  const baseUrl = env.OPENAI_BASE_URL?.trim()?.replace(/\/+$/, "");
36255
35982
  const apiUrl = env.PRLL_API_URL?.trim()?.replace(/\/+$/, "");
@@ -36257,16 +35984,50 @@ function isParallProxyMode(env = process.env) {
36257
35984
  return false;
36258
35985
  return baseUrl === apiUrl || baseUrl.startsWith(`${apiUrl}/`);
36259
35986
  }
36260
- function ensureParallProvider(codexHome, apiUrl, log2) {
36261
- withConfigLock(codexHome, log2, () => ensureParallProviderLocked(codexHome, apiUrl));
35987
+ function tomlBasicString(value) {
35988
+ let out = "";
35989
+ for (const ch of value) {
35990
+ const code = ch.codePointAt(0) ?? 0;
35991
+ if (ch === "\\")
35992
+ out += "\\\\";
35993
+ else if (ch === '"')
35994
+ out += '\\"';
35995
+ else if (ch === "\b")
35996
+ out += "\\b";
35997
+ else if (ch === " ")
35998
+ out += "\\t";
35999
+ else if (ch === "\n")
36000
+ out += "\\n";
36001
+ else if (ch === "\f")
36002
+ out += "\\f";
36003
+ else if (ch === "\r")
36004
+ out += "\\r";
36005
+ else if (code < 32 || code === 127)
36006
+ out += `\\u${code.toString(16).padStart(4, "0")}`;
36007
+ else
36008
+ out += ch;
36009
+ }
36010
+ return `"${out}"`;
36262
36011
  }
36263
- function ensureParallProviderLocked(codexHome, apiUrl) {
36264
- const configPath = path10.join(codexHome, "config.toml");
36012
+ function parallAuthLines(opts) {
36013
+ const platform = opts?.platform ?? process.platform;
36014
+ if (platform !== "win32") {
36015
+ return ['command = "printenv"', 'args = ["OPENAI_API_KEY"]'];
36016
+ }
36017
+ const nodeBin = opts?.nodeBin ?? process.execPath;
36018
+ const script = "const v=process.env.OPENAI_API_KEY;if(v===undefined)process.exit(1);process.stdout.write(v)";
36019
+ return [`command = ${tomlBasicString(nodeBin)}`, `args = ["-e", ${tomlBasicString(script)}]`];
36020
+ }
36021
+ function ensureParallProvider(codexHome, apiUrl, log2, opts) {
36022
+ withConfigLock(codexHome, log2, () => ensureParallProviderLocked(codexHome, apiUrl, opts));
36023
+ }
36024
+ function ensureParallProviderLocked(codexHome, apiUrl, opts) {
36025
+ const configPath = path11.join(codexHome, "config.toml");
36265
36026
  const baseUrl = apiUrl.replace(/\/$/, "") + "/api/llm/v1";
36266
36027
  try {
36267
36028
  let content = "";
36268
36029
  try {
36269
- content = fs9.readFileSync(configPath, "utf8");
36030
+ content = fs10.readFileSync(configPath, "utf8");
36270
36031
  } catch (err) {
36271
36032
  if (err.code !== "ENOENT")
36272
36033
  throw err;
@@ -36276,14 +36037,13 @@ function ensureParallProviderLocked(codexHome, apiUrl) {
36276
36037
  const providerBlock = [
36277
36038
  sectionHeader,
36278
36039
  'name = "Parall Proxy"',
36279
- `base_url = "${baseUrl}"`,
36040
+ `base_url = ${tomlBasicString(baseUrl)}`,
36280
36041
  'wire_api = "responses"',
36281
36042
  "supports_websockets = false",
36282
36043
  "requires_openai_auth = false",
36283
36044
  "",
36284
36045
  authHeader,
36285
- 'command = "printenv"',
36286
- 'args = ["OPENAI_API_KEY"]'
36046
+ ...parallAuthLines(opts)
36287
36047
  ].join("\n");
36288
36048
  const headerIdx = content.indexOf(sectionHeader);
36289
36049
  if (headerIdx !== -1) {
@@ -36297,7 +36057,7 @@ function ensureParallProviderLocked(codexHome, apiUrl) {
36297
36057
  } else {
36298
36058
  content = content.trimEnd() + "\n\n" + providerBlock + "\n";
36299
36059
  }
36300
- fs9.mkdirSync(codexHome, { recursive: true });
36060
+ fs10.mkdirSync(codexHome, { recursive: true });
36301
36061
  writeConfigAtomic(configPath, content);
36302
36062
  } catch (err) {
36303
36063
  throw new Error(`failed to write Parall provider config: ${String(err)}`);
@@ -36307,6 +36067,9 @@ function findSectionEnd(content, fromIndex) {
36307
36067
  const nextHeader = content.indexOf("\n[", fromIndex);
36308
36068
  return nextHeader === -1 ? content.length : nextHeader;
36309
36069
  }
36070
+ function systemPromptCopyPath(workspaceDir) {
36071
+ return path11.join(workspaceDir, ".parall", "system-prompt.md");
36072
+ }
36310
36073
  function writeCodexSystemPrompt(workspaceDir, agentIdentity, capabilityFragments) {
36311
36074
  const parts = [
36312
36075
  buildIdentity(agentIdentity),
@@ -36319,21 +36082,27 @@ function writeCodexSystemPrompt(workspaceDir, agentIdentity, capabilityFragments
36319
36082
  }
36320
36083
  parts.push(buildSkillReferences(workspaceDir));
36321
36084
  const systemPrompt = parts.join("\n\n");
36322
- const parallDir = path10.join(workspaceDir, ".parall");
36323
- fs9.mkdirSync(parallDir, { recursive: true });
36324
- fs9.writeFileSync(path10.join(parallDir, "system-prompt.md"), systemPrompt, "utf8");
36325
- const codexConfigDir = path10.join(workspaceDir, ".codex");
36326
- fs9.mkdirSync(codexConfigDir, { recursive: true });
36327
- const toml = `developer_instructions = """
36328
- ${systemPrompt.replace(/\\/g, "\\\\").replace(/"""/g, '\\"""')}
36329
- """
36330
- `;
36331
- fs9.writeFileSync(path10.join(codexConfigDir, "config.toml"), toml, "utf8");
36085
+ fs10.mkdirSync(path11.join(workspaceDir, ".parall"), { recursive: true });
36086
+ fs10.writeFileSync(systemPromptCopyPath(workspaceDir), systemPrompt, "utf8");
36087
+ return systemPrompt;
36332
36088
  }
36333
36089
  function ensureCodexWorkspace(workspaceDir, log2, agentIdentity, capabilityFragments) {
36334
- fs9.mkdirSync(workspaceDir, { recursive: true });
36335
- writeCodexSystemPrompt(workspaceDir, agentIdentity, capabilityFragments);
36336
- writeSkillFiles(path10.join(workspaceDir, ".parall", "skills"));
36090
+ fs10.mkdirSync(workspaceDir, { recursive: true });
36091
+ runLegacyWorkspaceConfigMigration(workspaceDir, () => readAuthorshipProof(workspaceDir, log2), log2);
36092
+ const systemPrompt = writeCodexSystemPrompt(workspaceDir, agentIdentity, capabilityFragments);
36093
+ writeSkillFiles(path11.join(workspaceDir, ".parall", "skills"));
36094
+ return systemPrompt;
36095
+ }
36096
+ function readAuthorshipProof(workspaceDir, log2) {
36097
+ const proofPath = systemPromptCopyPath(workspaceDir);
36098
+ try {
36099
+ return { kind: "present", prompt: fs10.readFileSync(proofPath, "utf8") };
36100
+ } catch (err) {
36101
+ if (err.code === "ENOENT")
36102
+ return { kind: "absent" };
36103
+ log2?.warn(`could not read the authorship proof ${proofPath}: ${String(err)}`);
36104
+ return { kind: "unreadable" };
36105
+ }
36337
36106
  }
36338
36107
 
36339
36108
  // ts/codex-agent/dist/index.js
@@ -36381,7 +36150,6 @@ async function main() {
36381
36150
  const agentUserId = me.id;
36382
36151
  const agentLog = childLogger(activeLog, agentUserId);
36383
36152
  activeLog = agentLog;
36384
- ensureWorkspaceTrusted(config.codexHome, config.workspaceDir, agentLog);
36385
36153
  const useParallProvider = isParallProxyMode();
36386
36154
  if (useParallProvider) {
36387
36155
  ensureParallProvider(config.codexHome, config.apiUrl, agentLog);
@@ -36415,7 +36183,7 @@ async function main() {
36415
36183
  };
36416
36184
  const bootCapabilityFragments = applyChannelCapabilities();
36417
36185
  let lastCapabilityFragments = bootCapabilityFragments.join("\n\n");
36418
- ensureCodexWorkspace(config.workspaceDir, agentLog, agentIdentity, bootCapabilityFragments);
36186
+ const developerInstructions = ensureCodexWorkspace(config.workspaceDir, agentLog, agentIdentity, bootCapabilityFragments);
36419
36187
  const platformModelOverride = deriveModelIsPin(platformDefaults, me.agent_profile);
36420
36188
  const resolvedModel = resolveRuntimeModel(platformModelOverride, platformDefaults.model, config.model);
36421
36189
  const resolvedEffort = platformDefaults.thinkingEffort ?? config.reasoningEffort;
@@ -36436,7 +36204,8 @@ async function main() {
36436
36204
  contextFilePath: mainContextFilePath,
36437
36205
  contextDirPath: dispatchContextDirPath(config.stateDir),
36438
36206
  useParallProvider,
36439
- capabilityBinDir: capabilityBinDir(config.stateDir)
36207
+ capabilityBinDir: capabilityBinDir(config.stateDir),
36208
+ developerInstructions
36440
36209
  });
36441
36210
  const refreshPlatformConfig = async () => {
36442
36211
  const updated = await configMgr.fetch();
@@ -36457,7 +36226,8 @@ async function main() {
36457
36226
  const joinedFragments = fragments.join("\n\n");
36458
36227
  let promptWritten = true;
36459
36228
  try {
36460
- writeCodexSystemPrompt(config.workspaceDir, agentIdentity, fragments);
36229
+ const refreshedPrompt = writeCodexSystemPrompt(config.workspaceDir, agentIdentity, fragments);
36230
+ adapter.updateConfig({ developerInstructions: refreshedPrompt });
36461
36231
  } catch (err) {
36462
36232
  promptWritten = false;
36463
36233
  agentLog.warn(`system prompt refresh write failed (retrying next refresh): ${String(err)}`);
@@ -36527,42 +36297,3 @@ main().catch((err) => {
36527
36297
  activeLog.error(`fatal: ${String(err)}`);
36528
36298
  process.exitCode = 1;
36529
36299
  });
36530
- /*! Bundled license information:
36531
-
36532
- smol-toml/dist/error.js:
36533
- smol-toml/dist/util.js:
36534
- smol-toml/dist/date.js:
36535
- smol-toml/dist/primitive.js:
36536
- smol-toml/dist/extract.js:
36537
- smol-toml/dist/struct.js:
36538
- smol-toml/dist/parse.js:
36539
- smol-toml/dist/stringify.js:
36540
- smol-toml/dist/index.js:
36541
- (*!
36542
- * Copyright (c) Squirrel Chat et al., All rights reserved.
36543
- * SPDX-License-Identifier: BSD-3-Clause
36544
- *
36545
- * Redistribution and use in source and binary forms, with or without
36546
- * modification, are permitted provided that the following conditions are met:
36547
- *
36548
- * 1. Redistributions of source code must retain the above copyright notice, this
36549
- * list of conditions and the following disclaimer.
36550
- * 2. Redistributions in binary form must reproduce the above copyright notice,
36551
- * this list of conditions and the following disclaimer in the
36552
- * documentation and/or other materials provided with the distribution.
36553
- * 3. Neither the name of the copyright holder nor the names of its contributors
36554
- * may be used to endorse or promote products derived from this software without
36555
- * specific prior written permission.
36556
- *
36557
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
36558
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
36559
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
36560
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
36561
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
36562
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
36563
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
36564
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
36565
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36566
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36567
- *)
36568
- */