baxian 2.0.6 → 2.0.7

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 (182) hide show
  1. package/README.md +2 -3
  2. package/README.zh-CN.md +2 -3
  3. package/dist/agent/bootstrap-poller.d.ts +2 -2
  4. package/dist/agent/bootstrap-poller.d.ts.map +1 -1
  5. package/dist/agent/bootstrap-poller.js.map +1 -1
  6. package/dist/agent/bootstrap.d.ts +1 -1
  7. package/dist/agent/bootstrap.d.ts.map +1 -1
  8. package/dist/agent/bootstrap.js +24 -48
  9. package/dist/agent/bootstrap.js.map +1 -1
  10. package/dist/agent/manager.d.ts +9 -35
  11. package/dist/agent/manager.d.ts.map +1 -1
  12. package/dist/agent/manager.js +86 -335
  13. package/dist/agent/manager.js.map +1 -1
  14. package/dist/agent/net-exec.d.ts +0 -1
  15. package/dist/agent/net-exec.d.ts.map +1 -1
  16. package/dist/agent/net-exec.js +0 -1
  17. package/dist/agent/net-exec.js.map +1 -1
  18. package/dist/agent/pane-streamer-manager.d.ts.map +1 -1
  19. package/dist/agent/pane-streamer-manager.js +1 -3
  20. package/dist/agent/pane-streamer-manager.js.map +1 -1
  21. package/dist/agent/preflight.d.ts +1 -20
  22. package/dist/agent/preflight.d.ts.map +1 -1
  23. package/dist/agent/preflight.js +39 -122
  24. package/dist/agent/preflight.js.map +1 -1
  25. package/dist/agent/prompt.d.ts +6 -16
  26. package/dist/agent/prompt.d.ts.map +1 -1
  27. package/dist/agent/prompt.js +128 -79
  28. package/dist/agent/prompt.js.map +1 -1
  29. package/dist/agent/repo-store.d.ts +1 -5
  30. package/dist/agent/repo-store.d.ts.map +1 -1
  31. package/dist/agent/repo-store.js +8 -33
  32. package/dist/agent/repo-store.js.map +1 -1
  33. package/dist/agent/runner.d.ts +1 -1
  34. package/dist/agent/runner.d.ts.map +1 -1
  35. package/dist/agent/runner.js +1 -3
  36. package/dist/agent/runner.js.map +1 -1
  37. package/dist/api/config.d.ts +1 -2
  38. package/dist/api/config.d.ts.map +1 -1
  39. package/dist/api/config.js +5 -22
  40. package/dist/api/config.js.map +1 -1
  41. package/dist/api/platform-guard.d.ts.map +1 -1
  42. package/dist/api/platform-guard.js +5 -15
  43. package/dist/api/platform-guard.js.map +1 -1
  44. package/dist/api/projects.d.ts.map +1 -1
  45. package/dist/api/projects.js +18 -25
  46. package/dist/api/projects.js.map +1 -1
  47. package/dist/api/tasks.js +2 -2
  48. package/dist/api/tasks.js.map +1 -1
  49. package/dist/cli.js.map +1 -1
  50. package/dist/config/hot-reload.d.ts +3 -2
  51. package/dist/config/hot-reload.d.ts.map +1 -1
  52. package/dist/config/hot-reload.js +6 -16
  53. package/dist/config/hot-reload.js.map +1 -1
  54. package/dist/config/loader.d.ts +1 -5
  55. package/dist/config/loader.d.ts.map +1 -1
  56. package/dist/config/loader.js +31 -31
  57. package/dist/config/loader.js.map +1 -1
  58. package/dist/config/validator.d.ts +2 -8
  59. package/dist/config/validator.d.ts.map +1 -1
  60. package/dist/config/validator.js +42 -120
  61. package/dist/config/validator.js.map +1 -1
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +7 -29
  64. package/dist/index.js.map +1 -1
  65. package/dist/platform/driver-host.d.ts +4 -3
  66. package/dist/platform/driver-host.d.ts.map +1 -1
  67. package/dist/platform/driver-host.js +12 -11
  68. package/dist/platform/driver-host.js.map +1 -1
  69. package/dist/platform/feedback.d.ts +5 -6
  70. package/dist/platform/feedback.d.ts.map +1 -1
  71. package/dist/platform/feedback.js +4 -7
  72. package/dist/platform/feedback.js.map +1 -1
  73. package/dist/platform/github-driver.d.ts +36 -0
  74. package/dist/platform/github-driver.d.ts.map +1 -0
  75. package/dist/platform/github-driver.js +374 -0
  76. package/dist/platform/github-driver.js.map +1 -0
  77. package/dist/platform/markers.d.ts +1 -4
  78. package/dist/platform/markers.d.ts.map +1 -1
  79. package/dist/platform/markers.js +0 -8
  80. package/dist/platform/markers.js.map +1 -1
  81. package/dist/platform/platform-poller.d.ts +2 -17
  82. package/dist/platform/platform-poller.d.ts.map +1 -1
  83. package/dist/platform/platform-poller.js +9 -15
  84. package/dist/platform/platform-poller.js.map +1 -1
  85. package/dist/platform/response-parser.d.ts.map +1 -1
  86. package/dist/platform/response-parser.js +6 -63
  87. package/dist/platform/response-parser.js.map +1 -1
  88. package/dist/platform/review-timeline.d.ts +6 -8
  89. package/dist/platform/review-timeline.d.ts.map +1 -1
  90. package/dist/platform/review-timeline.js +4 -6
  91. package/dist/platform/review-timeline.js.map +1 -1
  92. package/dist/platform/row-schema.d.ts +1 -2
  93. package/dist/platform/row-schema.d.ts.map +1 -1
  94. package/dist/platform/row-schema.js +13 -1
  95. package/dist/platform/row-schema.js.map +1 -1
  96. package/dist/platform/startup.d.ts +6 -34
  97. package/dist/platform/startup.d.ts.map +1 -1
  98. package/dist/platform/startup.js +10 -109
  99. package/dist/platform/startup.js.map +1 -1
  100. package/dist/platform/types.d.ts +61 -54
  101. package/dist/platform/types.d.ts.map +1 -1
  102. package/dist/platform/types.js +38 -26
  103. package/dist/platform/types.js.map +1 -1
  104. package/dist/platform/verdict-engine.d.ts +1 -1
  105. package/dist/platform/verdict-engine.d.ts.map +1 -1
  106. package/dist/platform/verdict-engine.js +1 -1
  107. package/dist/platform/verdict-engine.js.map +1 -1
  108. package/dist/shared/constants.d.ts +2 -6
  109. package/dist/shared/constants.d.ts.map +1 -1
  110. package/dist/shared/constants.js +2 -17
  111. package/dist/shared/constants.js.map +1 -1
  112. package/dist/shared/git-url.d.ts +0 -14
  113. package/dist/shared/git-url.d.ts.map +1 -1
  114. package/dist/shared/git-url.js +4 -57
  115. package/dist/shared/git-url.js.map +1 -1
  116. package/dist/shared/types.d.ts +2 -10
  117. package/dist/shared/types.d.ts.map +1 -1
  118. package/dist/shared/types.js.map +1 -1
  119. package/dist/state/agent-store.js +0 -3
  120. package/dist/state/agent-store.js.map +1 -1
  121. package/dist/state/task-store.js +2 -2
  122. package/dist/state/task-store.js.map +1 -1
  123. package/dist/web/assets/index-KH_4rYrI.js +15 -0
  124. package/dist/web/index.html +1 -1
  125. package/package.json +1 -1
  126. package/dist/config/normalizer.d.ts +0 -2
  127. package/dist/config/normalizer.d.ts.map +0 -1
  128. package/dist/config/normalizer.js +0 -46
  129. package/dist/config/normalizer.js.map +0 -1
  130. package/dist/platform/command-renderer.d.ts +0 -29
  131. package/dist/platform/command-renderer.d.ts.map +0 -1
  132. package/dist/platform/command-renderer.js +0 -113
  133. package/dist/platform/command-renderer.js.map +0 -1
  134. package/dist/platform/driver-spec.d.ts +0 -7
  135. package/dist/platform/driver-spec.d.ts.map +0 -1
  136. package/dist/platform/driver-spec.js +0 -506
  137. package/dist/platform/driver-spec.js.map +0 -1
  138. package/dist/platform/field-mapper.d.ts +0 -7
  139. package/dist/platform/field-mapper.d.ts.map +0 -1
  140. package/dist/platform/field-mapper.js +0 -109
  141. package/dist/platform/field-mapper.js.map +0 -1
  142. package/dist/platform/git-driver.d.ts +0 -64
  143. package/dist/platform/git-driver.d.ts.map +0 -1
  144. package/dist/platform/git-driver.js +0 -230
  145. package/dist/platform/git-driver.js.map +0 -1
  146. package/dist/platform/manifest.d.ts +0 -11
  147. package/dist/platform/manifest.d.ts.map +0 -1
  148. package/dist/platform/manifest.js +0 -42
  149. package/dist/platform/manifest.js.map +0 -1
  150. package/dist/platform/plugin-registry.d.ts +0 -31
  151. package/dist/platform/plugin-registry.d.ts.map +0 -1
  152. package/dist/platform/plugin-registry.js +0 -239
  153. package/dist/platform/plugin-registry.js.map +0 -1
  154. package/dist/platform/plugin-roots.d.ts +0 -3
  155. package/dist/platform/plugin-roots.d.ts.map +0 -1
  156. package/dist/platform/plugin-roots.js +0 -9
  157. package/dist/platform/plugin-roots.js.map +0 -1
  158. package/dist/platform/plugins/github/baxian-plugin.json +0 -8
  159. package/dist/platform/plugins/github/driver.json +0 -153
  160. package/dist/platform/plugins/github/skills/baxian-cli-gh/SKILL.md +0 -115
  161. package/dist/platform/plugins/github/skills/baxian-cli-gh/agents/openai.yaml +0 -2
  162. package/dist/platform/preflight-exec.d.ts +0 -21
  163. package/dist/platform/preflight-exec.d.ts.map +0 -1
  164. package/dist/platform/preflight-exec.js +0 -53
  165. package/dist/platform/preflight-exec.js.map +0 -1
  166. package/dist/skill/registry.d.ts +0 -37
  167. package/dist/skill/registry.d.ts.map +0 -1
  168. package/dist/skill/registry.js +0 -234
  169. package/dist/skill/registry.js.map +0 -1
  170. package/dist/skills/baxian-greeting/SKILL.md +0 -15
  171. package/dist/skills/baxian-greeting/agents/openai.yaml +0 -2
  172. package/dist/skills/baxian-pr-feedback/SKILL.md +0 -41
  173. package/dist/skills/baxian-pr-feedback/agents/openai.yaml +0 -2
  174. package/dist/skills/baxian-pr-recheck/SKILL.md +0 -27
  175. package/dist/skills/baxian-pr-recheck/agents/openai.yaml +0 -2
  176. package/dist/skills/baxian-pr-review/SKILL.md +0 -33
  177. package/dist/skills/baxian-pr-review/agents/openai.yaml +0 -2
  178. package/dist/skills/baxian-signals/SKILL.md +0 -86
  179. package/dist/skills/baxian-signals/agents/openai.yaml +0 -2
  180. package/dist/skills/baxian-task-check/SKILL.md +0 -52
  181. package/dist/skills/baxian-task-check/agents/openai.yaml +0 -2
  182. package/dist/web/assets/index-CF2C4Fq-.js +0 -15
@@ -6,8 +6,8 @@ import { tmpdir } from 'node:os';
6
6
  import { createSignalToken, PASSIVE_VERDICT_WATCH } from './phase-signal.js';
7
7
  import { tmuxInstallHint } from './preflight.js';
8
8
  import { BRANCH_PREFIX, isValidBranchName, PHASE_EXPECTED_STATUS, PHASE_REQUIRES_AGENT_BOUND_TO_TASK, TASK_TERMINAL_STATUSES as TERMINAL_STATUSES, TASK_ACTIVE_STATUS_SET as ACTIVE_TASK_STATUSES, TASK_OWNER_ROLES, effectiveTaskReviewRound, isSpecStagePhase, isTaskOpen, repoIdentityKey, taskAttentionGeneration, taskGenerationGuard, taskReviewRound, taskMatchesGeneration, } from '../shared/index.js';
9
- import { buildProjectDriver, makeDriverExec } from '../platform/driver-host.js';
10
- import { DriverOpError, buildDriverRunContext, GitDriver, safeDriverErrorText } from '../platform/git-driver.js';
9
+ import { buildProjectDriver, buildProjectPromptContext, makeDriverExec, } from '../platform/driver-host.js';
10
+ import { DriverOpError, safeDriverErrorText, validateCommentBody, } from '../platform/types.js';
11
11
  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';
@@ -15,21 +15,18 @@ import { prReviewCacheRevision } from '../platform/pr-conversation-cache.js';
15
15
  import { deadTokens, recheckPassProvenance } 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
- import { SkillRegistry } from '../skill/registry.js';
19
- import { createRunner, LocalRunner, shellQuote, resolveAgentHost, hostGroupKey, workdirHostGroupKey, } from './runner.js';
20
- import { execOutcomeUnknown, isTransientNetworkFailure } from './net-exec.js';
18
+ import { createRunner, LocalRunner, shellQuote, resolveAgentHost, workdirHostGroupKey, } from './runner.js';
19
+ import { isTransientNetworkFailure } from './net-exec.js';
21
20
  import { imageFilename, agentHostPath, writeImageToHost } from './image-input.js';
22
21
  import { TmuxManager, ReplNotReadyError, detectStartupDialog, detectRuntimeMenu, runtimeBusyCheck, hasRuntimeReadyView, hasReplProcTitle, hasOscTitleWorking, hasOscTitleIdle, screenAllowsTitleIdle, PaneGoneError, } from './tmux.js';
23
22
  import { BranchManager, DirtyWorkdirError, ReviewHeadMismatchError, isAutoDeletableTaskBranch } from './branch.js';
24
- import { RepoStore, createRepoStoreCache, moveFileIntoPlace, stageFile, trashBatchDirUnder } from './repo-store.js';
23
+ import { RepoStore, createRepoStoreCache } from './repo-store.js';
25
24
  import { PhaseSignalWatcher, } from './phase-signal-watcher.js';
26
- import { buildPromptInline, buildGreetingPrompt, PromptSizeError, RequiredSkillsMissingError, MAX_PROMPT_BYTES_ROUTE_LIMIT, } from './prompt.js';
25
+ import { buildPromptInline, buildGreetingPrompt, PromptSizeError, MAX_PROMPT_BYTES_ROUTE_LIMIT, } from './prompt.js';
27
26
  import { ApiError } from '../errors.js';
28
27
  import { prepareConfig } from '../config/loader.js';
29
- import { resolveProjectTool } from '../config/validator.js';
30
28
  import { SHA_HEX_SOURCE } from '../platform/types.js';
31
29
  import { bodyDigest } from '../platform/body-digest.js';
32
- import { validateCommentBody } from '../platform/command-renderer.js';
33
30
  import { buildReviewTokenLine, extractReviewTokens, rowTokens } from '../platform/markers.js';
34
31
  const BENIGN_DISPATCH_CONFLICT_CODES = new Set(['dispatch-superseded', 'dispatch-in-flight']);
35
32
  export function isBenignDispatchConflict(err) {
@@ -127,6 +124,7 @@ const REPL_EXIT_COMMAND = {
127
124
  qodercli: '/quit',
128
125
  };
129
126
  const WORKDIR_SESSION_OPTION = '@baxian-workdir';
127
+ const TASK_CONTEXT_SESSION_OPTION = '@baxian-context-task-id';
130
128
  function normalizedDir(left) {
131
129
  if (!left)
132
130
  return null;
@@ -147,17 +145,6 @@ async function sameDirOnHost(runner, left, right) {
147
145
  };
148
146
  return await physical(left) === await physical(right);
149
147
  }
150
- export function skillSubdirFor(runtime) {
151
- switch (runtime) {
152
- case 'codex':
153
- case 'opencode':
154
- return '.agents/skills';
155
- case 'qodercli':
156
- return '.qoder/skills';
157
- default:
158
- return '.claude/skills';
159
- }
160
- }
161
148
  const DEFAULT_DISPATCH_ACK_TIMEOUT_MS = 30_000;
162
149
  const DEFAULT_NEED_INPUT_RETRY_INTERVAL_MS = 5_000;
163
150
  const DEFAULT_DISPATCH_SETTLE_TIMEOUT_MS = 3_000;
@@ -184,7 +171,7 @@ const RUNTIME_LIVENESS_SAMPLES = 3;
184
171
  export function canDispatchWithBinding(binding) {
185
172
  return !binding?.taskId && !binding?.creationToken && binding?.status !== 'awaiting_human';
186
173
  }
187
- export const RECOVERABLE_QA_DISPATCH_HOLD_PHASES = new Set([
174
+ const RECOVERABLE_QA_DISPATCH_HOLD_PHASES = new Set([
188
175
  'checkout-preparation-failed',
189
176
  'dirty-workdir',
190
177
  ]);
@@ -233,12 +220,10 @@ export class AgentManager {
233
220
  taskStore;
234
221
  lockManager;
235
222
  eventBus;
236
- skillRegistry;
237
223
  runnerFactory;
238
224
  repoStoreFactory;
239
225
  repoCache;
240
226
  paneStreamerManager;
241
- pluginRegistry;
242
227
  platformDefaultBranchOf;
243
228
  prConversationCache;
244
229
  platformDriverExec;
@@ -277,7 +262,7 @@ export class AgentManager {
277
262
  divergedAgents = new Set();
278
263
  deletionGeneration = new Map();
279
264
  compactInFlight = new Set();
280
- configInterventionKeys = new Set();
265
+ platformBindingInterventionKeys = new Set();
281
266
  constructor(deps) {
282
267
  const config = prepareConfig(deps.config);
283
268
  this.config = config;
@@ -285,11 +270,9 @@ export class AgentManager {
285
270
  this.taskStore = deps.taskStore;
286
271
  this.lockManager = deps.lockManager;
287
272
  this.eventBus = deps.eventBus;
288
- this.skillRegistry = deps.skillRegistry ?? new SkillRegistry();
289
273
  this.runnerFactory = deps.runnerFactory;
290
274
  this.repoStoreFactory = deps.repoStoreFactory;
291
275
  this.paneStreamerManager = deps.paneStreamerManager;
292
- this.pluginRegistry = deps.pluginRegistry;
293
276
  this.platformDefaultBranchOf = deps.platformDefaultBranchOf;
294
277
  this.prConversationCache = deps.prConversationCache;
295
278
  this.errorRecordStore = deps.errorRecordStore;
@@ -415,30 +398,19 @@ export class AgentManager {
415
398
  data,
416
399
  });
417
400
  }
418
- async emitConfigIntervention(projectId, data) {
419
- const key = this.configInterventionKey(projectId, data);
420
- if (this.configInterventionKeys.has(key))
421
- return;
422
- if (await this.emitIntervention(projectId, undefined, undefined, data)) {
423
- this.configInterventionKeys.add(key);
424
- }
425
- }
426
- configInterventionKey(projectId, data) {
427
- return `config:${projectId}:${JSON.stringify(data)}`;
428
- }
429
401
  platformBindingInterventionKey(task, mismatch) {
430
402
  return `binding:${task.id}:${mismatch.reason}:${mismatch.differences.join(',')}:` +
431
403
  `${JSON.stringify(mismatch.binding ?? null)}:${JSON.stringify(mismatch.live ?? null)}`;
432
404
  }
433
- retainConfigInterventionKeys(keys) {
434
- for (const key of this.configInterventionKeys) {
405
+ retainPlatformBindingInterventionKeys(keys) {
406
+ for (const key of this.platformBindingInterventionKeys) {
435
407
  if (!keys.has(key))
436
- this.configInterventionKeys.delete(key);
408
+ this.platformBindingInterventionKeys.delete(key);
437
409
  }
438
410
  }
439
411
  async emitPlatformBindingIntervention(task, mismatch) {
440
412
  const key = this.platformBindingInterventionKey(task, mismatch);
441
- if (this.configInterventionKeys.has(key))
413
+ if (this.platformBindingInterventionKeys.has(key))
442
414
  return;
443
415
  const emitted = await this.emitIntervention(task.projectId, task.agentId || undefined, task.id, {
444
416
  phase: 'platform-binding-mismatch',
@@ -448,7 +420,7 @@ export class AgentManager {
448
420
  ...(mismatch.live !== undefined ? { live: mismatch.live } : {}),
449
421
  });
450
422
  if (emitted)
451
- this.configInterventionKeys.add(key);
423
+ this.platformBindingInterventionKeys.add(key);
452
424
  }
453
425
  async recordError(input) {
454
426
  if (!this.errorRecordStore)
@@ -606,13 +578,6 @@ export class AgentManager {
606
578
  return { ...existing, workdir, updatedAt: new Date().toISOString() };
607
579
  });
608
580
  assertGenerationOpen();
609
- try {
610
- await this.provisionRepoSkills(runner, agent, workdir);
611
- }
612
- catch (err) {
613
- throw new EnsureSessionError({ createdSession: false, agentId }, `skill provisioning failed: ${err instanceof Error ? err.message : String(err)}`);
614
- }
615
- assertGenerationOpen();
616
581
  let snapshot;
617
582
  try {
618
583
  snapshot = await tmux.getSessionSnapshot(agentId);
@@ -641,31 +606,6 @@ export class AgentManager {
641
606
  }
642
607
  return this.adoptOrRestartSessionLocked(tmux, runner, agent, agentId, workdir, workdir, snapshot);
643
608
  }
644
- agentSkillScope(agentId) {
645
- const projectId = this.getAgentConfig(agentId)?.projectId;
646
- if (projectId === undefined)
647
- return { pluginTools: [] };
648
- const project = this.getProjectConfig(projectId);
649
- const tool = project === undefined ? undefined : resolveProjectTool(project);
650
- return { pluginTools: tool === undefined ? [] : [tool] };
651
- }
652
- async provisionRepoSkills(runner, agent, workdir) {
653
- if (this.skillRegistry.names().length === 0)
654
- return;
655
- const scope = this.agentSkillScope(agent.id);
656
- const subdir = skillSubdirFor(agent.runtime);
657
- const destRoot = `${workdir}/${subdir}`;
658
- await this.excludeInjectedSkills(runner, workdir, subdir);
659
- // Batch inside the swept tree itself: a symlinked skills dir may live on another filesystem, where mv degrades to copy+unlink.
660
- const trashDir = trashBatchDirUnder(`${destRoot}/.baxian-trash`, 'skills');
661
- await this.runUnderSkillDirLock(this.skillDirLockKey(agent, workdir), async () => {
662
- await this.ensureSkillDirSafe(runner, workdir, subdir, [
663
- ...this.skillRegistry.names(),
664
- ...this.skillRegistry.pluginSkillNames(scope),
665
- ], trashDir);
666
- await this.skillRegistry.materialize((path, content) => this.atomicWriteFile(runner, path, content, trashDir), destRoot, scope);
667
- });
668
- }
669
609
  sessionLifecycleChain = new Map();
670
610
  adoptGeneration = new Map();
671
611
  runUnderSessionLifecycle(agentId, fn) {
@@ -733,109 +673,6 @@ export class AgentManager {
733
673
  return;
734
674
  await this.rollbackCreatedSession(agentId, err.partial, reason);
735
675
  }
736
- skillDirChain = new Map();
737
- skillDirLockKey(agent, workdir) {
738
- const host = hostGroupKey(agent.mode, resolveAgentHost(this.config.host, agent.host));
739
- const subdir = skillSubdirFor(agent.runtime);
740
- const dir = workdir.replace(/\/+$/, '');
741
- return `${host}:${dir}:${subdir}`;
742
- }
743
- runUnderSkillDirLock(key, fn) {
744
- const prev = this.skillDirChain.get(key) ?? Promise.resolve();
745
- const run = prev.then(fn, fn);
746
- const settled = run.then(() => undefined, () => undefined);
747
- this.skillDirChain.set(key, settled);
748
- void settled.finally(() => {
749
- if (this.skillDirChain.get(key) === settled)
750
- this.skillDirChain.delete(key);
751
- });
752
- return run;
753
- }
754
- async atomicWriteFile(runner, finalPath, content, trashDir) {
755
- const tmp = `${finalPath}.baxian-tmp-${randomBytes(6).toString('hex')}`;
756
- try {
757
- await stageFile(runner, tmp, content, { trashDir });
758
- await moveFileIntoPlace(runner, tmp, finalPath, { trashDir });
759
- }
760
- catch (err) {
761
- throw new Error(`atomic skill write failed (${finalPath}): ${err instanceof Error ? err.message : String(err)}`);
762
- }
763
- }
764
- async ensureSkillDirSafe(runner, workdir, subdir, keepNames, trashDir) {
765
- const SKILLS_SWEPT = 'BX_SKILLS_SWEPT';
766
- const keep = keepNames.map((n) => `! -name ${shellQuote(n)}`).join(' ');
767
- const batch = shellQuote(trashDir);
768
- // Stale directories go first so the later link/file passes only see what stayed behind in kept dirs. -H follows a symlinked skills dir given as the start path; -exec … + propagates a mover failure into find's exit code (\; would swallow it); pruning .baxian-trash keeps the never-emptied trash out of the walk (it only grows, and one unreadable old batch would fail every sweep).
769
- const prune = `-name .baxian-trash -prune -o`;
770
- const findStaleDirs = `find -H ${subdir} -maxdepth 1 ${prune} -name 'baxian-*' ${keep}`;
771
- const findStaleLinks = `find -H ${subdir} ${prune} -path '${subdir}/baxian-*' -type l`;
772
- const findStaleFiles = `find -H ${subdir} ${prune} -path '${subdir}/baxian-*/*' -type f ! -name 'SKILL.md'`;
773
- const mover = `dest="$0"; for f in "$@"; do ` +
774
- `mkdir -p "$dest" || exit 1; ` +
775
- `base=$(basename -- "$f"); t="$dest/$base"; n=0; ` +
776
- `while [ -e "$t" ] || [ -L "$t" ]; do n=$((n+1)); t="$dest/$base.$n"; done; ` +
777
- `mv -- "$f" "$t" || exit 1; ` +
778
- `done`;
779
- const moveStale = (find) => `${find} -exec sh -c '${mover}' ${batch} {} +`;
780
- const inner = `cd ${shellQuote(workdir)} && ` +
781
- `mkdir -p ${subdir} && ` +
782
- `${moveStale(findStaleDirs)} && ` +
783
- `${moveStale(findStaleLinks)} && ` +
784
- `${moveStale(findStaleFiles)} && ` +
785
- `printf '%s' ${SKILLS_SWEPT}`;
786
- const res = await runner.exec(`sh -c ${shellQuote(inner)}`);
787
- if (execOutcomeUnknown(res)) {
788
- throw new Error(`skill sweep outcome unknown in ${workdir}/${subdir} (exit ${res.exitCode}): ${res.stderr.trim() || res.stdout.trim() || 'no output'}`);
789
- }
790
- if (res.exitCode !== 0 || !res.stdout.includes(SKILLS_SWEPT)) {
791
- throw new Error(`failed to prepare ${subdir} in ${workdir}: ${res.stderr.trim() || `exit ${res.exitCode}, no completion marker`}`);
792
- }
793
- }
794
- async tagSessionSkillsVersion(tmux, agentId, ref) {
795
- if (this.skillRegistry.names().length === 0)
796
- return;
797
- await this.setSessionOptions(tmux, agentId, ref, [
798
- ['@baxian-skills-version', this.skillRegistry.contentHash(this.agentSkillScope(agentId))],
799
- ]);
800
- }
801
- async replSkillsStale(tmux, agentId, ref) {
802
- if (this.skillRegistry.names().length === 0)
803
- return false;
804
- const tagged = await tmux.getSessionOptionByRef(ref, agentId, '@baxian-skills-version');
805
- return tagged !== this.skillRegistry.contentHash(this.agentSkillScope(agentId));
806
- }
807
- async excludeInjectedSkills(runner, workdir, subdir) {
808
- // cd -P first: tracked/exclude/probe all run against the skills dir's PHYSICAL owning repo, so a symlink pointing back inside a worktree keeps the tracked-content protection while one pointing outside degrades to the plain non-git path.
809
- const inner = `cd ${shellQuote(workdir)} && ` +
810
- `mkdir -p ${subdir} && cd -P ${subdir} && ` +
811
- `if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then echo BX_SKILLS_NON_GIT; exit 0; fi; ` +
812
- `tracked="$(git ls-files -- 'baxian-*' 2>/dev/null | head -5)"; ` +
813
- `if [ -n "$tracked" ]; then printf 'BX_SKILLS_TRACKED %s\\n' "$tracked" | head -1; exit 0; fi; ` +
814
- `p="$(git rev-parse --git-path info/exclude)" || { echo BX_SKILLS_EXCLUDE_WRITE_FAILED; exit 0; }; ` +
815
- // show-prefix is a literal path but lands in a gitignore pattern: escape fnmatch metacharacters or a dir like skill[dir] never matches its own rule.
816
- `pre="$(git rev-parse --show-prefix 2>/dev/null | sed 's/[][\\*?]/\\\\&/g')"; rule="/\${pre}baxian-*"; trashrule="/\${pre}.baxian-trash"; ` +
817
- `{ mkdir -p "$(dirname "$p")" && { grep -qxF "$rule" "$p" 2>/dev/null || printf '%s\\n' "$rule" >> "$p"; } ` +
818
- `&& { grep -qxF "$trashrule" "$p" 2>/dev/null || printf '%s\\n' "$trashrule" >> "$p"; }; } ` +
819
- `|| { echo BX_SKILLS_EXCLUDE_WRITE_FAILED; exit 0; }; ` +
820
- `if git check-ignore -q -- 'baxian-__probe__/SKILL.md' ` +
821
- `&& git check-ignore -q -- '.baxian-trash/__probe__'; then echo BX_SKILLS_OK; ` +
822
- `else echo BX_SKILLS_EXCLUDE_INEFFECTIVE; fi`;
823
- const res = await runner.exec(`sh -c ${shellQuote(inner)}`);
824
- if (execOutcomeUnknown(res)) {
825
- throw new Error(`skill exclusion probe outcome unknown in ${workdir} (exit ${res.exitCode}): ${res.stderr.trim() || res.stdout.trim() || 'no output'}`);
826
- }
827
- if (res.exitCode !== 0) {
828
- throw new Error(`skill exclusion probe failed in ${workdir}: ${res.stderr.trim() || `exit ${res.exitCode}`}`);
829
- }
830
- const verdict = res.stdout.trim().split('\n').pop() ?? '';
831
- if (verdict === 'BX_SKILLS_NON_GIT' || verdict === 'BX_SKILLS_OK')
832
- return;
833
- if (verdict.startsWith('BX_SKILLS_TRACKED')) {
834
- throw new Error(`refusing to inject baxian skills into ${workdir}: project tracks ` +
835
- `${verdict.replace('BX_SKILLS_TRACKED ', '')}`);
836
- }
837
- throw new Error(`cannot make injected skills invisible to Git in ${workdir}: ${verdict || 'no probe output'}`);
838
- }
839
676
  runtimePinEntries() {
840
677
  return [
841
678
  ['prefix', 'C-b'],
@@ -849,6 +686,22 @@ export class AgentManager {
849
686
  throw new Error(`tmux session ${ref.sessionId} (${agentId}) vanished before its options could be applied`);
850
687
  }
851
688
  }
689
+ async taskContextForSession(tmux, agentId, ensure) {
690
+ if (ensure.freshRuntime) {
691
+ await this.setSessionOptions(tmux, agentId, ensure.sessionRef, [[TASK_CONTEXT_SESSION_OPTION, '']]);
692
+ return null;
693
+ }
694
+ return tmux.getSessionOptionByRef(ensure.sessionRef, agentId, TASK_CONTEXT_SESSION_OPTION);
695
+ }
696
+ async rememberTaskContext(tmux, agentId, ref, taskId) {
697
+ try {
698
+ await this.setSessionOptions(tmux, agentId, ref, [[TASK_CONTEXT_SESSION_OPTION, taskId]]);
699
+ }
700
+ catch (err) {
701
+ console.warn(`[AgentManager] prompt for ${agentId}/${taskId} was delivered, but its context marker could not be saved; ` +
702
+ `the next phase will clear once and resend the full task:`, err);
703
+ }
704
+ }
852
705
  async resolveClaimedPane(tmux, agentId, expectedPaneId) {
853
706
  const snapshot = await tmux.getSessionSnapshot(agentId);
854
707
  if (!snapshot)
@@ -863,7 +716,7 @@ export class AgentManager {
863
716
  return pane;
864
717
  }
865
718
  async buildFreshSession(tmux, agent, agentId, workdir, sessionDir = workdir) {
866
- return this.runUnderSkillDirLock(this.skillDirLockKey(agent, sessionDir), () => this.buildFreshSessionLocked(tmux, agent, agentId, workdir, sessionDir));
719
+ return this.buildFreshSessionLocked(tmux, agent, agentId, workdir, sessionDir);
867
720
  }
868
721
  async buildFreshSessionLocked(tmux, agent, agentId, workdir, sessionDir) {
869
722
  let createdSession = false;
@@ -895,7 +748,6 @@ export class AgentManager {
895
748
  timeoutMs: this.bootstrapTimeoutsMs.waitReplReady,
896
749
  scrollback: 0,
897
750
  });
898
- await this.tagSessionSkillsVersion(tmux, agentId, createdRef);
899
751
  return { ok: true, createdSession: true, freshRuntime: true, paneId: pane.paneId, pane, workdir, sessionRef: createdRef };
900
752
  }
901
753
  catch (err) {
@@ -988,7 +840,7 @@ export class AgentManager {
988
840
  for (let attempt = 1; attempt <= this.greetingMaxAttempts; attempt++) {
989
841
  const token = createSignalToken();
990
842
  try {
991
- await this.injectAndAwaitAckSteps(tmux, pane, buildGreetingPrompt(token, agent.runtime), agentId, agent.runtime);
843
+ await this.injectAndAwaitAckSteps(tmux, pane, buildGreetingPrompt(token), agentId, agent.runtime);
992
844
  }
993
845
  catch (err) {
994
846
  console.warn(`[bootstrap] greeting inject failed for ${agentId} (attempt ${attempt}):`, err);
@@ -1021,8 +873,8 @@ export class AgentManager {
1021
873
  return;
1022
874
  const now = new Date().toISOString();
1023
875
  const reason = 'Greeting capability check failed: the agent did not echo a valid [bx:greeting] signal ' +
1024
- 'per the baxian-signals skill within the timeout. Its runtime/model may not meet baxian ' +
1025
- 'requirements (skill loading or pane signalling). Fix the runtime, then restart-repl or ' +
876
+ 'from the inline startup instruction within the timeout. Its runtime/model may not meet baxian ' +
877
+ 'pane signalling requirements. Fix the runtime, then restart-repl or ' +
1026
878
  'retry the agent to re-run the check (Resume will not clear it — capability must be re-proven).';
1027
879
  let wrote = false;
1028
880
  await this.agentStore.update(agentId, (fresh) => {
@@ -1519,28 +1371,8 @@ export class AgentManager {
1519
1371
  }
1520
1372
  await this.setSessionOptions(tmux, agentId, sessionRef, [[WORKDIR_SESSION_OPTION, sessionDir]]);
1521
1373
  switch (state.kind) {
1522
- case 'live-runtime': {
1523
- let stale;
1524
- try {
1525
- stale = await this.replSkillsStale(tmux, agentId, sessionRef);
1526
- }
1527
- catch (err) {
1528
- throw new EnsureSessionError({ createdSession: false, agentId }, `skills-version probe failed: ${err instanceof Error ? err.message : String(err)}`);
1529
- }
1530
- if (stale) {
1531
- return {
1532
- ok: true,
1533
- createdSession: false,
1534
- freshRuntime: false,
1535
- skillsStale: true,
1536
- paneId,
1537
- pane,
1538
- workdir,
1539
- sessionRef,
1540
- };
1541
- }
1374
+ case 'live-runtime':
1542
1375
  return { ok: true, createdSession: false, freshRuntime: false, paneId, pane, workdir, sessionRef };
1543
- }
1544
1376
  case 'startup-dialog':
1545
1377
  throw new EnsureSessionError({
1546
1378
  createdSession: false,
@@ -1561,7 +1393,6 @@ export class AgentManager {
1561
1393
  timeoutMs: this.bootstrapTimeoutsMs.waitReplReady,
1562
1394
  scrollback: 0,
1563
1395
  });
1564
- await this.tagSessionSkillsVersion(tmux, agentId, sessionRef);
1565
1396
  return { ok: true, createdSession: false, freshRuntime: true, paneId, pane, workdir, sessionRef };
1566
1397
  }
1567
1398
  catch (trustErr) {
@@ -1589,7 +1420,6 @@ export class AgentManager {
1589
1420
  timeoutMs: this.bootstrapTimeoutsMs.waitReplReady,
1590
1421
  scrollback: 0,
1591
1422
  });
1592
- await this.tagSessionSkillsVersion(tmux, agentId, sessionRef);
1593
1423
  return { ok: true, createdSession: false, freshRuntime: true, paneId, pane, workdir, sessionRef };
1594
1424
  }
1595
1425
  catch (relErr) {
@@ -3436,7 +3266,6 @@ export class AgentManager {
3436
3266
  if (!this.deletionGateOpen(agentId, genAtEntry)) {
3437
3267
  throw new Error(`restart-repl: agent ${agentId} is being deleted or was recreated; aborting`);
3438
3268
  }
3439
- await this.provisionRepoSkills(runner, cfg, workdir);
3440
3269
  await this.setSessionOptions(tmux, agentId, snapshot.ref, [[WORKDIR_SESSION_OPTION, workdir]]);
3441
3270
  const runtime = agentRuntimeKindFor(cfg);
3442
3271
  const relaunch = async () => {
@@ -3449,9 +3278,9 @@ export class AgentManager {
3449
3278
  timeoutMs: this.bootstrapTimeoutsMs.waitReplReady,
3450
3279
  scrollback: 0,
3451
3280
  });
3452
- await this.tagSessionSkillsVersion(tmux, agentId, snapshot.ref);
3281
+ await this.setSessionOptions(tmux, agentId, snapshot.ref, [[TASK_CONTEXT_SESSION_OPTION, '']]);
3453
3282
  };
3454
- await this.runUnderSkillDirLock(this.skillDirLockKey(cfg, workdir), relaunch);
3283
+ await relaunch();
3455
3284
  await this.agentStore.update(agentId, (state) => {
3456
3285
  if (!state || !this.deletionGateOpen(agentId, genAtEntry))
3457
3286
  return AGENT_STORE_NOOP;
@@ -3959,9 +3788,8 @@ export class AgentManager {
3959
3788
  phase: 'develop',
3960
3789
  agent: cfg,
3961
3790
  workdir: workdirGuess,
3962
- skillRegistry: this.skillRegistry,
3963
3791
  signalToken: 'preview-signal-token',
3964
- platformCli: this.platformCliContextOf(fakeTask),
3792
+ platform: this.platformPromptContextOf(fakeTask),
3965
3793
  });
3966
3794
  return Buffer.byteLength(fullPrompt, 'utf8');
3967
3795
  }
@@ -3972,12 +3800,10 @@ export class AgentManager {
3972
3800
  return this.config.project.find(p => p.id === projectId);
3973
3801
  }
3974
3802
  platformDriverFor(projectId) {
3975
- if (!this.pluginRegistry)
3976
- return undefined;
3977
3803
  const project = this.getProjectConfig(projectId);
3978
3804
  if (!project)
3979
3805
  return undefined;
3980
- return buildProjectDriver(project, this.pluginRegistry, this.platformDriverExec);
3806
+ return buildProjectDriver(project, this.platformDriverExec);
3981
3807
  }
3982
3808
  findAgentTeam(anchorAgentId) {
3983
3809
  for (const project of this.config.project) {
@@ -4484,25 +4310,6 @@ export class AgentManager {
4484
4310
  headSha: completion.approvedHeadSha,
4485
4311
  });
4486
4312
  }
4487
- async ensurePluginSkillPools(next) {
4488
- if (!this.pluginRegistry)
4489
- return;
4490
- for (const project of next.project) {
4491
- const tool = resolveProjectTool(project);
4492
- if (tool === undefined)
4493
- continue;
4494
- const plugin = this.pluginRegistry.resolveTool(tool);
4495
- if (plugin === undefined) {
4496
- throw new Error(`project '${project.id}': no git-driver plugin provides tool '${tool}' — install it under <home>/plugins/ first`);
4497
- }
4498
- if (this.skillRegistry.pluginSkillNames({ pluginTools: [tool] }).length > 0)
4499
- continue;
4500
- await this.skillRegistry.scanPluginSkills(tool, join(plugin.pluginPath, 'skills'));
4501
- if (this.skillRegistry.pluginSkillNames({ pluginTools: [tool] }).length === 0) {
4502
- throw new Error(`plugin '${plugin.manifest.name}' provides no materializable skills for tool '${tool}'`);
4503
- }
4504
- }
4505
- }
4506
4313
  async guardGitConfigCommit(current, next, scan, commit) {
4507
4314
  return this.withTaskLock(async () => {
4508
4315
  const blockers = await scan(this, current, next);
@@ -5227,51 +5034,16 @@ export class AgentManager {
5227
5034
  const project = this.getProjectConfig(projectId);
5228
5035
  if (!project)
5229
5036
  throw new Error(`project ${projectId} not found while capturing platform binding`);
5230
- const tool = resolveProjectTool(project);
5231
- if (tool === undefined)
5232
- throw new Error(`project ${projectId} has no resolved platform tool`);
5233
5037
  return {
5234
- mode: 'git',
5235
5038
  repoKey: repoIdentityKey(project.repo),
5236
- tool,
5237
5039
  };
5238
5040
  }
5239
- agentGitPreflightContext(projectId) {
5240
- const project = this.getProjectConfig(projectId);
5241
- if (!project || !this.pluginRegistry)
5242
- return undefined;
5243
- const tool = resolveProjectTool(project);
5244
- if (tool === undefined)
5245
- return undefined;
5246
- const plugin = this.pluginRegistry.resolveTool(tool);
5247
- if (plugin === undefined)
5248
- return undefined;
5249
- const renderCtx = buildDriverRunContext(project.repo, tool);
5250
- return {
5251
- tool,
5252
- minToolVersion: plugin.manifest.minToolVersion,
5253
- steps: plugin.spec.preflight,
5254
- agentCommands: plugin.spec.agentCommands,
5255
- renderCtx,
5256
- driverFor: (exec) => new GitDriver(plugin, renderCtx, exec),
5257
- };
5258
- }
5259
- platformCliContextOf(task) {
5041
+ platformPromptContextOf(task) {
5260
5042
  this.assertPlatformBinding(task);
5261
5043
  const project = this.getProjectConfig(task.projectId);
5262
5044
  if (!project)
5263
5045
  return undefined;
5264
- const tool = resolveProjectTool(project);
5265
- if (tool === undefined)
5266
- return undefined;
5267
- const ctx = buildDriverRunContext(project.repo, tool);
5268
- return {
5269
- tool,
5270
- host: ctx.host,
5271
- repo: ctx.repoPath,
5272
- repoEncoded: encodeURIComponent(ctx.repoPath),
5273
- ...(project.gitCli?.notes !== undefined ? { notes: project.gitCli.notes } : {}),
5274
- };
5046
+ return buildProjectPromptContext(project);
5275
5047
  }
5276
5048
  async ensureGitBaseSnapshot(task, phase) {
5277
5049
  if (phase !== 'develop' && phase !== 'code')
@@ -5340,8 +5112,7 @@ export class AgentManager {
5340
5112
  const { task, driver } = await this.platformTaskAndDriver(taskId);
5341
5113
  if (task.prNumber === undefined)
5342
5114
  throw new Error(`task ${taskId} has no bound PR`);
5343
- const [row] = await driver.runOp('prView', { prNumber: task.prNumber });
5344
- return row;
5115
+ return driver.prView(task.prNumber);
5345
5116
  }
5346
5117
  async platformFetchPrHeadSha(taskId) {
5347
5118
  const headSha = String((await this.platformFetchPrView(taskId)).headSha);
@@ -5352,11 +5123,11 @@ export class AgentManager {
5352
5123
  }
5353
5124
  async platformVerifyPrBinding(taskId, prNumber, opts = {}) {
5354
5125
  const { task, driver } = await this.platformTaskAndDriver(taskId);
5355
- const [row] = await driver.runOp('prView', { prNumber });
5126
+ const row = await driver.prView(prNumber);
5356
5127
  let expectedBase = task.baseBranch;
5357
5128
  if (expectedBase === undefined) {
5358
- const [project] = await driver.runOp('projectView');
5359
- if (typeof project?.defaultBranch === 'string')
5129
+ const project = await driver.projectView();
5130
+ if (typeof project.defaultBranch === 'string')
5360
5131
  expectedBase = project.defaultBranch;
5361
5132
  }
5362
5133
  const check = checkOpenPrBinding(row, { branch: opts.branchOverride ?? task.branch, expectedBase });
@@ -5473,8 +5244,8 @@ export class AgentManager {
5473
5244
  let pr;
5474
5245
  let project;
5475
5246
  try {
5476
- [pr] = await driver.runOp('prView', { prNumber: intent.prNumber });
5477
- [project] = await driver.runOp('projectView');
5247
+ pr = await driver.prView(intent.prNumber);
5248
+ project = await driver.projectView();
5478
5249
  }
5479
5250
  catch (err) {
5480
5251
  if (err instanceof DriverOpError
@@ -5520,7 +5291,7 @@ export class AgentManager {
5520
5291
  task = credentialed;
5521
5292
  intent = credentialed.remoteCleanup;
5522
5293
  try {
5523
- await driver.runOp('close', { prNumber: intent.prNumber });
5294
+ await driver.closePr(intent.prNumber);
5524
5295
  }
5525
5296
  catch (err) {
5526
5297
  if (err instanceof DriverOpError
@@ -5582,21 +5353,14 @@ export class AgentManager {
5582
5353
  return;
5583
5354
  let mutationError;
5584
5355
  try {
5585
- await driver.runOp('deleteBranch', {
5586
- branch: intent.branch,
5587
- expectedHeadSha: intent.expectedHeadSha,
5588
- remoteProjectId: intent.remoteProjectId,
5589
- });
5356
+ await driver.deleteBranch(intent.remoteProjectId, intent.branch, intent.expectedHeadSha);
5590
5357
  }
5591
5358
  catch (err) {
5592
5359
  mutationError = err;
5593
5360
  }
5594
5361
  let branch;
5595
5362
  try {
5596
- [branch] = await driver.runOp('branchView', {
5597
- branch: intent.branch,
5598
- remoteProjectId: intent.remoteProjectId,
5599
- });
5363
+ branch = await driver.branchView(intent.remoteProjectId, intent.branch);
5600
5364
  }
5601
5365
  catch (err) {
5602
5366
  const mutation = mutationError === undefined ? '' : `; delete attempt: ${safeDriverErrorText(mutationError)}`;
@@ -5700,7 +5464,7 @@ export class AgentManager {
5700
5464
  if (task.prNumber === undefined)
5701
5465
  throw new Error(`task ${taskId} has no bound PR`);
5702
5466
  const prNumber = task.prNumber;
5703
- const [row] = await driver.runOp('prView', { prNumber });
5467
+ const row = await driver.prView(prNumber);
5704
5468
  const binding = checkOpenPrBinding(row, { branch: task.branch, expectedBase: task.baseBranch });
5705
5469
  if (!binding.ok)
5706
5470
  throw new Error(`merge blocked: binding ${binding.reason}`);
@@ -5747,13 +5511,13 @@ export class AgentManager {
5747
5511
  throw new Error(`merge blocked: task left its merge gate (status=${fresh?.status ?? 'gone'})`);
5748
5512
  }
5749
5513
  try {
5750
- await driver.runOp('merge', { prNumber, expectedHeadSha: opts.expectedHeadSha });
5514
+ await driver.mergePr(prNumber, opts.expectedHeadSha);
5751
5515
  }
5752
5516
  catch (err) {
5753
5517
  if (err instanceof DriverOpError && err.info.errorClass === 'MERGE_BLOCKED') {
5754
5518
  let mergeStatus;
5755
5519
  try {
5756
- const [fresh] = await driver.runOp('prView', { prNumber });
5520
+ const fresh = await driver.prView(prNumber);
5757
5521
  if (typeof fresh?.detailedMergeStatus === 'string')
5758
5522
  mergeStatus = fresh.detailedMergeStatus;
5759
5523
  }
@@ -5774,9 +5538,7 @@ export class AgentManager {
5774
5538
  taskBelongsToPlatformEntry(task, entryBinding) {
5775
5539
  const binding = task.platformBinding;
5776
5540
  return binding !== undefined
5777
- && binding.mode === entryBinding.mode
5778
- && binding.repoKey === entryBinding.repoKey
5779
- && binding.tool === entryBinding.tool;
5541
+ && binding.repoKey === entryBinding.repoKey;
5780
5542
  }
5781
5543
  async listTasksForPlatformEntry(projectId) {
5782
5544
  const project = this.getProjectConfig(projectId);
@@ -5784,9 +5546,7 @@ export class AgentManager {
5784
5546
  return [];
5785
5547
  const key = repoIdentityKey(project.repo);
5786
5548
  const entryBinding = {
5787
- mode: 'git',
5788
5549
  repoKey: key,
5789
- tool: resolveProjectTool(project) ?? '',
5790
5550
  };
5791
5551
  const ids = new Set(this.config.project.filter(p => repoIdentityKey(p.repo) === key).map(p => p.id));
5792
5552
  const tasks = await this.taskStore.listStrict();
@@ -5973,8 +5733,7 @@ export class AgentManager {
5973
5733
  if (this.repoStoreFactory) {
5974
5734
  return this.repoStoreFactory(runner, project.repo, agent.mode, host, this.repoCache, agent.id, agent.workdir);
5975
5735
  }
5976
- const cloneViaGh = resolveProjectTool(project) === 'gh';
5977
- return new RepoStore(runner, project.repo, agent.mode, host, this.repoCache, agent.id, agent.workdir, cloneViaGh);
5736
+ return new RepoStore(runner, project.repo, agent.mode, host, this.repoCache, agent.id, agent.workdir);
5978
5737
  }
5979
5738
  async ensureWorkdir(agent, project, runner) {
5980
5739
  const repoStore = this.createRepoStore(agent, project, runner);
@@ -6444,6 +6203,9 @@ export class AgentManager {
6444
6203
  await tmux.clearComposerDraft(pane);
6445
6204
  await waitReady();
6446
6205
  await assertSessionUnchanged();
6206
+ if (command === '/clear') {
6207
+ await this.setSessionOptions(tmux, agentId, pane.session, [[TASK_CONTEXT_SESSION_OPTION, '']]);
6208
+ }
6447
6209
  await tmux.sendKeysLiteral(pane, command);
6448
6210
  await tmux.sendEnter(pane);
6449
6211
  guardHandedOff = true;
@@ -6544,7 +6306,7 @@ export class AgentManager {
6544
6306
  `${phase} queued for redispatch when idle (${(err.message.split('\n')[0])})`);
6545
6307
  return new EnsureSessionError({ createdSession, agentId, handled: true, busyPending: true }, `QA REPL busy; ${phase} dispatch for task ${taskId} queued for redispatch when idle`);
6546
6308
  }
6547
- async clearRuntimeForDispatchBoundary(tmux, pane, agentId, runtime, revalidate) {
6309
+ async clearRuntimeForDispatchBoundary(tmux, pane, agentId, runtime, sessionRef, revalidate) {
6548
6310
  await this.acquireCompactGuard(agentId);
6549
6311
  try {
6550
6312
  await this.waitForReplPromptReady(tmux, pane, runtime, this.cleanComposerWaitMs, { stableIdle: true });
@@ -6554,6 +6316,7 @@ export class AgentManager {
6554
6316
  timeoutMs: this.dispatchSettleTimeoutMs,
6555
6317
  });
6556
6318
  const baselineTitle = await tmux.readPaneTitle(pane);
6319
+ await this.setSessionOptions(tmux, agentId, sessionRef, [[TASK_CONTEXT_SESSION_OPTION, '']]);
6557
6320
  await tmux.sendKeysLiteral(pane, '/clear');
6558
6321
  await tmux.sendEnter(pane);
6559
6322
  await tmux.waitSubmitAck(pane, baseline, runtime, {
@@ -6952,6 +6715,7 @@ export class AgentManager {
6952
6715
  if (!lockState || lockState.taskId !== taskId || lockState.lockToken !== lockToken)
6953
6716
  return false;
6954
6717
  await assertOwner();
6718
+ const reuseTaskContext = (await this.taskContextForSession(tmux, agentId, ensure)) === taskId;
6955
6719
  try {
6956
6720
  await this.waitForReplPromptReady(tmux, pane, agent.runtime, this.cleanComposerWaitMs, { stableIdle: true });
6957
6721
  }
@@ -6978,10 +6742,8 @@ export class AgentManager {
6978
6742
  await this.clearBranchLocalCleaned(taskId);
6979
6743
  }
6980
6744
  await assertOwner();
6981
- if (!ensure.freshRuntime) {
6982
- await this.clearRuntimeForDispatchBoundary(tmux, pane, agentId, agent.runtime, assertOwner);
6983
- if (ensure.skillsStale)
6984
- await this.tagSessionSkillsVersion(tmux, agentId, ensure.sessionRef);
6745
+ if (!ensure.freshRuntime && !reuseTaskContext) {
6746
+ await this.clearRuntimeForDispatchBoundary(tmux, pane, agentId, agent.runtime, ensure.sessionRef, assertOwner);
6985
6747
  }
6986
6748
  }
6987
6749
  catch (err) {
@@ -7039,14 +6801,14 @@ export class AgentManager {
7039
6801
  try {
7040
6802
  const imagePaths = await this.imagePathsForDispatch(runner, task, phase);
7041
6803
  const taskForPrompt = await this.ensureGitBaseSnapshot(task, phase);
7042
- const platformCli = this.platformCliContextOf(taskForPrompt);
6804
+ const platform = this.platformPromptContextOf(taskForPrompt);
7043
6805
  prompt = buildPromptInline({
7044
6806
  task: taskForPrompt,
7045
6807
  phase,
7046
6808
  agent,
7047
6809
  workdir: dispatchWorkdir,
7048
- skillRegistry: this.skillRegistry,
7049
- ...(platformCli ? { platformCli } : {}),
6810
+ includeTaskContext: ensure.freshRuntime || !reuseTaskContext,
6811
+ ...(platform ? { platform } : {}),
7050
6812
  ...(promptSignalToken ? { signalToken: promptSignalToken } : {}),
7051
6813
  ...(imagePaths.length ? { imagePaths } : {}),
7052
6814
  });
@@ -7056,9 +6818,6 @@ export class AgentManager {
7056
6818
  if (err instanceof PromptSizeError) {
7057
6819
  throw new DispatchTerminalError('prompt_too_large', err.message);
7058
6820
  }
7059
- if (err instanceof RequiredSkillsMissingError) {
7060
- throw new DispatchTerminalError('required_skills_missing', err.message);
7061
- }
7062
6821
  throw err;
7063
6822
  }
7064
6823
  const taskFresh = await this.taskStore.get(taskId);
@@ -7159,6 +6918,9 @@ export class AgentManager {
7159
6918
  console.warn(`[AgentManager] startSession[${phase}]: injection fenced off (pass superseded) for task ${taskId}`);
7160
6919
  return false;
7161
6920
  }
6921
+ if (!reuseTaskContext) {
6922
+ await this.rememberTaskContext(tmux, agentId, ensure.sessionRef, taskId);
6923
+ }
7162
6924
  if (opts.dispatchPassToken !== undefined) {
7163
6925
  this.clearPendingDispatchRetryIfMatches(taskId, { agentId, signalToken: opts.dispatchPassToken });
7164
6926
  }
@@ -7522,6 +7284,7 @@ export class AgentManager {
7522
7284
  return false;
7523
7285
  }
7524
7286
  await this.assertTaskLockOwner(agentId, taskId, lockToken);
7287
+ const reuseTaskContext = (await this.taskContextForSession(tmux, agentId, ensure)) === taskId;
7525
7288
  const verifiedWorkdir = stateAfterEnsure.workdir;
7526
7289
  if (!verifiedWorkdir
7527
7290
  || verifiedWorkdir !== ensure.workdir
@@ -7565,14 +7328,14 @@ export class AgentManager {
7565
7328
  try {
7566
7329
  const imagePaths = await this.imagePathsForDispatch(runner, task, phase);
7567
7330
  const taskForPrompt = await this.ensureGitBaseSnapshot(task, phase);
7568
- const platformCli = this.platformCliContextOf(taskForPrompt);
7331
+ const platform = this.platformPromptContextOf(taskForPrompt);
7569
7332
  prompt = buildPromptInline({
7570
7333
  task: taskForPrompt,
7571
7334
  phase,
7572
7335
  agent,
7573
7336
  workdir: verifiedWorkdir,
7574
- skillRegistry: this.skillRegistry,
7575
- ...(platformCli ? { platformCli } : {}),
7337
+ includeTaskContext: ensure.freshRuntime || !reuseTaskContext,
7338
+ ...(platform ? { platform } : {}),
7576
7339
  ...(signalToken ? { signalToken } : {}),
7577
7340
  ...(imagePaths.length ? { imagePaths } : {}),
7578
7341
  });
@@ -7581,9 +7344,6 @@ export class AgentManager {
7581
7344
  if (err instanceof PromptSizeError) {
7582
7345
  throw new DispatchTerminalError('prompt_too_large', err.message);
7583
7346
  }
7584
- if (err instanceof RequiredSkillsMissingError) {
7585
- throw new DispatchTerminalError('required_skills_missing', err.message);
7586
- }
7587
7347
  throw err;
7588
7348
  }
7589
7349
  const taskFresh = await this.taskStore.get(taskId);
@@ -7641,8 +7401,8 @@ export class AgentManager {
7641
7401
  return false;
7642
7402
  }
7643
7403
  const guardBeforeInject = opts.guardBeforeInject ?? (phase === 'post-approve' ? postApprovePassStillLive : undefined);
7644
- if (!ensure.freshRuntime && ensure.skillsStale) {
7645
- const revalidateSkillRefresh = async () => {
7404
+ if (!ensure.freshRuntime && !reuseTaskContext) {
7405
+ const revalidateContextReset = async () => {
7646
7406
  const [currentTask, currentAgent] = await Promise.all([
7647
7407
  this.taskStore.get(taskId),
7648
7408
  this.agentStore.get(agentId),
@@ -7657,15 +7417,14 @@ export class AgentManager {
7657
7417
  || !bindingStillDispatchable
7658
7418
  || currentAgent?.lockToken !== lockToken
7659
7419
  || currentAgent.workdir !== verifiedWorkdir) {
7660
- throw new Error(`Task or agent ownership changed while refreshing skills for ${phase} dispatch`);
7420
+ throw new Error(`Task or agent ownership changed while resetting context for ${phase} dispatch`);
7661
7421
  }
7662
7422
  await this.assertTaskLockOwner(agentId, taskId, lockToken);
7663
7423
  if (guardBeforeInject && !(await guardBeforeInject())) {
7664
- throw new Error(`Task generation changed while refreshing skills for ${phase} dispatch`);
7424
+ throw new Error(`Task generation changed while resetting context for ${phase} dispatch`);
7665
7425
  }
7666
7426
  };
7667
- await this.clearRuntimeForDispatchBoundary(tmux, pane, agentId, agent.runtime, revalidateSkillRefresh);
7668
- await this.tagSessionSkillsVersion(tmux, agentId, ensure.sessionRef);
7427
+ await this.clearRuntimeForDispatchBoundary(tmux, pane, agentId, agent.runtime, ensure.sessionRef, revalidateContextReset);
7669
7428
  }
7670
7429
  if (opts.armBeforeInject && !(await opts.armBeforeInject())) {
7671
7430
  return false;
@@ -7708,6 +7467,9 @@ export class AgentManager {
7708
7467
  console.warn(`[AgentManager] continueSession[${phase}]: pre-paste guard rejected the inject for task ${taskId}; skipping`);
7709
7468
  return false;
7710
7469
  }
7470
+ if (!reuseTaskContext) {
7471
+ await this.rememberTaskContext(tmux, agentId, ensure.sessionRef, taskId);
7472
+ }
7711
7473
  return true;
7712
7474
  }
7713
7475
  async rollbackUndeliveredBootstrap(state) {
@@ -8256,14 +8018,11 @@ export class AgentManager {
8256
8018
  previewBytes = this.previewPromptBytesForTaskInput(projectId, input);
8257
8019
  }
8258
8020
  catch (err) {
8259
- if (err instanceof RequiredSkillsMissingError) {
8260
- throw new ApiError(500, err.message);
8261
- }
8262
8021
  throw new ApiError(400, err instanceof Error ? err.message : String(err));
8263
8022
  }
8264
8023
  if (previewBytes > MAX_PROMPT_BYTES_ROUTE_LIMIT) {
8265
8024
  throw new ApiError(400, `estimated prompt size ${previewBytes} bytes exceeds ${MAX_PROMPT_BYTES_ROUTE_LIMIT} limit; ` +
8266
- `reduce task description or remove some skills from AGENT_PHASES[develop]`);
8025
+ `reduce the task description or platform workflow instructions`);
8267
8026
  }
8268
8027
  }
8269
8028
  gitReviewClaimMatches(task, expected) {
@@ -9669,7 +9428,7 @@ export class AgentManager {
9669
9428
  let opposite = false;
9670
9429
  for (const scan of scans) {
9671
9430
  for (const row of scan.rows) {
9672
- if (row.system === true || versionTimeOf(row) === undefined)
9431
+ if (versionTimeOf(row) === undefined)
9673
9432
  continue;
9674
9433
  for (const marker of rowTokens(row)) {
9675
9434
  if (dead.has(marker.token))
@@ -9694,9 +9453,7 @@ export class AgentManager {
9694
9453
  return 'unknown';
9695
9454
  if (deadTokens(scans).has(entry.data.token))
9696
9455
  return 'dead';
9697
- if (scans.some(scan => scan.rows.some(row => row.system !== true
9698
- && versionTimeOf(row) !== undefined
9699
- && row.bodyDigest === expectedDigest)))
9456
+ if (scans.some(scan => scan.rows.some(row => versionTimeOf(row) !== undefined && row.bodyDigest === expectedDigest)))
9700
9457
  return 'present';
9701
9458
  return 'absent';
9702
9459
  }
@@ -9753,10 +9510,7 @@ export class AgentManager {
9753
9510
  entry = claimedEntry;
9754
9511
  let writeFailure;
9755
9512
  try {
9756
- await driver.runOp('comment', {
9757
- prNumber: entry.data.prNumber,
9758
- body: this.codeVerdictCommentBody(entry),
9759
- });
9513
+ await driver.postComment(entry.data.prNumber, this.codeVerdictCommentBody(entry));
9760
9514
  }
9761
9515
  catch (error) {
9762
9516
  writeFailure = { error };
@@ -9956,10 +9710,7 @@ export class AgentManager {
9956
9710
  entry = claimedEntry;
9957
9711
  let writeFailure;
9958
9712
  try {
9959
- await driver.runOp('comment', {
9960
- prNumber: entry.data.prNumber,
9961
- body: this.specVerdictCommentBody(entry),
9962
- });
9713
+ await driver.postComment(entry.data.prNumber, this.specVerdictCommentBody(entry));
9963
9714
  }
9964
9715
  catch (error) {
9965
9716
  writeFailure = { error };