@adhdev/daemon-core 0.9.54 → 0.9.56

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.
package/dist/index.mjs CHANGED
@@ -1940,6 +1940,7 @@ function resolveCliAdapterConfig(provider) {
1940
1940
  sendDelayMs: typeof provider.sendDelayMs === "number" ? Math.max(0, provider.sendDelayMs) : 0,
1941
1941
  sendKey: typeof provider.sendKey === "string" && provider.sendKey.length > 0 ? provider.sendKey : "\r",
1942
1942
  submitStrategy: provider.submitStrategy === "immediate" ? "immediate" : "wait_for_echo",
1943
+ requirePromptEchoBeforeSubmit: provider.requirePromptEchoBeforeSubmit === true,
1943
1944
  providerResolutionMeta: {
1944
1945
  type: provider.type,
1945
1946
  name: provider.name,
@@ -2204,6 +2205,7 @@ var init_provider_cli_adapter = __esm({
2204
2205
  this.sendDelayMs = resolvedConfig.sendDelayMs;
2205
2206
  this.sendKey = resolvedConfig.sendKey;
2206
2207
  this.submitStrategy = resolvedConfig.submitStrategy;
2208
+ this.requirePromptEchoBeforeSubmit = resolvedConfig.requirePromptEchoBeforeSubmit;
2207
2209
  this.providerResolutionMeta = resolvedConfig.providerResolutionMeta;
2208
2210
  this.cliScripts = provider.scripts || {};
2209
2211
  const scriptNames = listCliScriptNames(this.cliScripts);
@@ -2300,6 +2302,12 @@ var init_provider_cli_adapter = __esm({
2300
2302
  accumulatedRawBuffer = "";
2301
2303
  /** Current visible terminal screen snapshot */
2302
2304
  terminalScreen = new TerminalScreen(24, 80);
2305
+ static MAX_RESPONSE_BUFFER = 8e3;
2306
+ static MAX_RECENT_OUTPUT_BUFFER = 1e3;
2307
+ responseBufferDroppedChars = 0;
2308
+ recentOutputDroppedChars = 0;
2309
+ accumulatedBufferDroppedChars = 0;
2310
+ accumulatedRawBufferDroppedChars = 0;
2303
2311
  /** Max accumulated buffer size. Sized to comfortably hold a single long
2304
2312
  * Hermes turn (tool calls + reasoning + final bubble) without the
2305
2313
  * rolling window pushing the turn's ╭─ opening line out of view. */
@@ -2317,6 +2325,23 @@ var init_provider_cli_adapter = __esm({
2317
2325
  providerResolutionMeta;
2318
2326
  static FINISH_RETRY_DELAY_MS = 300;
2319
2327
  static MAX_FINISH_RETRIES = 2;
2328
+ getBufferState() {
2329
+ const build = (droppedChars, maxChars) => droppedChars > 0 ? { truncated: true, droppedChars, maxChars } : void 0;
2330
+ const responseBuffer = build(this.responseBufferDroppedChars, _ProviderCliAdapter.MAX_RESPONSE_BUFFER);
2331
+ const recentOutputBuffer = build(this.recentOutputDroppedChars, _ProviderCliAdapter.MAX_RECENT_OUTPUT_BUFFER);
2332
+ const accumulatedBuffer = build(this.accumulatedBufferDroppedChars, _ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
2333
+ const accumulatedRawBuffer = build(this.accumulatedRawBufferDroppedChars, _ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
2334
+ if (!responseBuffer && !recentOutputBuffer && !accumulatedBuffer && !accumulatedRawBuffer) return void 0;
2335
+ return {
2336
+ ...responseBuffer ? { responseBuffer } : {},
2337
+ ...recentOutputBuffer ? { recentOutputBuffer } : {},
2338
+ ...accumulatedBuffer ? { accumulatedBuffer } : {},
2339
+ ...accumulatedRawBuffer ? { accumulatedRawBuffer } : {}
2340
+ };
2341
+ }
2342
+ recordBoundedAppendDrop(previousLength, appendedLength, nextLength) {
2343
+ return Math.max(0, previousLength + appendedLength - nextLength);
2344
+ }
2320
2345
  buildCommittedMessagesActivitySignature() {
2321
2346
  const last = this.committedMessages[this.committedMessages.length - 1];
2322
2347
  return [
@@ -2500,6 +2525,7 @@ var init_provider_cli_adapter = __esm({
2500
2525
  sendDelayMs;
2501
2526
  sendKey;
2502
2527
  submitStrategy;
2528
+ requirePromptEchoBeforeSubmit;
2503
2529
  static SCRIPT_STATUS_DEBOUNCE_MS = 3e3;
2504
2530
  /** Inject CLI scripts after construction (e.g. when resolved by ProviderLoader) */
2505
2531
  setCliScripts(scripts) {
@@ -2681,7 +2707,9 @@ var init_provider_cli_adapter = __esm({
2681
2707
  this.scheduleStartupSettleCheck();
2682
2708
  }
2683
2709
  if (this.isWaitingForResponse && cleanData) {
2684
- this.responseBuffer = appendBoundedText(this.responseBuffer, cleanData, 8e3);
2710
+ const previousResponseLen = this.responseBuffer.length;
2711
+ this.responseBuffer = appendBoundedText(this.responseBuffer, cleanData, _ProviderCliAdapter.MAX_RESPONSE_BUFFER);
2712
+ this.responseBufferDroppedChars += this.recordBoundedAppendDrop(previousResponseLen, cleanData.length, this.responseBuffer.length);
2685
2713
  }
2686
2714
  if (cleanData.trim()) {
2687
2715
  if (this.serverConn) {
@@ -2690,14 +2718,19 @@ var init_provider_cli_adapter = __esm({
2690
2718
  this.logBuffer.push({ message: cleanData.trim(), level: "info" });
2691
2719
  }
2692
2720
  }
2721
+ const prevRecentLen = this.recentOutputBuffer.length;
2693
2722
  const prevAccumulatedLen = this.accumulatedBuffer.length;
2694
2723
  const prevAccumulatedRawLen = this.accumulatedRawBuffer.length;
2695
- this.recentOutputBuffer = appendBoundedText(this.recentOutputBuffer, cleanData, 1e3);
2724
+ this.recentOutputBuffer = appendBoundedText(this.recentOutputBuffer, cleanData, _ProviderCliAdapter.MAX_RECENT_OUTPUT_BUFFER);
2696
2725
  this.accumulatedBuffer = appendBoundedText(this.accumulatedBuffer, cleanData, _ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
2697
2726
  this.accumulatedRawBuffer = appendBoundedText(this.accumulatedRawBuffer, rawData, _ProviderCliAdapter.MAX_ACCUMULATED_BUFFER);
2727
+ const droppedRecent = this.recordBoundedAppendDrop(prevRecentLen, cleanData.length, this.recentOutputBuffer.length);
2728
+ const droppedClean = this.recordBoundedAppendDrop(prevAccumulatedLen, cleanData.length, this.accumulatedBuffer.length);
2729
+ const droppedRaw = this.recordBoundedAppendDrop(prevAccumulatedRawLen, rawData.length, this.accumulatedRawBuffer.length);
2730
+ this.recentOutputDroppedChars += droppedRecent;
2731
+ this.accumulatedBufferDroppedChars += droppedClean;
2732
+ this.accumulatedRawBufferDroppedChars += droppedRaw;
2698
2733
  if (this.currentTurnScope) {
2699
- const droppedClean = prevAccumulatedLen + cleanData.length - this.accumulatedBuffer.length;
2700
- const droppedRaw = prevAccumulatedRawLen + rawData.length - this.accumulatedRawBuffer.length;
2701
2734
  if (droppedClean > 0) {
2702
2735
  this.currentTurnScope.bufferStart = Math.max(0, this.currentTurnScope.bufferStart - droppedClean);
2703
2736
  }
@@ -3527,13 +3560,15 @@ var init_provider_cli_adapter = __esm({
3527
3560
  effectiveModal = parsedModal;
3528
3561
  }
3529
3562
  }
3563
+ const bufferState = this.getBufferState();
3530
3564
  return {
3531
3565
  status: effectiveStatus,
3532
3566
  messages: [...this.committedMessages],
3533
3567
  workingDir: this.workingDir,
3534
3568
  activeModal: effectiveModal,
3535
3569
  errorMessage: this.parseErrorMessage || void 0,
3536
- errorReason: this.parseErrorMessage ? "parse_error" : void 0
3570
+ errorReason: this.parseErrorMessage ? "parse_error" : void 0,
3571
+ ...bufferState ? { bufferState } : {}
3537
3572
  };
3538
3573
  }
3539
3574
  seedCommittedMessages(messages) {
@@ -3715,10 +3750,12 @@ var init_provider_cli_adapter = __esm({
3715
3750
  messages: hydratedMessages,
3716
3751
  activeModal: parsed.activeModal ?? this.activeModal,
3717
3752
  providerSessionId: typeof parsed.providerSessionId === "string" ? parsed.providerSessionId : void 0,
3753
+ ...this.getBufferState() ? { bufferState: this.getBufferState() } : {},
3718
3754
  ...this.providerOwnsTranscript() ? { transcriptAuthority: "provider", coverage: this.shouldUseFullProviderTranscriptContext() ? "full" : "tail" } : {}
3719
3755
  };
3720
3756
  } else {
3721
3757
  const messages = [...this.committedMessages];
3758
+ const bufferState = this.getBufferState();
3722
3759
  result = {
3723
3760
  id: "cli_session",
3724
3761
  status: this.currentStatus,
@@ -3729,7 +3766,8 @@ var init_provider_cli_adapter = __esm({
3729
3766
  index: typeof message.index === "number" ? message.index : index,
3730
3767
  receivedAt: typeof message.receivedAt === "number" ? message.receivedAt : message.timestamp
3731
3768
  })),
3732
- activeModal: this.activeModal
3769
+ activeModal: this.activeModal,
3770
+ ...bufferState ? { bufferState } : {}
3733
3771
  };
3734
3772
  }
3735
3773
  const hasVisibleAssistantMessage = Array.isArray(result?.messages) && result.messages.some((message) => message?.role === "assistant" && typeof message?.content === "string" && message.content.trim());
@@ -4020,6 +4058,22 @@ var init_provider_cli_adapter = __esm({
4020
4058
  }
4021
4059
  }
4022
4060
  if (elapsed >= state.maxEchoWaitMs) {
4061
+ const diagnostic = {
4062
+ elapsed,
4063
+ maxEchoWaitMs: state.maxEchoWaitMs,
4064
+ submitDelayMs: state.submitDelayMs,
4065
+ promptSnippet: state.normalizedPromptSnippet,
4066
+ requirePromptEchoBeforeSubmit: this.requirePromptEchoBeforeSubmit,
4067
+ screenText: summarizeCliTraceText(screenText, 1e3)
4068
+ };
4069
+ this.recordTrace("submit_echo_missing", diagnostic);
4070
+ if (this.requirePromptEchoBeforeSubmit) {
4071
+ const message = `${this.cliName} prompt echo was not observed on the PTY screen before submit`;
4072
+ LOG.warn("CLI", `[${this.cliType}] ${message} elapsed=${elapsed}ms maxEchoWaitMs=${state.maxEchoWaitMs} screen=${JSON.stringify(diagnostic.screenText).slice(0, 240)}`);
4073
+ completion.rejectOnce(new Error(message));
4074
+ return;
4075
+ }
4076
+ LOG.warn("CLI", `[${this.cliType}] prompt echo was not observed before submit; sending submit key anyway elapsed=${elapsed}ms maxEchoWaitMs=${state.maxEchoWaitMs}`);
4023
4077
  this.submitSendKey(state, completion);
4024
4078
  return;
4025
4079
  }
@@ -4484,6 +4538,7 @@ var init_provider_cli_adapter = __esm({
4484
4538
  sendDelayMs: this.sendDelayMs,
4485
4539
  sendKey: this.sendKey,
4486
4540
  submitStrategy: this.submitStrategy,
4541
+ requirePromptEchoBeforeSubmit: this.requirePromptEchoBeforeSubmit,
4487
4542
  submitPendingUntil: this.submitPendingUntil,
4488
4543
  responseSettleIgnoreUntil: this.responseSettleIgnoreUntil,
4489
4544
  resizeSuppressUntil: this.resizeSuppressUntil,
@@ -5406,13 +5461,8 @@ var GIT_COMMAND_NAMES = /* @__PURE__ */ new Set([
5406
5461
  "git_checkpoint",
5407
5462
  "git_stash_push",
5408
5463
  "git_stash_pop",
5409
- "git_checkout_files"
5410
- ]);
5411
- var MUTATING_COMMAND_NAMES = /* @__PURE__ */ new Set([
5412
- "git_checkpoint",
5413
- "git_stash_push",
5414
- "git_stash_pop",
5415
- "git_checkout_files"
5464
+ "git_checkout_files",
5465
+ "git_remote_url"
5416
5466
  ]);
5417
5467
  var SNAPSHOT_REASONS = /* @__PURE__ */ new Set([
5418
5468
  "session_baseline",
@@ -5453,7 +5503,12 @@ function createDefaultGitCommandServices() {
5453
5503
  turnId
5454
5504
  }),
5455
5505
  compareSnapshots: ({ beforeSnapshotId, afterSnapshotId }) => defaultSnapshotStore.compare(beforeSnapshotId, afterSnapshotId),
5456
- getLog: ({ workspace, limit, path: filePath, since, until }) => getGitLog(workspace, { limit, path: filePath, since, until })
5506
+ getLog: ({ workspace, limit, path: filePath, since, until }) => getGitLog(workspace, { limit, path: filePath, since, until }),
5507
+ checkpoint: async ({ workspace, message, includeUntracked = false }) => gitCheckpoint(workspace, message, includeUntracked),
5508
+ stashPush: async ({ workspace, message, includeUntracked = false }) => gitStashPush(workspace, message, includeUntracked),
5509
+ stashPop: async ({ workspace, stashRef }) => gitStashPop(workspace, stashRef),
5510
+ checkoutFiles: async ({ workspace, paths }) => gitCheckoutFiles(workspace, paths),
5511
+ getRemoteUrl: async ({ workspace, remote = "origin" }) => gitGetRemoteUrl(workspace, remote)
5457
5512
  };
5458
5513
  }
5459
5514
  var defaultGitCommandServices = createDefaultGitCommandServices();
@@ -5517,9 +5572,6 @@ async function handleGitCommand(command, args, services = defaultGitCommandServi
5517
5572
  if (!isGitCommandName(command)) {
5518
5573
  return failure("invalid_args", `Unknown Git command: ${command}`);
5519
5574
  }
5520
- if (MUTATING_COMMAND_NAMES.has(command)) {
5521
- return failure("invalid_args", `${command} is not implemented in daemon-core read-only Git routing`);
5522
- }
5523
5575
  const workspaceResult = validateWorkspace2(args);
5524
5576
  if ("success" in workspaceResult) return workspaceResult;
5525
5577
  const { workspace } = workspaceResult;
@@ -5579,10 +5631,153 @@ async function handleGitCommand(command, args, services = defaultGitCommandServi
5579
5631
  }));
5580
5632
  return "success" in log ? log : { success: true, log };
5581
5633
  }
5634
+ case "git_checkpoint": {
5635
+ if (!services.checkpoint) return serviceNotImplemented(command);
5636
+ const msg = validateMutatingMessage(args?.message);
5637
+ if (typeof msg !== "string") return msg;
5638
+ const includeUntracked = Boolean(args?.includeUntracked);
5639
+ const checkpoint = await runService(() => services.checkpoint({ workspace, message: msg, includeUntracked }));
5640
+ return "success" in checkpoint ? checkpoint : { success: true, checkpoint };
5641
+ }
5642
+ case "git_stash_push": {
5643
+ if (!services.stashPush) return serviceNotImplemented(command);
5644
+ const msg = validateMutatingMessage(args?.message);
5645
+ if (typeof msg !== "string") return msg;
5646
+ const includeUntracked = Boolean(args?.includeUntracked);
5647
+ const stash = await runService(() => services.stashPush({ workspace, message: msg, includeUntracked }));
5648
+ return "success" in stash ? stash : { success: true, stash };
5649
+ }
5650
+ case "git_stash_pop": {
5651
+ if (!services.stashPop) return serviceNotImplemented(command);
5652
+ const stashRef = optionalString(args?.stashRef);
5653
+ if (stashRef !== void 0 && !/^stash@\{\d+\}$/.test(stashRef)) {
5654
+ return failure("invalid_args", "stashRef must match stash@{N} format");
5655
+ }
5656
+ const popResult = await runService(() => services.stashPop({ workspace, stashRef }));
5657
+ if (popResult !== void 0 && "success" in popResult) return popResult;
5658
+ return { success: true, stashPopped: true };
5659
+ }
5660
+ case "git_checkout_files": {
5661
+ if (!services.checkoutFiles) return serviceNotImplemented(command);
5662
+ const paths = args?.paths;
5663
+ if (!Array.isArray(paths) || paths.length === 0) {
5664
+ return failure("invalid_args", "paths must be a non-empty array");
5665
+ }
5666
+ if (paths.length > 50) {
5667
+ return failure("invalid_args", "paths array exceeds maximum of 50 entries");
5668
+ }
5669
+ const checkoutResult = await runService(() => services.checkoutFiles({ workspace, paths }));
5670
+ return "success" in checkoutResult ? checkoutResult : { success: true, checkedOut: checkoutResult.checkedOut };
5671
+ }
5672
+ case "git_remote_url": {
5673
+ if (!services.getRemoteUrl) return serviceNotImplemented(command);
5674
+ const remote = typeof args?.remote === "string" && args.remote.trim() ? args.remote.trim() : "origin";
5675
+ const remoteResult = await runService(() => services.getRemoteUrl({ workspace, remote }));
5676
+ if ("success" in remoteResult) return remoteResult;
5677
+ return { success: true, remoteUrl: remoteResult.remoteUrl, remote: remoteResult.remote };
5678
+ }
5582
5679
  default:
5583
5680
  return failure("invalid_args", `Unknown Git command: ${command}`);
5584
5681
  }
5585
5682
  }
5683
+ function validateMutatingMessage(value) {
5684
+ if (typeof value !== "string" || !value.trim()) {
5685
+ return failure("invalid_args", "message must be a non-empty string");
5686
+ }
5687
+ const msg = value.trim();
5688
+ if (msg.length > 200) {
5689
+ return failure("invalid_args", "message must be 200 characters or fewer");
5690
+ }
5691
+ return msg;
5692
+ }
5693
+ async function gitCheckpoint(workspace, message, includeUntracked) {
5694
+ const repo = await resolveGitRepository(workspace);
5695
+ const repoRoot = repo.repoRoot;
5696
+ const statusResult = await getGitRepoStatus(workspace);
5697
+ if (statusResult.hasConflicts) {
5698
+ throw new GitCommandError("conflict", "Repository has conflicts \u2014 resolve before checkpointing");
5699
+ }
5700
+ const addArgs = includeUntracked ? ["-A"] : ["-u"];
5701
+ await runGit(repo, ["add", ...addArgs], { cwd: repoRoot });
5702
+ const fullMsg = `adhdev: checkpoint ${message}`;
5703
+ let commitSha;
5704
+ try {
5705
+ await runGit(repo, ["commit", "-m", fullMsg], { cwd: repoRoot });
5706
+ const revResult = await runGit(repo, ["rev-parse", "HEAD"], { cwd: repoRoot });
5707
+ commitSha = revResult.stdout.trim();
5708
+ } catch (err) {
5709
+ const output = (err?.stdout || "") + (err?.stderr || "");
5710
+ if (/nothing to commit/i.test(output)) {
5711
+ throw new GitCommandError("git_command_failed", "Nothing to commit");
5712
+ }
5713
+ throw err;
5714
+ }
5715
+ return {
5716
+ workspace: repo.workspace,
5717
+ repoRoot,
5718
+ isGitRepo: true,
5719
+ commit: commitSha,
5720
+ message: fullMsg,
5721
+ lastCheckedAt: Date.now()
5722
+ };
5723
+ }
5724
+ async function gitStashPush(workspace, message, includeUntracked) {
5725
+ const repo = await resolveGitRepository(workspace);
5726
+ const repoRoot = repo.repoRoot;
5727
+ const stashArgs = ["stash", "push", "-m", message];
5728
+ if (includeUntracked) stashArgs.push("--include-untracked");
5729
+ const result = await runGit(repo, stashArgs, { cwd: repoRoot });
5730
+ if (/No local changes to save/i.test(result.stdout + result.stderr)) {
5731
+ throw new GitCommandError("git_command_failed", "Nothing to stash");
5732
+ }
5733
+ return {
5734
+ workspace: repo.workspace,
5735
+ repoRoot,
5736
+ isGitRepo: true,
5737
+ stashRef: "stash@{0}",
5738
+ message,
5739
+ lastCheckedAt: Date.now()
5740
+ };
5741
+ }
5742
+ async function gitStashPop(workspace, stashRef) {
5743
+ const repo = await resolveGitRepository(workspace);
5744
+ const repoRoot = repo.repoRoot;
5745
+ const popArgs = stashRef ? ["stash", "pop", stashRef] : ["stash", "pop"];
5746
+ await runGit(repo, popArgs, { cwd: repoRoot });
5747
+ }
5748
+ async function gitCheckoutFiles(workspace, paths) {
5749
+ const repo = await resolveGitRepository(workspace);
5750
+ const repoRoot = repo.repoRoot;
5751
+ const normalizedPaths = [];
5752
+ for (const p of paths) {
5753
+ if (typeof p !== "string" || !p.trim() || p.includes("\0")) {
5754
+ throw new GitCommandError("invalid_args", `Invalid path: ${String(p)}`);
5755
+ }
5756
+ if (path3.isAbsolute(p)) {
5757
+ throw new GitCommandError("invalid_args", `Path must be repository-relative, not absolute: ${p}`);
5758
+ }
5759
+ const normalized = path3.normalize(p.trim()).split(path3.sep).join("/");
5760
+ if (normalized.startsWith("../") || normalized === "..") {
5761
+ throw new GitCommandError("path_outside_repo", `Path is outside repository root: ${p}`);
5762
+ }
5763
+ const absolutePath = path3.resolve(repoRoot, normalized);
5764
+ if (!isPathInside(repoRoot, absolutePath)) {
5765
+ throw new GitCommandError("path_outside_repo", `Path is outside repository root: ${p}`);
5766
+ }
5767
+ normalizedPaths.push(normalized);
5768
+ }
5769
+ await runGit(repo, ["checkout", "--", ...normalizedPaths], { cwd: repoRoot });
5770
+ return { checkedOut: normalizedPaths };
5771
+ }
5772
+ async function gitGetRemoteUrl(workspace, remote) {
5773
+ const repo = await resolveGitRepository(workspace);
5774
+ const result = await runGit(repo, ["remote", "get-url", remote], { cwd: repo.repoRoot });
5775
+ const remoteUrl = result.stdout.trim();
5776
+ if (!remoteUrl) {
5777
+ throw new GitCommandError("git_command_failed", `Remote '${remote}' has no URL`);
5778
+ }
5779
+ return { remoteUrl, remote };
5780
+ }
5586
5781
  function formatOptionalGitLogRangeArg(flag, value) {
5587
5782
  return value ? [`${flag}=${value}`] : [];
5588
5783
  }
@@ -5641,6 +5836,27 @@ function validateGitLogPath(repoRoot, filePath) {
5641
5836
  return normalized;
5642
5837
  }
5643
5838
 
5839
+ // src/git/turn-snapshot-tracker.ts
5840
+ var BUSY_STATUSES = /* @__PURE__ */ new Set(["streaming", "waiting_approval"]);
5841
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["idle", "error"]);
5842
+ var TurnSnapshotTracker = class {
5843
+ lastStatus = /* @__PURE__ */ new Map();
5844
+ onTurnCompleted;
5845
+ constructor(onTurnCompleted) {
5846
+ this.onTurnCompleted = onTurnCompleted;
5847
+ }
5848
+ record(sessionId, status, workspace) {
5849
+ const prev = this.lastStatus.get(sessionId);
5850
+ this.lastStatus.set(sessionId, status);
5851
+ if (workspace && prev && BUSY_STATUSES.has(prev) && TERMINAL_STATUSES.has(status)) {
5852
+ this.onTurnCompleted({ sessionId, workspace });
5853
+ }
5854
+ }
5855
+ forget(sessionId) {
5856
+ this.lastStatus.delete(sessionId);
5857
+ }
5858
+ };
5859
+
5644
5860
  // src/index.ts
5645
5861
  init_config();
5646
5862
 
@@ -14528,6 +14744,16 @@ var DaemonCommandHandler = class {
14528
14744
  return result;
14529
14745
  }
14530
14746
  }
14747
+ if (cmd === "send_chat" && this._ctx.onBeforeSendChat) {
14748
+ const sessionId = this._currentRoute.session?.sessionId;
14749
+ const workspace = sessionId ? this._ctx.instanceManager?.getInstance(sessionId)?.getState?.()?.workspace : void 0;
14750
+ if (workspace && sessionId) {
14751
+ try {
14752
+ this._ctx.onBeforeSendChat({ workspace, sessionId });
14753
+ } catch {
14754
+ }
14755
+ }
14756
+ }
14531
14757
  try {
14532
14758
  result = await this.dispatch(cmd, args);
14533
14759
  this.logCommandEnd(cmd, result, startedAt);
@@ -17768,6 +17994,7 @@ var KNOWN_PROVIDER_FIELDS = /* @__PURE__ */ new Set([
17768
17994
  "sendDelayMs",
17769
17995
  "sendKey",
17770
17996
  "submitStrategy",
17997
+ "requirePromptEchoBeforeSubmit",
17771
17998
  "timeouts",
17772
17999
  "disableUpstream"
17773
18000
  ]);
@@ -29292,6 +29519,7 @@ async function initDaemonComponents(config) {
29292
29519
  providerLoader,
29293
29520
  instanceManager,
29294
29521
  sessionRegistry,
29522
+ gitCommandServices: createDefaultGitCommandServices(),
29295
29523
  onProviderSettingChanged: async (providerType) => {
29296
29524
  await refreshProviderAvailability(providerType);
29297
29525
  config.onStatusChange?.();
@@ -29299,7 +29527,8 @@ async function initDaemonComponents(config) {
29299
29527
  onProviderSourceConfigChanged: async () => {
29300
29528
  await refreshProviderAvailability();
29301
29529
  config.onStatusChange?.();
29302
- }
29530
+ },
29531
+ onBeforeSendChat: config.onBeforeSendChat
29303
29532
  });
29304
29533
  agentStreamManager = new DaemonAgentStreamManager(
29305
29534
  LOG.forComponent("AgentStream").asLogFn(),
@@ -29450,6 +29679,7 @@ export {
29450
29679
  ProviderLoader,
29451
29680
  STANDALONE_CDP_SCAN_INTERVAL_MS,
29452
29681
  SessionHostPtyTransportFactory,
29682
+ TurnSnapshotTracker,
29453
29683
  VersionArchive,
29454
29684
  appendRecentActivity,
29455
29685
  buildAssistantChatMessage,