baxian 2.0.16 → 2.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/agent/branch.d.ts +5 -0
  2. package/dist/agent/branch.d.ts.map +1 -1
  3. package/dist/agent/branch.js +119 -29
  4. package/dist/agent/branch.js.map +1 -1
  5. package/dist/agent/manager.d.ts +4 -1
  6. package/dist/agent/manager.d.ts.map +1 -1
  7. package/dist/agent/manager.js +95 -152
  8. package/dist/agent/manager.js.map +1 -1
  9. package/dist/agent/prompt.d.ts +1 -0
  10. package/dist/agent/prompt.d.ts.map +1 -1
  11. package/dist/agent/prompt.js +38 -13
  12. package/dist/agent/prompt.js.map +1 -1
  13. package/dist/event/handlers.d.ts.map +1 -1
  14. package/dist/event/handlers.js +7 -4
  15. package/dist/event/handlers.js.map +1 -1
  16. package/dist/platform/driver-host.d.ts.map +1 -1
  17. package/dist/platform/driver-host.js +8 -6
  18. package/dist/platform/driver-host.js.map +1 -1
  19. package/dist/platform/feedback.d.ts +3 -7
  20. package/dist/platform/feedback.d.ts.map +1 -1
  21. package/dist/platform/feedback.js +11 -18
  22. package/dist/platform/feedback.js.map +1 -1
  23. package/dist/platform/github-driver.d.ts.map +1 -1
  24. package/dist/platform/github-driver.js +1 -3
  25. package/dist/platform/github-driver.js.map +1 -1
  26. package/dist/platform/markers.d.ts +1 -10
  27. package/dist/platform/markers.d.ts.map +1 -1
  28. package/dist/platform/markers.js +11 -30
  29. package/dist/platform/markers.js.map +1 -1
  30. package/dist/platform/verdict-engine.d.ts +3 -16
  31. package/dist/platform/verdict-engine.d.ts.map +1 -1
  32. package/dist/platform/verdict-engine.js +6 -18
  33. package/dist/platform/verdict-engine.js.map +1 -1
  34. package/dist/shared/types.d.ts +0 -1
  35. package/dist/shared/types.d.ts.map +1 -1
  36. package/dist/shared/types.js.map +1 -1
  37. package/dist/state/task-store.js +1 -1
  38. package/dist/state/task-store.js.map +1 -1
  39. package/package.json +1 -1
@@ -12,14 +12,14 @@ import { versionTimeOf } from '../platform/row-schema.js';
12
12
  import { checkOpenPrBinding, checkPrBinding } from '../platform/pr-binding.js';
13
13
  import { collectPendingFeedback, scanCommentSourcesOnce } from '../platform/feedback.js';
14
14
  import { prReviewCacheRevision } from '../platform/pr-conversation-cache.js';
15
- import { deadTokens, recheckPassProvenance } from '../platform/verdict-engine.js';
15
+ import { deadTokens, liveVerdictVeto } from '../platform/verdict-engine.js';
16
16
  import { platformBindingMismatch, taskNeedsPlatformBindingAudit, } from '../platform/startup.js';
17
17
  import { AGENT_STORE_NOOP } from '../state/agent-store.js';
18
18
  import { createRunner, LocalRunner, shellQuote, resolveAgentHost, workdirHostGroupKey, } from './runner.js';
19
19
  import { isTransientNetworkFailure } from './net-exec.js';
20
20
  import { imageFilename, agentHostPath, writeImageToHost } from './image-input.js';
21
21
  import { TmuxManager, ReplNotReadyError, detectStartupDialog, detectRuntimeMenu, runtimeBusyCheck, hasRuntimeReadyView, hasReplProcTitle, isShellProcTitle, hasOscTitleWorking, hasOscTitleIdle, screenAllowsTitleIdle, PaneGoneError, } from './tmux.js';
22
- import { BranchManager, DirtyWorkdirError, ReviewHeadMismatchError, isAutoDeletableTaskBranch } from './branch.js';
22
+ import { BranchManager, DirtyWorkdirError, ReviewHeadMismatchError, isAutoDeletableTaskBranch, } from './branch.js';
23
23
  import { RepoStore, createRepoStoreCache } from './repo-store.js';
24
24
  import { PhaseSignalWatcher, } from './phase-signal-watcher.js';
25
25
  import { buildPromptInline, buildGreetingPrompt, PromptSizeError, MAX_PROMPT_BYTES_ROUTE_LIMIT, } from './prompt.js';
@@ -45,7 +45,6 @@ function samePassProvenance(left, right) {
45
45
  && right !== undefined
46
46
  && left.sourceKey === right.sourceKey
47
47
  && left.id === right.id
48
- && left.bodyDigest === right.bodyDigest
49
48
  && left.token === right.token
50
49
  && left.failToken === right.failToken
51
50
  && left.anchorSha === right.anchorSha;
@@ -150,6 +149,13 @@ const DEFAULT_NEED_INPUT_RETRY_INTERVAL_MS = 5_000;
150
149
  const DEFAULT_DISPATCH_SETTLE_TIMEOUT_MS = 3_000;
151
150
  const DELETE_CLEANUP_TIMEOUT_MS = 15_000;
152
151
  const PLATFORM_HEAD_SHA_RE = new RegExp(`^${SHA_HEX_SOURCE}$`);
152
+ function branchCleanupIdentity(task) {
153
+ return {
154
+ taskId: task.id,
155
+ taskBranch: task.branch,
156
+ branchCreatedByBaxian: task.branchCreatedByBaxian,
157
+ };
158
+ }
153
159
  function createSignalTokenExcluding(...excluded) {
154
160
  let token = createSignalToken();
155
161
  while (excluded.includes(token))
@@ -1701,9 +1707,7 @@ export class AgentManager {
1701
1707
  }
1702
1708
  const pendingAt = new Date().toISOString();
1703
1709
  if (boundTask && cfg.role === 'dev') {
1704
- boundTask.branchCleanupPending = { agentId, reason, updatedAt: pendingAt };
1705
- boundTask.updatedAt = pendingAt;
1706
- await this.taskStore.set(boundTask);
1710
+ await this.persistBranchCleanupOutcome(boundTask, agentId, { status: 'pending', reason });
1707
1711
  }
1708
1712
  let wrote = false;
1709
1713
  await this.agentStore.update(agentId, (latest2) => {
@@ -1769,59 +1773,8 @@ export class AgentManager {
1769
1773
  }
1770
1774
  if (cfg.role === 'dev' && boundTask) {
1771
1775
  const branches = new BranchManager(runner);
1772
- const cleanup = await branches.cleanupTaskBranch(releaseWorkdir, {
1773
- taskId: boundTask.id,
1774
- taskBranch: boundTask.branch,
1775
- branchCreatedByBaxian: boundTask.branchCreatedByBaxian,
1776
- }, () => this.assertTaskGeneration(agentId, expectedTaskId, lockToken, releaseWorkdir).then(() => undefined));
1777
- let cleanupStateChanged = false;
1778
- if (cleanup.status === 'pending') {
1779
- if (boundTask.branchCleanupPending?.agentId !== agentId
1780
- || boundTask.branchCleanupPending.reason !== cleanup.reason
1781
- || boundTask.branchCleanupSkipped !== undefined) {
1782
- boundTask.branchCleanupPending = {
1783
- agentId,
1784
- reason: cleanup.reason,
1785
- updatedAt: new Date().toISOString(),
1786
- };
1787
- delete boundTask.branchCleanupSkipped;
1788
- cleanupStateChanged = true;
1789
- }
1790
- }
1791
- else if (cleanup.status === 'skipped') {
1792
- if (boundTask.branchCleanupSkipped?.agentId !== agentId
1793
- || boundTask.branchCleanupSkipped.reason !== cleanup.reason
1794
- || boundTask.branchCleanupPending !== undefined) {
1795
- boundTask.branchCleanupSkipped = {
1796
- agentId,
1797
- reason: cleanup.reason,
1798
- updatedAt: new Date().toISOString(),
1799
- };
1800
- delete boundTask.branchCleanupPending;
1801
- cleanupStateChanged = true;
1802
- }
1803
- }
1804
- else {
1805
- if (boundTask.branchCleanupPending?.agentId === agentId) {
1806
- delete boundTask.branchCleanupPending;
1807
- cleanupStateChanged = true;
1808
- }
1809
- if (boundTask.branchCleanupSkipped?.agentId === agentId) {
1810
- delete boundTask.branchCleanupSkipped;
1811
- cleanupStateChanged = true;
1812
- }
1813
- if (cleanup.remoteTipSha) {
1814
- boundTask.branchLocalCleaned = {
1815
- remoteTipSha: cleanup.remoteTipSha,
1816
- updatedAt: new Date().toISOString(),
1817
- };
1818
- cleanupStateChanged = true;
1819
- }
1820
- }
1821
- if (cleanupStateChanged) {
1822
- boundTask.updatedAt = new Date().toISOString();
1823
- await this.taskStore.set(boundTask);
1824
- }
1776
+ const cleanup = await branches.cleanupTaskBranch(releaseWorkdir, this.cleanupIdentityFor(boundTask), () => this.assertTaskGeneration(agentId, expectedTaskId, lockToken, releaseWorkdir).then(() => undefined));
1777
+ await this.persistBranchCleanupOutcome(boundTask, agentId, cleanup);
1825
1778
  if (cleanup.status !== 'deleted')
1826
1779
  await branches.parkOnDefaultDetached(releaseWorkdir);
1827
1780
  }
@@ -1891,6 +1844,64 @@ export class AgentManager {
1891
1844
  console.warn(`[AgentManager] clearing branchLocalCleaned(${taskId}) failed:`, err);
1892
1845
  });
1893
1846
  }
1847
+ cleanupIdentityFor(task) {
1848
+ const identity = branchCleanupIdentity(task);
1849
+ if (task.status === 'merged' && task.prNumber !== undefined) {
1850
+ identity.resolveMergedHeadSha = () => this.platformFetchPrHeadSha(task.id);
1851
+ }
1852
+ return identity;
1853
+ }
1854
+ async persistBranchCleanupOutcome(task, agentId, cleanup, guard) {
1855
+ await this.withTaskLock(async () => {
1856
+ const latest = await this.taskStore.get(task.id);
1857
+ if (!latest || latest.branch !== task.branch || (guard !== undefined && !guard(latest)))
1858
+ return;
1859
+ const effective = cleanup;
1860
+ const now = new Date().toISOString();
1861
+ let changed = false;
1862
+ if (effective.status === 'pending') {
1863
+ if (latest.branchCleanupPending?.agentId !== agentId
1864
+ || latest.branchCleanupPending.reason !== effective.reason
1865
+ || latest.branchCleanupSkipped !== undefined) {
1866
+ latest.branchCleanupPending = { agentId, reason: effective.reason, updatedAt: now };
1867
+ delete latest.branchCleanupSkipped;
1868
+ changed = true;
1869
+ }
1870
+ if (effective.remoteTipSha !== undefined
1871
+ && latest.branchLocalCleaned?.remoteTipSha !== effective.remoteTipSha) {
1872
+ latest.branchLocalCleaned = { remoteTipSha: effective.remoteTipSha, updatedAt: now };
1873
+ changed = true;
1874
+ }
1875
+ }
1876
+ else if (effective.status === 'skipped') {
1877
+ if (latest.branchCleanupSkipped?.agentId !== agentId
1878
+ || latest.branchCleanupSkipped.reason !== effective.reason
1879
+ || latest.branchCleanupPending !== undefined) {
1880
+ latest.branchCleanupSkipped = { agentId, reason: effective.reason, updatedAt: now };
1881
+ delete latest.branchCleanupPending;
1882
+ changed = true;
1883
+ }
1884
+ }
1885
+ else {
1886
+ if (latest.branchCleanupPending !== undefined) {
1887
+ delete latest.branchCleanupPending;
1888
+ changed = true;
1889
+ }
1890
+ if (latest.branchCleanupSkipped !== undefined) {
1891
+ delete latest.branchCleanupSkipped;
1892
+ changed = true;
1893
+ }
1894
+ if (effective.remoteTipSha) {
1895
+ latest.branchLocalCleaned = { remoteTipSha: effective.remoteTipSha, updatedAt: now };
1896
+ changed = true;
1897
+ }
1898
+ }
1899
+ if (!changed)
1900
+ return;
1901
+ latest.updatedAt = now;
1902
+ await this.taskStore.set(latest);
1903
+ });
1904
+ }
1894
1905
  async releaseAgentIfBound(agentId, expectedTaskId, opts) {
1895
1906
  const state = await this.agentStore.get(agentId);
1896
1907
  if (state?.taskId !== expectedTaskId)
@@ -1911,50 +1922,7 @@ export class AgentManager {
1911
1922
  && task.branchCleanupSkipped === undefined)
1912
1923
  .map(task => [task.branch, task]));
1913
1924
  const owner = 'maintenance:branch-reconcile';
1914
- const persistCleanup = async (task, agentId, cleanup) => {
1915
- await this.withTaskLock(async () => {
1916
- const latest = await this.taskStore.get(task.id);
1917
- if (!latest
1918
- || latest.branch !== task.branch
1919
- || latest.branchCreatedByBaxian !== true
1920
- || !TERMINAL_STATUSES.includes(latest.status))
1921
- return;
1922
- const now = new Date().toISOString();
1923
- let changed = false;
1924
- if (cleanup.status === 'pending') {
1925
- if (latest.branchCleanupPending?.agentId !== agentId
1926
- || latest.branchCleanupPending.reason !== cleanup.reason
1927
- || latest.branchCleanupSkipped !== undefined) {
1928
- latest.branchCleanupPending = { agentId, reason: cleanup.reason, updatedAt: now };
1929
- delete latest.branchCleanupSkipped;
1930
- changed = true;
1931
- }
1932
- }
1933
- else if (cleanup.status === 'skipped') {
1934
- if (latest.branchCleanupSkipped?.agentId !== agentId
1935
- || latest.branchCleanupSkipped.reason !== cleanup.reason
1936
- || latest.branchCleanupPending !== undefined) {
1937
- latest.branchCleanupSkipped = { agentId, reason: cleanup.reason, updatedAt: now };
1938
- delete latest.branchCleanupPending;
1939
- changed = true;
1940
- }
1941
- }
1942
- else {
1943
- if (latest.branchCleanupPending !== undefined) {
1944
- delete latest.branchCleanupPending;
1945
- changed = true;
1946
- }
1947
- if (latest.branchCleanupSkipped !== undefined) {
1948
- delete latest.branchCleanupSkipped;
1949
- changed = true;
1950
- }
1951
- }
1952
- if (!changed)
1953
- return;
1954
- latest.updatedAt = now;
1955
- await this.taskStore.set(latest);
1956
- });
1957
- };
1925
+ const persistCleanup = (task, agentId, cleanup) => this.persistBranchCleanupOutcome(task, agentId, cleanup, latest => latest.branchCreatedByBaxian === true && TERMINAL_STATUSES.includes(latest.status));
1958
1926
  for (const cfg of this.agentIndex.values()) {
1959
1927
  if (cfg.role !== 'dev')
1960
1928
  continue;
@@ -1980,7 +1948,9 @@ export class AgentManager {
1980
1948
  `${err instanceof Error ? err.message : String(err)}`);
1981
1949
  continue;
1982
1950
  }
1983
- if (!refs.some(ref => candidateByBranch.has(ref.slice('refs/heads/'.length))))
1951
+ const isTarget = (task, present) => present.has(task.branch) || task.branchCleanupPending !== undefined;
1952
+ let present = new Set(refs.map(ref => ref.slice('refs/heads/'.length)));
1953
+ if (!candidateTasks.some(task => isTarget(task, present)))
1984
1954
  continue;
1985
1955
  if (this.isDeletionInFlight(cfg.id))
1986
1956
  continue;
@@ -1988,6 +1958,7 @@ export class AgentManager {
1988
1958
  if (!token)
1989
1959
  continue;
1990
1960
  const cleanupResults = [];
1961
+ let targets = [];
1991
1962
  try {
1992
1963
  if (!this.deletionGateOpen(cfg.id, genAtEntry))
1993
1964
  continue;
@@ -1997,8 +1968,9 @@ export class AgentManager {
1997
1968
  if (!(await this.lockManager.isOwner(cfg.id, owner, token)))
1998
1969
  continue;
1999
1970
  refs = await branches.listLocalTaskRefs(workdir);
2000
- refs = refs.filter(ref => candidateByBranch.has(ref.slice('refs/heads/'.length)));
2001
- if (refs.length === 0)
1971
+ present = new Set(refs.map(ref => ref.slice('refs/heads/'.length)));
1972
+ targets = [...candidateByBranch.values()].filter(task => isTarget(task, present));
1973
+ if (targets.length === 0)
2002
1974
  continue;
2003
1975
  const tmux = new TmuxManager(runner);
2004
1976
  if (await tmux.hasSession(cfg.id)) {
@@ -2007,16 +1979,8 @@ export class AgentManager {
2007
1979
  }
2008
1980
  if (!(await this.lockManager.isOwner(cfg.id, owner, token)))
2009
1981
  continue;
2010
- for (const actualRef of refs) {
2011
- const branch = actualRef.slice('refs/heads/'.length);
2012
- const task = candidateByBranch.get(branch);
2013
- if (!task)
2014
- continue;
2015
- const cleanup = await branches.cleanupTaskBranch(workdir, {
2016
- taskId: task.id,
2017
- taskBranch: task.branch,
2018
- branchCreatedByBaxian: task.branchCreatedByBaxian,
2019
- }, () => this.assertTaskLockOwner(cfg.id, owner, token));
1982
+ for (const task of targets) {
1983
+ const cleanup = await branches.cleanupTaskBranch(workdir, this.cleanupIdentityFor(task), () => this.assertTaskLockOwner(cfg.id, owner, token));
2020
1984
  cleanupResults.push({ task, cleanup });
2021
1985
  }
2022
1986
  }
@@ -2026,10 +1990,7 @@ export class AgentManager {
2026
1990
  await this.markAwaitingHuman(cfg.id, 'dirty-workdir', message, { expectedTaskId: null });
2027
1991
  }
2028
1992
  else if (err instanceof ReplNotReadyError || message.includes('stayed busy past')) {
2029
- for (const actualRef of refs) {
2030
- const task = candidateByBranch.get(actualRef.slice('refs/heads/'.length));
2031
- if (!task)
2032
- continue;
1993
+ for (const task of targets) {
2033
1994
  cleanupResults.push({
2034
1995
  task,
2035
1996
  cleanup: {
@@ -5457,25 +5418,16 @@ export class AgentManager {
5457
5418
  const { task, driver } = await this.platformTaskAndDriver(taskId);
5458
5419
  if (task.prNumber === undefined)
5459
5420
  return { kind: 'provenance-invalid', reason: 'no bound PR' };
5460
- const provenance = task.passProvenance;
5461
- if (!provenance)
5421
+ if (!task.passProvenance)
5462
5422
  return { kind: 'provenance-invalid', reason: 'no accepted pass provenance' };
5463
5423
  const scans = await this.platformScanComments(driver, task.prNumber);
5464
- const recheck = recheckPassProvenance({
5465
- token: provenance.token,
5466
- failToken: provenance.failToken,
5467
- anchorSha: provenance.anchorSha,
5468
- carrier: { sourceKey: provenance.sourceKey, id: provenance.id, bodyDigest: provenance.bodyDigest },
5469
- }, scans);
5470
- if (!recheck.ok) {
5471
- return recheck.reason === 'source-scan-incomplete'
5472
- ? { kind: 'retryable', reason: recheck.reason }
5473
- : { kind: 'provenance-invalid', reason: `provenance ${recheck.reason}` };
5474
- }
5475
5424
  const pending = collectPendingFeedback(scans);
5476
5425
  if (!pending.allSourcesOk)
5477
5426
  return { kind: 'retryable', reason: 'source scan incomplete' };
5478
- return { kind: 'valid', pendingCount: pending.pending.size };
5427
+ const veto = liveVerdictVeto(task.passProvenance, scans);
5428
+ if (veto)
5429
+ return { kind: 'provenance-invalid', reason: `provenance ${veto}` };
5430
+ return { kind: 'valid', pending: pending.pending };
5479
5431
  }
5480
5432
  async platformPendingFeedback(taskId) {
5481
5433
  const { task, driver } = await this.platformTaskAndDriver(taskId);
@@ -5508,24 +5460,14 @@ export class AgentManager {
5508
5460
  if (provenance.anchorSha.toLowerCase() !== opts.expectedHeadSha.toLowerCase()) {
5509
5461
  throw new PlatformMergeRecheckError('provenance-invalid', `pass provenance anchors ${provenance.anchorSha}, not the merge head ${opts.expectedHeadSha}`);
5510
5462
  }
5511
- const scans = await this.platformScanComments(driver, prNumber);
5512
- const recheck = recheckPassProvenance({
5513
- token: provenance.token,
5514
- failToken: provenance.failToken,
5515
- anchorSha: provenance.anchorSha,
5516
- carrier: { sourceKey: provenance.sourceKey, id: provenance.id, bodyDigest: provenance.bodyDigest },
5517
- }, scans);
5518
- if (!recheck.ok) {
5519
- if (recheck.reason === 'source-scan-incomplete') {
5520
- throw new Error('merge blocked: provenance source-scan-incomplete');
5521
- }
5522
- throw new PlatformMergeRecheckError('provenance-invalid', `provenance ${recheck.reason}`);
5523
- }
5524
- const pending = collectPendingFeedback(scans);
5525
- if (!pending.allSourcesOk)
5463
+ const verified = await this.platformVerifyAcceptedPass(taskId);
5464
+ if (verified.kind === 'retryable')
5526
5465
  throw new Error('merge blocked: pending-feedback scan incomplete');
5527
- if (pending.pending.size > 0) {
5528
- throw new PlatformMergeRecheckError('pending-feedback', `${pending.pending.size} pending feedback revision(s) without ack`);
5466
+ if (verified.kind === 'provenance-invalid') {
5467
+ throw new PlatformMergeRecheckError('provenance-invalid', verified.reason);
5468
+ }
5469
+ if (verified.pending.size > 0) {
5470
+ throw new PlatformMergeRecheckError('pending-feedback', `${verified.pending.size} pending feedback comment(s) without ack`);
5529
5471
  }
5530
5472
  }
5531
5473
  const fresh = await this.taskStore.get(taskId);
@@ -7329,6 +7271,7 @@ export class AgentManager {
7329
7271
  ...(platform ? { platform } : {}),
7330
7272
  ...(signalToken ? { signalToken } : {}),
7331
7273
  ...(imagePaths.length ? { imagePaths } : {}),
7274
+ ...(opts.pendingFeedback?.length ? { pendingFeedback: opts.pendingFeedback } : {}),
7332
7275
  });
7333
7276
  }
7334
7277
  catch (err) {
@@ -8417,7 +8360,7 @@ export class AgentManager {
8417
8360
  if (accepted.kind !== 'valid') {
8418
8361
  throw new ApiError(409, `Cannot restore task ${task.id}: ${accepted.reason}`);
8419
8362
  }
8420
- if (accepted.pendingCount > 0) {
8363
+ if (accepted.pending.size > 0) {
8421
8364
  throw new ApiError(409, `Cannot restore task ${task.id}: the accepted pass has pending feedback`);
8422
8365
  }
8423
8366
  const reviewedPass = task.passProvenance;