overlord-cli 0.2606090710.0 → 0.2606091312.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.
@@ -703,6 +703,19 @@ function resolveGitRepoRoot(workspacePath) {
703
703
  }
704
704
  }
705
705
 
706
+ // The server caps snapshot.diffStat at 20,000 chars (snapshotContextSchema in
707
+ // lib/overlord/validation.ts). A `git diff --stat` over a large change set can
708
+ // blow past that and make the update POST fail validation. diffstat is purely
709
+ // informational provenance, so truncate it to a safe size before sending.
710
+ const MAX_DIFFSTAT_CHARS = 16_000;
711
+
712
+ function clampDiffStat(diffStat) {
713
+ if (typeof diffStat !== 'string' || diffStat.length <= MAX_DIFFSTAT_CHARS) {
714
+ return diffStat || null;
715
+ }
716
+ return `${diffStat.slice(0, MAX_DIFFSTAT_CHARS)}\n… (diffstat truncated; ${diffStat.length} chars total)`;
717
+ }
718
+
706
719
  /**
707
720
  * Snapshot the working tree to a hidden ref refs/overlord/checkpoints/<objectiveId>.
708
721
  * Captures tracked + untracked + staged + unstaged in one commit-tree object.
@@ -727,9 +740,11 @@ function createLocalCheckpoint({ flags, kind = 'objective', objectiveId, snapsho
727
740
  const gitCommitId = runLocalCommand('git', ['-C', repoRoot, 'rev-parse', 'HEAD'], {
728
741
  cwd: repoRoot
729
742
  }).trim();
730
- const diffStat = runLocalCommand('git', ['-C', repoRoot, 'diff', '--stat', 'HEAD'], {
731
- cwd: repoRoot
732
- }).trim();
743
+ const diffStat = clampDiffStat(
744
+ runLocalCommand('git', ['-C', repoRoot, 'diff', '--stat', 'HEAD'], {
745
+ cwd: repoRoot
746
+ }).trim()
747
+ );
733
748
  return {
734
749
  checkpoint: { diffStat: diffStat || null, kind },
735
750
  snapshot: {
@@ -761,9 +776,11 @@ function createLocalCheckpoint({ flags, kind = 'objective', objectiveId, snapsho
761
776
  if (existingSha) {
762
777
  let diffStat = '';
763
778
  try {
764
- diffStat = runLocalCommand('git', ['-C', repoRoot, 'diff', '--stat', `${existingSha}^!`], {
765
- cwd: repoRoot
766
- }).trim();
779
+ diffStat = clampDiffStat(
780
+ runLocalCommand('git', ['-C', repoRoot, 'diff', '--stat', `${existingSha}^!`], {
781
+ cwd: repoRoot
782
+ }).trim()
783
+ );
767
784
  } catch {
768
785
  /* ignore */
769
786
  }
@@ -812,9 +829,11 @@ function createLocalCheckpoint({ flags, kind = 'objective', objectiveId, snapsho
812
829
  });
813
830
  let diffStat = '';
814
831
  try {
815
- diffStat = runLocalCommand('git', ['-C', repoRoot, 'diff', '--stat', `${commit}^!`], {
816
- cwd: repoRoot
817
- }).trim();
832
+ diffStat = clampDiffStat(
833
+ runLocalCommand('git', ['-C', repoRoot, 'diff', '--stat', `${commit}^!`], {
834
+ cwd: repoRoot
835
+ }).trim()
836
+ );
818
837
  } catch {
819
838
  /* ignore */
820
839
  }
@@ -947,22 +966,31 @@ async function createAndRecordPendingCheckpoints({
947
966
  continue;
948
967
  }
949
968
 
950
- await apiPost(
951
- platformUrl,
952
- bearerToken,
953
- localSecret,
954
- organizationId,
955
- '/api/protocol/update',
956
- {
957
- sessionKey,
958
- ticketId,
959
- summary: `Created local git checkpoint for objective ${objectiveId}.`,
960
- phase: 'execute',
961
- snapshot: checkpointResult.snapshot
962
- },
963
- timeoutMs
964
- );
965
- recorded.push(objectiveId);
969
+ try {
970
+ await apiPost(
971
+ platformUrl,
972
+ bearerToken,
973
+ localSecret,
974
+ organizationId,
975
+ '/api/protocol/update',
976
+ {
977
+ sessionKey,
978
+ ticketId,
979
+ summary: `Created local git checkpoint for objective ${objectiveId}.`,
980
+ phase: 'execute',
981
+ snapshot: checkpointResult.snapshot
982
+ },
983
+ timeoutMs
984
+ );
985
+ recorded.push(objectiveId);
986
+ } catch (error) {
987
+ // A failed checkpoint update must not block the remaining objectives or
988
+ // the attach itself. The session key is already persisted by the caller.
989
+ const message = error instanceof Error ? error.message : String(error);
990
+ process.stderr.write(
991
+ `[protocol] Failed to record checkpoint for objective ${objectiveId}: ${message}\n`
992
+ );
993
+ }
966
994
  }
967
995
  return recorded;
968
996
  }
@@ -1275,25 +1303,45 @@ async function protocolAttach(args) {
1275
1303
  timeoutMs
1276
1304
  );
1277
1305
 
1278
- const recordedCheckpointObjectiveIds = await createAndRecordPendingCheckpoints({
1279
- attachData: data,
1280
- bearerToken,
1281
- flags,
1282
- localSecret,
1283
- organizationId,
1284
- platformUrl,
1285
- ticketId,
1286
- timeoutMs
1287
- });
1306
+ // Persist the new session key BEFORE any best-effort follow-up work. The
1307
+ // checkpoint step below posts a separate /update call whose payload can be
1308
+ // rejected by the server (e.g. a large diffstat exceeding the size cap). If
1309
+ // that throws, we must not lose the freshly minted session key — otherwise a
1310
+ // stale key from a previous ticket lingers in the persisted session file and
1311
+ // every later `ovld protocol` call in this directory resolves the wrong
1312
+ // session. Persisting first guarantees the new key survives a checkpoint
1313
+ // failure. See ticket 1:1430.
1314
+ const sessionKey = data.session?.sessionKey;
1315
+ if (sessionKey) {
1316
+ persistSession(sessionKey, ticketId);
1317
+ }
1318
+
1319
+ // Best-effort: record local git checkpoints for any pending objectives. This
1320
+ // must never abort the attach or prevent the session key from being persisted
1321
+ // and emitted, so swallow any failure (size limits, network, no git repo).
1322
+ let recordedCheckpointObjectiveIds = [];
1323
+ try {
1324
+ recordedCheckpointObjectiveIds = await createAndRecordPendingCheckpoints({
1325
+ attachData: data,
1326
+ bearerToken,
1327
+ flags,
1328
+ localSecret,
1329
+ organizationId,
1330
+ platformUrl,
1331
+ ticketId,
1332
+ timeoutMs
1333
+ });
1334
+ } catch (error) {
1335
+ const message = error instanceof Error ? error.message : String(error);
1336
+ process.stderr.write(`[protocol] Checkpoint recording skipped (attach still succeeded): ${message}\n`);
1337
+ }
1288
1338
  if (recordedCheckpointObjectiveIds.length > 0) {
1289
1339
  data.recordedCheckpointObjectiveIds = recordedCheckpointObjectiveIds;
1290
1340
  }
1291
1341
 
1292
- const sessionKey = data.session?.sessionKey;
1293
1342
  console.log(JSON.stringify(data, null, 2));
1294
1343
 
1295
1344
  if (sessionKey) {
1296
- persistSession(sessionKey, ticketId);
1297
1345
  // Emit a machine-readable line for easy shell capture:
1298
1346
  // SESSION_KEY=$(ovld protocol attach --ticket-id ... | grep ^SESSION_KEY= | cut -d= -f2)
1299
1347
  process.stderr.write(`\nSESSION_KEY=${sessionKey}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord-cli",
3
- "version": "0.2606090710.0",
3
+ "version": "0.2606091312.0",
4
4
  "description": "Overlord CLI — launch AI agents on tickets from anywhere",
5
5
  "homepage": "https://www.ovld.ai/docs/",
6
6
  "type": "module",