@ours.network/fleet 0.16.0 → 0.17.1

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.
@@ -1,7 +1,7 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import { randomUUID } from 'node:crypto';
3
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
4
- import { isAbsolute, join, relative, resolve } from 'node:path';
3
+ import { existsSync, lstatSync, readFileSync, readlinkSync, realpathSync, writeFileSync, } from 'node:fs';
4
+ import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
5
5
  import { Readable, Writable } from 'node:stream';
6
6
  import * as acp from '@agentclientprotocol/sdk';
7
7
  import { normalizeSessionUpdate } from './conversation-normalizer.js';
@@ -13,9 +13,124 @@ const CANCEL_SETTLE_GRACE_MS = 15_000;
13
13
  const PERMISSION_TIMEOUT_MS = 10 * 60_000;
14
14
  /** Spec §4.3: 10-15 s before a vanished controller triggers the unattended policy. */
15
15
  const CONTROLLER_GRACE_MS = 12_000;
16
+ /** Bound safe-boundary waiting without turning a hung tool into cancellation. */
17
+ export const AFTER_TOOL_BOUNDARY_TIMEOUT_MS = 120_000;
18
+ const TERMINAL_TOOL_STATUSES = new Set(['completed', 'failed']);
16
19
  const SCHEDULED_LOOP_REDACTION = '[scheduled-loop content redacted]';
17
20
  const OWNER_COMMENTARY_REDACTION = '[assistant commentary redacted]';
21
+ const MAX_CANONICAL_SYMLINK_DEPTH = 40;
18
22
  const scheduledTurn = (turn) => turn?.origin?.kind === 'scheduled-loop';
23
+ /**
24
+ * Two matching realpath observations narrow the opportunity for a concurrent
25
+ * retarget, but are only an advisory consistency check: they do not lock the
26
+ * path. A mutation after the completed check remains an unavoidable TOCTOU
27
+ * window until ACP offers handle-based access.
28
+ */
29
+ function stableRealpath(path) {
30
+ const first = realpathSync.native(path);
31
+ const second = realpathSync.native(path);
32
+ return first === second ? first : undefined;
33
+ }
34
+ /** Distinguish an absent component from a dangling symlink at that component. */
35
+ function inspectMissingPath(path) {
36
+ try {
37
+ const stat = lstatSync(path);
38
+ // realpath said ENOENT but lstat found a non-link: the path changed while
39
+ // inspected, so there is no coherent canonical answer to trust.
40
+ if (!stat.isSymbolicLink())
41
+ return { kind: 'unsafe' };
42
+ try {
43
+ return { kind: 'symlink', target: resolve(dirname(path), readlinkSync(path)) };
44
+ }
45
+ catch {
46
+ return { kind: 'unsafe' };
47
+ }
48
+ }
49
+ catch (error) {
50
+ return error.code === 'ENOENT'
51
+ ? { kind: 'absent' }
52
+ : { kind: 'unsafe' };
53
+ }
54
+ }
55
+ /**
56
+ * Canonicalize an existing path, or a not-yet-created target through its
57
+ * nearest existing ancestor. Dangling links are followed explicitly because
58
+ * realpath reports their absent target as ENOENT. Only genuine absence is
59
+ * recoverable; loops, permissions, races, and every other error fail closed.
60
+ */
61
+ function canonicalTarget(path, symlinkDepth = 0) {
62
+ let probe = resolve(path);
63
+ const missing = [];
64
+ while (true) {
65
+ try {
66
+ let canonical = stableRealpath(probe);
67
+ if (!canonical)
68
+ return undefined;
69
+ // A component may have appeared while the ancestor search was in
70
+ // progress. Re-walk the suffix so a newly-created symlink is resolved,
71
+ // not treated as a lexical child of the old ancestor.
72
+ let logical = probe;
73
+ for (let i = 0; i < missing.length; i++) {
74
+ logical = resolve(logical, missing[i]);
75
+ try {
76
+ const appeared = stableRealpath(logical);
77
+ if (!appeared)
78
+ return undefined;
79
+ canonical = appeared;
80
+ }
81
+ catch (error) {
82
+ if (error.code !== 'ENOENT')
83
+ return undefined;
84
+ const inspected = inspectMissingPath(logical);
85
+ if (inspected.kind === 'unsafe')
86
+ return undefined;
87
+ if (inspected.kind === 'symlink') {
88
+ if (symlinkDepth >= MAX_CANONICAL_SYMLINK_DEPTH)
89
+ return undefined;
90
+ return canonicalTarget(resolve(inspected.target, ...missing.slice(i + 1)), symlinkDepth + 1);
91
+ }
92
+ return resolve(canonical, ...missing.slice(i));
93
+ }
94
+ }
95
+ return canonical;
96
+ }
97
+ catch (error) {
98
+ if (error.code !== 'ENOENT')
99
+ return undefined;
100
+ const inspected = inspectMissingPath(probe);
101
+ if (inspected.kind === 'unsafe')
102
+ return undefined;
103
+ if (inspected.kind === 'symlink') {
104
+ if (symlinkDepth >= MAX_CANONICAL_SYMLINK_DEPTH)
105
+ return undefined;
106
+ return canonicalTarget(resolve(inspected.target, ...missing), symlinkDepth + 1);
107
+ }
108
+ const parent = dirname(probe);
109
+ if (parent === probe)
110
+ return undefined;
111
+ missing.unshift(basename(probe));
112
+ probe = parent;
113
+ }
114
+ }
115
+ }
116
+ function canonicallyWithin(root, candidates) {
117
+ if (candidates.length === 0)
118
+ return false;
119
+ try {
120
+ const firstRoot = realpathSync.native(root);
121
+ const paths = candidates.map(canonicalTarget);
122
+ const secondRoot = realpathSync.native(root);
123
+ if (firstRoot !== secondRoot || paths.some(path => path === undefined))
124
+ return false;
125
+ return paths.every(candidate => {
126
+ const rel = relative(firstRoot, candidate);
127
+ return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
128
+ });
129
+ }
130
+ catch {
131
+ return false;
132
+ }
133
+ }
19
134
  /**
20
135
  * Map typed prompt provenance to the conversation ledger's source vocabulary.
21
136
  * Only operator-authored local sources may persist prompt bodies; external
@@ -95,9 +210,13 @@ export class AcpSession {
95
210
  runtimeModel;
96
211
  reasoningEffort;
97
212
  controllerCount = 0;
213
+ closing = false;
98
214
  /** Armed when the last controller detaches; unattended policy applies on fire. */
99
215
  controllerGrace;
100
216
  cancelEscalation;
217
+ /** ACP-authenticated in-flight calls, including independently reserved permissions. */
218
+ activeToolCalls = new Map();
219
+ toolBoundaryWaiters = new Set();
101
220
  activeTurn;
102
221
  constructor(options, child, connection) {
103
222
  this.options = options;
@@ -215,6 +334,142 @@ export class AcpSession {
215
334
  permissionMode: this.options.permissionMode,
216
335
  };
217
336
  }
337
+ toolCall(toolCallId) {
338
+ const existing = this.activeToolCalls.get(toolCallId);
339
+ if (existing)
340
+ return existing;
341
+ const created = { lifecycle: false, permissions: new Map() };
342
+ this.activeToolCalls.set(toolCallId, created);
343
+ return created;
344
+ }
345
+ reserveTool(toolCallId) {
346
+ if (toolCallId)
347
+ this.toolCall(toolCallId).lifecycle = true;
348
+ }
349
+ reservePermission(toolCallId, permissionId) {
350
+ if (toolCallId)
351
+ this.toolCall(toolCallId).permissions.set(permissionId, 'pending');
352
+ }
353
+ allowPermission(toolCallId, permissionId) {
354
+ if (!toolCallId)
355
+ return;
356
+ const permission = this.activeToolCalls.get(toolCallId)?.permissions;
357
+ if (permission?.has(permissionId))
358
+ permission.set(permissionId, 'allowed');
359
+ }
360
+ releasePermission(toolCallId, permissionId) {
361
+ const call = toolCallId && this.activeToolCalls.get(toolCallId);
362
+ if (!call || !call.permissions.delete(permissionId))
363
+ return;
364
+ this.releaseToolIfIdle(toolCallId, call);
365
+ }
366
+ releaseTool(toolCallId) {
367
+ const call = toolCallId && this.activeToolCalls.get(toolCallId);
368
+ if (!call)
369
+ return;
370
+ call.lifecycle = false;
371
+ // Terminal tool evidence consumes permissions already granted for this
372
+ // call, but never a separate request that is still awaiting a decision.
373
+ for (const [permissionId, state] of call.permissions)
374
+ if (state === 'allowed')
375
+ call.permissions.delete(permissionId);
376
+ this.releaseToolIfIdle(toolCallId, call);
377
+ }
378
+ releaseToolIfIdle(toolCallId, call) {
379
+ if (call.lifecycle || call.permissions.size > 0)
380
+ return;
381
+ if (!this.activeToolCalls.delete(toolCallId) || this.activeToolCalls.size > 0)
382
+ return;
383
+ for (const notify of [...this.toolBoundaryWaiters])
384
+ notify();
385
+ }
386
+ releaseAllTools() {
387
+ if (this.activeToolCalls.size === 0)
388
+ return;
389
+ this.activeToolCalls.clear();
390
+ for (const notify of [...this.toolBoundaryWaiters])
391
+ notify();
392
+ }
393
+ waitForToolBoundary(timeoutMs) {
394
+ if (this.activeToolCalls.size === 0)
395
+ return Promise.resolve(true);
396
+ return new Promise(resolve => {
397
+ let settled = false;
398
+ const finish = (atBoundary) => {
399
+ if (settled)
400
+ return;
401
+ settled = true;
402
+ clearTimeout(timer);
403
+ this.toolBoundaryWaiters.delete(check);
404
+ resolve(atBoundary);
405
+ };
406
+ const check = () => {
407
+ if (this.activeToolCalls.size === 0 || !this.isAlive())
408
+ finish(this.activeToolCalls.size === 0);
409
+ };
410
+ const timer = setTimeout(() => finish(false), Math.max(0, timeoutMs));
411
+ timer.unref?.();
412
+ this.toolBoundaryWaiters.add(check);
413
+ // Close the subscribe/check race without guessing about elapsed time.
414
+ check();
415
+ });
416
+ }
417
+ recordAfterToolDelivery(state, activeToolCount, waitedMs) {
418
+ this.events.emit('monitor_delivery', {
419
+ status: state, monitorPolicy: 'after_tool', activeToolCount, waitedMs,
420
+ });
421
+ this.conversation.appendSafe({
422
+ kind: 'monitor.delivery', sessionGeneration: this.sessionGeneration,
423
+ acpSessionId: this.sessionId, source: 'fleet_monitor',
424
+ payload: { policy: 'after_tool', state, activeToolCount, waitedMs },
425
+ });
426
+ }
427
+ /**
428
+ * Monitor-only safe-boundary delivery. Steering is the interruption: this
429
+ * path never calls session/cancel and never resolves a pending permission.
430
+ */
431
+ async submitPromptAfterTool(text, options = {}) {
432
+ if (this.closing || !this.isAlive())
433
+ return turnResult(false, 'failed', this.lastError ?? 'ACP session is closing');
434
+ const startedAt = Date.now();
435
+ const initialToolCount = this.activeToolCalls.size;
436
+ if (!this.steeringSupported) {
437
+ this.recordAfterToolDelivery('unsupported', initialToolCount, 0);
438
+ const result = await this.submitPrompt(text, { ...options, interrupt: false, steer: false });
439
+ return {
440
+ ...result,
441
+ safeBoundary: { state: 'unsupported', waitedMs: 0, activeToolCount: initialToolCount },
442
+ };
443
+ }
444
+ if (initialToolCount === 0) {
445
+ this.recordAfterToolDelivery('direct', 0, 0);
446
+ const result = await this.steerPrompt(text);
447
+ return { ...result, safeBoundary: { state: 'direct', waitedMs: 0, activeToolCount: 0 } };
448
+ }
449
+ this.recordAfterToolDelivery('deferred', initialToolCount, 0);
450
+ const timeoutMs = this.options.afterToolBoundaryTimeoutMs ?? AFTER_TOOL_BOUNDARY_TIMEOUT_MS;
451
+ const deadline = startedAt + timeoutMs;
452
+ let atBoundary = false;
453
+ // Re-check after every wake: another authenticated tool event may have
454
+ // arrived before this continuation ran. Only an empty tracked set is safe.
455
+ while (this.activeToolCalls.size > 0) {
456
+ const remaining = deadline - Date.now();
457
+ if (remaining <= 0 || !(await this.waitForToolBoundary(remaining)))
458
+ break;
459
+ }
460
+ atBoundary = this.activeToolCalls.size === 0;
461
+ if (this.closing || !this.isAlive())
462
+ return turnResult(false, 'failed', this.lastError ?? 'ACP session closed during after_tool wait');
463
+ const waitedMs = Math.max(0, Date.now() - startedAt);
464
+ const state = atBoundary ? 'after_tool' : 'timeout';
465
+ const remainingToolCount = this.activeToolCalls.size;
466
+ this.recordAfterToolDelivery(state, remainingToolCount, waitedMs);
467
+ const result = await this.steerPrompt(text);
468
+ return {
469
+ ...result,
470
+ safeBoundary: { state, waitedMs, activeToolCount: remainingToolCount },
471
+ };
472
+ }
218
473
  /**
219
474
  * Accept responsibility for a prompt, then return. The turn itself may run
220
475
  * for minutes behind other queued turns; making an interactive caller wait
@@ -365,6 +620,10 @@ export class AcpSession {
365
620
  clearTimeout(pending.expiry);
366
621
  pending.resolve({ outcome: { outcome: 'selected', optionId } });
367
622
  const decision = chosen.kind.startsWith('reject') ? 'denied' : 'allowed';
623
+ if (decision === 'allowed')
624
+ this.allowPermission(pending.toolCallId, permissionId);
625
+ else
626
+ this.releasePermission(pending.toolCallId, permissionId);
368
627
  this.events.emit('permission', {
369
628
  turnId: this.activeTurn?.id,
370
629
  origin: this.activeTurn?.origin,
@@ -381,7 +640,7 @@ export class AcpSession {
381
640
  promptId: this.activeTurn?.id, turnId: this.activeTurn?.id,
382
641
  payload: { decision, decisionSource: 'manual', optionId },
383
642
  });
384
- this.readiness = 'running';
643
+ this.readiness = this.pendingPermissions.size > 0 ? 'awaiting_permission' : 'running';
385
644
  return true;
386
645
  }
387
646
  /**
@@ -446,11 +705,12 @@ export class AcpSession {
446
705
  ? { outcome: { outcome: 'selected', optionId: rejectOption.optionId } }
447
706
  : { outcome: { outcome: 'cancelled' } });
448
707
  const settled = decision === 'denied' && !rejectOption ? 'cancelled' : decision;
708
+ this.releasePermission(pending.toolCallId, permissionId);
449
709
  this.events.emit('permission', {
450
710
  turnId: this.activeTurn?.id,
451
711
  origin: this.activeTurn?.origin,
452
712
  permissionId,
453
- toolCallId: pending.toolCallId,
713
+ toolCallId: pending.eventToolCallId,
454
714
  status: 'completed',
455
715
  decision: settled === 'expired' ? 'cancelled' : settled,
456
716
  decisionSource: 'automatic',
@@ -462,7 +722,7 @@ export class AcpSession {
462
722
  kind: 'permission.resolved', sessionGeneration: this.sessionGeneration,
463
723
  acpSessionId: this.sessionId, permissionId,
464
724
  promptId: this.activeTurn?.id, turnId: this.activeTurn?.id,
465
- toolCallId: pending.toolCallId,
725
+ toolCallId: pending.eventToolCallId,
466
726
  payload: {
467
727
  decision: settled, decisionSource: 'automatic',
468
728
  ...(policy ? { policy } : {}), reason,
@@ -476,6 +736,7 @@ export class AcpSession {
476
736
  return this.exit;
477
737
  }
478
738
  async close() {
739
+ this.closing = true;
479
740
  if (this.cancelEscalation)
480
741
  clearTimeout(this.cancelEscalation);
481
742
  this.cancelEscalation = undefined;
@@ -484,6 +745,7 @@ export class AcpSession {
484
745
  this.controllerGrace = undefined;
485
746
  for (const [permissionId, pending] of [...this.pendingPermissions])
486
747
  this.settlePendingAutomatically(permissionId, pending, 'cancelled', undefined, 'the session closed while this request was pending');
748
+ this.releaseAllTools();
487
749
  if (this.sessionId && this.capabilities?.sessionCapabilities?.close != null) {
488
750
  await this.connection.agent.request(acp.methods.agent.session.close, { sessionId: this.sessionId }).catch(() => undefined);
489
751
  }
@@ -627,6 +889,7 @@ export class AcpSession {
627
889
  return turnResult(false, 'failed', this.lastError, this.activeTurn?.id === turnId ? this.activeTurn.output : undefined);
628
890
  }
629
891
  finally {
892
+ this.releaseAllTools();
630
893
  if (this.activeTurn?.id === turnId) {
631
894
  if (this.cancelEscalation)
632
895
  clearTimeout(this.cancelEscalation);
@@ -667,9 +930,19 @@ export class AcpSession {
667
930
  }
668
931
  return undefined;
669
932
  };
933
+ const toolCallId = params.toolCall.toolCallId;
934
+ const permissionId = randomUUID();
935
+ // Permission is part of the tool lifecycle. Reserve before any policy or
936
+ // human decision so a monitor wake cannot slip between request and answer.
937
+ this.reservePermission(toolCallId, permissionId);
670
938
  if (this.options.permissions.approval === 'allow' && this.withinAutomaticBoundary(params)) {
671
939
  const option = choose(['allow_always', 'allow_once']);
672
- return Promise.resolve(this.settleAutomatically(params, option, 'allowed', 'permissions.approval=allow', `the request is inside the ${this.options.permissions.filesystem} boundary`));
940
+ const response = this.settleAutomatically(params, option, 'allowed', 'permissions.approval=allow', `the request is inside the ${this.options.permissions.filesystem} boundary`);
941
+ if (option)
942
+ this.allowPermission(toolCallId, permissionId);
943
+ else
944
+ this.releasePermission(toolCallId, permissionId);
945
+ return Promise.resolve(response);
673
946
  }
674
947
  // A live grace window still counts as attended: the controller may be
675
948
  // mid-reconnect, and denying instantly is exactly what the grace prevents.
@@ -680,11 +953,12 @@ export class AcpSession {
680
953
  // a decision no human made, so one unattended denial would silently disable
681
954
  // the tool for the rest of the session.
682
955
  const option = choose(['reject_once', 'reject_always']);
683
- return Promise.resolve(this.settleAutomatically(params, option, 'denied', unattended ? 'permissions.unattended=deny' : 'permissions.approval=deny', unattended
956
+ const response = this.settleAutomatically(params, option, 'denied', unattended ? 'permissions.unattended=deny' : 'permissions.approval=deny', unattended
684
957
  ? 'no controller is attached, so the request cannot be shown to anyone'
685
- : 'the role denies every permission request by policy'));
958
+ : 'the role denies every permission request by policy');
959
+ this.releasePermission(toolCallId, permissionId);
960
+ return Promise.resolve(response);
686
961
  }
687
- const permissionId = randomUUID();
688
962
  const timeoutMs = this.options.permissionTimeoutMs ?? PERMISSION_TIMEOUT_MS;
689
963
  const expiresAt = new Date(Date.now() + timeoutMs).toISOString();
690
964
  this.readiness = 'awaiting_permission';
@@ -723,8 +997,8 @@ export class AcpSession {
723
997
  return new Promise(resolve => {
724
998
  const pending = {
725
999
  options: params.options, resolve,
726
- toolCallId: scheduledTurn(this.activeTurn)
727
- ? 'scheduled-loop-tool' : params.toolCall.toolCallId,
1000
+ toolCallId,
1001
+ eventToolCallId: scheduledTurn(this.activeTurn) ? 'scheduled-loop-tool' : toolCallId,
728
1002
  };
729
1003
  pending.expiry = setTimeout(() => {
730
1004
  this.settlePendingAutomatically(permissionId, pending, 'expired', undefined, `no decision arrived within ${Math.round(timeoutMs / 1000)}s`);
@@ -785,11 +1059,7 @@ export class AcpSession {
785
1059
  if (locations.length === 0)
786
1060
  return false;
787
1061
  const cwd = resolve(this.options.cwd);
788
- return locations.every(location => {
789
- const path = resolve(location.path);
790
- const rel = relative(cwd, path);
791
- return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
792
- });
1062
+ return canonicallyWithin(cwd, locations.map(location => resolve(location.path)));
793
1063
  }
794
1064
  recordUpdate(update) {
795
1065
  const scheduled = this.activeTurn?.origin?.kind === 'scheduled-loop';
@@ -832,6 +1102,10 @@ export class AcpSession {
832
1102
  title: scheduled ? 'scheduled-loop tool' : update.title,
833
1103
  status: update.status,
834
1104
  });
1105
+ if (TERMINAL_TOOL_STATUSES.has(update.status ?? ''))
1106
+ this.releaseTool(update.toolCallId);
1107
+ else
1108
+ this.reserveTool(update.toolCallId);
835
1109
  break;
836
1110
  case 'tool_call_update':
837
1111
  this.events.emit('tool_update', {
@@ -841,6 +1115,10 @@ export class AcpSession {
841
1115
  title: scheduled ? 'scheduled-loop tool' : update.title ?? undefined,
842
1116
  status: update.status ?? undefined,
843
1117
  });
1118
+ if (TERMINAL_TOOL_STATUSES.has(update.status ?? ''))
1119
+ this.releaseTool(update.toolCallId);
1120
+ else if (update.status !== undefined)
1121
+ this.reserveTool(update.toolCallId);
844
1122
  break;
845
1123
  default:
846
1124
  break;
@@ -864,7 +1142,10 @@ export class AcpSession {
864
1142
  }
865
1143
  /** Normalize every ACP update losslessly into the durable ledger. */
866
1144
  recordConversationUpdate(update, scheduled, commentary = false) {
867
- const normalized = normalizeSessionUpdate(update, scheduled ? { redactText: SCHEDULED_LOOP_REDACTION }
1145
+ const normalized = normalizeSessionUpdate(update, scheduled ? {
1146
+ redactText: SCHEDULED_LOOP_REDACTION,
1147
+ redactToolCallId: 'scheduled-loop-tool',
1148
+ }
868
1149
  : commentary ? { redactText: OWNER_COMMENTARY_REDACTION } : {});
869
1150
  this.conversation.appendSafe({
870
1151
  kind: normalized.kind,
@@ -27,6 +27,11 @@ export declare class RoleTurnArbiter implements SessionHandle {
27
27
  private track;
28
28
  queuePrompt(text: string, options?: SubmitPromptOptions): Promise<QueuedPrompt>;
29
29
  submitPrompt(text: string, options?: SubmitPromptOptions): Promise<TurnResult>;
30
+ /**
31
+ * Do not hold `exclusive` while ACP waits for a tool boundary: permission
32
+ * answers and explicit interrupts must remain able to pass immediately.
33
+ */
34
+ submitPromptAfterTool(text: string, options?: SubmitPromptOptions): Promise<TurnResult>;
30
35
  tryScheduled(text: string, origin: Extract<PromptOrigin, {
31
36
  kind: 'scheduled-loop';
32
37
  }>, beforeQueue?: () => void | Promise<void>): Promise<ScheduledAttempt>;
@@ -32,6 +32,14 @@ export class RoleTurnArbiter {
32
32
  async submitPrompt(text, options = {}) {
33
33
  return (await this.queuePrompt(text, options)).completion;
34
34
  }
35
+ /**
36
+ * Do not hold `exclusive` while ACP waits for a tool boundary: permission
37
+ * answers and explicit interrupts must remain able to pass immediately.
38
+ */
39
+ submitPromptAfterTool(text, options = {}) {
40
+ return this.session.submitPromptAfterTool?.(text, options)
41
+ ?? this.session.submitPrompt(text, { ...options, interrupt: false, steer: false });
42
+ }
35
43
  async tryScheduled(text, origin, beforeQueue) {
36
44
  // Give owner/console/I/O callbacks already ready in this event-loop turn a
37
45
  // chance to claim the arbiter first. Scheduled work is best-effort; humans
@@ -23,6 +23,12 @@ export interface NormalizeOptions {
23
23
  * digest. Used for scheduled-loop turns whose output must not be retained.
24
24
  */
25
25
  redactText?: string;
26
+ /**
27
+ * Replace ACP tool-call IDs in both the normalized event correlation field
28
+ * and payload. The caller may still use the original update for in-memory
29
+ * lifecycle tracking without persisting its private identifier.
30
+ */
31
+ redactToolCallId?: string;
26
32
  }
27
33
  export interface NormalizedUpdate {
28
34
  kind: ConversationEventKind;
@@ -203,9 +203,10 @@ function normalizeToolContent(raw, redact) {
203
203
  }
204
204
  });
205
205
  }
206
- function toolUpsert(update, snapshot, redact) {
206
+ function toolUpsert(update, snapshot, options) {
207
+ const redact = options.redactText;
207
208
  const payload = {
208
- toolCallId: asString(update.toolCallId) ?? '',
209
+ toolCallId: options.redactToolCallId ?? asString(update.toolCallId) ?? '',
209
210
  snapshot,
210
211
  };
211
212
  if (asString(update.title) !== undefined)
@@ -271,7 +272,7 @@ export function normalizeSessionUpdate(update, options = {}) {
271
272
  }
272
273
  case 'tool_call':
273
274
  case 'tool_call_update': {
274
- const payload = toolUpsert(raw, raw.sessionUpdate === 'tool_call', redact);
275
+ const payload = toolUpsert(raw, raw.sessionUpdate === 'tool_call', options);
275
276
  return withMeta({
276
277
  kind: 'tool.upsert', payload,
277
278
  ...(payload.toolCallId ? { toolCallId: payload.toolCallId } : {}),
@@ -7,7 +7,7 @@ import type { PromptOrigin, TurnCancellationSource, TurnOutcome } from './types.
7
7
  * file touches the wire: ACP updates are reduced into these shapes by the
8
8
  * normalizer, and the store (phase 1) assigns `seq`/`eventId`/timestamps.
9
9
  */
10
- export type ConversationEventKind = 'prompt.admitted' | 'prompt.started' | 'prompt.interrupt_requested' | 'message.chunk' | 'message.replace' | 'thought.chunk' | 'thought.replace' | 'plan.replace' | 'tool.upsert' | 'tool.content_chunk' | 'permission.requested' | 'permission.resolved' | 'usage.updated' | 'turn.state' | 'turn.completed' | 'session.state' | 'session.info' | 'capabilities.updated' | 'error'
10
+ export type ConversationEventKind = 'prompt.admitted' | 'prompt.started' | 'prompt.interrupt_requested' | 'message.chunk' | 'message.replace' | 'thought.chunk' | 'thought.replace' | 'plan.replace' | 'tool.upsert' | 'tool.content_chunk' | 'permission.requested' | 'permission.resolved' | 'monitor.delivery' | 'usage.updated' | 'turn.state' | 'turn.completed' | 'session.state' | 'session.info' | 'capabilities.updated' | 'error'
11
11
  /** A well-formed ACP update this version cannot represent. Bounded, never a crash. */
12
12
  | 'unsupported';
13
13
  /** Where a conversation record came from. Typed provenance, never prompt text. */
@@ -178,6 +178,13 @@ export interface PermissionResolvedPayload {
178
178
  policy?: string;
179
179
  reason?: string;
180
180
  }
181
+ /** Body-free monitor evidence. Tool ids, titles, output, and wake text are never stored. */
182
+ export interface MonitorDeliveryPayload {
183
+ policy: 'after_tool';
184
+ state: 'deferred' | 'direct' | 'after_tool' | 'timeout' | 'unsupported';
185
+ activeToolCount: number;
186
+ waitedMs: number;
187
+ }
181
188
  export interface TurnStatePayload {
182
189
  state: 'queued' | 'running' | 'awaiting_permission' | 'interrupt_requested';
183
190
  }
@@ -201,7 +208,7 @@ export interface BoundedJson {
201
208
  digest?: string;
202
209
  redacted?: true;
203
210
  }
204
- export type ConversationPayload = MessageChunkPayload | ThoughtChunkPayload | PlanReplacePayload | ToolUpsertPayload | UsageUpdatedPayload | SessionStatePayload | SessionInfoPayload | CapabilitiesUpdatedPayload | UnsupportedPayload | PromptAdmittedPayload | PromptStartedPayload | PromptInterruptRequestedPayload | PermissionRequestedPayload | PermissionResolvedPayload | TurnStatePayload | TurnCompletedPayload | SessionLifecyclePayload | ErrorPayload;
211
+ export type ConversationPayload = MessageChunkPayload | ThoughtChunkPayload | PlanReplacePayload | ToolUpsertPayload | UsageUpdatedPayload | SessionStatePayload | SessionInfoPayload | CapabilitiesUpdatedPayload | UnsupportedPayload | PromptAdmittedPayload | PromptStartedPayload | PromptInterruptRequestedPayload | PermissionRequestedPayload | PermissionResolvedPayload | TurnStatePayload | TurnCompletedPayload | MonitorDeliveryPayload | SessionLifecyclePayload | ErrorPayload;
205
212
  export interface ConversationEventV1 {
206
213
  schemaVersion: 1;
207
214
  roleId: string;
@@ -49,6 +49,12 @@ export interface TurnResult {
49
49
  cancellationSource?: TurnCancellationSource;
50
50
  /** Final assistant text captured structurally by a backend, when available. */
51
51
  output?: string;
52
+ /** Body-free monitor safe-boundary disposition, when this was an after_tool wake. */
53
+ safeBoundary?: {
54
+ state: 'direct' | 'after_tool' | 'timeout' | 'unsupported';
55
+ waitedMs: number;
56
+ activeToolCount: number;
57
+ };
52
58
  }
53
59
  /**
54
60
  * Why a control operation failed. The distinctions exist because collapsing
@@ -137,7 +143,7 @@ export interface SessionSnapshot {
137
143
  nativeMode: string;
138
144
  };
139
145
  }
140
- export type SessionEventKind = 'state' | 'agent_text' | 'thought' | 'tool_call' | 'tool_update' | 'permission' | 'turn_stop' | 'error';
146
+ export type SessionEventKind = 'state' | 'agent_text' | 'thought' | 'tool_call' | 'tool_update' | 'permission' | 'monitor_delivery' | 'turn_stop' | 'error';
141
147
  /** What a settled permission request resolved to. */
142
148
  export type PermissionDecision = 'allowed' | 'denied' | 'cancelled';
143
149
  export interface SessionEvent {
@@ -178,6 +184,10 @@ export interface SessionEvent {
178
184
  reason?: string;
179
185
  /** The option actually selected, when one was. */
180
186
  optionId?: string;
187
+ /** Body-free evidence for monitor safe-boundary delivery. */
188
+ monitorPolicy?: 'after_tool';
189
+ activeToolCount?: number;
190
+ waitedMs?: number;
181
191
  }
182
192
  export interface ConversationHandlePage {
183
193
  events: ConversationEventV1[];
@@ -206,6 +216,8 @@ export interface SessionHandle {
206
216
  queuePrompt(text: string, options?: SubmitPromptOptions): Promise<QueuedPrompt>;
207
217
  /** Queue a prompt and wait for its terminal result. */
208
218
  submitPrompt(text: string, options?: SubmitPromptOptions): Promise<TurnResult>;
219
+ /** Monitor-only ACP safe-boundary delivery. Never implies human/control cancellation. */
220
+ submitPromptAfterTool?(text: string, options?: SubmitPromptOptions): Promise<TurnResult>;
209
221
  interrupt(source?: TurnCancellationSource): Promise<void>;
210
222
  respondPermission(permissionId: string, optionId: string): boolean;
211
223
  /** Generation-bound browser decision; stale/settled/invalid all fail closed. */
@@ -1,4 +1,4 @@
1
- import{r as le,a as Ee,j as re}from"./index-CsHEL0f6.js";var ge={exports:{}},Se;function ke(){return Se||(Se=1,(function(se,ne){(function(Q,X){se.exports=X()})(globalThis,(()=>(()=>{var Q={4567:function(B,r,o){var l=this&&this.__decorate||function(e,i,a,v){var f,g=arguments.length,c=g<3?i:v===null?v=Object.getOwnPropertyDescriptor(i,a):v;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")c=Reflect.decorate(e,i,a,v);else for(var m=e.length-1;m>=0;m--)(f=e[m])&&(c=(g<3?f(c):g>3?f(i,a,c):f(i,a))||c);return g>3&&c&&Object.defineProperty(i,a,c),c},_=this&&this.__param||function(e,i){return function(a,v){i(a,v,e)}};Object.defineProperty(r,"__esModule",{value:!0}),r.AccessibilityManager=void 0;const n=o(9042),d=o(9924),u=o(844),p=o(4725),h=o(2585),t=o(3656);let s=r.AccessibilityManager=class extends u.Disposable{constructor(e,i,a,v){super(),this._terminal=e,this._coreBrowserService=a,this._renderService=v,this._rowColumns=new WeakMap,this._liveRegionLineCount=0,this._charsToConsume=[],this._charsToAnnounce="",this._accessibilityContainer=this._coreBrowserService.mainDocument.createElement("div"),this._accessibilityContainer.classList.add("xterm-accessibility"),this._rowContainer=this._coreBrowserService.mainDocument.createElement("div"),this._rowContainer.setAttribute("role","list"),this._rowContainer.classList.add("xterm-accessibility-tree"),this._rowElements=[];for(let f=0;f<this._terminal.rows;f++)this._rowElements[f]=this._createAccessibilityTreeNode(),this._rowContainer.appendChild(this._rowElements[f]);if(this._topBoundaryFocusListener=f=>this._handleBoundaryFocus(f,0),this._bottomBoundaryFocusListener=f=>this._handleBoundaryFocus(f,1),this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions(),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=this._coreBrowserService.mainDocument.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this.register(new d.TimeBasedDebouncer(this._renderRows.bind(this))),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityContainer),this.register(this._terminal.onResize((f=>this._handleResize(f.rows)))),this.register(this._terminal.onRender((f=>this._refreshRows(f.start,f.end)))),this.register(this._terminal.onScroll((()=>this._refreshRows()))),this.register(this._terminal.onA11yChar((f=>this._handleChar(f)))),this.register(this._terminal.onLineFeed((()=>this._handleChar(`
1
+ import{r as le,a as Ee,j as re}from"./index-CliHATFt.js";var ge={exports:{}},Se;function ke(){return Se||(Se=1,(function(se,ne){(function(Q,X){se.exports=X()})(globalThis,(()=>(()=>{var Q={4567:function(B,r,o){var l=this&&this.__decorate||function(e,i,a,v){var f,g=arguments.length,c=g<3?i:v===null?v=Object.getOwnPropertyDescriptor(i,a):v;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")c=Reflect.decorate(e,i,a,v);else for(var m=e.length-1;m>=0;m--)(f=e[m])&&(c=(g<3?f(c):g>3?f(i,a,c):f(i,a))||c);return g>3&&c&&Object.defineProperty(i,a,c),c},_=this&&this.__param||function(e,i){return function(a,v){i(a,v,e)}};Object.defineProperty(r,"__esModule",{value:!0}),r.AccessibilityManager=void 0;const n=o(9042),d=o(9924),u=o(844),p=o(4725),h=o(2585),t=o(3656);let s=r.AccessibilityManager=class extends u.Disposable{constructor(e,i,a,v){super(),this._terminal=e,this._coreBrowserService=a,this._renderService=v,this._rowColumns=new WeakMap,this._liveRegionLineCount=0,this._charsToConsume=[],this._charsToAnnounce="",this._accessibilityContainer=this._coreBrowserService.mainDocument.createElement("div"),this._accessibilityContainer.classList.add("xterm-accessibility"),this._rowContainer=this._coreBrowserService.mainDocument.createElement("div"),this._rowContainer.setAttribute("role","list"),this._rowContainer.classList.add("xterm-accessibility-tree"),this._rowElements=[];for(let f=0;f<this._terminal.rows;f++)this._rowElements[f]=this._createAccessibilityTreeNode(),this._rowContainer.appendChild(this._rowElements[f]);if(this._topBoundaryFocusListener=f=>this._handleBoundaryFocus(f,0),this._bottomBoundaryFocusListener=f=>this._handleBoundaryFocus(f,1),this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions(),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=this._coreBrowserService.mainDocument.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this.register(new d.TimeBasedDebouncer(this._renderRows.bind(this))),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityContainer),this.register(this._terminal.onResize((f=>this._handleResize(f.rows)))),this.register(this._terminal.onRender((f=>this._refreshRows(f.start,f.end)))),this.register(this._terminal.onScroll((()=>this._refreshRows()))),this.register(this._terminal.onA11yChar((f=>this._handleChar(f)))),this.register(this._terminal.onLineFeed((()=>this._handleChar(`
2
2
  `)))),this.register(this._terminal.onA11yTab((f=>this._handleTab(f)))),this.register(this._terminal.onKey((f=>this._handleKey(f.key)))),this.register(this._terminal.onBlur((()=>this._clearLiveRegion()))),this.register(this._renderService.onDimensionsChange((()=>this._refreshRowsDimensions()))),this.register((0,t.addDisposableDomListener)(document,"selectionchange",(()=>this._handleSelectionChange()))),this.register(this._coreBrowserService.onDprChange((()=>this._refreshRowsDimensions()))),this._refreshRows(),this.register((0,u.toDisposable)((()=>{this._accessibilityContainer.remove(),this._rowElements.length=0})))}_handleTab(e){for(let i=0;i<e;i++)this._handleChar(" ")}_handleChar(e){this._liveRegionLineCount<21&&(this._charsToConsume.length>0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,e===`
3
3
  `&&(this._liveRegionLineCount++,this._liveRegionLineCount===21&&(this._liveRegion.textContent+=n.tooMuchOutput)))}_clearLiveRegion(){this._liveRegion.textContent="",this._liveRegionLineCount=0}_handleKey(e){this._clearLiveRegion(),new RegExp("\\p{Control}","u").test(e)||this._charsToConsume.push(e)}_refreshRows(e,i){this._liveRegionDebouncer.refresh(e,i,this._terminal.rows)}_renderRows(e,i){const a=this._terminal.buffer,v=a.lines.length.toString();for(let f=e;f<=i;f++){const g=a.lines.get(a.ydisp+f),c=[],m=g?.translateToString(!0,void 0,void 0,c)||"",E=(a.ydisp+f+1).toString(),k=this._rowElements[f];k&&(m.length===0?(k.innerText=" ",this._rowColumns.set(k,[0,1])):(k.textContent=m,this._rowColumns.set(k,c)),k.setAttribute("aria-posinset",E),k.setAttribute("aria-setsize",v))}this._announceCharacters()}_announceCharacters(){this._charsToAnnounce.length!==0&&(this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce="")}_handleBoundaryFocus(e,i){const a=e.target,v=this._rowElements[i===0?1:this._rowElements.length-2];if(a.getAttribute("aria-posinset")===(i===0?"1":`${this._terminal.buffer.lines.length}`)||e.relatedTarget!==v)return;let f,g;if(i===0?(f=a,g=this._rowElements.pop(),this._rowContainer.removeChild(g)):(f=this._rowElements.shift(),g=a,this._rowContainer.removeChild(f)),f.removeEventListener("focus",this._topBoundaryFocusListener),g.removeEventListener("focus",this._bottomBoundaryFocusListener),i===0){const c=this._createAccessibilityTreeNode();this._rowElements.unshift(c),this._rowContainer.insertAdjacentElement("afterbegin",c)}else{const c=this._createAccessibilityTreeNode();this._rowElements.push(c),this._rowContainer.appendChild(c)}this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._terminal.scrollLines(i===0?-1:1),this._rowElements[i===0?1:this._rowElements.length-2].focus(),e.preventDefault(),e.stopImmediatePropagation()}_handleSelectionChange(){if(this._rowElements.length===0)return;const e=document.getSelection();if(!e)return;if(e.isCollapsed)return void(this._rowContainer.contains(e.anchorNode)&&this._terminal.clearSelection());if(!e.anchorNode||!e.focusNode)return void console.error("anchorNode and/or focusNode are null");let i={node:e.anchorNode,offset:e.anchorOffset},a={node:e.focusNode,offset:e.focusOffset};if((i.node.compareDocumentPosition(a.node)&Node.DOCUMENT_POSITION_PRECEDING||i.node===a.node&&i.offset>a.offset)&&([i,a]=[a,i]),i.node.compareDocumentPosition(this._rowElements[0])&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_FOLLOWING)&&(i={node:this._rowElements[0].childNodes[0],offset:0}),!this._rowContainer.contains(i.node))return;const v=this._rowElements.slice(-1)[0];if(a.node.compareDocumentPosition(v)&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_PRECEDING)&&(a={node:v,offset:v.textContent?.length??0}),!this._rowContainer.contains(a.node))return;const f=({node:m,offset:E})=>{const k=m instanceof Text?m.parentNode:m;let D=parseInt(k?.getAttribute("aria-posinset"),10)-1;if(isNaN(D))return console.warn("row is invalid. Race condition?"),null;const b=this._rowColumns.get(k);if(!b)return console.warn("columns is null. Race condition?"),null;let x=E<b.length?b[E]:b.slice(-1)[0]+1;return x>=this._terminal.cols&&(++D,x=0),{row:D,column:x}},g=f(i),c=f(a);if(g&&c){if(g.row>c.row||g.row===c.row&&g.column>=c.column)throw new Error("invalid range");this._terminal.select(g.column,g.row,(c.row-g.row)*this._terminal.cols-g.column+c.column)}}_handleResize(e){this._rowElements[this._rowElements.length-1].removeEventListener("focus",this._bottomBoundaryFocusListener);for(let i=this._rowContainer.children.length;i<this._terminal.rows;i++)this._rowElements[i]=this._createAccessibilityTreeNode(),this._rowContainer.appendChild(this._rowElements[i]);for(;this._rowElements.length>e;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){const e=this._coreBrowserService.mainDocument.createElement("div");return e.setAttribute("role","listitem"),e.tabIndex=-1,this._refreshRowDimensions(e),e}_refreshRowsDimensions(){if(this._renderService.dimensions.css.cell.height){this._accessibilityContainer.style.width=`${this._renderService.dimensions.css.canvas.width}px`,this._rowElements.length!==this._terminal.rows&&this._handleResize(this._terminal.rows);for(let e=0;e<this._terminal.rows;e++)this._refreshRowDimensions(this._rowElements[e])}}_refreshRowDimensions(e){e.style.height=`${this._renderService.dimensions.css.cell.height}px`}};r.AccessibilityManager=s=l([_(1,h.IInstantiationService),_(2,p.ICoreBrowserService),_(3,p.IRenderService)],s)},3614:(B,r)=>{function o(d){return d.replace(/\r?\n/g,"\r")}function l(d,u){return u?"\x1B[200~"+d+"\x1B[201~":d}function _(d,u,p,h){d=l(d=o(d),p.decPrivateModes.bracketedPasteMode&&h.rawOptions.ignoreBracketedPasteMode!==!0),p.triggerDataEvent(d,!0),u.value=""}function n(d,u,p){const h=p.getBoundingClientRect(),t=d.clientX-h.left-10,s=d.clientY-h.top-10;u.style.width="20px",u.style.height="20px",u.style.left=`${t}px`,u.style.top=`${s}px`,u.style.zIndex="1000",u.focus()}Object.defineProperty(r,"__esModule",{value:!0}),r.rightClickHandler=r.moveTextAreaUnderMouseCursor=r.paste=r.handlePasteEvent=r.copyHandler=r.bracketTextForPaste=r.prepareTextForTerminal=void 0,r.prepareTextForTerminal=o,r.bracketTextForPaste=l,r.copyHandler=function(d,u){d.clipboardData&&d.clipboardData.setData("text/plain",u.selectionText),d.preventDefault()},r.handlePasteEvent=function(d,u,p,h){d.stopPropagation(),d.clipboardData&&_(d.clipboardData.getData("text/plain"),u,p,h)},r.paste=_,r.moveTextAreaUnderMouseCursor=n,r.rightClickHandler=function(d,u,p,h,t){n(d,u,p),t&&h.rightClickSelect(d),u.value=h.selectionText,u.select()}},7239:(B,r,o)=>{Object.defineProperty(r,"__esModule",{value:!0}),r.ColorContrastCache=void 0;const l=o(1505);r.ColorContrastCache=class{constructor(){this._color=new l.TwoKeyMap,this._css=new l.TwoKeyMap}setCss(_,n,d){this._css.set(_,n,d)}getCss(_,n){return this._css.get(_,n)}setColor(_,n,d){this._color.set(_,n,d)}getColor(_,n){return this._color.get(_,n)}clear(){this._color.clear(),this._css.clear()}}},3656:(B,r)=>{Object.defineProperty(r,"__esModule",{value:!0}),r.addDisposableDomListener=void 0,r.addDisposableDomListener=function(o,l,_,n){o.addEventListener(l,_,n);let d=!1;return{dispose:()=>{d||(d=!0,o.removeEventListener(l,_,n))}}}},3551:function(B,r,o){var l=this&&this.__decorate||function(s,e,i,a){var v,f=arguments.length,g=f<3?e:a===null?a=Object.getOwnPropertyDescriptor(e,i):a;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")g=Reflect.decorate(s,e,i,a);else for(var c=s.length-1;c>=0;c--)(v=s[c])&&(g=(f<3?v(g):f>3?v(e,i,g):v(e,i))||g);return f>3&&g&&Object.defineProperty(e,i,g),g},_=this&&this.__param||function(s,e){return function(i,a){e(i,a,s)}};Object.defineProperty(r,"__esModule",{value:!0}),r.Linkifier=void 0;const n=o(3656),d=o(8460),u=o(844),p=o(2585),h=o(4725);let t=r.Linkifier=class extends u.Disposable{get currentLink(){return this._currentLink}constructor(s,e,i,a,v){super(),this._element=s,this._mouseService=e,this._renderService=i,this._bufferService=a,this._linkProviderService=v,this._linkCacheDisposables=[],this._isMouseOut=!0,this._wasResized=!1,this._activeLine=-1,this._onShowLinkUnderline=this.register(new d.EventEmitter),this.onShowLinkUnderline=this._onShowLinkUnderline.event,this._onHideLinkUnderline=this.register(new d.EventEmitter),this.onHideLinkUnderline=this._onHideLinkUnderline.event,this.register((0,u.getDisposeArrayDisposable)(this._linkCacheDisposables)),this.register((0,u.toDisposable)((()=>{this._lastMouseEvent=void 0,this._activeProviderReplies?.clear()}))),this.register(this._bufferService.onResize((()=>{this._clearCurrentLink(),this._wasResized=!0}))),this.register((0,n.addDisposableDomListener)(this._element,"mouseleave",(()=>{this._isMouseOut=!0,this._clearCurrentLink()}))),this.register((0,n.addDisposableDomListener)(this._element,"mousemove",this._handleMouseMove.bind(this))),this.register((0,n.addDisposableDomListener)(this._element,"mousedown",this._handleMouseDown.bind(this))),this.register((0,n.addDisposableDomListener)(this._element,"mouseup",this._handleMouseUp.bind(this)))}_handleMouseMove(s){this._lastMouseEvent=s;const e=this._positionFromMouseEvent(s,this._element,this._mouseService);if(!e)return;this._isMouseOut=!1;const i=s.composedPath();for(let a=0;a<i.length;a++){const v=i[a];if(v.classList.contains("xterm"))break;if(v.classList.contains("xterm-hover"))return}this._lastBufferCell&&e.x===this._lastBufferCell.x&&e.y===this._lastBufferCell.y||(this._handleHover(e),this._lastBufferCell=e)}_handleHover(s){if(this._activeLine!==s.y||this._wasResized)return this._clearCurrentLink(),this._askForLink(s,!1),void(this._wasResized=!1);this._currentLink&&this._linkAtPosition(this._currentLink.link,s)||(this._clearCurrentLink(),this._askForLink(s,!0))}_askForLink(s,e){this._activeProviderReplies&&e||(this._activeProviderReplies?.forEach((a=>{a?.forEach((v=>{v.link.dispose&&v.link.dispose()}))})),this._activeProviderReplies=new Map,this._activeLine=s.y);let i=!1;for(const[a,v]of this._linkProviderService.linkProviders.entries())e?this._activeProviderReplies?.get(a)&&(i=this._checkLinkProviderResult(a,s,i)):v.provideLinks(s.y,(f=>{if(this._isMouseOut)return;const g=f?.map((c=>({link:c})));this._activeProviderReplies?.set(a,g),i=this._checkLinkProviderResult(a,s,i),this._activeProviderReplies?.size===this._linkProviderService.linkProviders.length&&this._removeIntersectingLinks(s.y,this._activeProviderReplies)}))}_removeIntersectingLinks(s,e){const i=new Set;for(let a=0;a<e.size;a++){const v=e.get(a);if(v)for(let f=0;f<v.length;f++){const g=v[f],c=g.link.range.start.y<s?0:g.link.range.start.x,m=g.link.range.end.y>s?this._bufferService.cols:g.link.range.end.x;for(let E=c;E<=m;E++){if(i.has(E)){v.splice(f--,1);break}i.add(E)}}}}_checkLinkProviderResult(s,e,i){if(!this._activeProviderReplies)return i;const a=this._activeProviderReplies.get(s);let v=!1;for(let f=0;f<s;f++)this._activeProviderReplies.has(f)&&!this._activeProviderReplies.get(f)||(v=!0);if(!v&&a){const f=a.find((g=>this._linkAtPosition(g.link,e)));f&&(i=!0,this._handleNewLink(f))}if(this._activeProviderReplies.size===this._linkProviderService.linkProviders.length&&!i)for(let f=0;f<this._activeProviderReplies.size;f++){const g=this._activeProviderReplies.get(f)?.find((c=>this._linkAtPosition(c.link,e)));if(g){i=!0,this._handleNewLink(g);break}}return i}_handleMouseDown(){this._mouseDownLink=this._currentLink}_handleMouseUp(s){if(!this._currentLink)return;const e=this._positionFromMouseEvent(s,this._element,this._mouseService);e&&this._mouseDownLink===this._currentLink&&this._linkAtPosition(this._currentLink.link,e)&&this._currentLink.link.activate(s,this._currentLink.link.text)}_clearCurrentLink(s,e){this._currentLink&&this._lastMouseEvent&&(!s||!e||this._currentLink.link.range.start.y>=s&&this._currentLink.link.range.end.y<=e)&&(this._linkLeave(this._element,this._currentLink.link,this._lastMouseEvent),this._currentLink=void 0,(0,u.disposeArray)(this._linkCacheDisposables))}_handleNewLink(s){if(!this._lastMouseEvent)return;const e=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);e&&this._linkAtPosition(s.link,e)&&(this._currentLink=s,this._currentLink.state={decorations:{underline:s.link.decorations===void 0||s.link.decorations.underline,pointerCursor:s.link.decorations===void 0||s.link.decorations.pointerCursor},isHovered:!0},this._linkHover(this._element,s.link,this._lastMouseEvent),s.link.decorations={},Object.defineProperties(s.link.decorations,{pointerCursor:{get:()=>this._currentLink?.state?.decorations.pointerCursor,set:i=>{this._currentLink?.state&&this._currentLink.state.decorations.pointerCursor!==i&&(this._currentLink.state.decorations.pointerCursor=i,this._currentLink.state.isHovered&&this._element.classList.toggle("xterm-cursor-pointer",i))}},underline:{get:()=>this._currentLink?.state?.decorations.underline,set:i=>{this._currentLink?.state&&this._currentLink?.state?.decorations.underline!==i&&(this._currentLink.state.decorations.underline=i,this._currentLink.state.isHovered&&this._fireUnderlineEvent(s.link,i))}}}),this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange((i=>{if(!this._currentLink)return;const a=i.start===0?0:i.start+1+this._bufferService.buffer.ydisp,v=this._bufferService.buffer.ydisp+1+i.end;if(this._currentLink.link.range.start.y>=a&&this._currentLink.link.range.end.y<=v&&(this._clearCurrentLink(a,v),this._lastMouseEvent)){const f=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);f&&this._askForLink(f,!1)}}))))}_linkHover(s,e,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!0,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(e,!0),this._currentLink.state.decorations.pointerCursor&&s.classList.add("xterm-cursor-pointer")),e.hover&&e.hover(i,e.text)}_fireUnderlineEvent(s,e){const i=s.range,a=this._bufferService.buffer.ydisp,v=this._createLinkUnderlineEvent(i.start.x-1,i.start.y-a-1,i.end.x,i.end.y-a-1,void 0);(e?this._onShowLinkUnderline:this._onHideLinkUnderline).fire(v)}_linkLeave(s,e,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!1,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(e,!1),this._currentLink.state.decorations.pointerCursor&&s.classList.remove("xterm-cursor-pointer")),e.leave&&e.leave(i,e.text)}_linkAtPosition(s,e){const i=s.range.start.y*this._bufferService.cols+s.range.start.x,a=s.range.end.y*this._bufferService.cols+s.range.end.x,v=e.y*this._bufferService.cols+e.x;return i<=v&&v<=a}_positionFromMouseEvent(s,e,i){const a=i.getCoords(s,e,this._bufferService.cols,this._bufferService.rows);if(a)return{x:a[0],y:a[1]+this._bufferService.buffer.ydisp}}_createLinkUnderlineEvent(s,e,i,a,v){return{x1:s,y1:e,x2:i,y2:a,cols:this._bufferService.cols,fg:v}}};r.Linkifier=t=l([_(1,h.IMouseService),_(2,h.IRenderService),_(3,p.IBufferService),_(4,h.ILinkProviderService)],t)},9042:(B,r)=>{Object.defineProperty(r,"__esModule",{value:!0}),r.tooMuchOutput=r.promptLabel=void 0,r.promptLabel="Terminal input",r.tooMuchOutput="Too much output to announce, navigate to rows manually to read"},3730:function(B,r,o){var l=this&&this.__decorate||function(h,t,s,e){var i,a=arguments.length,v=a<3?t:e===null?e=Object.getOwnPropertyDescriptor(t,s):e;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")v=Reflect.decorate(h,t,s,e);else for(var f=h.length-1;f>=0;f--)(i=h[f])&&(v=(a<3?i(v):a>3?i(t,s,v):i(t,s))||v);return a>3&&v&&Object.defineProperty(t,s,v),v},_=this&&this.__param||function(h,t){return function(s,e){t(s,e,h)}};Object.defineProperty(r,"__esModule",{value:!0}),r.OscLinkProvider=void 0;const n=o(511),d=o(2585);let u=r.OscLinkProvider=class{constructor(h,t,s){this._bufferService=h,this._optionsService=t,this._oscLinkService=s}provideLinks(h,t){const s=this._bufferService.buffer.lines.get(h-1);if(!s)return void t(void 0);const e=[],i=this._optionsService.rawOptions.linkHandler,a=new n.CellData,v=s.getTrimmedLength();let f=-1,g=-1,c=!1;for(let m=0;m<v;m++)if(g!==-1||s.hasContent(m)){if(s.loadCell(m,a),a.hasExtendedAttrs()&&a.extended.urlId){if(g===-1){g=m,f=a.extended.urlId;continue}c=a.extended.urlId!==f}else g!==-1&&(c=!0);if(c||g!==-1&&m===v-1){const E=this._oscLinkService.getLinkData(f)?.uri;if(E){const k={start:{x:g+1,y:h},end:{x:m+(c||m!==v-1?0:1),y:h}};let D=!1;if(!i?.allowNonHttpProtocols)try{const b=new URL(E);["http:","https:"].includes(b.protocol)||(D=!0)}catch{D=!0}D||e.push({text:E,range:k,activate:(b,x)=>i?i.activate(b,x,k):p(0,x),hover:(b,x)=>i?.hover?.(b,x,k),leave:(b,x)=>i?.leave?.(b,x,k)})}c=!1,a.hasExtendedAttrs()&&a.extended.urlId?(g=m,f=a.extended.urlId):(g=-1,f=-1)}}t(e)}};function p(h,t){if(confirm(`Do you want to navigate to ${t}?
4
4