diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..279ed54b50840fc7db83ba48fb918a5b21e7ced7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +model.safetensors filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index 52f0ab1aa0b5426df56ee220de79bef3dcfcb5d1..a17cf2937892e3149b307ab217080e591b12cd31 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,3 @@ Thumbs.db # Project specific namer_model.pt .pip-tmp/ -pip-tmp/ -*.safetensors -*.bin diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index a3fa34a0ecc1dcf262db94a09b099cb07dc376da..0000000000000000000000000000000000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,56 +0,0 @@ -# Changelog - -All notable changes to the Namer project will be documented in this file. - -## [2.0.0] - 2025-05-09 - -### Added -- Support for numbers up to 999,999,999,999 (trillions) - increased from 999,999 -- Stratified sampling during training for balanced representation across number scales -- Extended max output length from 20 to 25 tokens -- Extended max sequence length from 20 to 25 tokens -- Special case handling for zero in inference -- New test cases for billion and trillion ranges - -### Changed -- `InfiniteNamerDataset` now uses stratified sampling by default -- Default `max_int` changed from 999,999 to 999,999,999,999 -- Training now samples equally across: units, thousands, millions, billions, trillions -- Model architecture unchanged but supports longer outputs - -### Fixed -- Small numbers (under 1M) now work correctly with large-range model -- Zero is now handled as a special case to prevent token repetition - -### Technical Details -- Training uses 5 stratified buckets (20% each): - - 0-999 (units) - - 1,000-999,999 (thousands) - - 1M-999M (millions) - - 1B-999B (billions) - - 1T-999T (trillions) -- Validation accuracy: >99.9% -- Model parameters: ~869K - -## [1.0.0] - 2025-05-08 - -### Added -- Initial release -- Support for numbers 0-999,999 (millions) -- Transformer-based sequence-to-sequence model -- HuggingFace Transformers integration -- PyTorch native model format -- Interactive inference mode -- Training pipeline with infinite dataset - -### Features -- 41-token vocabulary (number words + EOS) -- 20-token max output length -- 20-digit max input sequence length -- 4-layer transformer encoder -- Cross-attention mechanism with learned queries - ---- - -[2.0.0]: https://github.com/edwinhere/namer/compare/v1.0.0...v2.0.0 -[1.0.0]: https://github.com/edwinhere/namer/releases/tag/v1.0.0 diff --git a/README.md.git b/README.md.git deleted file mode 100644 index f44fc73b276c10b794d31c58d280ec55a8e518d0..0000000000000000000000000000000000000000 --- a/README.md.git +++ /dev/null @@ -1,159 +0,0 @@ ---- -language: en -license: mit -library_name: pytorch -tags: - - text-generation - - number-to-text - - pytorch - - transformer ---- - -# Namer - -[![HuggingFace](https://img.shields.io/badge/🤗_HuggingFace-Model_Card-yellow)](https://huggingface.co/edwinhere/namer) -[![GitHub](https://img.shields.io/badge/🐙_GitHub-Source_Code-blue)](https://github.com/edwinhere/namer) - -A PyTorch transformer model that converts **integers to their English names** (e.g., `42` → "forty two", `123` → "one hundred twenty three"). - -> 🔗 **This repository is mirrored on both [HuggingFace](https://huggingface.co/edwinhere/namer) and [GitHub](https://github.com/edwinhere/namer). Use whichever you prefer!** - -## Model Description - -Namer is a sequence-to-sequence transformer trained to read digits of a number and generate the corresponding English textual representation. It handles numbers from 0 up to billions, learning the patterns of English number naming conventions. - -**Example conversions:** -| Integer | English Name | -|---------|-------------| -| 0 | zero | -| 42 | forty two | -| 123 | one hundred twenty three | -| 1000 | one thousand | -| 1234567 | one million two hundred thirty four thousand five hundred sixty seven | - -## Usage - -### 🚀 HuggingFace Transformers (Recommended) - -Load and use the model with HuggingFace's `AutoModel` API: - -```python -from transformers import AutoModel -from namer import NamerPipeline - -# Load model from HuggingFace -model = AutoModel.from_pretrained( - "edwinhere/namer", - trust_remote_code=True -) - -# Create pipeline -pipe = NamerPipeline(model) - -# Generate number names -result = pipe.generate(42) # "forty two" -result = pipe.generate(1234567) # "one million two hundred thirty four thousand five hundred sixty seven" - -# Or use callable interface (HF compatible) -result = pipe(42) # {"generated_text": "forty two"} -``` - -Alternatively, use the convenience function: - -```python -from namer import load_namer_pipeline - -pipe = load_namer_pipeline("edwinhere/namer") -print(pipe.generate(42)) # "forty two" -``` - -### 🔄 Original API (Local) - -```python -import torch -from namer import load_namer_model, predict_number_name - -# Load model -model = load_namer_model("namer_model.pt") - -# Convert number to name -name = predict_number_name(model, 42) -print(f"42 -> '{name}'") -``` - -### 💻 Interactive Mode - -```bash -python -m namer infer -``` - -Then enter numbers to convert interactively. - -## Installation - -Choose either repository — both have identical code: - -**Option 1: Clone from HuggingFace** -```bash -git clone https://huggingface.co/edwinhere/namer -cd namer -pip install -e . -``` - -**Option 2: Clone from GitHub** -```bash -git clone https://github.com/edwinhere/namer.git -cd namer -pip install -e . -``` - -**Option 3: Direct pip install (from GitHub)** -```bash -pip install git+https://github.com/edwinhere/namer.git -``` - -## Model Architecture - -- **Type**: Sequence-to-sequence transformer -- **Input**: Digits of the integer (as token indices) -- **Output**: English words representing the number -- **Vocabulary**: English number words (zero-nineteen, twenty-ninety, hundred, thousand, million, billion, etc.) -- **Max Output Length**: 20 tokens - -## Files - -| File | Description | -|------|-------------| -| `pytorch_model.bin` | HuggingFace model weights | -| `config.json` | Model configuration | -| `generation_config.json` | Generation parameters | -| `modeling_namer.py` | HF-compatible model implementation | -| `namer_model.pt` | Original PyTorch checkpoint | -| `namer/` | Source code package | - -## Training - -To train from scratch: - -```bash -python -m namer train -``` - -## Citation - -If you use this model, please cite: - -```bibtex -@software{namer, - author = {Edwin Jose Palathinkal}, - title = {Namer: Integer to English Name Converter}, - url = {https://huggingface.co/edwinhere/namer} -} -``` - -## Links - -| Platform | URL | Purpose | -|----------|-----|---------| -| 🤗 HuggingFace | [huggingface.co/edwinhere/namer](https://huggingface.co/edwinhere/namer) | Model card, inference API, downloads | -| 🐙 GitHub | [github.com/edwinhere/namer](https://github.com/edwinhere/namer) | Source code, issues, development | diff --git a/README.md.tmp b/README.md.tmp deleted file mode 100644 index 8caddbd34d2a3417fd00b33a3c77f6281dbe53d4..0000000000000000000000000000000000000000 --- a/README.md.tmp +++ /dev/null @@ -1,114 +0,0 @@ ---- -language: en -license: mit -library_name: pytorch -tags: - - text-generation - - number-to-text - - pytorch - - transformer - - stratified-sampling -pipeline_tag: text-generation ---- - -# Namer - -A PyTorch transformer model that converts **integers to their English names** — now supporting numbers up to **999,999,999,999** (nearly one trillion)! - -## Quick Start - -```python -from transformers import AutoModel -from namer import NamerPipeline - -# Load model -model = AutoModel.from_pretrained( - "edwinhere/namer", - trust_remote_code=True -) - -# Create pipeline -pipe = NamerPipeline(model) - -# Generate number names -print(pipe.generate(42)) # "forty two" -print(pipe.generate(1234567890)) # "one billion two hundred thirty four million..." -print(pipe.generate(999999999999)) # "nine hundred ninety nine billion..." -``` - -## Model Description - -Namer is a sequence-to-sequence transformer trained to read digits of a number and generate the corresponding English textual representation. - -### Key Features - -- 🎯 **Stratified Training**: Balanced sampling across number scales ensures accurate performance on both small and large numbers -- 📈 **Large Range**: Handles numbers from 0 to ~1 trillion (12 digits) -- 🚀 **Fast Inference**: Single forward pass, no autoregressive generation needed -- 🎓 **High Accuracy**: >99.9% validation accuracy - -### Example Conversions - -| Integer | English Name | -|---------|-------------| -| 0 | zero | -| 42 | forty two | -| 123 | one hundred twenty three | -| 1000 | one thousand | -| 999999 | nine hundred ninety nine thousand nine hundred ninety nine | -| 1234567890 | one billion two hundred thirty four million five hundred sixty seven thousand eight hundred ninety | -| 999999999999 | nine hundred ninety nine billion nine hundred ninety nine million nine hundred ninety nine thousand nine hundred ninety nine | - -## Architecture - -- **Type**: Transformer encoder with learned queries and cross-attention -- **Parameters**: ~869K -- **Vocabulary**: 41 tokens (number words + EOS) -- **Max Output Length**: 25 tokens -- **Input**: Digit sequences (0-9 + padding) - -## Training Details - -- **Dataset**: Infinite stratified sampling across 5 scales (units, thousands, millions, billions, trillions) -- **Optimizer**: Adam (lr=0.001) -- **Epochs**: 30 with early stopping (patience=10) -- **Hardware**: NVIDIA RTX 3070 -- **Validation Accuracy**: >99.9% - -### Why Stratified Sampling? - -With uniform random sampling from 0-1T, 99.9% of samples would be >1M, causing the model to fail on small numbers. Stratified sampling gives each magnitude equal representation (20% each), ensuring robust performance across the entire range. - -## Version History - -**v2.0 (Current)** -- Range: 0 to 999,999,999,999 (trillions) -- Stratified sampling for balanced training -- Max output length: 25 tokens - -**v1.0** -- Range: 0 to 999,999 (millions) -- Uniform random sampling -- Max output length: 20 tokens - -## Limitations - -- Maximum: 999,999,999,999 (12 digits) -- No negative numbers (uses absolute value) -- No decimal/fractional numbers - -## Citation - -```bibtex -@software{namer, - author = {Edwin Jose Palathinkal}, - title = {Namer: Integer to English Name Converter}, - url = {https://huggingface.co/edwinhere/namer}, - year = {2025} -} -``` - -## Links - -- GitHub: https://github.com/edwinhere/namer -- HuggingFace: https://huggingface.co/edwinhere/namer diff --git a/README_HF.md b/README_HF.md deleted file mode 100644 index 8caddbd34d2a3417fd00b33a3c77f6281dbe53d4..0000000000000000000000000000000000000000 --- a/README_HF.md +++ /dev/null @@ -1,114 +0,0 @@ ---- -language: en -license: mit -library_name: pytorch -tags: - - text-generation - - number-to-text - - pytorch - - transformer - - stratified-sampling -pipeline_tag: text-generation ---- - -# Namer - -A PyTorch transformer model that converts **integers to their English names** — now supporting numbers up to **999,999,999,999** (nearly one trillion)! - -## Quick Start - -```python -from transformers import AutoModel -from namer import NamerPipeline - -# Load model -model = AutoModel.from_pretrained( - "edwinhere/namer", - trust_remote_code=True -) - -# Create pipeline -pipe = NamerPipeline(model) - -# Generate number names -print(pipe.generate(42)) # "forty two" -print(pipe.generate(1234567890)) # "one billion two hundred thirty four million..." -print(pipe.generate(999999999999)) # "nine hundred ninety nine billion..." -``` - -## Model Description - -Namer is a sequence-to-sequence transformer trained to read digits of a number and generate the corresponding English textual representation. - -### Key Features - -- 🎯 **Stratified Training**: Balanced sampling across number scales ensures accurate performance on both small and large numbers -- 📈 **Large Range**: Handles numbers from 0 to ~1 trillion (12 digits) -- 🚀 **Fast Inference**: Single forward pass, no autoregressive generation needed -- 🎓 **High Accuracy**: >99.9% validation accuracy - -### Example Conversions - -| Integer | English Name | -|---------|-------------| -| 0 | zero | -| 42 | forty two | -| 123 | one hundred twenty three | -| 1000 | one thousand | -| 999999 | nine hundred ninety nine thousand nine hundred ninety nine | -| 1234567890 | one billion two hundred thirty four million five hundred sixty seven thousand eight hundred ninety | -| 999999999999 | nine hundred ninety nine billion nine hundred ninety nine million nine hundred ninety nine thousand nine hundred ninety nine | - -## Architecture - -- **Type**: Transformer encoder with learned queries and cross-attention -- **Parameters**: ~869K -- **Vocabulary**: 41 tokens (number words + EOS) -- **Max Output Length**: 25 tokens -- **Input**: Digit sequences (0-9 + padding) - -## Training Details - -- **Dataset**: Infinite stratified sampling across 5 scales (units, thousands, millions, billions, trillions) -- **Optimizer**: Adam (lr=0.001) -- **Epochs**: 30 with early stopping (patience=10) -- **Hardware**: NVIDIA RTX 3070 -- **Validation Accuracy**: >99.9% - -### Why Stratified Sampling? - -With uniform random sampling from 0-1T, 99.9% of samples would be >1M, causing the model to fail on small numbers. Stratified sampling gives each magnitude equal representation (20% each), ensuring robust performance across the entire range. - -## Version History - -**v2.0 (Current)** -- Range: 0 to 999,999,999,999 (trillions) -- Stratified sampling for balanced training -- Max output length: 25 tokens - -**v1.0** -- Range: 0 to 999,999 (millions) -- Uniform random sampling -- Max output length: 20 tokens - -## Limitations - -- Maximum: 999,999,999,999 (12 digits) -- No negative numbers (uses absolute value) -- No decimal/fractional numbers - -## Citation - -```bibtex -@software{namer, - author = {Edwin Jose Palathinkal}, - title = {Namer: Integer to English Name Converter}, - url = {https://huggingface.co/edwinhere/namer}, - year = {2025} -} -``` - -## Links - -- GitHub: https://github.com/edwinhere/namer -- HuggingFace: https://huggingface.co/edwinhere/namer diff --git a/convert_checkpoint.py b/convert_checkpoint.py deleted file mode 100644 index 1fae3a59c4400c8342f085ff17fb9ae8141bb8c5..0000000000000000000000000000000000000000 --- a/convert_checkpoint.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Convert old checkpoint format to HuggingFace format.""" - -import torch -from modeling_namer import NamerModel, NamerConfig - -# Load old checkpoint -checkpoint = torch.load("namer_model.pt", map_location="cpu") - -# Create config from checkpoint -config = NamerConfig( - vocab_size=checkpoint["vocab_size"], - max_output_len=checkpoint["max_output_len"], - d_model=checkpoint.get("d_model", 128), - nhead=4, - num_encoder_layers=4, - dim_feedforward=512, - dropout=0.0, -) - -# Create new model -model = NamerModel(config) - -# Load old weights into new model -model.load_state_dict(checkpoint["model_state_dict"], strict=False) - -# Save in HF format -model.save_pretrained(".") -print("Model converted and saved to current directory") -print("Files saved: pytorch_model.bin, config.json") diff --git a/namer/__init__.py b/namer/__init__.py index 4d706fbde31cf0c095d3c62cc0d8432f8db47003..0eab3490760a257eb5ed29143dc8e724b661553f 100644 --- a/namer/__init__.py +++ b/namer/__init__.py @@ -1,8 +1,7 @@ """Namer - A PyTorch transformer model for converting numbers to English names.""" -__version__ = "0.3.0" +__version__ = "0.2.0" -# Original API from namer.models import NamerTransformer, load_namer_model from namer.inference import predict_number_name from namer.utils import ( @@ -16,20 +15,7 @@ from namer.utils import ( read_double, ) -# HuggingFace compatible API -try: - from .modeling_namer import ( - NamerModel, - NamerConfig, - NamerPipeline, - load_namer_pipeline, - ) - HF_AVAILABLE = True -except ImportError: - HF_AVAILABLE = False - __all__ = [ - # Original API "NamerTransformer", "load_namer_model", "predict_number_name", @@ -42,12 +28,3 @@ __all__ = [ "read_triplet", "read_double", ] - -if HF_AVAILABLE: - __all__.extend([ - # HuggingFace API - "NamerModel", - "NamerConfig", - "NamerPipeline", - "load_namer_pipeline", - ]) diff --git a/namer/modeling_namer.py b/namer/modeling_namer.py deleted file mode 100644 index ebdc7bf24d3e7238156294979a6f2ada08363888..0000000000000000000000000000000000000000 --- a/namer/modeling_namer.py +++ /dev/null @@ -1,342 +0,0 @@ -"""HuggingFace compatible Namer model.""" - -from __future__ import annotations - -import math -from typing import Optional, Union - -import torch -import torch.nn as nn -from transformers import PreTrainedModel, PretrainedConfig -from transformers.modeling_outputs import CausalLMOutputWithCrossAttentions -from transformers.generation import GenerationMixin - - -class NamerConfig(PretrainedConfig): - """Configuration class for NamerModel.""" - - model_type = "custom" - - def __init__( - self, - vocab_size: int = 41, - max_output_len: int = 20, - d_model: int = 128, - nhead: int = 4, - num_encoder_layers: int = 4, - dim_feedforward: int = 512, - dropout: float = 0.1, - pad_token_id: int = 10, - eos_token_id: int = 40, # token index - **kwargs, - ): - self.vocab_size = vocab_size - self.max_output_len = max_output_len - self.d_model = d_model - self.nhead = nhead - self.num_encoder_layers = num_encoder_layers - self.dim_feedforward = dim_feedforward - self.dropout = dropout - - super().__init__( - pad_token_id=pad_token_id, - eos_token_id=eos_token_id, - **kwargs, - ) - - -class PositionalEncoding(nn.Module): - """Sinusoidal positional encoding for transformer.""" - - def __init__(self, d_model: int, max_len: int = 5000) -> None: - super().__init__() - - pe = torch.zeros(max_len, d_model) - position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1) - div_term = torch.exp( - torch.arange(0, d_model, 2).float() - * (-math.log(10000.0) / d_model) - ) - - pe[:, 0::2] = torch.sin(position * div_term) - pe[:, 1::2] = torch.cos(position * div_term) - - self.register_buffer("pe", pe) - - def forward(self, x: torch.Tensor) -> torch.Tensor: - """Add positional encoding to input.""" - return x + self.pe[: x.size(1)] - - -class NamerModel(PreTrainedModel, GenerationMixin): - """HuggingFace compatible Namer transformer model. - - Converts integer digit sequences to English number names. - """ - - config_class = NamerConfig - base_model_prefix = "namer" - - def __init__(self, config: NamerConfig): - super().__init__(config) - - self.vocab_size = config.vocab_size - self.max_output_len = config.max_output_len - self.d_model = config.d_model - - # Digit embedding (10 digits + 1 padding token = 11) - self.digit_embedding = nn.Embedding(11, config.d_model, padding_idx=config.pad_token_id) - - # Positional encoding - self.pos_encoder = PositionalEncoding(config.d_model, max_len=100) - - # Transformer encoder - encoder_layer = nn.TransformerEncoderLayer( - d_model=config.d_model, - nhead=config.nhead, - dim_feedforward=config.dim_feedforward, - dropout=config.dropout, - batch_first=True, - ) - self.transformer_encoder = nn.TransformerEncoder( - encoder_layer, num_layers=config.num_encoder_layers - ) - - # Output projection - self.output_projection = nn.Linear(config.d_model, config.vocab_size) - - # Learned queries for each output position - self.output_queries = nn.Parameter(torch.randn(config.max_output_len, config.d_model)) - - # Cross-attention from output positions to encoded input - self.cross_attention = nn.MultiheadAttention( - config.d_model, config.nhead, dropout=config.dropout, batch_first=True - ) - - # Final output layers - self.output_norm = nn.LayerNorm(config.d_model) - - self.post_init() - - def forward( - self, - input_ids: Optional[torch.Tensor] = None, - attention_mask: Optional[torch.Tensor] = None, - labels: Optional[torch.Tensor] = None, - **kwargs, - ) -> CausalLMOutputWithCrossAttentions: - """Forward pass for HF compatibility. - - Args: - input_ids: (batch_size, seq_len) tensor of digit indices (0-9), padding=10 - attention_mask: Optional mask for padding - labels: Optional target labels for training - - Returns: - CausalLMOutputWithCrossAttentions with logits - """ - if input_ids is None: - raise ValueError("input_ids must be provided") - - batch_size, seq_len = input_ids.shape - - # Handle padding: convert -1 padding to 10 (our padding index) - digits = input_ids.clone() - digits[digits == -1] = self.config.pad_token_id - - # Create padding mask for transformer (True = padding) - if attention_mask is None: - src_key_padding_mask = digits == self.config.pad_token_id - else: - src_key_padding_mask = ~attention_mask.bool() - - # Embed digits: (batch, seq_len, d_model) - embedded = self.digit_embedding(digits) - - # Add positional encoding - embedded = self.pos_encoder(embedded) - - # Transformer encoder: (batch, seq_len, d_model) - memory = self.transformer_encoder( - embedded, src_key_padding_mask=src_key_padding_mask - ) - - # Expand queries for batch: (batch, max_output_len, d_model) - queries = self.output_queries.unsqueeze(0).expand(batch_size, -1, -1) - - # Cross-attention from queries to encoded input - attn_output, _ = self.cross_attention( - queries, memory, memory, key_padding_mask=src_key_padding_mask - ) - - # Normalize and project to vocab - output = self.output_norm(attn_output) - logits = self.output_projection(output) - - loss = None - if labels is not None: - loss_fct = nn.CrossEntropyLoss(ignore_index=-100) - loss = loss_fct(logits.view(-1, self.vocab_size), labels.view(-1)) - - return CausalLMOutputWithCrossAttentions( - loss=loss, - logits=logits, - hidden_states=None, - attentions=None, - cross_attentions=None, - ) - - def prepare_inputs_for_generation(self, input_ids, **kwargs): - """Prepare inputs for text generation.""" - return {"input_ids": input_ids} - - def _reorder_cache(self, past_key_values, beam_idx): - """Reorder cache for beam search.""" - return past_key_values - - -class NamerPipeline: - """Simple pipeline for Namer model inference. - - Usage: - from transformers import AutoModel - - # Load model - model = AutoModel.from_pretrained( - "edwinhere/namer", - trust_remote_code=True - ) - - # Create pipeline - pipe = NamerPipeline(model) - - # Generate - result = pipe.generate(42) # "forty two" - result = pipe(42) # {"generated_text": "forty two"} - """ - - def __init__(self, model: NamerModel, tokenizer=None, device: str = None): - if device is None: - device = "cuda" if torch.cuda.is_available() else "cpu" - self.model = model.to(device) - self.model.eval() - self.device = device - self.tokenizer = tokenizer # Placeholder if we add a tokenizer later - - # Vocabulary mapping (index -> word) - # Must match utils.py vocabulary exactly - self.id2word = { - 0: "zero", 1: "one", 2: "two", 3: "three", 4: "four", - 5: "five", 6: "six", 7: "seven", 8: "eight", 9: "nine", - 10: "ten", 11: "eleven", 12: "twelve", 13: "thirteen", 14: "fourteen", - 15: "fifteen", 16: "sixteen", 17: "seventeen", 18: "eighteen", 19: "nineteen", - 20: "twenty", 21: "thirty", 22: "forty", 23: "fifty", - 24: "sixty", 25: "seventy", 26: "eighty", 27: "ninety", - 28: "hundred", - 29: "thousand", 30: "million", 31: "billion", 32: "trillion", - 33: "quadrillion", 34: "quintillion", 35: "sextillion", - 36: "septillion", 37: "octillion", 38: "nonillion", 39: "decillion", - 40: "" - } - - # Reverse mapping - self.word2id = {v: k for k, v in self.id2word.items()} - - def _int_to_digits(self, n: int) -> list[int]: - """Convert integer to list of digit indices.""" - if n == 0: - return [0] - digits = [] - while n > 0: - digits.append(n % 10) - n //= 10 - return digits[::-1] # Reverse to get most significant digit first - - def _decode(self, token_ids: list[int]) -> str: - """Decode token IDs to text, stopping at first EOS.""" - words = [] - eos_idx = self.model.config.eos_token_id # Should be 40 - - for idx in token_ids: - if idx == eos_idx: # Stop at EOS - break - if idx in self.id2word: - word = self.id2word[idx] - if word != "": # Skip EOS token itself - words.append(word) - - return " ".join(words) if words else "zero" - - def generate(self, text: Union[str, int], **kwargs) -> str: - """Generate English name for a number. - - Args: - text: Integer or string representation of integer - - Returns: - English name of the number - """ - # Parse input - if isinstance(text, str): - n = int(text.strip()) - else: - n = int(text) - - # Convert to digits - digits = self._int_to_digits(n) - - # Pad to max length (20) - while len(digits) < 20: - digits.append(10) # padding token - - # Create tensor - input_ids = torch.tensor([digits], dtype=torch.long).to(self.device) - - # Forward pass - with torch.no_grad(): - outputs = self.model(input_ids) - logits = outputs.logits - predictions = logits.argmax(dim=-1)[0].cpu().tolist() - - # Decode - return self._decode(predictions) - - def __call__(self, text: Union[str, int], **kwargs) -> dict: - """Callable interface for pipeline. - - Returns dict with 'generated_text' key for HF pipeline compatibility. - """ - result = self.generate(text, **kwargs) - return {"generated_text": result} - - -def load_namer_pipeline(model_name_or_path: str = "edwinhere/namer", device: str = None, **kwargs): - """Load a Namer pipeline with model. - - This is a convenience function that loads both the model and creates - a pipeline for easy inference. - - Args: - model_name_or_path: HuggingFace model ID or local path - device: Device to run on ('cuda', 'cpu', or None for auto) - **kwargs: Additional args passed to from_pretrained - - Returns: - NamerPipeline instance ready for inference - - Example: - >>> pipe = load_namer_pipeline("edwinhere/namer") - >>> pipe.generate(42) - 'forty two' - >>> pipe(123) - {'generated_text': 'one hundred twenty three'} - """ - from transformers import AutoModel - - model = AutoModel.from_pretrained( - model_name_or_path, - trust_remote_code=True, - **kwargs - ) - - return NamerPipeline(model, device=device) diff --git a/pip-tmp/jiti/agents-agent-management.2587ecf7.mjs b/pip-tmp/jiti/agents-agent-management.2587ecf7.mjs new file mode 100644 index 0000000000000000000000000000000000000000..f5e9e9c657f8094aca06b73ed8313a8347a6c0b7 --- /dev/null +++ b/pip-tmp/jiti/agents-agent-management.2587ecf7.mjs @@ -0,0 +1,643 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.handleCreate = handleCreate;exports.handleList = handleList;exports.handleManagementAction = handleManagementAction;exports.handleUpdate = handleUpdate;var fs = _interopRequireWildcard(await jitiImport("node:fs")); +var path = _interopRequireWildcard(await jitiImport("node:path")); + + +var _agents = await jitiImport("./agents.ts"); + + + + + + + + + + + + + +var _agentSerializer = await jitiImport("./agent-serializer.ts"); +var _chainSerializer = await jitiImport("./chain-serializer.ts"); +var _skills = await jitiImport("./skills.ts");function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);} + + + + + + + + + + + + + + +function result(text, isError = false) { + return { content: [{ type: "text", text }], isError, details: { mode: "management", results: [] } }; +} + +function parseCsv(value) { + return [...new Set(value.split(",").map((v) => v.trim()).filter(Boolean))]; +} + +function configObject(config) { + let val = config; + if (typeof val === "string") { + try { + val = JSON.parse(val); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { error: `config must be valid JSON: ${message}` }; + } + } + if (!val || typeof val !== "object" || Array.isArray(val)) return {}; + return { value: val }; +} + +function hasKey(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); +} + +function asDisambiguationScope(scope) { + if (scope === "user" || scope === "project") return scope; + return undefined; +} + +function normalizeListScope(scope) { + if (scope === undefined) return "both"; + if (scope === "user" || scope === "project" || scope === "both") return scope; + return undefined; +} + +function sanitizeName(name) { + return name.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, ""); +} + +function parsePackageConfig(value) { + return (0, _agents.parsePackageName)(value, "config.package"); +} + +function allAgents(d) { + return [...d.builtin, ...d.user, ...d.project]; +} + +function availableNames(cwd, kind) { + const d = (0, _agents.discoverAgentsAll)(cwd); + const items = kind === "agent" ? allAgents(d) : d.chains; + return [...new Set(items.map((x) => x.name))].sort((a, b) => a.localeCompare(b)); +} + +function findAgents(name, cwd, scope = "both") { + const d = (0, _agents.discoverAgentsAll)(cwd); + const raw = name.trim(); + const sanitized = sanitizeName(raw); + return allAgents(d). + filter((a) => (scope === "both" || a.source === scope) && (a.name === raw || a.name === sanitized)). + sort((a, b) => a.source.localeCompare(b.source)); +} + +function findChains(name, cwd, scope = "both") { + const raw = name.trim(); + const sanitized = sanitizeName(raw); + return (0, _agents.discoverAgentsAll)(cwd).chains. + filter((c) => (scope === "both" || c.source === scope) && (c.name === raw || c.name === sanitized)). + sort((a, b) => a.source.localeCompare(b.source)); +} + +function nameExistsInScope(cwd, scope, name, excludePath) { + const d = (0, _agents.discoverAgentsAll)(cwd); + for (const a of scope === "user" ? d.user : d.project) { + if (a.name === name && a.filePath !== excludePath) return true; + } + for (const c of d.chains) { + if (c.source === scope && c.name === name && c.filePath !== excludePath) return true; + } + return false; +} + +function unknownChainAgents(cwd, steps) { + const d = (0, _agents.discoverAgentsAll)(cwd); + const known = new Set(allAgents(d).map((a) => a.name)); + return [...new Set(steps.map((s) => s.agent).filter((a) => !known.has(a)))].sort((a, b) => a.localeCompare(b)); +} + +function chainStepWarnings(ctx, steps) { + const warnings = []; + const available = new Set((0, _skills.discoverAvailableSkills)(ctx.cwd).map((s) => s.name)); + for (let i = 0; i < steps.length; i++) { + const s = steps[i]; + if (s.model) { + const found = ctx.modelRegistry.getAvailable().some((m) => `${m.provider}/${m.id}` === s.model || m.id === s.model); + if (!found) warnings.push(`Warning: step ${i + 1} (${s.agent}): model '${s.model}' is not in the current model registry.`); + } + if (Array.isArray(s.skills) && s.skills.length > 0) { + const missing = s.skills.filter((sk) => !available.has(sk)); + if (missing.length) warnings.push(`Warning: step ${i + 1} (${s.agent}): skills not found: ${missing.join(", ")}.`); + } + } + return warnings; +} + +function modelWarning(ctx, model) { + if (!model) return undefined; + const found = ctx.modelRegistry.getAvailable().some((m) => `${m.provider}/${m.id}` === model || m.id === model); + return found ? undefined : `Warning: model '${model}' is not in the current model registry.`; +} + +function fallbackModelsWarning(ctx, fallbackModels) { + if (!fallbackModels || fallbackModels.length === 0) return undefined; + const available = new Set(ctx.modelRegistry.getAvailable().flatMap((m) => [`${m.provider}/${m.id}`, m.id])); + const missing = fallbackModels.filter((model) => !available.has(model)); + return missing.length ? `Warning: fallback models not in the current model registry: ${missing.join(", ")}.` : undefined; +} + +function skillsWarning(cwd, skills) { + if (!skills || skills.length === 0) return undefined; + const available = new Set((0, _skills.discoverAvailableSkills)(cwd).map((s) => s.name)); + const missing = skills.filter((s) => !available.has(s)); + return missing.length ? `Warning: skills not found: ${missing.join(", ")}.` : undefined; +} + +function parseStepList(raw) { + if (!Array.isArray(raw)) return { error: "config.steps must be an array." }; + if (raw.length === 0) return { error: "config.steps must include at least one step." }; + const steps = []; + for (let i = 0; i < raw.length; i++) { + const item = raw[i]; + if (!item || typeof item !== "object" || Array.isArray(item)) return { error: `config.steps[${i}] must be an object.` }; + const s = item; + if (typeof s.agent !== "string" || !s.agent.trim()) return { error: `config.steps[${i}].agent must be a non-empty string.` }; + const step = { agent: s.agent.trim(), task: typeof s.task === "string" ? s.task : "" }; + if (hasKey(s, "output")) { + if (s.output === false) step.output = false;else + if (typeof s.output === "string") step.output = s.output;else + return { error: `config.steps[${i}].output must be a string or false.` }; + } + if (hasKey(s, "outputMode")) { + if (s.outputMode === "inline" || s.outputMode === "file-only") step.outputMode = s.outputMode;else + return { error: `config.steps[${i}].outputMode must be 'inline' or 'file-only'.` }; + } + if (hasKey(s, "reads")) { + if (s.reads === false) step.reads = false;else + if (Array.isArray(s.reads)) step.reads = s.reads.filter((v) => typeof v === "string").map((v) => v.trim()).filter(Boolean);else + return { error: `config.steps[${i}].reads must be an array or false.` }; + } + if (hasKey(s, "model")) { + if (typeof s.model === "string") step.model = s.model;else + return { error: `config.steps[${i}].model must be a string.` }; + } + if (hasKey(s, "skills")) { + if (s.skills === false) step.skills = false;else + if (Array.isArray(s.skills)) step.skills = s.skills.filter((v) => typeof v === "string").map((v) => v.trim()).filter(Boolean);else + return { error: `config.steps[${i}].skills must be an array or false.` }; + } + if (hasKey(s, "progress")) { + if (typeof s.progress === "boolean") step.progress = s.progress;else + return { error: `config.steps[${i}].progress must be a boolean.` }; + } + steps.push(step); + } + return { steps }; +} + +function parseTools(raw) { + const tools = []; + const mcpDirectTools = []; + for (const item of parseCsv(raw)) { + if (item.startsWith("mcp:")) { + const direct = item.slice(4).trim(); + if (direct) mcpDirectTools.push(direct); + } else tools.push(item); + } + return { tools: tools.length ? tools : undefined, mcpDirectTools: mcpDirectTools.length ? mcpDirectTools : undefined }; +} + +function applyAgentConfig(target, cfg) { + if (hasKey(cfg, "systemPrompt")) { + if (cfg.systemPrompt === false || cfg.systemPrompt === "") target.systemPrompt = "";else + if (typeof cfg.systemPrompt === "string") target.systemPrompt = cfg.systemPrompt;else + return "config.systemPrompt must be a string or false when provided."; + } + if (hasKey(cfg, "model")) { + if (cfg.model === false || cfg.model === "") target.model = undefined;else + if (typeof cfg.model === "string") target.model = cfg.model.trim() || undefined;else + return "config.model must be a string or false when provided."; + } + if (hasKey(cfg, "fallbackModels")) { + if (cfg.fallbackModels === false || cfg.fallbackModels === "") target.fallbackModels = undefined;else + if (typeof cfg.fallbackModels === "string") { + const models = parseCsv(cfg.fallbackModels); + target.fallbackModels = models.length ? models : undefined; + } else if (Array.isArray(cfg.fallbackModels)) { + const models = cfg.fallbackModels. + filter((value) => typeof value === "string"). + map((value) => value.trim()). + filter(Boolean); + target.fallbackModels = models.length ? [...new Set(models)] : undefined; + } else return "config.fallbackModels must be a comma-separated string, string array, or false when provided."; + } + if (hasKey(cfg, "tools")) { + if (cfg.tools === false || cfg.tools === "") {target.tools = undefined;target.mcpDirectTools = undefined;} else + if (typeof cfg.tools === "string") {const parsed = parseTools(cfg.tools);target.tools = parsed.tools;target.mcpDirectTools = parsed.mcpDirectTools;} else + return "config.tools must be a comma-separated string or false when provided."; + } + if (hasKey(cfg, "skills")) { + if (cfg.skills === false || cfg.skills === "") target.skills = undefined;else + if (typeof cfg.skills === "string") {const skills = parseCsv(cfg.skills);target.skills = skills.length ? skills : undefined;} else + return "config.skills must be a comma-separated string or false when provided."; + } + if (hasKey(cfg, "extensions")) { + if (cfg.extensions === false) target.extensions = undefined;else + if (cfg.extensions === "") target.extensions = [];else + if (typeof cfg.extensions === "string") target.extensions = parseCsv(cfg.extensions);else + return "config.extensions must be a comma-separated string, empty string, or false when provided."; + } + if (hasKey(cfg, "thinking")) { + if (cfg.thinking === false || cfg.thinking === "") target.thinking = undefined;else + if (typeof cfg.thinking === "string") target.thinking = cfg.thinking.trim() || undefined;else + return "config.thinking must be a string or false when provided."; + } + if (hasKey(cfg, "systemPromptMode")) { + if (cfg.systemPromptMode === "append" || cfg.systemPromptMode === "replace") target.systemPromptMode = cfg.systemPromptMode;else + return "config.systemPromptMode must be 'append' or 'replace' when provided."; + } + if (hasKey(cfg, "inheritProjectContext")) { + if (typeof cfg.inheritProjectContext !== "boolean") return "config.inheritProjectContext must be a boolean when provided."; + target.inheritProjectContext = cfg.inheritProjectContext; + } + if (hasKey(cfg, "inheritSkills")) { + if (typeof cfg.inheritSkills !== "boolean") return "config.inheritSkills must be a boolean when provided."; + target.inheritSkills = cfg.inheritSkills; + } + if (hasKey(cfg, "defaultContext")) { + if (cfg.defaultContext === false || cfg.defaultContext === "") target.defaultContext = undefined;else + if (cfg.defaultContext === "fresh" || cfg.defaultContext === "fork") target.defaultContext = cfg.defaultContext;else + return "config.defaultContext must be 'fresh', 'fork', or false when provided."; + } + if (hasKey(cfg, "output")) { + if (cfg.output === false || cfg.output === "") target.output = undefined;else + if (typeof cfg.output === "string") target.output = cfg.output;else + return "config.output must be a string or false when provided."; + } + if (hasKey(cfg, "reads")) { + if (cfg.reads === false || cfg.reads === "") target.defaultReads = undefined;else + if (typeof cfg.reads === "string") { + const reads = parseCsv(cfg.reads); + target.defaultReads = reads.length ? reads : undefined; + } else return "config.reads must be a comma-separated string or false when provided."; + } + if (hasKey(cfg, "progress")) { + if (typeof cfg.progress !== "boolean") return "config.progress must be a boolean when provided."; + target.defaultProgress = cfg.progress; + } + if (hasKey(cfg, "maxSubagentDepth")) { + if (cfg.maxSubagentDepth === false || cfg.maxSubagentDepth === "") target.maxSubagentDepth = undefined;else + if (typeof cfg.maxSubagentDepth === "number" && Number.isInteger(cfg.maxSubagentDepth) && cfg.maxSubagentDepth >= 0) { + target.maxSubagentDepth = cfg.maxSubagentDepth; + } else return "config.maxSubagentDepth must be an integer >= 0 or false when provided."; + } + return undefined; +} + +function resolveTarget( +kind, +name, +matches, +cwd, +scopeHint) +{ + const mutable = matches.filter((m) => m.source !== "builtin"); + if (mutable.length === 0) { + if (matches.length > 0) { + return result(`${kind === "agent" ? "Agent" : "Chain"} '${name}' is builtin and cannot be modified. Create a same-named ${kind} in user or project scope to override it.`, true); + } + const available = availableNames(cwd, kind); + return result(`${kind === "agent" ? "Agent" : "Chain"} '${name}' not found. Available: ${available.join(", ") || "none"}.`, true); + } + if (mutable.length === 1) return mutable[0]; + const scope = asDisambiguationScope(scopeHint); + if (!scope) { + const paths = mutable.map((m) => `${m.source}: ${m.filePath}`).join("\n"); + return result(`${kind === "agent" ? "Agent" : "Chain"} '${name}' exists in both scopes. Specify agentScope: 'user' or 'project'.\n${paths}`, true); + } + const scoped = mutable.filter((m) => m.source === scope); + if (scoped.length === 0) return result(`${kind === "agent" ? "Agent" : "Chain"} '${name}' not found in scope '${scope}'.`, true); + if (scoped.length > 1) return result(`Multiple ${kind}s named '${name}' found in scope '${scope}': ${scoped.map((m) => m.filePath).join(", ")}`, true); + return scoped[0]; +} + +function renamePath( +kind, +currentPath, +newName, +scope, +cwd) +{ + if (nameExistsInScope(cwd, scope, newName, currentPath)) return { error: `Name '${newName}' already exists in ${scope} scope.` }; + const ext = kind === "agent" ? ".md" : ".chain.md"; + const filePath = path.join(path.dirname(currentPath), `${newName}${ext}`); + if (fs.existsSync(filePath) && filePath !== currentPath) { + return { error: `File already exists at ${filePath} but is not a valid ${kind} definition. Remove or rename it first.` }; + } + fs.renameSync(currentPath, filePath); + return { filePath }; +} + +function formatAgentDetail(agent) { + const tools = [...(agent.tools ?? []), ...(agent.mcpDirectTools ?? []).map((t) => `mcp:${t}`)]; + const lines = [`Agent: ${agent.name} (${agent.source})`, `Path: ${agent.filePath}`, `Description: ${agent.description}`]; + if (agent.packageName) { + lines.push(`Local name: ${(0, _agents.frontmatterNameForConfig)(agent)}`); + lines.push(`Package: ${agent.packageName}`); + } + if (agent.model) lines.push(`Model: ${agent.model}`); + if (agent.fallbackModels?.length) lines.push(`Fallback models: ${agent.fallbackModels.join(", ")}`); + if (tools.length) lines.push(`Tools: ${tools.join(", ")}`); + if (agent.skills?.length) lines.push(`Skills: ${agent.skills.join(", ")}`); + lines.push(`System prompt mode: ${agent.systemPromptMode}`); + lines.push(`Inherit project context: ${agent.inheritProjectContext ? "true" : "false"}`); + lines.push(`Inherit skills: ${agent.inheritSkills ? "true" : "false"}`); + if (agent.defaultContext) lines.push(`Default context: ${agent.defaultContext}`); + if (agent.source === "builtin") lines.push(`Disabled: ${agent.disabled ? "true" : "false"}`); + if (agent.extensions !== undefined) lines.push(`Extensions: ${agent.extensions.length ? agent.extensions.join(", ") : "(none)"}`); + if (agent.thinking) lines.push(`Thinking: ${agent.thinking}`); + if (agent.output) lines.push(`Output: ${agent.output}`); + if (agent.defaultReads?.length) lines.push(`Reads: ${agent.defaultReads.join(", ")}`); + if (agent.defaultProgress) lines.push("Progress: true"); + if (agent.maxSubagentDepth !== undefined) lines.push(`Max subagent depth: ${agent.maxSubagentDepth}`); + if (agent.systemPrompt.trim()) lines.push("", "System Prompt:", agent.systemPrompt); + return lines.join("\n"); +} + +function formatChainDetail(chain) { + const lines = [`Chain: ${chain.name} (${chain.source})`, `Path: ${chain.filePath}`, `Description: ${chain.description}`]; + if (chain.packageName) { + lines.push(`Local name: ${(0, _agents.frontmatterNameForConfig)(chain)}`); + lines.push(`Package: ${chain.packageName}`); + } + lines.push("", "Steps:"); + for (let i = 0; i < chain.steps.length; i++) { + const s = chain.steps[i]; + lines.push(`${i + 1}. ${s.agent}`); + if (s.task.trim()) lines.push(` Task: ${s.task}`); + if (s.output === false) lines.push(" Output: false");else + if (s.output) lines.push(` Output: ${s.output}`); + if (s.outputMode) lines.push(` Output mode: ${s.outputMode}`); + if (s.reads === false) lines.push(" Reads: false");else + if (Array.isArray(s.reads) && s.reads.length > 0) lines.push(` Reads: ${s.reads.join(", ")}`); + if (s.model) lines.push(` Model: ${s.model}`); + if (s.skills === false) lines.push(" Skills: false");else + if (Array.isArray(s.skills) && s.skills.length > 0) lines.push(` Skills: ${s.skills.join(", ")}`); + if (s.progress !== undefined) lines.push(` Progress: ${s.progress ? "true" : "false"}`); + } + return lines.join("\n"); +} + +function handleList(params, ctx) { + const scope = normalizeListScope(params.agentScope) ?? "both"; + const d = (0, _agents.discoverAgentsAll)(ctx.cwd); + const scopedAgents = allAgents(d).filter((a) => scope === "both" || a.source === "builtin" || a.source === scope).sort((a, b) => a.name.localeCompare(b.name)); + const agents = scopedAgents.filter((a) => !a.disabled); + const chains = d.chains.filter((c) => scope === "both" || c.source === scope).sort((a, b) => a.name.localeCompare(b.name)); + const lines = [ + "Executable agents:", + ...(agents.length ? + agents.map((a) => `- ${a.name} (${a.source}${a.defaultContext ? `, context: ${a.defaultContext}` : ""}): ${a.description}`) : + ["- (none)"]), + "", + "Chains:", + ...(chains.length ? chains.map((c) => `- ${c.name} (${c.source}): ${c.description}`) : ["- (none)"])]; + + return result(lines.join("\n")); +} + +function handleGet(params, ctx) { + if (!params.agent && !params.chainName) return result("Specify 'agent' or 'chainName' for get.", true); + const hasBoth = Boolean(params.agent && params.chainName); + const blocks = []; + let anyFound = false; + if (params.agent) { + const matches = findAgents(params.agent, ctx.cwd, "both"); + if (!matches.length) { + const msg = `Agent '${params.agent}' not found. Available: ${availableNames(ctx.cwd, "agent").join(", ") || "none"}.`; + if (!hasBoth) return result(msg, true); + blocks.push(msg); + } else { + anyFound = true; + blocks.push(...matches.map(formatAgentDetail)); + } + } + if (params.chainName) { + const matches = findChains(params.chainName, ctx.cwd, "both"); + if (!matches.length) { + const msg = `Chain '${params.chainName}' not found. Available: ${availableNames(ctx.cwd, "chain").join(", ") || "none"}.`; + if (!hasBoth) return result(msg, true); + blocks.push(msg); + } else { + anyFound = true; + blocks.push(...matches.map(formatChainDetail)); + } + } + return result(blocks.join("\n\n"), !anyFound); +} + +function handleCreate(params, ctx) { + const parsedConfig = configObject(params.config); + if (parsedConfig.error) return result(parsedConfig.error, true); + const cfg = parsedConfig.value; + if (!cfg) return result("config required for create.", true); + if (typeof cfg.name !== "string" || !cfg.name.trim()) return result("config.name is required and must be a non-empty string.", true); + if (typeof cfg.description !== "string" || !cfg.description.trim()) return result("config.description is required and must be a non-empty string.", true); + const name = sanitizeName(cfg.name); + if (!name) return result("config.name is invalid after sanitization. Use letters, numbers, spaces, or hyphens.", true); + const parsedPackage = parsePackageConfig(cfg.package); + if (parsedPackage.error) return result(parsedPackage.error, true); + const runtimeName = (0, _agents.buildRuntimeName)(name, parsedPackage.packageName); + const scopeRaw = cfg.scope ?? "user"; + if (scopeRaw !== "user" && scopeRaw !== "project") return result("config.scope must be 'user' or 'project'.", true); + const scope = scopeRaw; + const isChain = hasKey(cfg, "steps"); + const d = (0, _agents.discoverAgentsAll)(ctx.cwd); + const targetDir = isChain ? + scope === "user" ? d.userChainDir : d.projectChainDir ?? path.join(ctx.cwd, ".pi", "chains") : + scope === "user" ? d.userDir : d.projectDir ?? path.join(ctx.cwd, ".pi", "agents"); + fs.mkdirSync(targetDir, { recursive: true }); + if (nameExistsInScope(ctx.cwd, scope, runtimeName)) return result(`Name '${runtimeName}' already exists in ${scope} scope. Use update instead.`, true); + const targetPath = path.join(targetDir, isChain ? `${runtimeName}.chain.md` : `${runtimeName}.md`); + if (fs.existsSync(targetPath)) return result(`File already exists at ${targetPath} but is not a valid ${isChain ? "chain" : "agent"} definition. Remove or rename it first.`, true); + const warnings = []; + if (!isChain && d.builtin.some((a) => a.name === runtimeName)) warnings.push(`Note: this shadows the builtin agent '${runtimeName}'.`); + if (isChain) { + const parsed = parseStepList(cfg.steps); + if (parsed.error) return result(parsed.error, true); + const chain = { name: runtimeName, localName: name, packageName: parsedPackage.packageName, description: cfg.description.trim(), source: scope, filePath: targetPath, steps: parsed.steps }; + fs.writeFileSync(targetPath, (0, _chainSerializer.serializeChain)(chain), "utf-8"); + const missing = unknownChainAgents(ctx.cwd, chain.steps); + if (missing.length) warnings.push(`Warning: chain steps reference unknown agents: ${missing.join(", ")}.`); + warnings.push(...chainStepWarnings(ctx, chain.steps)); + return result([`Created chain '${runtimeName}' at ${targetPath}.`, ...warnings].join("\n")); + } + const agent = { + name: runtimeName, + localName: name, + packageName: parsedPackage.packageName, + description: cfg.description.trim(), + source: scope, + filePath: targetPath, + systemPrompt: "", + systemPromptMode: (0, _agents.defaultSystemPromptMode)(name), + inheritProjectContext: (0, _agents.defaultInheritProjectContext)(name), + inheritSkills: (0, _agents.defaultInheritSkills)() + }; + const applyError = applyAgentConfig(agent, cfg); + if (applyError) return result(applyError, true); + const mw = modelWarning(ctx, agent.model); + if (mw) warnings.push(mw); + const fmw = fallbackModelsWarning(ctx, agent.fallbackModels); + if (fmw) warnings.push(fmw); + const sw = skillsWarning(ctx.cwd, agent.skills); + if (sw) warnings.push(sw); + fs.writeFileSync(targetPath, (0, _agentSerializer.serializeAgent)(agent), "utf-8"); + return result([`Created agent '${runtimeName}' at ${targetPath}.`, ...warnings].join("\n")); +} + +function handleUpdate(params, ctx) { + if (!params.agent && !params.chainName) return result("Specify 'agent' or 'chainName' for update.", true); + if (params.agent && params.chainName) return result("Specify either 'agent' or 'chainName', not both.", true); + const parsedConfig = configObject(params.config); + if (parsedConfig.error) return result(parsedConfig.error, true); + const cfg = parsedConfig.value; + if (!cfg) return result("config required for update.", true); + const warnings = []; + if (params.agent) { + const scopeHint = asDisambiguationScope(params.agentScope); + const targetOrError = resolveTarget("agent", params.agent, findAgents(params.agent, ctx.cwd, scopeHint ?? "both"), ctx.cwd, params.agentScope); + if ("content" in targetOrError) return targetOrError; + const target = targetOrError; + const updated = { ...target }; + const oldName = target.name; + if (hasKey(cfg, "name") && (typeof cfg.name !== "string" || !cfg.name.trim())) return result("config.name must be a non-empty string when provided.", true); + if (hasKey(cfg, "description") && (typeof cfg.description !== "string" || !cfg.description.trim())) return result("config.description must be a non-empty string when provided.", true); + let newLocalName = target.localName ?? (0, _agents.frontmatterNameForConfig)(target); + if (hasKey(cfg, "name")) { + newLocalName = sanitizeName(cfg.name); + if (!newLocalName) return result("config.name is invalid after sanitization.", true); + } + let newPackageName = target.packageName; + if (hasKey(cfg, "package")) { + const parsedPackage = parsePackageConfig(cfg.package); + if (parsedPackage.error) return result(parsedPackage.error, true); + newPackageName = parsedPackage.packageName; + } + const applyError = applyAgentConfig(updated, cfg); + if (applyError) return result(applyError, true); + updated.localName = newLocalName; + updated.packageName = newPackageName; + updated.name = (0, _agents.buildRuntimeName)(newLocalName, newPackageName); + if (hasKey(cfg, "description")) updated.description = cfg.description.trim(); + if (hasKey(cfg, "model")) { + const mw = modelWarning(ctx, updated.model); + if (mw) warnings.push(mw); + } + if (hasKey(cfg, "fallbackModels")) { + const fmw = fallbackModelsWarning(ctx, updated.fallbackModels); + if (fmw) warnings.push(fmw); + } + if (hasKey(cfg, "skills")) { + const sw = skillsWarning(ctx.cwd, updated.skills); + if (sw) warnings.push(sw); + } + if (updated.name !== oldName) { + const renamed = renamePath("agent", target.filePath, updated.name, target.source, ctx.cwd); + if (renamed.error) return result(renamed.error, true); + updated.filePath = renamed.filePath; + } + fs.writeFileSync(updated.filePath, (0, _agentSerializer.serializeAgent)(updated), "utf-8"); + if (updated.name !== oldName) { + const refs = (0, _agents.discoverAgentsAll)(ctx.cwd).chains.filter((c) => c.steps.some((s) => s.agent === oldName)).map((c) => `${c.name} (${c.source})`); + if (refs.length) warnings.push(`Warning: chains still reference '${oldName}': ${refs.join(", ")}.`); + } + const headline = updated.name === oldName ? + `Updated agent '${updated.name}' at ${updated.filePath}.` : + `Updated agent '${oldName}' to '${updated.name}' at ${updated.filePath}.`; + return result([headline, ...warnings].join("\n")); + } + const scopeHint = asDisambiguationScope(params.agentScope); + const targetOrError = resolveTarget("chain", params.chainName, findChains(params.chainName, ctx.cwd, scopeHint ?? "both"), ctx.cwd, params.agentScope); + if ("content" in targetOrError) return targetOrError; + const target = targetOrError; + const updated = { ...target, steps: [...target.steps] }; + const oldName = target.name; + if (hasKey(cfg, "name") && (typeof cfg.name !== "string" || !cfg.name.trim())) return result("config.name must be a non-empty string when provided.", true); + if (hasKey(cfg, "description") && (typeof cfg.description !== "string" || !cfg.description.trim())) return result("config.description must be a non-empty string when provided.", true); + let newLocalName = target.localName ?? (0, _agents.frontmatterNameForConfig)(target); + if (hasKey(cfg, "name")) { + newLocalName = sanitizeName(cfg.name); + if (!newLocalName) return result("config.name is invalid after sanitization.", true); + } + let newPackageName = target.packageName; + if (hasKey(cfg, "package")) { + const parsedPackage = parsePackageConfig(cfg.package); + if (parsedPackage.error) return result(parsedPackage.error, true); + newPackageName = parsedPackage.packageName; + } + let parsedSteps; + if (hasKey(cfg, "steps")) { + const parsed = parseStepList(cfg.steps); + if (parsed.error) return result(parsed.error, true); + parsedSteps = parsed.steps; + } + updated.localName = newLocalName; + updated.packageName = newPackageName; + updated.name = (0, _agents.buildRuntimeName)(newLocalName, newPackageName); + if (hasKey(cfg, "description")) updated.description = cfg.description.trim(); + if (parsedSteps) { + updated.steps = parsedSteps; + const missing = unknownChainAgents(ctx.cwd, updated.steps); + if (missing.length) warnings.push(`Warning: chain steps reference unknown agents: ${missing.join(", ")}.`); + warnings.push(...chainStepWarnings(ctx, updated.steps)); + } + if (updated.name !== oldName) { + const renamed = renamePath("chain", target.filePath, updated.name, target.source, ctx.cwd); + if (renamed.error) return result(renamed.error, true); + updated.filePath = renamed.filePath; + } + fs.writeFileSync(updated.filePath, (0, _chainSerializer.serializeChain)(updated), "utf-8"); + const headline = updated.name === oldName ? + `Updated chain '${updated.name}' at ${updated.filePath}.` : + `Updated chain '${oldName}' to '${updated.name}' at ${updated.filePath}.`; + return result([headline, ...warnings].join("\n")); +} + +function handleDelete(params, ctx) { + if (!params.agent && !params.chainName) return result("Specify 'agent' or 'chainName' for delete.", true); + if (params.agent && params.chainName) return result("Specify either 'agent' or 'chainName', not both.", true); + const scopeHint = asDisambiguationScope(params.agentScope); + if (params.agent) { + const targetOrError = resolveTarget("agent", params.agent, findAgents(params.agent, ctx.cwd, scopeHint ?? "both"), ctx.cwd, params.agentScope); + if ("content" in targetOrError) return targetOrError; + const target = targetOrError; + fs.unlinkSync(target.filePath); + const refs = (0, _agents.discoverAgentsAll)(ctx.cwd).chains.filter((c) => c.steps.some((s) => s.agent === target.name)).map((c) => `${c.name} (${c.source})`); + const lines = [`Deleted agent '${target.name}' at ${target.filePath}.`]; + if (refs.length) lines.push(`Warning: chains reference deleted agent '${target.name}': ${refs.join(", ")}.`); + return result(lines.join("\n")); + } + const targetOrError = resolveTarget("chain", params.chainName, findChains(params.chainName, ctx.cwd, scopeHint ?? "both"), ctx.cwd, params.agentScope); + if ("content" in targetOrError) return targetOrError; + const target = targetOrError; + fs.unlinkSync(target.filePath); + return result(`Deleted chain '${target.name}' at ${target.filePath}.`); +} + +function handleManagementAction(action, params, ctx) { + switch (action) { + case "list":return handleList(params, ctx); + case "get":return handleGet(params, ctx); + case "create":return handleCreate(params, ctx); + case "update":return handleUpdate(params, ctx); + case "delete":return handleDelete(params, ctx); + default:return result(`Unknown action: ${action}`, true); + } +} /* v9-f1aaa04295c5b5e7 */ diff --git a/pip-tmp/jiti/agents-agent-scope.515c0bc4.mjs b/pip-tmp/jiti/agents-agent-scope.515c0bc4.mjs new file mode 100644 index 0000000000000000000000000000000000000000..1d3b7c2c2b9f245126de3ab1c56f9c7e5b6c6c54 --- /dev/null +++ b/pip-tmp/jiti/agents-agent-scope.515c0bc4.mjs @@ -0,0 +1,6 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.resolveExecutionAgentScope = resolveExecutionAgentScope; + +function resolveExecutionAgentScope(scope) { + if (scope === "user" || scope === "project" || scope === "both") return scope; + return "both"; +} /* v9-28865fb4666b353e */ diff --git a/pip-tmp/jiti/agents-agent-selection.a867544e.mjs b/pip-tmp/jiti/agents-agent-selection.a867544e.mjs new file mode 100644 index 0000000000000000000000000000000000000000..4a5baad452929e135a73a30e32f5ff33e4ffe3c8 --- /dev/null +++ b/pip-tmp/jiti/agents-agent-selection.a867544e.mjs @@ -0,0 +1,23 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.mergeAgentsForScope = mergeAgentsForScope; + +function mergeAgentsForScope( +scope, +userAgents, +projectAgents, +builtinAgents = []) +{ + const agentMap = new Map(); + + for (const agent of builtinAgents) agentMap.set(agent.name, agent); + + if (scope === "both") { + for (const agent of userAgents) agentMap.set(agent.name, agent); + for (const agent of projectAgents) agentMap.set(agent.name, agent); + } else if (scope === "user") { + for (const agent of userAgents) agentMap.set(agent.name, agent); + } else { + for (const agent of projectAgents) agentMap.set(agent.name, agent); + } + + return Array.from(agentMap.values()); +} /* v9-2c3ea403e7844ffa */ diff --git a/pip-tmp/jiti/agents-agent-serializer.61dec55f.mjs b/pip-tmp/jiti/agents-agent-serializer.61dec55f.mjs new file mode 100644 index 0000000000000000000000000000000000000000..bf18fa21024a4f9c7203303c664ff8dcefa4ea29 --- /dev/null +++ b/pip-tmp/jiti/agents-agent-serializer.61dec55f.mjs @@ -0,0 +1,84 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.KNOWN_FIELDS = void 0;exports.serializeAgent = serializeAgent; +var _identity = await jitiImport("./identity.ts"); + +const KNOWN_FIELDS = exports.KNOWN_FIELDS = new Set([ +"name", +"package", +"description", +"tools", +"model", +"fallbackModels", +"thinking", +"systemPromptMode", +"inheritProjectContext", +"inheritSkills", +"defaultContext", +"skill", +"skills", +"extensions", +"output", +"defaultReads", +"defaultProgress", +"interactive", +"maxSubagentDepth"] +); + +function joinComma(values) { + if (!values || values.length === 0) return undefined; + return values.join(", "); +} + +function serializeAgent(config) { + const lines = []; + lines.push("---"); + lines.push(`name: ${(0, _identity.frontmatterNameForConfig)(config)}`); + if (config.packageName) lines.push(`package: ${config.packageName}`); + lines.push(`description: ${config.description}`); + + const tools = [ + ...(config.tools ?? []), + ...(config.mcpDirectTools ?? []).map((tool) => `mcp:${tool}`)]; + + const toolsValue = joinComma(tools); + if (toolsValue) lines.push(`tools: ${toolsValue}`); + + if (config.model) lines.push(`model: ${config.model}`); + const fallbackModelsValue = joinComma(config.fallbackModels); + if (fallbackModelsValue) lines.push(`fallbackModels: ${fallbackModelsValue}`); + if (config.thinking && config.thinking !== "off") lines.push(`thinking: ${config.thinking}`); + lines.push(`systemPromptMode: ${config.systemPromptMode}`); + lines.push(`inheritProjectContext: ${config.inheritProjectContext ? "true" : "false"}`); + lines.push(`inheritSkills: ${config.inheritSkills ? "true" : "false"}`); + if (config.defaultContext) lines.push(`defaultContext: ${config.defaultContext}`); + + const skillsValue = joinComma(config.skills); + if (skillsValue) lines.push(`skills: ${skillsValue}`); + + if (config.extensions !== undefined) { + const extensionsValue = joinComma(config.extensions); + lines.push(`extensions: ${extensionsValue ?? ""}`); + } + + if (config.output) lines.push(`output: ${config.output}`); + + const readsValue = joinComma(config.defaultReads); + if (readsValue) lines.push(`defaultReads: ${readsValue}`); + + if (config.defaultProgress) lines.push("defaultProgress: true"); + if (config.interactive) lines.push("interactive: true"); + if (Number.isInteger(config.maxSubagentDepth) && config.maxSubagentDepth >= 0) { + lines.push(`maxSubagentDepth: ${config.maxSubagentDepth}`); + } + + if (config.extraFields) { + for (const [key, value] of Object.entries(config.extraFields)) { + if (KNOWN_FIELDS.has(key)) continue; + lines.push(`${key}: ${value}`); + } + } + + lines.push("---"); + + const body = config.systemPrompt ?? ""; + return `${lines.join("\n")}\n\n${body}\n`; +} /* v9-0dca73d6d583a7d0 */ diff --git a/pip-tmp/jiti/agents-agents.f0509ee2.mjs b/pip-tmp/jiti/agents-agents.f0509ee2.mjs new file mode 100644 index 0000000000000000000000000000000000000000..ba6026e5d83d58419fd09eaacf2420c70daf339e --- /dev/null +++ b/pip-tmp/jiti/agents-agents.f0509ee2.mjs @@ -0,0 +1,808 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.buildBuiltinOverrideConfig = buildBuiltinOverrideConfig;Object.defineProperty(exports, "buildRuntimeName", { enumerable: true, get: function () {return _identity.buildRuntimeName;} });exports.defaultInheritProjectContext = defaultInheritProjectContext;exports.defaultInheritSkills = defaultInheritSkills;exports.defaultSystemPromptMode = defaultSystemPromptMode;exports.discoverAgents = discoverAgents;exports.discoverAgentsAll = discoverAgentsAll;Object.defineProperty(exports, "frontmatterNameForConfig", { enumerable: true, get: function () {return _identity.frontmatterNameForConfig;} });Object.defineProperty(exports, "parsePackageName", { enumerable: true, get: function () {return _identity.parsePackageName;} });exports.removeBuiltinAgentOverride = removeBuiltinAgentOverride;exports.saveBuiltinAgentOverride = saveBuiltinAgentOverride; + + + +var fs = _interopRequireWildcard(await jitiImport("node:fs")); +var os = _interopRequireWildcard(await jitiImport("node:os")); +var path = _interopRequireWildcard(await jitiImport("node:path")); +var _nodeUrl = await jitiImport("node:url"); + +var _agentSerializer = await jitiImport("./agent-serializer.ts"); +var _chainSerializer = await jitiImport("./chain-serializer.ts"); +var _agentSelection = await jitiImport("./agent-selection.ts"); +var _frontmatter = await jitiImport("./frontmatter.ts"); +var _identity = await jitiImport("./identity.ts");function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);} /** + * Agent discovery and configuration + */ + + + + + + +function defaultSystemPromptMode(name) { + return name === "delegate" ? "append" : "replace"; +} + +function defaultInheritProjectContext(name) { + return name === "delegate"; +} + +function defaultInheritSkills() { + return false; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +const EMPTY_SUBAGENT_SETTINGS = { overrides: {} }; + + + + + + + + + + + + + + + + + + + + + + + + + + + + +function getUserChainDir() { + return path.join(os.homedir(), ".pi", "agent", "chains"); +} + +function splitToolList(rawTools) { + const mcpDirectTools = []; + const tools = []; + for (const tool of rawTools ?? []) { + if (tool.startsWith("mcp:")) { + mcpDirectTools.push(tool.slice(4)); + } else { + tools.push(tool); + } + } + return { + ...(tools.length > 0 ? { tools } : {}), + ...(mcpDirectTools.length > 0 ? { mcpDirectTools } : {}) + }; +} + +function joinToolList(config) { + const joined = [ + ...(config.tools ?? []), + ...(config.mcpDirectTools ?? []).map((tool) => `mcp:${tool}`)]; + + return joined.length > 0 ? joined : undefined; +} + +function arraysEqual(a, b) { + if (!a && !b) return true; + if (!a || !b) return false; + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false; + } + return true; +} + +function cloneOverrideBase(agent) { + return { + model: agent.model, + fallbackModels: agent.fallbackModels ? [...agent.fallbackModels] : undefined, + thinking: agent.thinking, + systemPromptMode: agent.systemPromptMode, + inheritProjectContext: agent.inheritProjectContext, + inheritSkills: agent.inheritSkills, + defaultContext: agent.defaultContext, + disabled: agent.disabled, + systemPrompt: agent.systemPrompt, + skills: agent.skills ? [...agent.skills] : undefined, + tools: agent.tools ? [...agent.tools] : undefined, + mcpDirectTools: agent.mcpDirectTools ? [...agent.mcpDirectTools] : undefined + }; +} + +function cloneOverrideValue(override) { + return { + ...(override.model !== undefined ? { model: override.model } : {}), + ...(override.fallbackModels !== undefined ? + { fallbackModels: override.fallbackModels === false ? false : [...override.fallbackModels] } : + {}), + ...(override.thinking !== undefined ? { thinking: override.thinking } : {}), + ...(override.systemPromptMode !== undefined ? { systemPromptMode: override.systemPromptMode } : {}), + ...(override.inheritProjectContext !== undefined ? { inheritProjectContext: override.inheritProjectContext } : {}), + ...(override.inheritSkills !== undefined ? { inheritSkills: override.inheritSkills } : {}), + ...(override.defaultContext !== undefined ? { defaultContext: override.defaultContext } : {}), + ...(override.disabled !== undefined ? { disabled: override.disabled } : {}), + ...(override.systemPrompt !== undefined ? { systemPrompt: override.systemPrompt } : {}), + ...(override.skills !== undefined ? { skills: override.skills === false ? false : [...override.skills] } : {}), + ...(override.tools !== undefined ? { tools: override.tools === false ? false : [...override.tools] } : {}) + }; +} + +function findNearestProjectRoot(cwd) { + let currentDir = cwd; + while (true) { + if (isDirectory(path.join(currentDir, ".pi")) || isDirectory(path.join(currentDir, ".agents"))) { + return currentDir; + } + + const parentDir = path.dirname(currentDir); + if (parentDir === currentDir) return null; + currentDir = parentDir; + } +} + +function getUserAgentSettingsPath() { + return path.join(os.homedir(), ".pi", "agent", "settings.json"); +} + +function getProjectAgentSettingsPath(cwd) { + const projectRoot = findNearestProjectRoot(cwd); + return projectRoot ? path.join(projectRoot, ".pi", "settings.json") : null; +} + +function readSettingsFileStrict(filePath) { + if (!fs.existsSync(filePath)) return {}; + let raw; + try { + raw = fs.readFileSync(filePath, "utf-8"); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to read settings file '${filePath}': ${message}`, { cause: error }); + } + + let parsed; + try { + parsed = JSON.parse(raw); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to parse settings file '${filePath}': ${message}`, { cause: error }); + } + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { + throw new Error(`Settings file '${filePath}' must contain a JSON object.`); + } + return parsed; +} + +function writeSettingsFile(filePath, settings) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, JSON.stringify(settings, null, 2) + "\n", "utf-8"); +} + +function parseOverrideStringArrayOrFalse( +value, +meta) +{ + if (value === undefined) return undefined; + if (value === false) return false; + if (!Array.isArray(value)) { + throw new Error(`Builtin override '${meta.name}' in '${meta.filePath}' has invalid '${meta.field}'; expected an array of strings or false.`); + } + + const items = []; + for (const item of value) { + if (typeof item !== "string") { + throw new Error(`Builtin override '${meta.name}' in '${meta.filePath}' has invalid '${meta.field}'; expected an array of strings or false.`); + } + const trimmed = item.trim(); + if (trimmed) items.push(trimmed); + } + return items; +} + +function parseBuiltinOverrideEntry( +name, +value, +filePath) +{ + if (!value || typeof value !== "object" || Array.isArray(value)) { + throw new Error(`Builtin override '${name}' in '${filePath}' must be an object.`); + } + + const input = value; + const override = {}; + + if ("model" in input) { + if (typeof input.model === "string" || input.model === false) override.model = input.model;else + throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'model'; expected a string or false.`); + } + + if ("thinking" in input) { + if (typeof input.thinking === "string" || input.thinking === false) override.thinking = input.thinking;else + throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'thinking'; expected a string or false.`); + } + + if ("systemPromptMode" in input) { + if (input.systemPromptMode === "append" || input.systemPromptMode === "replace") { + override.systemPromptMode = input.systemPromptMode; + } else { + throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'systemPromptMode'; expected 'append' or 'replace'.`); + } + } + + if ("inheritProjectContext" in input) { + if (typeof input.inheritProjectContext === "boolean") { + override.inheritProjectContext = input.inheritProjectContext; + } else { + throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'inheritProjectContext'; expected a boolean.`); + } + } + + if ("inheritSkills" in input) { + if (typeof input.inheritSkills === "boolean") { + override.inheritSkills = input.inheritSkills; + } else { + throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'inheritSkills'; expected a boolean.`); + } + } + + if ("defaultContext" in input) { + if (input.defaultContext === "fresh" || input.defaultContext === "fork" || input.defaultContext === false) { + override.defaultContext = input.defaultContext; + } else { + throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'defaultContext'; expected 'fresh', 'fork', or false.`); + } + } + + if ("disabled" in input) { + if (typeof input.disabled === "boolean") { + override.disabled = input.disabled; + } else { + throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'disabled'; expected a boolean.`); + } + } + + if ("systemPrompt" in input) { + if (typeof input.systemPrompt === "string") override.systemPrompt = input.systemPrompt;else + throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'systemPrompt'; expected a string.`); + } + + const fallbackModels = parseOverrideStringArrayOrFalse(input.fallbackModels, { filePath, name, field: "fallbackModels" }); + if (fallbackModels !== undefined) override.fallbackModels = fallbackModels; + + const skills = parseOverrideStringArrayOrFalse(input.skills, { filePath, name, field: "skills" }); + if (skills !== undefined) override.skills = skills; + + const tools = parseOverrideStringArrayOrFalse(input.tools, { filePath, name, field: "tools" }); + if (tools !== undefined) override.tools = tools; + + return Object.keys(override).length > 0 ? override : undefined; +} + +function readSubagentSettings(filePath) { + if (!filePath) return EMPTY_SUBAGENT_SETTINGS; + const settings = readSettingsFileStrict(filePath); + const subagents = settings.subagents; + if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return EMPTY_SUBAGENT_SETTINGS; + + const subagentsObject = subagents; + let disableBuiltins; + if ("disableBuiltins" in subagentsObject) { + if (typeof subagentsObject.disableBuiltins === "boolean") { + disableBuiltins = subagentsObject.disableBuiltins; + } else { + throw new Error(`Subagent settings in '${filePath}' have invalid 'disableBuiltins'; expected a boolean.`); + } + } + + const parsed = {}; + const agentOverrides = subagentsObject.agentOverrides; + if (!agentOverrides || typeof agentOverrides !== "object" || Array.isArray(agentOverrides)) { + return { overrides: parsed, disableBuiltins }; + } + for (const [name, value] of Object.entries(agentOverrides)) { + const override = parseBuiltinOverrideEntry(name, value, filePath); + if (override) parsed[name] = override; + } + return { overrides: parsed, disableBuiltins }; +} + +function applyBuiltinOverride( +agent, +override, +meta) +{ + const next = { + ...agent, + override: { ...meta, base: cloneOverrideBase(agent) } + }; + + if (override.model !== undefined) next.model = override.model === false ? undefined : override.model; + if (override.fallbackModels !== undefined) { + next.fallbackModels = override.fallbackModels === false ? undefined : [...override.fallbackModels]; + } + if (override.thinking !== undefined) next.thinking = override.thinking === false ? undefined : override.thinking; + if (override.systemPromptMode !== undefined) next.systemPromptMode = override.systemPromptMode; + if (override.inheritProjectContext !== undefined) next.inheritProjectContext = override.inheritProjectContext; + if (override.inheritSkills !== undefined) next.inheritSkills = override.inheritSkills; + if (override.defaultContext !== undefined) next.defaultContext = override.defaultContext === false ? undefined : override.defaultContext; + if (override.disabled !== undefined) next.disabled = override.disabled; + if (override.systemPrompt !== undefined) next.systemPrompt = override.systemPrompt; + if (override.skills !== undefined) next.skills = override.skills === false ? undefined : [...override.skills]; + if (override.tools !== undefined) { + const { tools, mcpDirectTools } = splitToolList(override.tools === false ? [] : override.tools); + next.tools = tools; + next.mcpDirectTools = mcpDirectTools; + } + + return next; +} + +function applyBuiltinOverrides( +builtinAgents, +userSettings, +projectSettings, +userSettingsPath, +projectSettingsPath) +{ + const projectBulkDisabled = projectSettings.disableBuiltins === true && projectSettingsPath !== null; + const userBulkDisabled = projectSettings.disableBuiltins === undefined && userSettings.disableBuiltins === true; + + return builtinAgents.map((agent) => { + const projectOverride = projectSettings.overrides[agent.name]; + if (projectOverride && projectSettingsPath) { + return applyBuiltinOverride(agent, projectOverride, { scope: "project", path: projectSettingsPath }); + } + + if (projectBulkDisabled && projectSettingsPath) { + return applyBuiltinOverride(agent, { disabled: true }, { scope: "project", path: projectSettingsPath }); + } + + const userOverride = userSettings.overrides[agent.name]; + if (userOverride) { + return applyBuiltinOverride(agent, userOverride, { scope: "user", path: userSettingsPath }); + } + + if (userBulkDisabled) { + return applyBuiltinOverride(agent, { disabled: true }, { scope: "user", path: userSettingsPath }); + } + + return agent; + }); +} + +function buildBuiltinOverrideConfig( +base, +draft) +{ + const override = {}; + + if (draft.model !== base.model) override.model = draft.model ?? false; + if (!arraysEqual(draft.fallbackModels, base.fallbackModels)) override.fallbackModels = draft.fallbackModels ? [...draft.fallbackModels] : false; + if (draft.thinking !== base.thinking) override.thinking = draft.thinking ?? false; + if (draft.systemPromptMode !== base.systemPromptMode) override.systemPromptMode = draft.systemPromptMode; + if (draft.inheritProjectContext !== base.inheritProjectContext) override.inheritProjectContext = draft.inheritProjectContext; + if (draft.inheritSkills !== base.inheritSkills) override.inheritSkills = draft.inheritSkills; + if (draft.defaultContext !== base.defaultContext) override.defaultContext = draft.defaultContext ?? false; + if (draft.disabled !== base.disabled) override.disabled = draft.disabled ?? false; + if (draft.systemPrompt !== base.systemPrompt) override.systemPrompt = draft.systemPrompt; + if (!arraysEqual(draft.skills, base.skills)) override.skills = draft.skills ? [...draft.skills] : false; + + const baseTools = joinToolList(base); + const draftTools = joinToolList(draft); + if (!arraysEqual(draftTools, baseTools)) override.tools = draftTools ? [...draftTools] : false; + + return Object.keys(override).length > 0 ? override : undefined; +} + +function saveBuiltinAgentOverride( +cwd, +name, +scope, +override) +{ + const filePath = scope === "project" ? getProjectAgentSettingsPath(cwd) : getUserAgentSettingsPath(); + if (!filePath) throw new Error("Project override is not available here. No project config root was found."); + + const settings = readSettingsFileStrict(filePath); + const subagents = settings.subagents && typeof settings.subagents === "object" && !Array.isArray(settings.subagents) ? + { ...settings.subagents } : + {}; + const agentOverrides = subagents.agentOverrides && typeof subagents.agentOverrides === "object" && !Array.isArray(subagents.agentOverrides) ? + { ...subagents.agentOverrides } : + {}; + + agentOverrides[name] = cloneOverrideValue(override); + subagents.agentOverrides = agentOverrides; + settings.subagents = subagents; + writeSettingsFile(filePath, settings); + return filePath; +} + +function removeBuiltinAgentOverride(cwd, name, scope) { + const filePath = scope === "project" ? getProjectAgentSettingsPath(cwd) : getUserAgentSettingsPath(); + if (!filePath) throw new Error("Project override is not available here. No project config root was found."); + if (!fs.existsSync(filePath)) return filePath; + + const settings = readSettingsFileStrict(filePath); + const subagents = settings.subagents; + if (!subagents || typeof subagents !== "object" || Array.isArray(subagents)) return filePath; + const nextSubagents = { ...subagents }; + const agentOverrides = nextSubagents.agentOverrides; + if (!agentOverrides || typeof agentOverrides !== "object" || Array.isArray(agentOverrides)) return filePath; + + const nextOverrides = { ...agentOverrides }; + delete nextOverrides[name]; + if (Object.keys(nextOverrides).length > 0) nextSubagents.agentOverrides = nextOverrides;else + delete nextSubagents.agentOverrides; + + if (Object.keys(nextSubagents).length > 0) settings.subagents = nextSubagents;else + delete settings.subagents; + + writeSettingsFile(filePath, settings); + return filePath; +} + +function listMarkdownFilesRecursive(dir, predicate) { + const files = []; + if (!fs.existsSync(dir)) return files; + + let entries; + try { + entries = fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name)); + } catch { + return files; + } + + for (const entry of entries) { + const filePath = path.join(dir, entry.name); + if (entry.isDirectory()) { + files.push(...listMarkdownFilesRecursive(filePath, predicate)); + continue; + } + if (!entry.isFile() && !entry.isSymbolicLink()) continue; + if (!predicate(entry.name)) continue; + files.push(filePath); + } + return files; +} + +function loadAgentsFromDir(dir, source) { + const agents = []; + + for (const filePath of listMarkdownFilesRecursive(dir, (fileName) => fileName.endsWith(".md") && !fileName.endsWith(".chain.md"))) { + let content; + try { + content = fs.readFileSync(filePath, "utf-8"); + } catch { + continue; + } + + const { frontmatter, body } = (0, _frontmatter.parseFrontmatter)(content); + + if (!frontmatter.name || !frontmatter.description) { + continue; + } + + const localName = frontmatter.name; + const parsedPackage = (0, _identity.parsePackageName)(frontmatter.package, `Agent '${localName}' package`); + if (parsedPackage.error) continue; + const packageName = parsedPackage.packageName; + const runtimeName = (0, _identity.buildRuntimeName)(localName, packageName); + + const rawTools = frontmatter.tools?. + split(","). + map((t) => t.trim()). + filter(Boolean); + + const mcpDirectTools = []; + const tools = []; + if (rawTools) { + for (const tool of rawTools) { + if (tool.startsWith("mcp:")) { + mcpDirectTools.push(tool.slice(4)); + } else { + tools.push(tool); + } + } + } + + const defaultReads = frontmatter.defaultReads?. + split(","). + map((f) => f.trim()). + filter(Boolean); + + const skillStr = frontmatter.skill || frontmatter.skills; + const skills = skillStr?. + split(","). + map((s) => s.trim()). + filter(Boolean); + const fallbackModels = frontmatter.fallbackModels?. + split(","). + map((model) => model.trim()). + filter(Boolean); + const systemPromptMode = frontmatter.systemPromptMode === "replace" ? + "replace" : + frontmatter.systemPromptMode === "append" ? + "append" : + defaultSystemPromptMode(localName); + const inheritProjectContext = frontmatter.inheritProjectContext === "true" ? + true : + frontmatter.inheritProjectContext === "false" ? + false : + defaultInheritProjectContext(localName); + const inheritSkills = frontmatter.inheritSkills === "true" ? + true : + frontmatter.inheritSkills === "false" ? + false : + defaultInheritSkills(); + const defaultContext = frontmatter.defaultContext === "fork" ? + "fork" : + frontmatter.defaultContext === "fresh" ? + "fresh" : + undefined; + + let extensions; + if (frontmatter.extensions !== undefined) { + extensions = frontmatter.extensions. + split(","). + map((e) => e.trim()). + filter(Boolean); + } + + const extraFields = {}; + for (const [key, value] of Object.entries(frontmatter)) { + if (!_agentSerializer.KNOWN_FIELDS.has(key)) extraFields[key] = value; + } + + const parsedMaxSubagentDepth = Number(frontmatter.maxSubagentDepth); + + agents.push({ + name: runtimeName, + localName, + packageName, + description: frontmatter.description, + tools: tools.length > 0 ? tools : undefined, + mcpDirectTools: mcpDirectTools.length > 0 ? mcpDirectTools : undefined, + model: frontmatter.model, + fallbackModels: fallbackModels && fallbackModels.length > 0 ? fallbackModels : undefined, + thinking: frontmatter.thinking, + systemPromptMode, + inheritProjectContext, + inheritSkills, + defaultContext, + systemPrompt: body, + source, + filePath, + skills: skills && skills.length > 0 ? skills : undefined, + extensions, + output: frontmatter.output, + defaultReads: defaultReads && defaultReads.length > 0 ? defaultReads : undefined, + defaultProgress: frontmatter.defaultProgress === "true", + interactive: frontmatter.interactive === "true", + maxSubagentDepth: + Number.isInteger(parsedMaxSubagentDepth) && parsedMaxSubagentDepth >= 0 ? + parsedMaxSubagentDepth : + undefined, + extraFields: Object.keys(extraFields).length > 0 ? extraFields : undefined + }); + } + + return agents; +} + +function loadChainsFromDir(dir, source) { + const chains = []; + + for (const filePath of listMarkdownFilesRecursive(dir, (fileName) => fileName.endsWith(".chain.md"))) { + let content; + try { + content = fs.readFileSync(filePath, "utf-8"); + } catch { + continue; + } + + try { + chains.push((0, _chainSerializer.parseChain)(content, source, filePath)); + } catch { + continue; + } + } + + return chains; +} + +function isDirectory(p) { + try { + return fs.statSync(p).isDirectory(); + } catch { + return false; + } +} + +function resolveNearestProjectAgentDirs(cwd) { + const projectRoot = findNearestProjectRoot(cwd); + if (!projectRoot) return { readDirs: [], preferredDir: null }; + + const legacyDir = path.join(projectRoot, ".agents"); + const preferredDir = path.join(projectRoot, ".pi", "agents"); + const readDirs = []; + if (isDirectory(legacyDir)) readDirs.push(legacyDir); + if (isDirectory(preferredDir)) readDirs.push(preferredDir); + + return { + readDirs, + preferredDir + }; +} + +function resolveNearestProjectChainDirs(cwd) { + const projectRoot = findNearestProjectRoot(cwd); + if (!projectRoot) return { readDirs: [], preferredDir: null }; + + const preferredDir = path.join(projectRoot, ".pi", "chains"); + return { + readDirs: isDirectory(preferredDir) ? [preferredDir] : [], + preferredDir + }; +} +const BUILTIN_AGENTS_DIR = path.resolve(path.dirname((0, _nodeUrl.fileURLToPath)("file:///home/edwin/.config/nvm/versions/node/v22.20.0/lib/node_modules/pi-subagents/src/agents/agents.ts")), "..", "..", "agents"); + +function discoverAgents(cwd, scope) { + const userDirOld = path.join(os.homedir(), ".pi", "agent", "agents"); + const userDirNew = path.join(os.homedir(), ".agents"); + const { readDirs: projectAgentDirs, preferredDir: projectAgentsDir } = resolveNearestProjectAgentDirs(cwd); + const userSettingsPath = getUserAgentSettingsPath(); + const projectSettingsPath = getProjectAgentSettingsPath(cwd); + const userSettings = scope === "project" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(userSettingsPath); + const projectSettings = scope === "user" ? EMPTY_SUBAGENT_SETTINGS : readSubagentSettings(projectSettingsPath); + + const builtinAgents = applyBuiltinOverrides( + loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"), + userSettings, + projectSettings, + userSettingsPath, + projectSettingsPath + ); + + const userAgentsOld = scope === "project" ? [] : loadAgentsFromDir(userDirOld, "user"); + const userAgentsNew = scope === "project" ? [] : loadAgentsFromDir(userDirNew, "user"); + const userAgents = [...userAgentsOld, ...userAgentsNew]; + + const projectAgents = scope === "user" ? [] : projectAgentDirs.flatMap((dir) => loadAgentsFromDir(dir, "project")); + const agents = (0, _agentSelection.mergeAgentsForScope)(scope, userAgents, projectAgents, builtinAgents). + filter((agent) => agent.disabled !== true); + + return { agents, projectAgentsDir }; +} + +function discoverAgentsAll(cwd) + + + + + + + + + + +{ + const userDirOld = path.join(os.homedir(), ".pi", "agent", "agents"); + const userDirNew = path.join(os.homedir(), ".agents"); + const userChainDir = getUserChainDir(); + const { readDirs: projectDirs, preferredDir: projectDir } = resolveNearestProjectAgentDirs(cwd); + const { readDirs: projectChainDirs, preferredDir: projectChainDir } = resolveNearestProjectChainDirs(cwd); + const userSettingsPath = getUserAgentSettingsPath(); + const projectSettingsPath = getProjectAgentSettingsPath(cwd); + const userSettings = readSubagentSettings(userSettingsPath); + const projectSettings = readSubagentSettings(projectSettingsPath); + + const builtin = applyBuiltinOverrides( + loadAgentsFromDir(BUILTIN_AGENTS_DIR, "builtin"), + userSettings, + projectSettings, + userSettingsPath, + projectSettingsPath + ); + const user = [ + ...loadAgentsFromDir(userDirOld, "user"), + ...loadAgentsFromDir(userDirNew, "user")]; + + const projectMap = new Map(); + for (const dir of projectDirs) { + for (const agent of loadAgentsFromDir(dir, "project")) { + projectMap.set(agent.name, agent); + } + } + const project = Array.from(projectMap.values()); + + const chainMap = new Map(); + for (const dir of projectChainDirs) { + for (const chain of loadChainsFromDir(dir, "project")) { + chainMap.set(chain.name, chain); + } + } + const chains = [ + ...loadChainsFromDir(userChainDir, "user"), + ...Array.from(chainMap.values())]; + + + const userDir = fs.existsSync(userDirNew) ? userDirNew : userDirOld; + + return { builtin, user, project, chains, userDir, projectDir, userChainDir, projectChainDir, userSettingsPath, projectSettingsPath }; +} /* v9-467e5e633d01fc28 */ diff --git a/pip-tmp/jiti/agents-chain-serializer.a832da0e.mjs b/pip-tmp/jiti/agents-chain-serializer.a832da0e.mjs new file mode 100644 index 0000000000000000000000000000000000000000..29782bb0a21c377bf29f049317727a76d6203525 --- /dev/null +++ b/pip-tmp/jiti/agents-chain-serializer.a832da0e.mjs @@ -0,0 +1,137 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.parseChain = parseChain;exports.serializeChain = serializeChain; +var _identity = await jitiImport("./identity.ts"); +var _frontmatter = await jitiImport("./frontmatter.ts"); + +function parseStepBody(agent, sectionBody) { + const lines = sectionBody.split("\n"); + const blankIndex = lines.findIndex((line) => line.trim() === ""); + const configLines = blankIndex === -1 ? lines : lines.slice(0, blankIndex); + const task = (blankIndex === -1 ? "" : lines.slice(blankIndex + 1).join("\n")).trim(); + + const step = { agent, task }; + for (const line of configLines) { + const match = line.match(/^([\w-]+):\s*(.*)$/); + if (!match) continue; + const key = match[1].trim().toLowerCase(); + const rawValue = match[2].trim(); + + if (key === "output") { + if (rawValue === "false") step.output = false;else + if (rawValue) step.output = rawValue; + continue; + } + if (key === "outputmode") { + if (rawValue === "inline" || rawValue === "file-only") step.outputMode = rawValue; + continue; + } + if (key === "reads") { + if (rawValue === "false") { + step.reads = false; + } else { + const reads = rawValue. + split(","). + map((v) => v.trim()). + filter(Boolean); + step.reads = reads.length > 0 ? reads : false; + } + continue; + } + if (key === "model") { + if (rawValue) step.model = rawValue; + continue; + } + if (key === "skills") { + if (rawValue === "false") { + step.skills = false; + } else { + const skills = rawValue. + split(","). + map((v) => v.trim()). + filter(Boolean); + step.skills = skills.length > 0 ? skills : false; + } + continue; + } + if (key === "progress") { + if (rawValue === "true") step.progress = true;else + if (rawValue === "false") step.progress = false; + } + } + + return step; +} + +function parseChain(content, source, filePath) { + const { frontmatter, body } = (0, _frontmatter.parseFrontmatter)(content); + if (!frontmatter.name || !frontmatter.description) { + throw new Error("Chain frontmatter must include name and description"); + } + + const matches = [...body.matchAll(/^##\s+(.+)[^\S\n]*$/gm)]; + const steps = []; + + for (let i = 0; i < matches.length; i++) { + const match = matches[i]; + const agent = match[1].trim(); + const lineEndOffset = body[match.index + match[0].length] === "\n" ? 1 : 0; + const sectionStart = match.index + match[0].length + lineEndOffset; + const sectionEnd = i + 1 < matches.length ? matches[i + 1].index : body.length; + const sectionBody = body.slice(sectionStart, sectionEnd).trimEnd(); + steps.push(parseStepBody(agent, sectionBody)); + } + + const localName = frontmatter.name; + const parsedPackage = (0, _identity.parsePackageName)(frontmatter.package, `Chain '${localName}' package`); + if (parsedPackage.error) throw new Error(parsedPackage.error); + const packageName = parsedPackage.packageName; + const extraFields = {}; + for (const [key, value] of Object.entries(frontmatter)) { + if (key === "name" || key === "package" || key === "description") continue; + extraFields[key] = value; + } + + return { + name: (0, _identity.buildRuntimeName)(localName, packageName), + localName, + packageName, + description: frontmatter.description, + source, + filePath, + steps, + extraFields: Object.keys(extraFields).length > 0 ? extraFields : undefined + }; +} + +function serializeChain(config) { + const lines = []; + lines.push("---"); + lines.push(`name: ${(0, _identity.frontmatterNameForConfig)(config)}`); + if (config.packageName) lines.push(`package: ${config.packageName}`); + lines.push(`description: ${config.description}`); + if (config.extraFields) { + for (const [key, value] of Object.entries(config.extraFields)) { + lines.push(`${key}: ${value}`); + } + } + lines.push("---"); + lines.push(""); + + for (let i = 0; i < config.steps.length; i++) { + const step = config.steps[i]; + lines.push(`## ${step.agent}`); + if (step.output === false) lines.push("output: false");else + if (step.output) lines.push(`output: ${step.output}`); + if (step.outputMode) lines.push(`outputMode: ${step.outputMode}`); + if (step.reads === false) lines.push("reads: false");else + if (Array.isArray(step.reads) && step.reads.length > 0) lines.push(`reads: ${step.reads.join(", ")}`); + if (step.model) lines.push(`model: ${step.model}`); + if (step.skills === false) lines.push("skills: false");else + if (Array.isArray(step.skills) && step.skills.length > 0) lines.push(`skills: ${step.skills.join(", ")}`); + if (step.progress !== undefined) lines.push(`progress: ${step.progress ? "true" : "false"}`); + lines.push(""); + lines.push(step.task ?? ""); + if (i < config.steps.length - 1) lines.push(""); + } + + return `${lines.join("\n")}\n`; +} /* v9-06495e239253795d */ diff --git a/pip-tmp/jiti/agents-frontmatter.6976eaf1.mjs b/pip-tmp/jiti/agents-frontmatter.6976eaf1.mjs new file mode 100644 index 0000000000000000000000000000000000000000..cd29916d4f6b7a3df9e071c7914af19bb431410b --- /dev/null +++ b/pip-tmp/jiti/agents-frontmatter.6976eaf1.mjs @@ -0,0 +1,29 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.parseFrontmatter = parseFrontmatter;function parseFrontmatter(content) { + const frontmatter = {}; + const normalized = content.replace(/\r\n/g, "\n"); + + if (!normalized.startsWith("---")) { + return { frontmatter, body: normalized }; + } + + const endIndex = normalized.indexOf("\n---", 3); + if (endIndex === -1) { + return { frontmatter, body: normalized }; + } + + const frontmatterBlock = normalized.slice(4, endIndex); + const body = normalized.slice(endIndex + 4).trim(); + + for (const line of frontmatterBlock.split("\n")) { + const match = line.match(/^([\w-]+):\s*(.*)$/); + if (match) { + let value = match[2].trim(); + if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) { + value = value.slice(1, -1); + } + frontmatter[match[1]] = value; + } + } + + return { frontmatter, body }; +} /* v9-41830bb6fbb8227a */ diff --git a/pip-tmp/jiti/agents-identity.568fcc02.mjs b/pip-tmp/jiti/agents-identity.568fcc02.mjs new file mode 100644 index 0000000000000000000000000000000000000000..c0490ef192729cc01d9b90eefaf0cd88e53109a0 --- /dev/null +++ b/pip-tmp/jiti/agents-identity.568fcc02.mjs @@ -0,0 +1,30 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.buildRuntimeName = buildRuntimeName;exports.frontmatterNameForConfig = frontmatterNameForConfig;exports.parsePackageName = parsePackageName; + +const IDENTIFIER_PATTERN = /^[a-z0-9][a-z0-9-]*(?:\.[a-z0-9][a-z0-9-]*)*$/; + +function normalizePackageName(value) { + const trimmed = value?.trim(); + if (!trimmed) return undefined; + return trimmed.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9.-]/g, "").replace(/-+/g, "-").replace(/\.+/g, ".").replace(/(?:^[-.]+|[-.]+$)/g, ""); +} + +function parsePackageName(value, label = "package") { + if (value === undefined || value === false || value === "") return { packageName: undefined }; + if (typeof value !== "string") return { error: `${label} must be a string or false when provided.` }; + const packageName = normalizePackageName(value); + if (!packageName || !IDENTIFIER_PATTERN.test(packageName)) return { error: `${label} is invalid after sanitization.` }; + return { packageName }; +} + +function buildRuntimeName(localName, packageName) { + const trimmedPackage = packageName?.trim(); + return trimmedPackage ? `${trimmedPackage}.${localName}` : localName; +} + +function frontmatterNameForConfig(config) { + if (config.localName) return config.localName; + if (config.packageName && config.name.startsWith(`${config.packageName}.`)) { + return config.name.slice(config.packageName.length + 1); + } + return config.name; +} /* v9-1a128e76bae61f4f */ diff --git a/pip-tmp/jiti/agents-skills.af023ec8.mjs b/pip-tmp/jiti/agents-skills.af023ec8.mjs new file mode 100644 index 0000000000000000000000000000000000000000..18c1db6d64bef22d88fd3e399072ae561c7227ca --- /dev/null +++ b/pip-tmp/jiti/agents-skills.af023ec8.mjs @@ -0,0 +1,630 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.buildSkillInjection = buildSkillInjection;exports.clearSkillCache = clearSkillCache;exports.discoverAvailableSkills = discoverAvailableSkills;exports.normalizeSkillInput = normalizeSkillInput;exports.resolveSkillPath = resolveSkillPath;exports.resolveSkills = resolveSkills;exports.resolveSkillsWithFallback = resolveSkillsWithFallback; + + + +var _nodeChild_process = await jitiImport("node:child_process"); +var fs = _interopRequireWildcard(await jitiImport("node:fs")); +var os = _interopRequireWildcard(await jitiImport("node:os")); +var path = _interopRequireWildcard(await jitiImport("node:path"));function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);} /** + * Skill resolution and caching for subagent extension + */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +const skillCache = new Map(); +const MAX_CACHE_SIZE = 50; + +let loadSkillsCache = null; +const LOAD_SKILLS_CACHE_TTL_MS = 5000; + +const CONFIG_DIR = ".pi"; +const AGENT_DIR = path.join(os.homedir(), ".pi", "agent"); +const SUBAGENT_ORCHESTRATION_SKILL = "pi-subagents"; + +const SOURCE_PRIORITY = { + project: 700, + "project-settings": 650, + "project-package": 600, + user: 300, + "user-settings": 250, + "user-package": 200, + extension: 150, + builtin: 100, + unknown: 0 +}; + +function stripSkillFrontmatter(content) { + const normalized = content.replace(/\r\n/g, "\n"); + if (!normalized.startsWith("---")) return normalized; + + const endIndex = normalized.indexOf("\n---", 3); + if (endIndex === -1) return normalized; + + return normalized.slice(endIndex + 4).trim(); +} + +function isWithinPath(filePath, dir) { + const relative = path.relative(dir, filePath); + return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative); +} + +function readOptionalJsonFile(filePath, label) { + try { + return JSON.parse(fs.readFileSync(filePath, "utf-8")); + } catch (error) { + const code = typeof error === "object" && error !== null && "code" in error ? + error.code : + undefined; + if (code === "ENOENT") return null; + const message = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to read ${label} '${filePath}': ${message}`, { + cause: error instanceof Error ? error : undefined + }); + } +} + +function readJsonFileBestEffort(filePath) { + try { + return JSON.parse(fs.readFileSync(filePath, "utf-8")); + } catch { + // Package scans over installed dependencies are opportunistic. + return null; + } +} + +function extractSkillPathsFromPackageRoot(packageRoot, source, bestEffort = false) { + const packageJsonPath = path.join(packageRoot, "package.json"); + const pkg = bestEffort ? + readJsonFileBestEffort(packageJsonPath) : + readOptionalJsonFile(packageJsonPath, "package manifest"); + if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) return []; + const pi = pkg.pi; + if (!pi || typeof pi !== "object" || Array.isArray(pi)) return []; + const skills = pi.skills; + if (!Array.isArray(skills)) return []; + return skills. + filter((entry) => typeof entry === "string"). + map((entry) => ({ path: path.resolve(packageRoot, entry), source })); +} + +let cachedGlobalNpmRoot = null; + +function getGlobalNpmRoot() { + if (cachedGlobalNpmRoot !== null) return cachedGlobalNpmRoot; + try { + cachedGlobalNpmRoot = (0, _nodeChild_process.execSync)("npm root -g", { encoding: "utf-8", timeout: 5000 }).trim(); + return cachedGlobalNpmRoot; + } catch { + // Global npm root is optional in constrained environments. + cachedGlobalNpmRoot = ""; // Empty string means "tried but failed" + return null; + } +} + +function collectInstalledPackageSkillPaths(cwd) { + const dirs = [ + { path: path.join(cwd, CONFIG_DIR, "npm", "node_modules"), source: "project-package" }, + { path: path.join(AGENT_DIR, "npm", "node_modules"), source: "user-package" }]; + + + const globalRoot = getGlobalNpmRoot(); + if (globalRoot) { + dirs.push({ path: globalRoot, source: "user-package" }); + } + + const results = []; + + for (const dir of dirs) { + if (!fs.existsSync(dir.path)) continue; + let entries; + try { + entries = fs.readdirSync(dir.path, { withFileTypes: true }); + } catch { + continue; + } + + for (const entry of entries) { + if (entry.name.startsWith(".")) continue; + if (!entry.isDirectory() && !entry.isSymbolicLink()) continue; + + if (entry.name.startsWith("@")) { + const scopeDir = path.join(dir.path, entry.name); + let scopeEntries; + try { + scopeEntries = fs.readdirSync(scopeDir, { withFileTypes: true }); + } catch { + continue; + } + for (const scopeEntry of scopeEntries) { + if (scopeEntry.name.startsWith(".")) continue; + if (!scopeEntry.isDirectory() && !scopeEntry.isSymbolicLink()) continue; + const pkgRoot = path.join(scopeDir, scopeEntry.name); + results.push(...extractSkillPathsFromPackageRoot(pkgRoot, dir.source, true)); + } + continue; + } + + const pkgRoot = path.join(dir.path, entry.name); + results.push(...extractSkillPathsFromPackageRoot(pkgRoot, dir.source, true)); + } + } + + return results; +} + +function collectSettingsSkillPaths(cwd) { + const results = []; + const settingsFiles = [ + { file: path.join(cwd, CONFIG_DIR, "settings.json"), base: path.join(cwd, CONFIG_DIR), source: "project-settings" }, + { file: path.join(AGENT_DIR, "settings.json"), base: AGENT_DIR, source: "user-settings" }]; + + + for (const { file, base, source } of settingsFiles) { + const settings = readOptionalJsonFile(file, "skills settings file"); + if (!settings || typeof settings !== "object" || Array.isArray(settings)) continue; + const skills = settings.skills; + if (!Array.isArray(skills)) continue; + for (const entry of skills) { + if (typeof entry !== "string") continue; + let resolved = entry; + if (resolved.startsWith("~/")) { + resolved = path.join(os.homedir(), resolved.slice(2)); + } else if (!path.isAbsolute(resolved)) { + resolved = path.resolve(base, resolved); + } + results.push({ path: resolved, source }); + } + } + + return results; +} + +function isSafePackagePath(value) { + return value.length > 0 && + !path.isAbsolute(value) && + value.split(/[\\/]/).every((part) => part.length > 0 && part !== "." && part !== ".."); +} + +function parseNpmPackageName(source) { + const spec = source.slice(4).trim(); + if (!spec) return undefined; + const match = spec.match(/^(@?[^@]+(?:\/[^@]+)?)(?:@(.+))?$/); + const packageName = match?.[1] ?? spec; + return isSafePackagePath(packageName) ? packageName : undefined; +} + +function stripGitRef(repoPath) { + const atIndex = repoPath.indexOf("@"); + const hashIndex = repoPath.indexOf("#"); + const refIndex = [atIndex, hashIndex].filter((index) => index >= 0).sort((a, b) => a - b)[0]; + return refIndex === undefined ? repoPath : repoPath.slice(0, refIndex); +} + +function parseGitPackagePath(source) { + const spec = source.slice(4).trim(); + if (!spec) return undefined; + + let host = ""; + let repoPath = ""; + const scpLike = spec.match(/^git@([^:]+):(.+)$/); + if (scpLike) { + host = scpLike[1] ?? ""; + repoPath = scpLike[2] ?? ""; + } else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(spec)) { + try { + const url = new URL(spec); + host = url.hostname; + repoPath = url.pathname.replace(/^\/+/, ""); + } catch { + return undefined; + } + } else { + const slashIndex = spec.indexOf("/"); + if (slashIndex < 0) return undefined; + host = spec.slice(0, slashIndex); + repoPath = spec.slice(slashIndex + 1); + } + + const normalizedPath = stripGitRef(repoPath).replace(/\.git$/, "").replace(/^\/+/, ""); + if (!host || !isSafePackagePath(host) || !isSafePackagePath(normalizedPath) || normalizedPath.split(/[\\/]/).length < 2) { + return undefined; + } + return { host, repoPath: normalizedPath }; +} + +function resolveSettingsPackageRoot(source, baseDir) { + const trimmed = source.trim(); + if (!trimmed) return undefined; + if (trimmed.startsWith("git:")) { + const parsed = parseGitPackagePath(trimmed); + return parsed ? path.join(baseDir, "git", parsed.host, parsed.repoPath) : undefined; + } + if (trimmed.startsWith("npm:")) { + const packageName = parseNpmPackageName(trimmed); + return packageName ? path.join(baseDir, "npm", "node_modules", packageName) : undefined; + } + const normalized = trimmed.startsWith("file:") ? trimmed.slice(5) : trimmed; + if (normalized === "~") return os.homedir(); + if (normalized.startsWith("~/")) return path.join(os.homedir(), normalized.slice(2)); + if (path.isAbsolute(normalized)) return normalized; + if (normalized === "." || normalized === ".." || normalized.startsWith("./") || normalized.startsWith("../")) { + return path.resolve(baseDir, normalized); + } + return undefined; +} + +function collectSettingsPackageSkillPaths(cwd) { + const settingsFiles = [ + { file: path.join(cwd, CONFIG_DIR, "settings.json"), base: path.join(cwd, CONFIG_DIR), source: "project-package" }, + { file: path.join(AGENT_DIR, "settings.json"), base: AGENT_DIR, source: "user-package" }]; + + const results = []; + + for (const { file, base, source } of settingsFiles) { + const settings = readOptionalJsonFile(file, "skills settings file"); + if (!settings || typeof settings !== "object" || Array.isArray(settings)) continue; + const packages = settings.packages; + if (!Array.isArray(packages)) continue; + + for (const entry of packages) { + const packageSource = typeof entry === "string" ? + entry : + typeof entry === "object" && entry !== null && typeof entry.source === "string" ? + entry.source : + undefined; + if (!packageSource) continue; + + const packageRoot = resolveSettingsPackageRoot(packageSource, base); + if (!packageRoot) continue; + results.push(...extractSkillPathsFromPackageRoot(packageRoot, source)); + } + } + + return results; +} + +function buildSkillPaths(cwd) { + const skillPaths = [ + { path: path.join(cwd, CONFIG_DIR, "skills"), source: "project" }, + { path: path.join(cwd, ".agents", "skills"), source: "project" }, + { path: path.join(AGENT_DIR, "skills"), source: "user" }, + { path: path.join(os.homedir(), ".agents", "skills"), source: "user" }, + ...collectInstalledPackageSkillPaths(cwd), + ...collectSettingsPackageSkillPaths(cwd), + ...extractSkillPathsFromPackageRoot(cwd, "project-package"), + ...collectSettingsSkillPaths(cwd)]; + + + const deduped = new Map(); + for (const entry of skillPaths) { + const resolvedPath = path.resolve(entry.path); + if (!deduped.has(resolvedPath)) { + deduped.set(resolvedPath, { path: resolvedPath, source: entry.source }); + } + } + return [...deduped.values()]; +} + +function inferSkillSource(filePath, cwd, sourceHint) { + if (sourceHint) return sourceHint; + + const projectConfigRoot = path.resolve(cwd, CONFIG_DIR); + const projectSkillsRoot = path.resolve(cwd, CONFIG_DIR, "skills"); + const projectPackagesRoot = path.resolve(cwd, CONFIG_DIR, "npm", "node_modules"); + const projectAgentsRoot = path.resolve(cwd, ".agents"); + const userSkillsRoot = path.resolve(AGENT_DIR, "skills"); + const userPackagesRoot = path.resolve(AGENT_DIR, "npm", "node_modules"); + const userAgentsRoot = path.resolve(os.homedir(), ".agents"); + + if (isWithinPath(filePath, projectPackagesRoot)) return "project-package"; + if (isWithinPath(filePath, projectSkillsRoot) || isWithinPath(filePath, projectAgentsRoot)) return "project"; + if (isWithinPath(filePath, projectConfigRoot)) return "project-settings"; + + if (isWithinPath(filePath, userPackagesRoot)) return "user-package"; + if (isWithinPath(filePath, userSkillsRoot) || isWithinPath(filePath, userAgentsRoot)) return "user"; + if (isWithinPath(filePath, AGENT_DIR)) return "user-settings"; + + const globalRoot = getGlobalNpmRoot(); + if (globalRoot && isWithinPath(filePath, globalRoot)) return "user-package"; + + return "unknown"; +} + +function chooseHigherPrioritySkill(existing, candidate) { + if (!existing) return candidate; + const existingPriority = SOURCE_PRIORITY[existing.source] ?? 0; + const candidatePriority = SOURCE_PRIORITY[candidate.source] ?? 0; + if (candidatePriority > existingPriority) return candidate; + if (candidatePriority < existingPriority) return existing; + return candidate.order < existing.order ? candidate : existing; +} + +function maybeReadSkillDescription(filePath) { + try { + const content = fs.readFileSync(filePath, "utf-8"); + const normalized = content.replace(/\r\n/g, "\n"); + if (!normalized.startsWith("---")) return undefined; + + const endIndex = normalized.indexOf("\n---", 3); + if (endIndex === -1) return undefined; + + const frontmatter = normalized.slice(3, endIndex).trim(); + const match = frontmatter.match(/^description:\s*(.+)$/m); + if (!match) return undefined; + return match[1]?.trim().replace(/^['\"]|['\"]$/g, ""); + } catch { + // Description parsing is best-effort metadata extraction. + return undefined; + } +} + +function collectFilesystemSkills(cwd, skillPaths) { + const entries = []; + const seen = new Set(); + let order = 0; + + const pushEntry = (name, filePath, sourceHint) => { + const resolvedFile = path.resolve(filePath); + if (seen.has(resolvedFile)) return; + if (!fs.existsSync(resolvedFile)) return; + seen.add(resolvedFile); + entries.push({ + name, + filePath: resolvedFile, + source: inferSkillSource(resolvedFile, cwd, sourceHint), + description: maybeReadSkillDescription(resolvedFile), + order: order++ + }); + }; + + for (const skillPath of skillPaths) { + if (!fs.existsSync(skillPath.path)) continue; + + let stat; + try { + stat = fs.statSync(skillPath.path); + } catch { + continue; + } + + if (stat.isFile()) { + const fileName = path.basename(skillPath.path); + if (!fileName.toLowerCase().endsWith(".md")) continue; + const skillName = fileName.toLowerCase() === "skill.md" ? + path.basename(path.dirname(skillPath.path)) : + path.basename(fileName, path.extname(fileName)); + pushEntry(skillName, skillPath.path, skillPath.source); + continue; + } + + if (!stat.isDirectory()) continue; + + const rootSkillFile = path.join(skillPath.path, "SKILL.md"); + if (fs.existsSync(rootSkillFile)) { + pushEntry(path.basename(skillPath.path), rootSkillFile, skillPath.source); + } + + let childEntries; + try { + childEntries = fs.readdirSync(skillPath.path, { withFileTypes: true }); + } catch { + continue; + } + + for (const child of childEntries) { + if (child.name.startsWith(".")) continue; + const childPath = path.join(skillPath.path, child.name); + if (child.isDirectory() || child.isSymbolicLink()) { + const nestedSkillPath = path.join(childPath, "SKILL.md"); + if (fs.existsSync(nestedSkillPath)) { + pushEntry(child.name, nestedSkillPath, skillPath.source); + } + continue; + } + if (child.isFile() && child.name.toLowerCase().endsWith(".md")) { + pushEntry(path.basename(child.name, path.extname(child.name)), childPath, skillPath.source); + } + } + } + + return entries; +} + +function getCachedSkills(cwd) { + const now = Date.now(); + if (loadSkillsCache && loadSkillsCache.cwd === cwd && now - loadSkillsCache.timestamp < LOAD_SKILLS_CACHE_TTL_MS) { + return loadSkillsCache.skills; + } + + const skillPaths = buildSkillPaths(cwd); + const loaded = collectFilesystemSkills(cwd, skillPaths); + const dedupedByName = new Map(); + + for (const entry of loaded) { + const current = dedupedByName.get(entry.name); + dedupedByName.set(entry.name, chooseHigherPrioritySkill(current, entry)); + } + + const skills = [...dedupedByName.values()].sort((a, b) => a.order - b.order); + loadSkillsCache = { cwd, skills, timestamp: now }; + return skills; +} + +function resolveSkillPath( +skillName, +cwd) +{ + const skills = getCachedSkills(cwd); + const skill = skills.find((s) => s.name === skillName); + if (!skill) return undefined; + return { path: skill.filePath, source: skill.source }; +} + +function readSkill( +skillName, +skillPath, +source) +{ + try { + const stat = fs.statSync(skillPath); + const cached = skillCache.get(skillPath); + if (cached && cached.mtime === stat.mtimeMs) { + return cached.skill; + } + + const raw = fs.readFileSync(skillPath, "utf-8"); + const content = stripSkillFrontmatter(raw); + const skill = { + name: skillName, + path: skillPath, + content, + source + }; + + skillCache.set(skillPath, { mtime: stat.mtimeMs, skill }); + if (skillCache.size > MAX_CACHE_SIZE) { + const firstKey = skillCache.keys().next().value; + if (firstKey) skillCache.delete(firstKey); + } + + return skill; + } catch { + // Treat unreadable skill files as unresolved so callers can surface as missing. + return undefined; + } +} + +function resolveSkills( +skillNames, +cwd) +{ + const resolved = []; + const missing = []; + + for (const name of skillNames) { + const trimmed = name.trim(); + if (!trimmed) continue; + if (trimmed === SUBAGENT_ORCHESTRATION_SKILL) { + missing.push(trimmed); + continue; + } + + const location = resolveSkillPath(trimmed, cwd); + if (!location) { + missing.push(trimmed); + continue; + } + + const skill = readSkill(trimmed, location.path, location.source); + if (skill) { + resolved.push(skill); + } else { + missing.push(trimmed); + } + } + + return { resolved, missing }; +} + +function resolveSkillsWithFallback( +skillNames, +primaryCwd, +fallbackCwd) +{ + const primary = resolveSkills(skillNames, primaryCwd); + if (!fallbackCwd || primary.missing.length === 0) return primary; + if (path.resolve(primaryCwd) === path.resolve(fallbackCwd)) return primary; + + const fallback = resolveSkills(primary.missing, fallbackCwd); + return { + resolved: [...primary.resolved, ...fallback.resolved], + missing: fallback.missing + }; +} + +function buildSkillInjection(skills) { + if (skills.length === 0) return ""; + + return skills. + map((s) => `\n${s.content}\n`). + join("\n\n"); +} + +function normalizeSkillInput( +input) +{ + if (input === false) return false; + if (input === true || input === undefined) return undefined; + if (Array.isArray(input)) { + return [...new Set(input.map((s) => s.trim()).filter((s) => s.length > 0))]; + } + // Guard against JSON-encoded arrays arriving as strings (e.g. '["a","b"]'). + // Models sometimes serialise the skill parameter as a JSON string instead of + // a native array, and naively splitting on "," would embed brackets/quotes + // into the skill names, causing resolution to silently fail. + const trimmed = input.trim(); + if (trimmed.startsWith("[")) { + try { + const parsed = JSON.parse(trimmed); + if (Array.isArray(parsed)) { + return normalizeSkillInput(parsed); + } + } catch { + + // Not valid JSON – fall through to comma-split + }} + return [...new Set(input.split(",").map((s) => s.trim()).filter((s) => s.length > 0))]; +} + +function discoverAvailableSkills(cwd) + + + +{ + const skills = getCachedSkills(cwd); + return skills. + filter((s) => s.name !== SUBAGENT_ORCHESTRATION_SKILL). + map((s) => ({ + name: s.name, + source: s.source, + description: s.description + })). + sort((a, b) => a.name.localeCompare(b.name)); +} + +function clearSkillCache() { + skillCache.clear(); + loadSkillsCache = null; +} /* v9-27d30b81130fc85f */ diff --git a/pip-tmp/jiti/background-async-execution.0e72180b.mjs b/pip-tmp/jiti/background-async-execution.0e72180b.mjs new file mode 100644 index 0000000000000000000000000000000000000000..c04c03474408ad5b9fd64f8a2e14eeef69b9e488 --- /dev/null +++ b/pip-tmp/jiti/background-async-execution.0e72180b.mjs @@ -0,0 +1,578 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.executeAsyncChain = executeAsyncChain;exports.executeAsyncSingle = executeAsyncSingle;exports.formatAsyncStartedMessage = formatAsyncStartedMessage;exports.isAsyncAvailable = isAsyncAvailable; + + + +var _nodeChild_process = await jitiImport("node:child_process"); +var fs = _interopRequireWildcard(await jitiImport("node:fs")); + +var path = _interopRequireWildcard(await jitiImport("node:path")); +var _nodeUrl = await jitiImport("node:url"); +var _nodeModule = await jitiImport("node:module"); + + +var _piArgs = await jitiImport("../shared/pi-args.ts"); +var _singleOutput = await jitiImport("../shared/single-output.ts"); +var _settings = await jitiImport("../../shared/settings.ts"); + +var _piSpawn = await jitiImport("../shared/pi-spawn.ts"); +var _skills = await jitiImport("../../agents/skills.ts"); +var _utils = await jitiImport("../../shared/utils.ts"); +var _modelFallback = await jitiImport("../shared/model-fallback.ts"); +var _worktree = await jitiImport("../shared/worktree.ts"); +var _types = await jitiImport("../../shared/types.ts");function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);} /** + * Async execution logic for subagent tool + */ + + + + + + + + + + + +const _require = (0, _nodeModule.createRequire)("file:///home/edwin/.config/nvm/versions/node/v22.20.0/lib/node_modules/pi-subagents/src/runs/background/async-execution.ts"); +const piPackageRoot = (0, _piSpawn.resolvePiPackageRoot)(); +const jitiCliPath = (() => { + const candidates = [ + () => path.join(path.dirname(_require.resolve("jiti/package.json")), "lib/jiti-cli.mjs"), + () => path.join(path.dirname(_require.resolve("@mariozechner/jiti/package.json")), "lib/jiti-cli.mjs"), + () => { + const piEntry = fs.realpathSync(process.argv[1]); + const piRequire = (0, _nodeModule.createRequire)(piEntry); + return path.join(path.dirname(piRequire.resolve("@mariozechner/jiti/package.json")), "lib/jiti-cli.mjs"); + }]; + + for (const candidate of candidates) { + try { + const p = candidate(); + if (fs.existsSync(p)) return p; + } catch { + + // Candidate not available in this install, continue probing. + }} + return undefined; +})(); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +function formatAsyncStartedMessage(headline) { + return [ + headline, + "", + "The async run is detached. Do not run sleep timers or polling loops just to wait for it.", + "If you have independent work, continue that work. If you have nothing else to do until the async result arrives, end your turn now; Pi will deliver the completion when the run finishes.", + "Use subagent({ action: \"status\", id: \"...\" }) when you need the current status/result, or to inspect a blocked/stale run. Do not poll just to wait."]. + join("\n"); +} + +/** + * Check if jiti is available for async execution + */ +function isAsyncAvailable() { + return jitiCliPath !== undefined; +} + +/** + * Spawn the async runner process + */ +function spawnRunner(cfg, suffix, cwd) { + if (!jitiCliPath) { + return { error: "jiti for TypeScript execution could not be found" }; + } + + try { + const cwdStats = fs.statSync(cwd); + if (!cwdStats.isDirectory()) { + return { error: `cwd is not a directory: ${cwd}` }; + } + } catch { + return { error: `cwd does not exist: ${cwd}` }; + } + + fs.mkdirSync(_types.TEMP_ROOT_DIR, { recursive: true }); + const cfgPath = (0, _types.getAsyncConfigPath)(suffix); + fs.writeFileSync(cfgPath, JSON.stringify(cfg)); + const runner = path.join(path.dirname((0, _nodeUrl.fileURLToPath)("file:///home/edwin/.config/nvm/versions/node/v22.20.0/lib/node_modules/pi-subagents/src/runs/background/async-execution.ts")), "subagent-runner.ts"); + + const proc = (0, _nodeChild_process.spawn)(process.execPath, [jitiCliPath, runner, cfgPath], { + cwd, + detached: true, + stdio: "ignore", + windowsHide: true + }); + proc.on("error", (error) => { + console.error(`[pi-subagents] async spawn failed: ${error.message}`); + }); + if (typeof proc.pid !== "number") { + return { error: `async runner did not produce a pid for cwd: ${cwd}` }; + } + proc.unref(); + return { pid: proc.pid }; +} + +function formatAsyncStartError(mode, message) { + return { + content: [{ type: "text", text: message }], + isError: true, + details: { mode, results: [] } + }; +} + +const UNAVAILABLE_SUBAGENT_SKILL_ERROR = "Skills not found: pi-subagents"; + +class UnavailableSubagentSkillError extends Error {} +class AsyncStartValidationError extends Error {} + +/** + * Execute a chain asynchronously + */ +function executeAsyncChain( +id, +params) +{ + const { + chain, + agents, + ctx, + cwd, + maxOutput, + artifactsDir, + artifactConfig, + shareEnabled, + sessionRoot, + sessionFilesByFlatIndex, + maxSubagentDepth, + worktreeSetupHook, + worktreeSetupHookTimeoutMs, + controlConfig, + controlIntercomTarget, + childIntercomTarget + } = params; + const resultMode = params.resultMode ?? "chain"; + const chainSkills = params.chainSkills ?? []; + const availableModels = params.availableModels; + const runnerCwd = (0, _utils.resolveChildCwd)(ctx.cwd, cwd); + const firstStep = chain[0]; + const originalTask = params.task ?? (firstStep ? + (0, _settings.isParallelStep)(firstStep) ? firstStep.parallel[0]?.task : firstStep.task : + undefined); + + for (const s of chain) { + const stepAgents = (0, _settings.isParallelStep)(s) ? + s.parallel.map((t) => t.agent) : + [s.agent]; + for (const agentName of stepAgents) { + if (!agents.find((x) => x.name === agentName)) { + return { + content: [{ type: "text", text: `Unknown agent: ${agentName}` }], + isError: true, + details: { mode: resultMode, results: [] } + }; + } + } + } + + const asyncDir = path.join(_types.ASYNC_DIR, id); + try { + fs.mkdirSync(asyncDir, { recursive: true }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { + content: [{ type: "text", text: `Failed to create async run directory '${asyncDir}': ${message}` }], + isError: true, + details: { mode: resultMode, results: [] } + }; + } + + let progressInstructionCreated = false; + const buildStepOverrides = (s) => { + const stepSkillInput = (0, _skills.normalizeSkillInput)(s.skill); + return { + ...(s.output !== undefined ? { output: s.output } : {}), + ...(s.outputMode !== undefined ? { outputMode: s.outputMode } : {}), + ...(s.reads !== undefined ? { reads: s.reads } : {}), + ...(s.progress !== undefined ? { progress: s.progress } : {}), + ...(stepSkillInput !== undefined ? { skills: stepSkillInput } : {}), + ...(s.model ? { model: s.model } : {}) + }; + }; + const buildSeqStep = (s, sessionFile, behaviorCwd, progressPrecreated = false, resolvedBehavior) => { + const a = agents.find((x) => x.name === s.agent); + const stepCwd = (0, _utils.resolveChildCwd)(runnerCwd, s.cwd); + const instructionCwd = behaviorCwd ?? stepCwd; + const behavior = (0, _settings.suppressProgressForReadOnlyTask)(resolvedBehavior ?? (0, _settings.resolveStepBehavior)(a, buildStepOverrides(s), chainSkills), s.task, originalTask); + const skillNames = behavior.skills === false ? [] : behavior.skills; + const { resolved: resolvedSkills, missing: missingSkills } = (0, _skills.resolveSkillsWithFallback)(skillNames, stepCwd, ctx.cwd); + if (missingSkills.includes("pi-subagents")) throw new UnavailableSubagentSkillError(UNAVAILABLE_SUBAGENT_SKILL_ERROR); + + let systemPrompt = a.systemPrompt?.trim() ?? ""; + if (resolvedSkills.length > 0) { + const injection = (0, _skills.buildSkillInjection)(resolvedSkills); + systemPrompt = systemPrompt ? `${systemPrompt}\n\n${injection}` : injection; + } + + const readInstructions = (0, _settings.buildChainInstructions)({ ...behavior, output: false, progress: false }, instructionCwd, false); + const isFirstProgressAgent = behavior.progress && !progressPrecreated && !progressInstructionCreated; + if (behavior.progress) progressInstructionCreated = true; + const progressInstructions = (0, _settings.buildChainInstructions)({ ...behavior, output: false, reads: false }, runnerCwd, isFirstProgressAgent); + const outputPath = (0, _singleOutput.resolveSingleOutputPath)(behavior.output, ctx.cwd, instructionCwd); + const validationError = (0, _singleOutput.validateFileOnlyOutputMode)(behavior.outputMode, outputPath, `Async step (${s.agent})`); + if (validationError) throw new AsyncStartValidationError(validationError); + const task = (0, _singleOutput.injectSingleOutputInstruction)(`${readInstructions.prefix}${s.task ?? "{previous}"}${progressInstructions.suffix}`, outputPath); + + const primaryModel = (0, _modelFallback.resolveModelCandidate)(behavior.model ?? a.model, availableModels, ctx.currentModelProvider); + return { + agent: s.agent, + task, + cwd: stepCwd, + model: (0, _piArgs.applyThinkingSuffix)(primaryModel, a.thinking), + modelCandidates: (0, _modelFallback.buildModelCandidates)(behavior.model ?? a.model, a.fallbackModels, availableModels, ctx.currentModelProvider).map((candidate) => + (0, _piArgs.applyThinkingSuffix)(candidate, a.thinking) + ), + tools: a.tools, + extensions: a.extensions, + mcpDirectTools: a.mcpDirectTools, + systemPrompt, + systemPromptMode: a.systemPromptMode, + inheritProjectContext: a.inheritProjectContext, + inheritSkills: a.inheritSkills, + skills: resolvedSkills.map((r) => r.name), + outputPath, + outputMode: behavior.outputMode, + sessionFile, + maxSubagentDepth: (0, _types.resolveChildMaxSubagentDepth)(maxSubagentDepth, a.maxSubagentDepth) + }; + }; + + let flatStepIndex = 0; + const nextSessionFile = () => { + const sessionFile = sessionFilesByFlatIndex?.[flatStepIndex]; + flatStepIndex++; + return sessionFile; + }; + + let steps; + try { + steps = chain.map((s, stepIndex) => { + if ((0, _settings.isParallelStep)(s)) { + const parallelBehaviors = s.parallel.map((task) => { + const agent = agents.find((candidate) => candidate.name === task.agent); + return (0, _settings.suppressProgressForReadOnlyTask)((0, _settings.resolveStepBehavior)(agent, buildStepOverrides(task), chainSkills), task.task, originalTask); + }); + const progressPrecreated = parallelBehaviors.some((behavior) => behavior.progress); + if (progressPrecreated) { + if (!s.worktree) (0, _settings.writeInitialProgressFile)(runnerCwd); + progressInstructionCreated = true; + } + return { + parallel: s.parallel.map((t, taskIndex) => { + let behaviorCwd; + if (s.worktree) { + try { + behaviorCwd = (0, _worktree.resolveExpectedWorktreeAgentCwd)(runnerCwd, `${id}-s${stepIndex}`, taskIndex); + } catch { + behaviorCwd = undefined; + } + } + return buildSeqStep(t, nextSessionFile(), behaviorCwd, progressPrecreated, parallelBehaviors[taskIndex]); + }), + concurrency: s.concurrency, + failFast: s.failFast, + worktree: s.worktree + }; + } + return buildSeqStep(s, nextSessionFile()); + }); + } catch (error) { + if (error instanceof UnavailableSubagentSkillError || error instanceof AsyncStartValidationError) return formatAsyncStartError(resultMode, error.message); + throw error; + } + let childTargetIndex = 0; + const childIntercomTargets = childIntercomTarget ? steps.flatMap((step) => { + if ("parallel" in step) { + return step.parallel.map((task) => childIntercomTarget(task.agent, childTargetIndex++)); + } + return [childIntercomTarget(step.agent, childTargetIndex++)]; + }) : undefined; + + let spawnResult = {}; + try { + spawnResult = spawnRunner( + { + id, + steps, + resultPath: path.join(_types.RESULTS_DIR, `${id}.json`), + cwd: runnerCwd, + placeholder: "{previous}", + maxOutput, + artifactsDir: artifactConfig.enabled ? artifactsDir : undefined, + artifactConfig, + share: shareEnabled, + sessionDir: sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined, + asyncDir, + sessionId: ctx.currentSessionId, + piPackageRoot, + piArgv1: process.argv[1], + worktreeSetupHook, + worktreeSetupHookTimeoutMs, + controlConfig, + controlIntercomTarget, + childIntercomTargets, + resultMode + }, + id, + runnerCwd + ); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': ${message}`); + } + + if (spawnResult.error) { + return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': ${spawnResult.error}`); + } + + if (spawnResult.pid) { + const firstStep = chain[0]; + const firstAgents = (0, _settings.isParallelStep)(firstStep) ? + firstStep.parallel.map((t) => t.agent) : + [firstStep.agent]; + const parallelGroups = []; + const flatAgents = []; + let flatStepStart = 0; + for (let stepIndex = 0; stepIndex < chain.length; stepIndex++) { + const step = chain[stepIndex]; + if ((0, _settings.isParallelStep)(step)) { + parallelGroups.push({ start: flatStepStart, count: step.parallel.length, stepIndex }); + flatAgents.push(...step.parallel.map((task) => task.agent)); + flatStepStart += step.parallel.length; + } else { + flatAgents.push(step.agent); + flatStepStart++; + } + } + ctx.pi.events.emit(_types.SUBAGENT_ASYNC_STARTED_EVENT, { + id, + pid: spawnResult.pid, + sessionId: ctx.currentSessionId, + mode: resultMode, + agent: firstAgents[0], + agents: flatAgents, + task: (0, _settings.isParallelStep)(firstStep) ? + firstStep.parallel[0]?.task?.slice(0, 50) : + firstStep.task?.slice(0, 50), + chain: chain.map((s) => + (0, _settings.isParallelStep)(s) ? `[${s.parallel.map((t) => t.agent).join("+")}]` : s.agent + ), + chainStepCount: chain.length, + parallelGroups, + cwd: runnerCwd, + asyncDir + }); + } + + const chainDesc = chain. + map((s) => + (0, _settings.isParallelStep)(s) ? `[${s.parallel.map((t) => t.agent).join("+")}]` : s.agent + ). + join(" -> "); + + return { + content: [{ type: "text", text: formatAsyncStartedMessage(`Async ${resultMode}: ${chainDesc} [${id}]`) }], + details: { mode: resultMode, runId: id, results: [], asyncId: id, asyncDir } + }; +} + +/** + * Execute a single agent asynchronously + */ +function executeAsyncSingle( +id, +params) +{ + const { + agent, + agentConfig, + ctx, + cwd, + maxOutput, + artifactsDir, + artifactConfig, + shareEnabled, + sessionRoot, + sessionFile, + maxSubagentDepth, + worktreeSetupHook, + worktreeSetupHookTimeoutMs, + controlConfig, + controlIntercomTarget, + childIntercomTarget + } = params; + const task = params.task ?? ""; + const runnerCwd = (0, _utils.resolveChildCwd)(ctx.cwd, cwd); + const skillNames = params.skills ?? agentConfig.skills ?? []; + const availableModels = params.availableModels; + const { resolved: resolvedSkills, missing: missingSkills } = (0, _skills.resolveSkillsWithFallback)(skillNames, runnerCwd, ctx.cwd); + if (missingSkills.includes("pi-subagents")) return formatAsyncStartError("single", UNAVAILABLE_SUBAGENT_SKILL_ERROR); + let systemPrompt = agentConfig.systemPrompt?.trim() ?? ""; + if (resolvedSkills.length > 0) { + const injection = (0, _skills.buildSkillInjection)(resolvedSkills); + systemPrompt = systemPrompt ? `${systemPrompt}\n\n${injection}` : injection; + } + + const asyncDir = path.join(_types.ASYNC_DIR, id); + try { + fs.mkdirSync(asyncDir, { recursive: true }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { + content: [{ type: "text", text: `Failed to create async run directory '${asyncDir}': ${message}` }], + isError: true, + details: { mode: "single", results: [] } + }; + } + + const outputPath = (0, _singleOutput.resolveSingleOutputPath)(params.output, ctx.cwd, runnerCwd); + const outputMode = params.outputMode ?? "inline"; + const validationError = (0, _singleOutput.validateFileOnlyOutputMode)(outputMode, outputPath, `Async single run (${agent})`); + if (validationError) return formatAsyncStartError("single", validationError); + const taskWithOutputInstruction = (0, _singleOutput.injectSingleOutputInstruction)(task, outputPath); + let spawnResult = {}; + try { + spawnResult = spawnRunner( + { + id, + steps: [ + { + agent, + task: taskWithOutputInstruction, + cwd: runnerCwd, + model: (0, _piArgs.applyThinkingSuffix)((0, _modelFallback.resolveModelCandidate)(params.modelOverride ?? agentConfig.model, availableModels, ctx.currentModelProvider), agentConfig.thinking), + modelCandidates: (0, _modelFallback.buildModelCandidates)(params.modelOverride ?? agentConfig.model, agentConfig.fallbackModels, availableModels, ctx.currentModelProvider).map((candidate) => + (0, _piArgs.applyThinkingSuffix)(candidate, agentConfig.thinking) + ), + tools: agentConfig.tools, + extensions: agentConfig.extensions, + mcpDirectTools: agentConfig.mcpDirectTools, + systemPrompt, + systemPromptMode: agentConfig.systemPromptMode, + inheritProjectContext: agentConfig.inheritProjectContext, + inheritSkills: agentConfig.inheritSkills, + skills: resolvedSkills.map((r) => r.name), + outputPath, + outputMode, + sessionFile, + maxSubagentDepth: (0, _types.resolveChildMaxSubagentDepth)(maxSubagentDepth, agentConfig.maxSubagentDepth) + }], + + resultPath: path.join(_types.RESULTS_DIR, `${id}.json`), + cwd: runnerCwd, + placeholder: "{previous}", + maxOutput, + artifactsDir: artifactConfig.enabled ? artifactsDir : undefined, + artifactConfig, + share: shareEnabled, + sessionDir: sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined, + asyncDir, + sessionId: ctx.currentSessionId, + piPackageRoot, + piArgv1: process.argv[1], + worktreeSetupHook, + worktreeSetupHookTimeoutMs, + controlConfig, + controlIntercomTarget, + childIntercomTargets: childIntercomTarget ? [childIntercomTarget(agent, 0)] : undefined, + resultMode: "single" + }, + id, + runnerCwd + ); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return formatAsyncStartError("single", `Failed to start async run '${id}': ${message}`); + } + + if (spawnResult.error) { + return formatAsyncStartError("single", `Failed to start async run '${id}': ${spawnResult.error}`); + } + + if (spawnResult.pid) { + ctx.pi.events.emit(_types.SUBAGENT_ASYNC_STARTED_EVENT, { + id, + pid: spawnResult.pid, + sessionId: ctx.currentSessionId, + mode: "single", + agent, + task: task?.slice(0, 50), + cwd: runnerCwd, + asyncDir + }); + } + + return { + content: [{ type: "text", text: formatAsyncStartedMessage(`Async: ${agent} [${id}]`) }], + details: { mode: "single", runId: id, results: [], asyncId: id, asyncDir } + }; +} /* v9-4e5f8b1d7a19d9c5 */ diff --git a/pip-tmp/jiti/background-async-job-tracker.6fb217e9.mjs b/pip-tmp/jiti/background-async-job-tracker.6fb217e9.mjs new file mode 100644 index 0000000000000000000000000000000000000000..db00e63d7db6a8a93d9d5d9c5b90307f298a9697 --- /dev/null +++ b/pip-tmp/jiti/background-async-job-tracker.6fb217e9.mjs @@ -0,0 +1,267 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.createAsyncJobTracker = createAsyncJobTracker; +var fs = _interopRequireWildcard(await jitiImport("node:fs")); +var path = _interopRequireWildcard(await jitiImport("node:path")); +var _render = await jitiImport("../../tui/render.ts"); +var _subagentControl = await jitiImport("../shared/subagent-control.ts"); +var _types = await jitiImport("../../shared/types.ts"); + + + + + + + + + +var _utils = await jitiImport("../../shared/utils.ts"); +var _parallelGroups = await jitiImport("./parallel-groups.ts"); +var _staleRunReconciler = await jitiImport("./stale-run-reconciler.ts");function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);} + + + + + + + + + +function createAsyncJobTracker(pi, state, asyncDirRoot, options = {}) + + + + +{ + const completionRetentionMs = options.completionRetentionMs ?? 10000; + const pollIntervalMs = options.pollIntervalMs ?? _types.POLL_INTERVAL_MS; + const resultsDir = options.resultsDir ?? _types.RESULTS_DIR; + const rerenderWidget = (ctx, jobs = Array.from(state.asyncJobs.values())) => { + (0, _render.renderWidget)(ctx, jobs); + ctx.ui.requestRender?.(); + }; + const scheduleCleanup = (asyncId) => { + const existingTimer = state.cleanupTimers.get(asyncId); + if (existingTimer) clearTimeout(existingTimer); + const timer = setTimeout(() => { + state.cleanupTimers.delete(asyncId); + state.asyncJobs.delete(asyncId); + if (state.lastUiContext) { + rerenderWidget(state.lastUiContext); + } + }, completionRetentionMs); + state.cleanupTimers.set(asyncId, timer); + }; + const emitNewControlEvents = (job) => { + const eventsPath = path.join(job.asyncDir, "events.jsonl"); + let fd; + try { + fd = fs.openSync(eventsPath, "r"); + } catch (error) { + if (error.code === "ENOENT") return; + console.error(`Failed to open async control events for '${job.asyncDir}':`, error); + return; + } + try { + const stat = fs.fstatSync(fd); + const cursor = stat.size < (job.controlEventCursor ?? 0) ? 0 : job.controlEventCursor ?? 0; + if (stat.size <= cursor) return; + const buffer = Buffer.alloc(stat.size - cursor); + fs.readSync(fd, buffer, 0, buffer.length, cursor); + const lastNewline = buffer.lastIndexOf(0x0a); + if (lastNewline === -1) return; + job.controlEventCursor = cursor + lastNewline + 1; + for (const line of buffer.subarray(0, lastNewline).toString("utf-8").split("\n")) { + if (!line.trim()) continue; + let parsed; + try { + parsed = JSON.parse(line); + } catch (error) { + console.error(`Ignoring malformed async control event in '${eventsPath}':`, error); + continue; + } + if (!parsed || typeof parsed !== "object" || parsed.type !== "subagent.control") continue; + const record = parsed; + if (!record.event || !Array.isArray(record.channels)) continue; + const payload = { + event: record.event, + source: "async", + asyncDir: job.asyncDir, + childIntercomTarget: record.childIntercomTarget, + noticeText: record.noticeText ?? (0, _subagentControl.formatControlNoticeMessage)(record.event, record.childIntercomTarget) + }; + if (record.channels.includes("event")) { + pi.events.emit(_types.SUBAGENT_CONTROL_EVENT, payload); + } + if (record.event.type !== "active_long_running" && record.channels.includes("intercom") && record.intercom?.to && record.intercom.message) { + pi.events.emit(_types.SUBAGENT_CONTROL_INTERCOM_EVENT, { + ...payload, + to: record.intercom.to, + message: record.intercom.message + }); + } + } + } catch (error) { + console.error(`Failed to read async control events for '${job.asyncDir}':`, error); + } finally { + fs.closeSync(fd); + } + }; + + const ensurePoller = () => { + if (state.poller) return; + state.poller = setInterval(() => { + if (state.asyncJobs.size === 0) { + if (state.lastUiContext?.hasUI) rerenderWidget(state.lastUiContext, []); + if (state.poller) { + clearInterval(state.poller); + state.poller = null; + } + return; + } + + for (const job of state.asyncJobs.values()) { + try { + emitNewControlEvents(job); + const reconciliation = (0, _staleRunReconciler.reconcileAsyncRun)(job.asyncDir, { + resultsDir, + kill: options.kill, + now: options.now, + startedRun: { + runId: job.asyncId, + pid: job.pid, + sessionId: job.sessionId, + mode: job.mode, + agents: job.agents, + chainStepCount: job.chainStepCount, + parallelGroups: job.parallelGroups, + startedAt: job.startedAt, + sessionFile: job.sessionFile + } + }); + const status = reconciliation.status ?? (0, _utils.readStatus)(job.asyncDir); + if (status) { + const previousStatus = job.status; + job.status = status.state; + job.sessionId = status.sessionId ?? job.sessionId; + job.activityState = status.activityState; + job.lastActivityAt = status.lastActivityAt ?? job.lastActivityAt; + job.currentTool = status.currentTool; + job.currentToolStartedAt = status.currentToolStartedAt; + job.currentPath = status.currentPath; + job.turnCount = status.turnCount ?? job.turnCount; + job.toolCount = status.toolCount ?? job.toolCount; + job.mode = status.mode; + job.currentStep = status.currentStep ?? job.currentStep; + job.chainStepCount = status.chainStepCount ?? job.chainStepCount; + job.startedAt = status.startedAt ?? job.startedAt; + job.updatedAt = status.lastUpdate ?? Date.now(); + if (status.steps?.length) { + const groups = (0, _parallelGroups.normalizeParallelGroups)(status.parallelGroups, status.steps.length, status.chainStepCount ?? status.steps.length); + job.parallelGroups = groups.length ? groups : job.parallelGroups; + job.hasParallelGroups = groups.length > 0 || job.hasParallelGroups; + const activeGroup = status.currentStep !== undefined ? + groups.find((group) => status.currentStep >= group.start && status.currentStep < group.start + group.count) : + undefined; + const visibleSteps = activeGroup ? + status.steps.slice(activeGroup.start, activeGroup.start + activeGroup.count).map((step, index) => ({ ...step, index: activeGroup.start + index })) : + status.steps.map((step, index) => ({ ...step, index })); + job.activeParallelGroup = Boolean(activeGroup); + job.agents = visibleSteps.map((step) => step.agent); + job.steps = visibleSteps; + job.stepsTotal = visibleSteps.length; + job.runningSteps = visibleSteps.filter((step) => step.status === "running").length; + job.completedSteps = visibleSteps.filter((step) => step.status === "complete" || step.status === "completed").length; + if (status.state === "complete") job.completedSteps = visibleSteps.length; + } + job.sessionDir = status.sessionDir ?? job.sessionDir; + job.outputFile = status.outputFile ?? job.outputFile; + job.totalTokens = status.totalTokens ?? job.totalTokens; + job.sessionFile = status.sessionFile ?? job.sessionFile; + if ((job.status === "complete" || job.status === "failed" || job.status === "paused") && (previousStatus !== job.status || !state.cleanupTimers.has(job.asyncId))) { + scheduleCleanup(job.asyncId); + } + continue; + } + job.status = job.status === "queued" ? "running" : job.status; + job.updatedAt = Date.now(); + } catch (error) { + console.error(`Failed to read async status for '${job.asyncDir}':`, error); + job.status = "failed"; + job.updatedAt = Date.now(); + if (!state.cleanupTimers.has(job.asyncId)) { + scheduleCleanup(job.asyncId); + } + } + } + + if (state.lastUiContext?.hasUI) rerenderWidget(state.lastUiContext); + }, pollIntervalMs); + state.poller.unref?.(); + }; + + const handleStarted = (data) => { + const info = data; + if (!info.id) return; + const now = Date.now(); + const asyncDir = info.asyncDir ?? path.join(asyncDirRoot, info.id); + const rawAgents = info.agents?.length ? info.agents : info.chain && info.chain.length > 0 ? info.chain : info.agent ? [info.agent] : undefined; + const validParallelGroups = (0, _parallelGroups.normalizeParallelGroups)(info.parallelGroups, Number.MAX_SAFE_INTEGER, info.chainStepCount ?? Number.MAX_SAFE_INTEGER); + const firstGroup = validParallelGroups.find((group) => group.start === 0); + const firstGroupCount = firstGroup?.count; + const agents = firstGroupCount && firstGroupCount > 0 ? + rawAgents?.slice(0, firstGroupCount) : + rawAgents; + state.asyncJobs.set(info.id, { + asyncId: info.id, + asyncDir, + status: "queued", + pid: typeof info.pid === "number" ? info.pid : undefined, + ...(typeof info.sessionId === "string" ? { sessionId: info.sessionId } : {}), + mode: info.mode ?? (info.chain ? "chain" : "single"), + agents, + chainStepCount: info.chainStepCount, + parallelGroups: validParallelGroups, + stepsTotal: firstGroupCount ?? agents?.length, + hasParallelGroups: validParallelGroups.length > 0, + activeParallelGroup: Boolean(firstGroupCount && firstGroupCount > 0), + startedAt: now, + updatedAt: now + }); + ensurePoller(); + if (state.lastUiContext) { + rerenderWidget(state.lastUiContext); + } + }; + + const handleComplete = (data) => { + const result = data; + const asyncId = result.id; + if (!asyncId) return; + const job = state.asyncJobs.get(asyncId); + if (job) { + job.status = result.success ? "complete" : "failed"; + job.updatedAt = Date.now(); + if (result.asyncDir) job.asyncDir = result.asyncDir; + } + if (state.lastUiContext) { + rerenderWidget(state.lastUiContext); + } + scheduleCleanup(asyncId); + }; + + const resetJobs = (ctx) => { + for (const timer of state.cleanupTimers.values()) { + clearTimeout(timer); + } + state.cleanupTimers.clear(); + state.asyncJobs.clear(); + state.foregroundControls?.clear(); + state.lastForegroundControlId = null; + state.resultFileCoalescer.clear(); + if (ctx?.hasUI) { + state.lastUiContext = ctx; + rerenderWidget(ctx, []); + } + }; + + return { ensurePoller, handleStarted, handleComplete, resetJobs }; +} /* v9-8d628c367d3af3d8 */ diff --git a/pip-tmp/jiti/background-async-resume.8fb594f6.mjs b/pip-tmp/jiti/background-async-resume.8fb594f6.mjs new file mode 100644 index 0000000000000000000000000000000000000000..b4a705b4fcc2274ff45fbfd37151753a70d30657 --- /dev/null +++ b/pip-tmp/jiti/background-async-resume.8fb594f6.mjs @@ -0,0 +1,332 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.buildRevivedAsyncTask = buildRevivedAsyncTask;exports.resolveAsyncResumeTarget = resolveAsyncResumeTarget;exports.resolveAsyncRunLocation = resolveAsyncRunLocation;var fs = _interopRequireWildcard(await jitiImport("node:fs")); +var path = _interopRequireWildcard(await jitiImport("node:path")); +var _types = await jitiImport("../../shared/types.ts"); +var _intercomBridge = await jitiImport("../../intercom/intercom-bridge.ts"); +var _staleRunReconciler = await jitiImport("./stale-run-reconciler.ts");function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +function getErrorMessage(error) { + return error instanceof Error ? error.message : String(error); +} + +function ensureObject(value, source) { + if (!value || typeof value !== "object" || Array.isArray(value)) { + throw new Error(`Async result file '${source}' must contain a JSON object.`); + } + return value; +} + +function validateOptionalString(value, field, source, displayField = field) { + const fieldValue = value[field]; + if (fieldValue === undefined) return undefined; + if (typeof fieldValue !== "string") throw new Error(`Invalid async result file '${source}': ${displayField} must be a string.`); + return fieldValue; +} + +function validateResultFile(value, resultPath) { + const data = ensureObject(value, resultPath); + const resultsValue = data.results; + let results; + if (resultsValue !== undefined) { + if (!Array.isArray(resultsValue)) throw new Error(`Invalid async result file '${resultPath}': results must be an array.`); + results = resultsValue.map((entry, index) => { + const child = ensureObject(entry, `${resultPath} results[${index}]`); + const agent = validateOptionalString(child, "agent", resultPath, `results[${index}].agent`); + const sessionFile = validateOptionalString(child, "sessionFile", resultPath, `results[${index}].sessionFile`); + const intercomTarget = validateOptionalString(child, "intercomTarget", resultPath, `results[${index}].intercomTarget`); + const success = child.success; + if (success !== undefined && typeof success !== "boolean") throw new Error(`Invalid async result file '${resultPath}': results[${index}].success must be a boolean.`); + return { agent, sessionFile, intercomTarget, ...(typeof success === "boolean" ? { success } : {}) }; + }); + } + const success = data.success; + if (success !== undefined && typeof success !== "boolean") throw new Error(`Invalid async result file '${resultPath}': success must be a boolean.`); + return { + id: validateOptionalString(data, "id", resultPath), + runId: validateOptionalString(data, "runId", resultPath), + agent: validateOptionalString(data, "agent", resultPath), + mode: validateOptionalString(data, "mode", resultPath), + state: validateOptionalString(data, "state", resultPath), + cwd: validateOptionalString(data, "cwd", resultPath), + sessionFile: validateOptionalString(data, "sessionFile", resultPath), + ...(typeof success === "boolean" ? { success } : {}), + ...(results ? { results } : {}) + }; +} + +function readResultFile(resultPath) { + let raw; + try { + raw = fs.readFileSync(resultPath, "utf-8"); + } catch (error) { + throw new Error(`Failed to read async result file '${resultPath}': ${getErrorMessage(error)}`, { + cause: error instanceof Error ? error : undefined + }); + } + try { + return validateResultFile(JSON.parse(raw), resultPath); + } catch (error) { + if (error instanceof SyntaxError) { + throw new Error(`Failed to parse async result file '${resultPath}': ${getErrorMessage(error)}`, { + cause: error + }); + } + throw error; + } +} + +function assertRunId(value, field) { + if (value === undefined) return undefined; + if (value.trim() === "") throw new Error(`${field} must not be empty.`); + if (path.isAbsolute(value) || /[\\/]/.test(value) || value.includes("..")) { + throw new Error(`${field} must be an async run id or prefix, not a path.`); + } + return value; +} + +function assertInsideRoot(root, target, label) { + const rootPath = path.resolve(root); + const targetPath = path.resolve(target); + const relative = path.relative(rootPath, targetPath); + if (relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative)) return; + throw new Error(`${label} must be inside ${rootPath}.`); +} + +function prefixedRunIds(dir, prefix, suffix = "") { + if (!fs.existsSync(dir)) return []; + return fs.readdirSync(dir). + filter((entry) => entry.startsWith(prefix) && (!suffix || entry.endsWith(suffix))). + map((entry) => suffix ? entry.slice(0, -suffix.length) : entry). + sort(); +} + +function exactResultPath(resultsDir, runId) { + const resultPath = path.join(resultsDir, `${runId}.json`); + assertInsideRoot(resultsDir, resultPath, "Async result file"); + return fs.existsSync(resultPath) ? resultPath : null; +} + +function resolveAsyncRunLocation(params, asyncDirRoot, resultsDir) { + const asyncRoot = path.resolve(asyncDirRoot); + const resultRoot = path.resolve(resultsDir); + const requestedId = assertRunId(params.id, "id") ?? assertRunId(params.runId, "runId"); + if (params.dir) { + const asyncDir = path.resolve(params.dir); + assertInsideRoot(asyncRoot, asyncDir, "Async run directory"); + const resolvedId = requestedId ?? path.basename(asyncDir); + if (requestedId && requestedId !== path.basename(asyncDir)) { + throw new Error(`Async run id '${requestedId}' does not match directory '${path.basename(asyncDir)}'.`); + } + return { asyncDir, resultPath: exactResultPath(resultRoot, resolvedId), resolvedId }; + } + if (!requestedId) return { asyncDir: null, resultPath: null }; + + const directAsyncDir = path.join(asyncRoot, requestedId); + assertInsideRoot(asyncRoot, directAsyncDir, "Async run directory"); + const directResultPath = exactResultPath(resultRoot, requestedId); + if (fs.existsSync(directAsyncDir) || directResultPath) { + return { + asyncDir: fs.existsSync(directAsyncDir) ? directAsyncDir : null, + resultPath: directResultPath, + resolvedId: requestedId + }; + } + + const matchingIds = [...new Set([ + ...prefixedRunIds(asyncRoot, requestedId), + ...prefixedRunIds(resultRoot, requestedId, ".json")] + )].sort(); + if (matchingIds.length === 0) return { asyncDir: null, resultPath: null, resolvedId: requestedId }; + if (matchingIds.length > 1) { + throw new Error(`Ambiguous async run id prefix '${requestedId}' matched: ${matchingIds.join(", ")}. Provide a longer id.`); + } + const resolvedId = matchingIds[0]; + const asyncDir = path.join(asyncRoot, resolvedId); + assertInsideRoot(asyncRoot, asyncDir, "Async run directory"); + return { + asyncDir: fs.existsSync(asyncDir) ? asyncDir : null, + resultPath: exactResultPath(resultRoot, resolvedId), + resolvedId + }; +} + +function resultState(result) { + if (result.state === "complete" || result.state === "failed" || result.state === "paused" || result.state === "running" || result.state === "queued") { + return result.state; + } + return result.success ? "complete" : "failed"; +} + +function validateStatusForResume(status, source) { + if (!status) return; + if (typeof status.runId !== "string") throw new Error(`Invalid async status '${source}': runId must be a string.`); + if (status.sessionId !== undefined && typeof status.sessionId !== "string") throw new Error(`Invalid async status '${source}': sessionId must be a string.`); + if (status.cwd !== undefined && typeof status.cwd !== "string") throw new Error(`Invalid async status '${source}': cwd must be a string.`); + if (status.sessionFile !== undefined && typeof status.sessionFile !== "string") throw new Error(`Invalid async status '${source}': sessionFile must be a string.`); + if (status.steps !== undefined) { + if (!Array.isArray(status.steps)) throw new Error(`Invalid async status '${source}': steps must be an array.`); + status.steps.forEach((step, index) => { + if (!step || typeof step !== "object" || Array.isArray(step)) throw new Error(`Invalid async status '${source}': steps[${index}] must be an object.`); + if (typeof step.agent !== "string") throw new Error(`Invalid async status '${source}': steps[${index}].agent must be a string.`); + if (step.sessionFile !== undefined && typeof step.sessionFile !== "string") throw new Error(`Invalid async status '${source}': steps[${index}].sessionFile must be a string.`); + }); + } +} + +function validateResumeSessionFile(runId, sessionFile) { + if (path.extname(sessionFile) !== ".jsonl") throw new Error(`Async run '${runId}' session file must be a .jsonl file: ${sessionFile}`); + const resolved = path.resolve(sessionFile); + if (!fs.existsSync(resolved)) throw new Error(`Async run '${runId}' session file does not exist: ${sessionFile}`); + return resolved; +} + +function resolveAsyncResumeTarget(params, deps = {}) { + const asyncDirRoot = deps.asyncDirRoot ?? _types.ASYNC_DIR; + const resultsDir = deps.resultsDir ?? _types.RESULTS_DIR; + const location = resolveAsyncRunLocation(params, asyncDirRoot, resultsDir); + if (!location.asyncDir && !location.resultPath) { + throw new Error("Async run not found. Provide id or dir."); + } + + const reconciliation = location.asyncDir ? + (0, _staleRunReconciler.reconcileAsyncRun)(location.asyncDir, { resultsDir, kill: deps.kill, now: deps.now }) : + undefined; + const status = reconciliation?.status ?? null; + validateStatusForResume(status, location.asyncDir ? path.join(location.asyncDir, "status.json") : "status.json"); + const result = location.resultPath ? readResultFile(location.resultPath) : undefined; + const runId = status?.runId ?? result?.runId ?? result?.id ?? location.resolvedId ?? (location.asyncDir ? path.basename(location.asyncDir) : "unknown"); + const state = status?.state ?? (result ? resultState(result) : undefined); + if (!state) throw new Error(`Status file not found for async run '${runId}'.`); + + const statusSteps = status?.steps ?? []; + const resultSteps = result?.results ?? []; + const stepCount = statusSteps.length || resultSteps.length || (result?.agent ? 1 : 0); + const requestedIndex = params.index; + if (requestedIndex !== undefined && !Number.isInteger(requestedIndex)) throw new Error(`Async run '${runId}' index must be an integer.`); + const terminalStepStatuses = new Set(["complete", "completed", "failed", "paused"]); + + if (state === "running") { + if (requestedIndex !== undefined) { + if (requestedIndex < 0 || requestedIndex >= stepCount) throw new Error(`Async run '${runId}' has ${stepCount} children. Index ${requestedIndex} is out of range.`); + const selectedStep = statusSteps[requestedIndex]; + if (selectedStep?.status === "running") { + return { + kind: "live", + runId, + asyncDir: location.asyncDir ?? undefined, + state, + agent: selectedStep.agent, + index: requestedIndex, + intercomTarget: (0, _intercomBridge.resolveSubagentIntercomTarget)(runId, selectedStep.agent, requestedIndex), + cwd: status?.cwd ?? result?.cwd, + sessionFile: selectedStep.sessionFile ?? status?.sessionFile ?? result?.sessionFile + }; + } + if (selectedStep?.status === "pending") throw new Error(`Async run '${runId}' child ${requestedIndex} is pending and has not started yet. Wait for it to run or complete before resuming.`); + if (selectedStep && !terminalStepStatuses.has(selectedStep.status)) throw new Error(`Async run '${runId}' child ${requestedIndex} is ${selectedStep.status} and cannot be revived yet.`); + } else { + const running = statusSteps. + map((step, index) => ({ step, index })). + filter(({ step }) => step.status === "running"); + const selected = running.length === 1 ? running[0] : undefined; + if (!selected) { + throw new Error(`Async run '${runId}' has ${running.length} running children. Provide index to choose one.`); + } + return { + kind: "live", + runId, + asyncDir: location.asyncDir ?? undefined, + state, + agent: selected.step.agent, + index: selected.index, + intercomTarget: (0, _intercomBridge.resolveSubagentIntercomTarget)(runId, selected.step.agent, selected.index), + cwd: status?.cwd ?? result?.cwd, + sessionFile: selected.step.sessionFile ?? status?.sessionFile ?? result?.sessionFile + }; + } + } + + if (stepCount > 1 && requestedIndex === undefined) { + throw new Error(`Async run '${runId}' has ${stepCount} children. Provide index to choose one.`); + } + const index = requestedIndex ?? 0; + if (!Number.isInteger(index)) throw new Error(`Async run '${runId}' index must be an integer.`); + if (index < 0 || index >= stepCount) throw new Error(`Async run '${runId}' has ${stepCount} children. Index ${index} is out of range.`); + const agent = statusSteps[index]?.agent ?? resultSteps[index]?.agent ?? result?.agent; + if (!agent) throw new Error(`Could not determine child agent for async run '${runId}'.`); + const sessionFile = statusSteps[index]?.sessionFile ?? + resultSteps[index]?.sessionFile ?? ( + stepCount === 1 ? status?.sessionFile ?? result?.sessionFile : undefined); + if (!sessionFile) throw new Error(`Async run '${runId}' child ${index} does not have a persisted session file to resume from.`); + const resolvedSessionFile = validateResumeSessionFile(runId, sessionFile); + + return { + kind: "revive", + runId, + asyncDir: location.asyncDir ?? undefined, + state, + agent, + index, + intercomTarget: (0, _intercomBridge.resolveSubagentIntercomTarget)(runId, agent, index), + cwd: status?.cwd ?? result?.cwd, + sessionFile: resolvedSessionFile + }; +} + +function buildRevivedAsyncTask(target, message) { + return [ + "You are reviving a previous subagent conversation.", + "", + `Original run: ${target.runId}`, + `Original agent: ${target.agent}`, + target.sessionFile ? `Original session file: ${target.sessionFile}` : undefined, + "", + "Use the stored session context as background. Answer the orchestrator's follow-up below. Do not assume the original child process is still alive.", + "", + "Follow-up:", + message]. + filter((line) => line !== undefined).join("\n"); +} /* v9-4bfa08afb714d9c3 */ diff --git a/pip-tmp/jiti/background-async-status.51801569.mjs b/pip-tmp/jiti/background-async-status.51801569.mjs new file mode 100644 index 0000000000000000000000000000000000000000..06de625cb5b29e6daf9e29a0fb105edbeb097cfe --- /dev/null +++ b/pip-tmp/jiti/background-async-status.51801569.mjs @@ -0,0 +1,292 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.formatAsyncRunList = formatAsyncRunList;exports.formatAsyncRunOutputPath = formatAsyncRunOutputPath;exports.formatAsyncRunProgressLabel = formatAsyncRunProgressLabel;exports.listAsyncRuns = listAsyncRuns;var fs = _interopRequireWildcard(await jitiImport("node:fs")); +var path = _interopRequireWildcard(await jitiImport("node:path")); +var _formatters = await jitiImport("../../shared/formatters.ts"); +var _statusFormat = await jitiImport("../../shared/status-format.ts"); + +var _utils = await jitiImport("../../shared/utils.ts"); +var _parallelGroups = await jitiImport("./parallel-groups.ts"); +var _staleRunReconciler = await jitiImport("./stale-run-reconciler.ts");function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +function getErrorMessage(error) { + return error instanceof Error ? error.message : String(error); +} + +function isNotFoundError(error) { + return typeof error === "object" && + error !== null && + "code" in error && + error.code === "ENOENT"; +} + +function isAsyncRunDir(root, entry) { + const entryPath = path.join(root, entry); + try { + return fs.statSync(entryPath).isDirectory(); + } catch (error) { + if (isNotFoundError(error)) return false; + throw new Error(`Failed to inspect async run path '${entryPath}': ${getErrorMessage(error)}`, { + cause: error instanceof Error ? error : undefined + }); + } +} + +function outputFileMtime(outputFile) { + if (!outputFile) return undefined; + try { + return fs.statSync(outputFile).mtimeMs; + } catch (error) { + if (isNotFoundError(error)) return undefined; + throw new Error(`Failed to inspect async output file '${outputFile}': ${getErrorMessage(error)}`, { + cause: error instanceof Error ? error : undefined + }); + } +} + +function deriveAsyncActivityState(asyncDir, status) { + if (status.state !== "running") return { activityState: status.activityState, lastActivityAt: status.lastActivityAt }; + const outputPath = status.outputFile ? path.isAbsolute(status.outputFile) ? status.outputFile : path.join(asyncDir, status.outputFile) : undefined; + const currentStep = typeof status.currentStep === "number" ? status.steps?.[status.currentStep] : undefined; + return { + activityState: status.activityState, + lastActivityAt: status.lastActivityAt ?? outputFileMtime(outputPath) ?? currentStep?.lastActivityAt ?? currentStep?.startedAt ?? status.startedAt + }; +} + +function statusToSummary(asyncDir, status) { + if (status.sessionId !== undefined && typeof status.sessionId !== "string") { + throw new Error(`Invalid async status '${path.join(asyncDir, "status.json")}': sessionId must be a string.`); + } + const { activityState, lastActivityAt } = deriveAsyncActivityState(asyncDir, status); + const steps = status.steps ?? []; + const chainStepCount = status.chainStepCount ?? steps.length; + const parallelGroups = (0, _parallelGroups.normalizeParallelGroups)(status.parallelGroups, steps.length, chainStepCount); + return { + id: status.runId || path.basename(asyncDir), + asyncDir, + ...(status.sessionId ? { sessionId: status.sessionId } : {}), + state: status.state, + activityState, + lastActivityAt, + currentTool: status.currentTool, + currentToolStartedAt: status.currentToolStartedAt, + currentPath: status.currentPath, + turnCount: status.turnCount, + toolCount: status.toolCount, + mode: status.mode, + cwd: status.cwd, + startedAt: status.startedAt, + lastUpdate: status.lastUpdate, + endedAt: status.endedAt, + currentStep: status.currentStep, + ...(status.chainStepCount !== undefined ? { chainStepCount: status.chainStepCount } : {}), + ...(parallelGroups.length ? { parallelGroups } : {}), + steps: steps.map((step, index) => { + const stepActivityState = step.activityState; + const stepLastActivityAt = step.lastActivityAt; + return { + index, + agent: step.agent, + status: step.status, + ...(stepActivityState ? { activityState: stepActivityState } : {}), + ...(stepLastActivityAt ? { lastActivityAt: stepLastActivityAt } : {}), + ...(step.currentTool ? { currentTool: step.currentTool } : {}), + ...(step.currentToolArgs ? { currentToolArgs: step.currentToolArgs } : {}), + ...(step.currentToolStartedAt ? { currentToolStartedAt: step.currentToolStartedAt } : {}), + ...(step.currentPath ? { currentPath: step.currentPath } : {}), + ...(step.recentTools ? { recentTools: step.recentTools.map((tool) => ({ ...tool })) } : {}), + ...(step.recentOutput ? { recentOutput: [...step.recentOutput] } : {}), + ...(step.turnCount !== undefined ? { turnCount: step.turnCount } : {}), + ...(step.toolCount !== undefined ? { toolCount: step.toolCount } : {}), + ...(step.durationMs !== undefined ? { durationMs: step.durationMs } : {}), + ...(step.tokens ? { tokens: step.tokens } : {}), + ...(step.skills ? { skills: step.skills } : {}), + ...(step.model ? { model: step.model } : {}), + ...(step.attemptedModels ? { attemptedModels: step.attemptedModels } : {}), + ...(step.error ? { error: step.error } : {}) + }; + }), + ...(status.sessionDir ? { sessionDir: status.sessionDir } : {}), + ...(status.outputFile ? { outputFile: status.outputFile } : {}), + ...(status.totalTokens ? { totalTokens: status.totalTokens } : {}), + ...(status.sessionFile ? { sessionFile: status.sessionFile } : {}) + }; +} + +function sortRuns(runs) { + const rank = (state) => { + switch (state) { + case "running":return 0; + case "queued":return 1; + case "failed":return 2; + case "paused":return 2; + case "complete":return 3; + } + }; + return [...runs].sort((a, b) => { + const byState = rank(a.state) - rank(b.state); + if (byState !== 0) return byState; + const aTime = a.lastUpdate ?? a.endedAt ?? a.startedAt; + const bTime = b.lastUpdate ?? b.endedAt ?? b.startedAt; + return bTime - aTime; + }); +} + +function listAsyncRuns(asyncDirRoot, options = {}) { + let entries; + try { + entries = fs.readdirSync(asyncDirRoot).filter((entry) => isAsyncRunDir(asyncDirRoot, entry)); + } catch (error) { + if (isNotFoundError(error)) return []; + throw new Error(`Failed to list async runs in '${asyncDirRoot}': ${getErrorMessage(error)}`, { + cause: error instanceof Error ? error : undefined + }); + } + + const allowedStates = options.states ? new Set(options.states) : undefined; + const runs = []; + for (const entry of entries) { + const asyncDir = path.join(asyncDirRoot, entry); + const reconciliation = options.reconcile === false ? + undefined : + (0, _staleRunReconciler.reconcileAsyncRun)(asyncDir, { resultsDir: options.resultsDir, kill: options.kill, now: options.now }); + const status = reconciliation?.status ?? (0, _utils.readStatus)(asyncDir); + if (!status) continue; + const summary = statusToSummary(asyncDir, status); + if (allowedStates && !allowedStates.has(summary.state)) continue; + if (options.sessionId && summary.sessionId !== options.sessionId) continue; + runs.push(summary); + } + + const sorted = sortRuns(runs); + return options.limit !== undefined ? sorted.slice(0, options.limit) : sorted; +} + +function formatActivityFacts(input) { + const facts = []; + if (input.currentTool && input.currentToolStartedAt !== undefined) facts.push(`tool ${input.currentTool} ${(0, _formatters.formatDuration)(Math.max(0, Date.now() - input.currentToolStartedAt))}`);else + if (input.currentTool) facts.push(`tool ${input.currentTool}`); + if (input.currentPath) facts.push((0, _formatters.shortenPath)(input.currentPath)); + if (input.turnCount !== undefined) facts.push(`${input.turnCount} turns`); + if (input.toolCount !== undefined) facts.push(`${input.toolCount} tools`); + const activity = (0, _statusFormat.formatActivityLabel)(input.lastActivityAt, input.activityState); + return activity || facts.length ? [activity, ...facts].filter(Boolean).join(" | ") : undefined; +} + +function formatStepLine(step) { + const parts = [`${step.index + 1}. ${step.agent}`, step.status]; + const activity = formatActivityFacts(step); + if (activity) parts.push(activity); + if (step.model) parts.push(step.model); + if (step.durationMs !== undefined) parts.push((0, _formatters.formatDuration)(step.durationMs)); + if (step.tokens) parts.push(`${(0, _formatters.formatTokens)(step.tokens.total)} tok`); + return parts.join(" | "); +} + +function formatAsyncRunOutputPath(run) { + if (!run.outputFile) return undefined; + return path.isAbsolute(run.outputFile) ? run.outputFile : path.join(run.asyncDir, run.outputFile); +} + +function formatAsyncRunProgressLabel(run) { + const stepCount = run.steps.length || 1; + const chainStepCount = run.chainStepCount ?? stepCount; + const groups = (0, _parallelGroups.normalizeParallelGroups)(run.parallelGroups, run.steps.length, chainStepCount); + const activeGroup = run.currentStep !== undefined ? + groups.find((group) => run.currentStep >= group.start && run.currentStep < group.start + group.count) : + undefined; + if (activeGroup) { + const groupSteps = run.steps.slice(activeGroup.start, activeGroup.start + activeGroup.count); + const groupLabel = (0, _statusFormat.formatParallelOutcome)(groupSteps, activeGroup.count, { showRunning: run.state === "running" }); + if (run.mode === "parallel") return groupLabel; + return `step ${activeGroup.stepIndex + 1}/${chainStepCount} · parallel group: ${groupLabel}`; + } + if (run.mode === "parallel") return (0, _statusFormat.formatParallelOutcome)(run.steps, stepCount, { showRunning: run.state === "running" }); + if (run.mode === "chain" && run.currentStep !== undefined && groups.length > 0) { + const logicalStep = (0, _parallelGroups.flatToLogicalStepIndex)(run.currentStep, chainStepCount, groups); + return `step ${logicalStep + 1}/${chainStepCount}`; + } + return run.currentStep !== undefined ? `step ${run.currentStep + 1}/${stepCount}` : `steps ${stepCount}`; +} + +function formatRunHeader(run) { + const stepLabel = formatAsyncRunProgressLabel(run); + const cwd = run.cwd ? (0, _formatters.shortenPath)(run.cwd) : (0, _formatters.shortenPath)(run.asyncDir); + const activity = formatActivityFacts(run); + return `${run.id} | ${run.state}${activity ? ` | ${activity}` : ""} | ${run.mode} | ${stepLabel} | ${cwd}`; +} + +function formatAsyncRunList(runs, heading = "Active async runs") { + if (runs.length === 0) return `No ${heading.toLowerCase()}.`; + + const lines = [`${heading}: ${runs.length}`, ""]; + for (const run of runs) { + lines.push(`- ${formatRunHeader(run)}`); + for (const step of run.steps) { + lines.push(` ${formatStepLine(step)}`); + } + const outputPath = formatAsyncRunOutputPath(run); + if (outputPath) lines.push(` output: ${(0, _formatters.shortenPath)(outputPath)}`); + if (run.sessionFile) lines.push(` session: ${(0, _formatters.shortenPath)(run.sessionFile)}`); + lines.push(""); + } + return lines.join("\n").trimEnd(); +} /* v9-c2a3317ae1027d19 */ diff --git a/pip-tmp/jiti/background-completion-dedupe.b4a5ebb9.mjs b/pip-tmp/jiti/background-completion-dedupe.b4a5ebb9.mjs new file mode 100644 index 0000000000000000000000000000000000000000..fc080c3b77efe92edd334620c7d88c5b96b40613 --- /dev/null +++ b/pip-tmp/jiti/background-completion-dedupe.b4a5ebb9.mjs @@ -0,0 +1,63 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.buildCompletionKey = buildCompletionKey;exports.getGlobalSeenMap = getGlobalSeenMap;exports.markSeenWithTtl = markSeenWithTtl; + + + + + + + + + +function asNonEmptyString(value) { + if (typeof value !== "string") return undefined; + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : undefined; +} + +function asFiniteNumber(value) { + if (typeof value !== "number") return undefined; + return Number.isFinite(value) ? value : undefined; +} + +function buildCompletionKey(data, fallback) { + const id = asNonEmptyString(data.id); + if (id) return `id:${id}`; + const sessionId = asNonEmptyString(data.sessionId) ?? "no-session"; + const agent = asNonEmptyString(data.agent) ?? "unknown"; + const timestamp = asFiniteNumber(data.timestamp); + const taskIndex = asFiniteNumber(data.taskIndex); + const totalTasks = asFiniteNumber(data.totalTasks); + const success = typeof data.success === "boolean" ? data.success ? "1" : "0" : "?"; + return [ + "meta", + sessionId, + agent, + timestamp !== undefined ? String(timestamp) : "no-ts", + taskIndex !== undefined ? String(taskIndex) : "-", + totalTasks !== undefined ? String(totalTasks) : "-", + success, + fallback]. + join(":"); +} + +function pruneSeenMap(seen, now, ttlMs) { + for (const [key, ts] of seen.entries()) { + if (now - ts > ttlMs) seen.delete(key); + } +} + +function markSeenWithTtl(seen, key, now, ttlMs) { + pruneSeenMap(seen, now, ttlMs); + if (seen.has(key)) return true; + seen.set(key, now); + return false; +} + +function getGlobalSeenMap(storeKey) { + const globalStore = globalThis; + const existing = globalStore[storeKey]; + if (existing instanceof Map) return existing; + const map = new Map(); + globalStore[storeKey] = map; + return map; +} /* v9-eb576fab2c31b37a */ diff --git a/pip-tmp/jiti/background-notify.20a22f54.mjs b/pip-tmp/jiti/background-notify.20a22f54.mjs new file mode 100644 index 0000000000000000000000000000000000000000..c716f83fbae1a3b100e14aae6c0777dde4c5c9ad --- /dev/null +++ b/pip-tmp/jiti/background-notify.20a22f54.mjs @@ -0,0 +1,108 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.default = registerSubagentNotify; + + + + +var _completionDedupe = await jitiImport("./completion-dedupe.ts"); +var _types = await jitiImport("../../shared/types.ts"); /** + * Subagent completion notifications. + */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +function registerSubagentNotify(pi) { + const unsubscribeStoreKey = "__pi_subagents_notify_unsubscribe__"; + const globalStore = globalThis; + const previousUnsubscribe = globalStore[unsubscribeStoreKey]; + if (typeof previousUnsubscribe === "function") { + try { + previousUnsubscribe(); + } catch { + + // Best effort cleanup for stale handlers from an older reload. + }} + + const seen = (0, _completionDedupe.getGlobalSeenMap)("__pi_subagents_notify_seen__"); + const ttlMs = 10 * 60 * 1000; + + const handleComplete = (data) => { + const result = data; + const now = Date.now(); + const key = (0, _completionDedupe.buildCompletionKey)(result, "notify"); + if ((0, _completionDedupe.markSeenWithTtl)(seen, key, now, ttlMs)) return; + + const agent = result.agent ?? "unknown"; + const summary = typeof result.summary === "string" ? result.summary : ""; + const paused = !result.success && ( + result.exitCode === 0 || + result.state === "paused" || + summary.startsWith("Paused after interrupt.")); + + const status = paused ? "paused" : result.success ? "completed" : "failed"; + + const taskInfo = + result.taskIndex !== undefined && result.totalTasks !== undefined ? + ` (${result.taskIndex + 1}/${result.totalTasks})` : + ""; + + const sessionLine = result.shareUrl ? + `Session: ${result.shareUrl}` : + result.shareError ? + `Session share error: ${result.shareError}` : + result.sessionFile ? + `Session file: ${result.sessionFile}` : + undefined; + + const displaySummary = summary.trim() ? summary : "(no output)"; + const content = [ + `Background task ${status}: **${agent}**${taskInfo}`, + "", + displaySummary, + sessionLine ? "" : undefined, + sessionLine]. + + filter((line) => line !== undefined). + join("\n"); + + pi.sendMessage( + { + customType: "subagent-notify", + content, + display: true + }, + { triggerTurn: true } + ); + }; + + globalStore[unsubscribeStoreKey] = pi.events.on(_types.SUBAGENT_ASYNC_COMPLETE_EVENT, handleComplete); +} /* v9-d92e3a54b93efe80 */ diff --git a/pip-tmp/jiti/background-parallel-groups.e70893a1.mjs b/pip-tmp/jiti/background-parallel-groups.e70893a1.mjs new file mode 100644 index 0000000000000000000000000000000000000000..96e3d7b83834b475a514104a8c686aec25772b41 --- /dev/null +++ b/pip-tmp/jiti/background-parallel-groups.e70893a1.mjs @@ -0,0 +1,45 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.flatToLogicalStepIndex = flatToLogicalStepIndex;exports.normalizeParallelGroups = normalizeParallelGroups; + +function isValidParallelGroup(group, stepCount, chainStepCount) { + if (typeof group !== "object" || group === null) return false; + const { start, count, stepIndex } = group; + return typeof start === "number" && + typeof count === "number" && + typeof stepIndex === "number" && + Number.isInteger(start) && + Number.isInteger(count) && + Number.isInteger(stepIndex) && + start >= 0 && + count > 0 && + stepIndex >= 0 && + stepIndex < chainStepCount && + start + count <= stepCount; +} + +function normalizeParallelGroups(groups, stepCount, chainStepCount) { + if (!Array.isArray(groups)) return []; + return groups. + filter((group) => isValidParallelGroup(group, stepCount, chainStepCount)). + sort((left, right) => left.stepIndex - right.stepIndex || left.start - right.start); +} + +function flatToLogicalStepIndex(flatIndex, chainStepCount, groups) { + let logicalIndex = 0; + let cursor = 0; + for (const group of groups) { + while (cursor < group.start && logicalIndex < chainStepCount) { + if (cursor === flatIndex) return logicalIndex; + cursor++; + logicalIndex++; + } + if (flatIndex >= group.start && flatIndex < group.start + group.count) return group.stepIndex; + cursor = group.start + group.count; + logicalIndex = group.stepIndex + 1; + } + while (cursor <= flatIndex && logicalIndex < chainStepCount) { + if (cursor === flatIndex) return logicalIndex; + cursor++; + logicalIndex++; + } + return Math.max(0, chainStepCount - 1); +} /* v9-bcbcba92133f8cc7 */ diff --git a/pip-tmp/jiti/background-result-watcher.056baed0.mjs b/pip-tmp/jiti/background-result-watcher.056baed0.mjs new file mode 100644 index 0000000000000000000000000000000000000000..498b761b687332608e60ca725e03dd4339042467 --- /dev/null +++ b/pip-tmp/jiti/background-result-watcher.056baed0.mjs @@ -0,0 +1,250 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.createResultWatcher = createResultWatcher;var fs = _interopRequireWildcard(await jitiImport("node:fs")); +var path = _interopRequireWildcard(await jitiImport("node:path")); +var _completionDedupe = await jitiImport("./completion-dedupe.ts"); +var _fileCoalescer = await jitiImport("../../shared/file-coalescer.ts"); +var _types = await jitiImport("../../shared/types.ts"); + + + + +var _resultIntercom = await jitiImport("../../intercom/result-intercom.ts");function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);} + + + + + +const WATCHER_RESTART_DELAY_MS = 3000; +const POLL_INTERVAL_MS = 3000; + + + + + + + + + + + + + + + +function getErrorCode(error) { + return typeof error === "object" && error !== null && "code" in error ? + error.code : + undefined; +} + +function isNotFoundError(error) { + return getErrorCode(error) === "ENOENT"; +} + +function shouldFallBackToPolling(error) { + const code = getErrorCode(error); + return code === "EMFILE" || code === "ENOSPC"; +} + +function createResultWatcher( +pi, +state, +resultsDir, +completionTtlMs, +deps = {}) + + + + +{ + const fsApi = deps.fs ?? fs; + const timers = deps.timers ?? { setTimeout, clearTimeout, setInterval, clearInterval }; + + const handleResult = async (file) => { + const resultPath = path.join(resultsDir, file); + if (!fsApi.existsSync(resultPath)) return; + try { + const data = JSON.parse(fsApi.readFileSync(resultPath, "utf-8")); + + + + + + + + + + + + + + + + + + + + + + + if (data.sessionId && data.sessionId !== state.currentSessionId) return; + if (!data.sessionId && data.cwd && data.cwd !== state.baseCwd) return; + + const now = Date.now(); + const completionKey = (0, _completionDedupe.buildCompletionKey)(data, `result:${file}`); + if ((0, _completionDedupe.markSeenWithTtl)(state.completionSeen, completionKey, now, completionTtlMs)) { + fsApi.unlinkSync(resultPath); + return; + } + + const intercomTarget = data.intercomTarget?.trim(); + if (intercomTarget) { + const childResults = Array.isArray(data.results) && data.results.length > 0 ? + data.results : + [{ + agent: data.agent, + output: data.summary, + success: data.success + }]; + const runId = data.runId ?? data.id ?? file.replace(/\.json$/i, ""); + const mode = data.mode === "single" || data.mode === "parallel" || data.mode === "chain" ? + data.mode : + childResults.length > 1 ? "chain" : "single"; + const payload = (0, _resultIntercom.buildSubagentResultIntercomPayload)({ + to: intercomTarget, + runId, + mode, + source: "async", + children: childResults.map((result = {}, index) => { + const baseOutput = result.output ?? data.summary; + const hasRealOutput = typeof baseOutput === "string" && baseOutput.trim().length > 0; + const output = hasRealOutput ? baseOutput : "(no output)"; + const summary = result.success === false && result.error ? + `${result.error}${hasRealOutput ? `\n\nOutput:\n${baseOutput}` : ""}` : + output; + const sessionPath = result.sessionFile ?? (childResults.length === 1 ? data.sessionFile : undefined); + return { + agent: result.agent ?? data.agent ?? `step-${index + 1}`, + status: (0, _resultIntercom.resolveSubagentResultStatus)({ + success: result.success, + state: data.state === "paused" || typeof result.success !== "boolean" ? data.state : undefined + }), + summary, + index, + artifactPath: result.artifactPaths?.outputPath, + ...(typeof sessionPath === "string" && fsApi.existsSync(sessionPath) ? { sessionPath } : {}), + intercomTarget: result.intercomTarget + }; + }), + asyncId: data.id, + asyncDir: data.asyncDir + }); + const delivered = await (0, _resultIntercom.deliverSubagentResultIntercomEvent)(pi.events, payload); + if (!delivered) { + console.error(`Subagent async grouped result intercom delivery was not acknowledged for '${resultPath}'.`); + } + } + + pi.events.emit(_types.SUBAGENT_ASYNC_COMPLETE_EVENT, data); + fsApi.unlinkSync(resultPath); + } catch (error) { + if (isNotFoundError(error)) return; + console.error(`Failed to process subagent result file '${resultPath}':`, error); + } + }; + + state.resultFileCoalescer = (0, _fileCoalescer.createFileCoalescer)((file) => { + void handleResult(file); + }, 50); + + const primeExistingResults = () => { + try { + fsApi.readdirSync(resultsDir). + filter((f) => f.endsWith(".json")). + forEach((file) => state.resultFileCoalescer.schedule(file, 0)); + } catch (error) { + if (isNotFoundError(error)) return; + console.error(`Failed to scan subagent result directory '${resultsDir}':`, error); + } + }; + + const startPollingFallback = (reason) => { + state.watcher?.close(); + state.watcher = null; + if (state.watcherRestartTimer) return; + + console.error( + `Subagent result watcher for '${resultsDir}' fell back to polling because native fs.watch is unavailable (${getErrorCode(reason) ?? "unknown error"}).` + ); + primeExistingResults(); + state.watcherRestartTimer = timers.setInterval(primeExistingResults, POLL_INTERVAL_MS); + state.watcherRestartTimer.unref?.(); + }; + + const scheduleRestart = () => { + if (state.watcherRestartTimer) return; + state.watcherRestartTimer = timers.setTimeout(() => { + state.watcherRestartTimer = null; + try { + fsApi.mkdirSync(resultsDir, { recursive: true }); + startResultWatcher(); + } catch (error) { + if (shouldFallBackToPolling(error)) { + startPollingFallback(error); + return; + } + console.error(`Failed to restart subagent result watcher for '${resultsDir}':`, error); + scheduleRestart(); + } + }, WATCHER_RESTART_DELAY_MS); + state.watcherRestartTimer.unref?.(); + }; + + const startResultWatcher = () => { + if (state.watcher) return; + if (state.watcherRestartTimer) { + timers.clearTimeout(state.watcherRestartTimer); + timers.clearInterval(state.watcherRestartTimer); + state.watcherRestartTimer = null; + } + try { + state.watcher = fsApi.watch(resultsDir, (ev, file) => { + if (ev !== "rename" || !file) return; + const fileName = file.toString(); + if (!fileName.endsWith(".json")) return; + state.resultFileCoalescer.schedule(fileName); + }); + state.watcher.on("error", (error) => { + if (shouldFallBackToPolling(error)) { + startPollingFallback(error); + return; + } + console.error(`Subagent result watcher failed for '${resultsDir}':`, error); + state.watcher?.close(); + state.watcher = null; + scheduleRestart(); + }); + state.watcher.unref?.(); + } catch (error) { + if (shouldFallBackToPolling(error)) { + startPollingFallback(error); + return; + } + console.error(`Failed to start subagent result watcher for '${resultsDir}':`, error); + state.watcher = null; + scheduleRestart(); + } + }; + + const stopResultWatcher = () => { + state.watcher?.close(); + state.watcher = null; + if (state.watcherRestartTimer) { + timers.clearTimeout(state.watcherRestartTimer); + timers.clearInterval(state.watcherRestartTimer); + } + state.watcherRestartTimer = null; + state.resultFileCoalescer.clear(); + }; + + return { startResultWatcher, primeExistingResults, stopResultWatcher }; +} /* v9-43f45ffab2803705 */ diff --git a/pip-tmp/jiti/background-run-status.37d535df.mjs b/pip-tmp/jiti/background-run-status.37d535df.mjs new file mode 100644 index 0000000000000000000000000000000000000000..dcd2ed229ff2214a197781a25ad304c4ac97a705 --- /dev/null +++ b/pip-tmp/jiti/background-run-status.37d535df.mjs @@ -0,0 +1,190 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.inspectSubagentStatus = inspectSubagentStatus;var fs = _interopRequireWildcard(await jitiImport("node:fs")); +var path = _interopRequireWildcard(await jitiImport("node:path")); + +var _asyncStatus = await jitiImport("./async-status.ts"); +var _statusFormat = await jitiImport("../../shared/status-format.ts"); +var _types = await jitiImport("../../shared/types.ts"); +var _intercomBridge = await jitiImport("../../intercom/intercom-bridge.ts"); +var _asyncResume = await jitiImport("./async-resume.ts"); +var _parallelGroups = await jitiImport("./parallel-groups.ts"); +var _staleRunReconciler = await jitiImport("./stale-run-reconciler.ts");function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);} + + + + + + + + + + + + + + + +function hasExistingSessionFile(value) { + return typeof value === "string" && fs.existsSync(value); +} + +function formatResumeGuidance(runId, children, fallbackSessionFile) { + const knownChildren = children. + map((child, index) => ({ child, index })). + filter(({ child }) => typeof child.agent === "string"); + if (!runId || knownChildren.length === 0) return "Resume: unavailable; no child session file was persisted."; + const singleSessionFile = knownChildren[0]?.child.sessionFile ?? fallbackSessionFile; + if (children.length === 1 && knownChildren.length === 1 && hasExistingSessionFile(singleSessionFile)) { + return `Revive: subagent({ action: "resume", id: "${runId}", message: "..." })`; + } + const childWithSession = knownChildren.find(({ child }) => hasExistingSessionFile(child.sessionFile)); + if (childWithSession) { + return `Revive child: subagent({ action: "resume", id: "${runId}", index: ${childWithSession.index}, message: "..." })`; + } + return "Resume: unavailable; no child session file was persisted."; +} + +function stepLineLabel(status, index) { + const steps = status.steps ?? []; + if (status.mode === "parallel") return `Agent ${index + 1}/${steps.length || 1}`; + if (status.mode === "chain") { + const chainStepCount = status.chainStepCount ?? (steps.length || 1); + const groups = (0, _parallelGroups.normalizeParallelGroups)(status.parallelGroups, steps.length, chainStepCount); + const group = groups.find((candidate) => index >= candidate.start && index < candidate.start + candidate.count); + if (group) return `Step ${group.stepIndex + 1}/${chainStepCount} Agent ${index - group.start + 1}/${group.count}`; + return `Step ${(0, _parallelGroups.flatToLogicalStepIndex)(index, chainStepCount, groups) + 1}/${chainStepCount}`; + } + return `Step ${index + 1}`; +} + +function inspectSubagentStatus(params, deps = {}) { + const asyncDirRoot = deps.asyncDirRoot ?? _types.ASYNC_DIR; + const resultsDir = deps.resultsDir ?? _types.RESULTS_DIR; + if (!params.id && !params.runId && !params.dir) { + try { + const runs = (0, _asyncStatus.listAsyncRuns)(asyncDirRoot, { states: ["queued", "running"], resultsDir, kill: deps.kill, now: deps.now }); + return { + content: [{ type: "text", text: (0, _asyncStatus.formatAsyncRunList)(runs) }], + details: { mode: "single", results: [] } + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { + content: [{ type: "text", text: message }], + isError: true, + details: { mode: "single", results: [] } + }; + } + } + + let location; + try { + location = (0, _asyncResume.resolveAsyncRunLocation)(params, asyncDirRoot, resultsDir); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { + content: [{ type: "text", text: message }], + isError: true, + details: { mode: "single", results: [] } + }; + } + const { asyncDir, resultPath, resolvedId } = location; + + if (!asyncDir && !resultPath) { + return { + content: [{ type: "text", text: "Async run not found. Provide id or dir." }], + isError: true, + details: { mode: "single", results: [] } + }; + } + + if (asyncDir) { + let reconciliation; + try { + reconciliation = (0, _staleRunReconciler.reconcileAsyncRun)(asyncDir, { resultsDir, kill: deps.kill, now: deps.now }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { + content: [{ type: "text", text: message }], + isError: true, + details: { mode: "single", results: [] } + }; + } + const status = reconciliation.status; + const effectiveRunId = status?.runId ?? resolvedId ?? "unknown"; + const logPath = path.join(asyncDir, `subagent-log-${effectiveRunId}.md`); + const eventsPath = path.join(asyncDir, "events.jsonl"); + if (status) { + const outputPath = (0, _asyncStatus.formatAsyncRunOutputPath)({ asyncDir, outputFile: status.outputFile }); + const progressLabel = (0, _asyncStatus.formatAsyncRunProgressLabel)({ + mode: status.mode, + state: status.state, + currentStep: status.currentStep, + chainStepCount: status.chainStepCount, + parallelGroups: status.parallelGroups, + steps: (status.steps ?? []).map((step, index) => ({ index, agent: step.agent, status: step.status })) + }); + const started = new Date(status.startedAt).toISOString(); + const updated = status.lastUpdate ? new Date(status.lastUpdate).toISOString() : "n/a"; + const statusActivityText = status.state === "running" ? (0, _statusFormat.formatActivityLabel)(status.lastActivityAt, status.activityState) : undefined; + + const lines = [ + `Run: ${status.runId}`, + `State: ${status.state}`, + statusActivityText ? `Activity: ${statusActivityText}` : undefined, + `Mode: ${status.mode}`, + `Progress: ${progressLabel}`, + `Started: ${started}`, + `Updated: ${updated}`, + `Dir: ${asyncDir}`, + outputPath ? `Output: ${outputPath}` : undefined, + reconciliation.message ? `Diagnosis: ${reconciliation.message}` : undefined, + reconciliation.resultPath && fs.existsSync(reconciliation.resultPath) ? `Result: ${reconciliation.resultPath}` : undefined]. + filter((line) => Boolean(line)); + for (const [index, step] of (status.steps ?? []).entries()) { + const stepActivityText = step.status === "running" ? (0, _statusFormat.formatActivityLabel)(step.lastActivityAt, step.activityState) : undefined; + const errorText = step.error ? `, error: ${step.error}` : ""; + lines.push(`${stepLineLabel(status, index)}: ${step.agent} ${step.status}${stepActivityText ? `, ${stepActivityText}` : ""}${errorText}`); + const stepOutputPath = path.join(asyncDir, `output-${index}.log`); + if (stepOutputPath !== outputPath && fs.existsSync(stepOutputPath)) lines.push(` Output: ${stepOutputPath}`); + if (step.status === "running") { + lines.push(` Intercom target: ${(0, _intercomBridge.resolveSubagentIntercomTarget)(status.runId, step.agent, index)} (if registered)`); + } + } + if (status.sessionFile) lines.push(`Session: ${status.sessionFile}`); + if (status.state !== "running") { + lines.push(formatResumeGuidance(status.runId, status.steps ?? [], status.sessionFile)); + } + if (fs.existsSync(logPath)) lines.push(`Log: ${logPath}`); + if (fs.existsSync(eventsPath)) lines.push(`Events: ${eventsPath}`); + + return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "single", results: [] } }; + } + } + + if (resultPath) { + try { + const raw = fs.readFileSync(resultPath, "utf-8"); + const data = JSON.parse(raw); + const status = data.success ? "complete" : data.state === "paused" || data.exitCode === 0 ? "paused" : "failed"; + const runId = data.runId ?? data.id ?? resolvedId; + const lines = [`Run: ${runId}`, `State: ${status}`, `Result: ${resultPath}`]; + const children = Array.isArray(data.results) ? data.results : data.agent ? [{ agent: data.agent, sessionFile: data.sessionFile }] : []; + lines.push(formatResumeGuidance(runId, children, data.sessionFile)); + if (data.summary) lines.push("", data.summary); + return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "single", results: [] } }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { + content: [{ type: "text", text: `Failed to read async result file: ${message}` }], + isError: true, + details: { mode: "single", results: [] } + }; + } + } + + return { + content: [{ type: "text", text: "Status file not found." }], + isError: true, + details: { mode: "single", results: [] } + }; +} /* v9-502343157cae842f */ diff --git a/pip-tmp/jiti/background-stale-run-reconciler.52d9f1fc.mjs b/pip-tmp/jiti/background-stale-run-reconciler.52d9f1fc.mjs new file mode 100644 index 0000000000000000000000000000000000000000..0a1293c525a70efaea4887a816ee890be7e11270 --- /dev/null +++ b/pip-tmp/jiti/background-stale-run-reconciler.52d9f1fc.mjs @@ -0,0 +1,291 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.checkPidLiveness = checkPidLiveness;exports.reconcileAsyncRun = reconcileAsyncRun;var fs = _interopRequireWildcard(await jitiImport("node:fs")); +var path = _interopRequireWildcard(await jitiImport("node:path")); +var _atomicJson = await jitiImport("../../shared/atomic-json.ts"); +var _types = await jitiImport("../../shared/types.ts"); +var _parallelGroups = await jitiImport("./parallel-groups.ts");function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +function getErrorMessage(error) { + return error instanceof Error ? error.message : String(error); +} + +function isNotFoundError(error) { + return typeof error === "object" && + error !== null && + "code" in error && + error.code === "ENOENT"; +} + +function appendJsonl(filePath, payload) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.appendFileSync(filePath, `${JSON.stringify(payload)}\n`, "utf-8"); +} + +function readStatusFile(asyncDir) { + const statusPath = path.join(asyncDir, "status.json"); + let content; + try { + content = fs.readFileSync(statusPath, "utf-8"); + } catch (error) { + if (isNotFoundError(error)) return null; + throw new Error(`Failed to read async status file '${statusPath}': ${getErrorMessage(error)}`, { + cause: error instanceof Error ? error : undefined + }); + } + try { + return JSON.parse(content); + } catch (error) { + throw new Error(`Failed to parse async status file '${statusPath}': ${getErrorMessage(error)}`, { + cause: error instanceof Error ? error : undefined + }); + } +} + + + + + + + + + + + + + + + + +function readResultRepairData(resultPath) { + try { + const data = JSON.parse(fs.readFileSync(resultPath, "utf-8")); + const state = data.success ? "complete" : data.state === "paused" || data.exitCode === 0 ? "paused" : "failed"; + return { state, ...(Array.isArray(data.results) ? { results: data.results } : {}) }; + } catch (error) { + if (isNotFoundError(error)) return undefined; + throw new Error(`Failed to read async result file '${resultPath}': ${getErrorMessage(error)}`, { + cause: error instanceof Error ? error : undefined + }); + } +} + +function childState(overallState, child) { + if (child?.success === true) return "complete"; + if (child?.success === false) return "failed"; + return overallState; +} + +function terminalStatusFromResult(status, resultPath, now) { + const repair = readResultRepairData(resultPath); + if (!repair) return undefined; + const steps = (status.steps ?? []).map((step, index) => { + if (step.status !== "running" && step.status !== "pending") return step; + const child = repair.results?.[index]; + const state = childState(repair.state, child); + return { + ...step, + status: state === "complete" ? "complete" : state, + endedAt: step.endedAt ?? now, + durationMs: step.startedAt !== undefined && step.durationMs === undefined ? Math.max(0, now - step.startedAt) : step.durationMs, + exitCode: step.exitCode ?? (state === "complete" || state === "paused" ? 0 : 1), + error: state === "failed" ? step.error ?? child?.error : step.error, + sessionFile: step.sessionFile ?? child?.sessionFile, + model: step.model ?? child?.model, + attemptedModels: step.attemptedModels ?? child?.attemptedModels, + modelAttempts: step.modelAttempts ?? child?.modelAttempts + }; + }); + return { + ...status, + state: repair.state, + activityState: undefined, + lastUpdate: now, + endedAt: status.endedAt ?? now, + steps + }; +} + +function buildStartedStatus(asyncDir, startedRun, now) { + const startedAt = startedRun.startedAt ?? now; + const agents = startedRun.agents?.length ? startedRun.agents : ["subagent"]; + const chainStepCount = startedRun.chainStepCount; + const parallelGroups = chainStepCount !== undefined ? + (0, _parallelGroups.normalizeParallelGroups)(startedRun.parallelGroups, agents.length, chainStepCount) : + []; + return { + runId: startedRun.runId || path.basename(asyncDir), + ...(startedRun.sessionId ? { sessionId: startedRun.sessionId } : {}), + mode: startedRun.mode ?? "single", + state: "running", + pid: startedRun.pid, + startedAt, + lastUpdate: now, + currentStep: 0, + ...(chainStepCount !== undefined ? { chainStepCount } : {}), + ...(parallelGroups.length ? { parallelGroups } : {}), + steps: agents.map((agent) => ({ + agent, + status: "running", + startedAt + })), + ...(startedRun.sessionFile ? { sessionFile: startedRun.sessionFile } : {}) + }; +} + +function buildFailedRepair(status, asyncDir, now, reason) { + const runId = status.runId || path.basename(asyncDir); + const pid = typeof status.pid === "number" ? status.pid : "unknown"; + const message = reason ?? `Async runner process ${pid} exited or disappeared before writing a result. Marked run failed by stale-run reconciliation.`; + const steps = status.steps?.length ? status.steps : [{ agent: "subagent", status: "running" }]; + const repairedSteps = steps.map((step) => step.status === "running" || step.status === "pending" ? + { + ...step, + status: "failed", + activityState: undefined, + endedAt: step.endedAt ?? now, + durationMs: step.startedAt !== undefined && step.durationMs === undefined ? Math.max(0, now - step.startedAt) : step.durationMs, + exitCode: step.exitCode ?? 1, + error: step.error ?? message + } : + step); + const repairedStatus = { + ...status, + state: "failed", + activityState: undefined, + lastUpdate: now, + endedAt: now, + steps: repairedSteps + }; + const resultAgent = repairedSteps[status.currentStep ?? 0]?.agent ?? repairedSteps[0]?.agent ?? "subagent"; + return { + status: repairedStatus, + message, + result: { + id: runId, + agent: resultAgent, + mode: status.mode, + success: false, + state: "failed", + summary: message, + results: repairedSteps.map((step) => ({ + agent: step.agent, + output: step.status === "complete" || step.status === "completed" ? "" : message, + error: step.status === "complete" || step.status === "completed" ? undefined : step.error ?? message, + success: step.status === "complete" || step.status === "completed", + model: step.model, + attemptedModels: step.attemptedModels, + modelAttempts: step.modelAttempts, + sessionFile: step.sessionFile + })), + exitCode: 1, + timestamp: now, + durationMs: Math.max(0, now - status.startedAt), + asyncDir, + sessionId: status.sessionId, + sessionFile: status.sessionFile + } + }; +} + +function writeFailedRepair(asyncDir, status, resultPath, now, reason) { + const repair = buildFailedRepair(status, asyncDir, now, reason); + (0, _atomicJson.writeAtomicJson)(resultPath, repair.result); + (0, _atomicJson.writeAtomicJson)(path.join(asyncDir, "status.json"), repair.status); + appendJsonl(path.join(asyncDir, "events.jsonl"), { + type: "subagent.run.repaired_stale", + ts: now, + runId: repair.status.runId, + pid: status.pid, + resultPath, + message: repair.message + }); + return { status: repair.status, repaired: true, resultPath, message: repair.message }; +} + +function checkPidLiveness(pid, kill = process.kill) { + try { + kill(pid, 0); + return "alive"; + } catch (error) { + const code = typeof error === "object" && error !== null && "code" in error ? + error.code : + undefined; + if (code === "ESRCH") return "dead"; + if (code === "EPERM") return "unknown"; + return "unknown"; + } +} + +function reconcileAsyncRun(asyncDir, options = {}) { + const now = options.now?.() ?? Date.now(); + const status = readStatusFile(asyncDir); + const startedStatus = !status && options.startedRun ? buildStartedStatus(asyncDir, options.startedRun, now) : undefined; + const effectiveStatus = status ?? startedStatus; + if (!effectiveStatus) return { status: null, repaired: false }; + + const runId = effectiveStatus.runId || path.basename(asyncDir); + const resultPath = path.join(options.resultsDir ?? _types.RESULTS_DIR, `${runId}.json`); + if (fs.existsSync(resultPath)) { + const terminalStatus = effectiveStatus.state === "running" || effectiveStatus.state === "queued" ? + terminalStatusFromResult(effectiveStatus, resultPath, now) : + undefined; + if (terminalStatus) { + (0, _atomicJson.writeAtomicJson)(path.join(asyncDir, "status.json"), terminalStatus); + return { status: terminalStatus, repaired: true, resultPath, message: "Existing async result file was used to repair stale running status." }; + } + return { status: effectiveStatus, repaired: false, resultPath }; + } + + if (effectiveStatus.state !== "running" || typeof effectiveStatus.pid !== "number") { + return { status: status ?? null, repaired: false, resultPath }; + } + + if (!status) { + const startedAt = options.startedRun?.startedAt ?? effectiveStatus.startedAt; + if (now - startedAt < (options.missingStatusGraceMs ?? 1000)) { + return { status: null, repaired: false, resultPath }; + } + } + + const liveness = checkPidLiveness(effectiveStatus.pid, options.kill); + if (liveness !== "dead") { + const staleAfterMs = options.staleAlivePidMs ?? 24 * 60 * 60 * 1000; + const lastUpdate = effectiveStatus.lastUpdate ?? effectiveStatus.startedAt; + if (now - lastUpdate <= staleAfterMs) return { status: status ?? null, repaired: false, resultPath }; + const message = `Async runner process ${effectiveStatus.pid} still has a live PID, but status has not updated for ${now - lastUpdate}ms. Marked run failed by stale-run reconciliation because PID ownership cannot be verified.`; + return writeFailedRepair(asyncDir, effectiveStatus, resultPath, now, message); + } + + return writeFailedRepair(asyncDir, effectiveStatus, resultPath, now); +} /* v9-0a417b7586570d91 */ diff --git a/pip-tmp/jiti/background-top-level-async.2257c9e0.mjs b/pip-tmp/jiti/background-top-level-async.2257c9e0.mjs new file mode 100644 index 0000000000000000000000000000000000000000..01b298c93d09c6c4b6dbc4fd51400c5adcd4f940 --- /dev/null +++ b/pip-tmp/jiti/background-top-level-async.2257c9e0.mjs @@ -0,0 +1,13 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.applyForceTopLevelAsyncOverride = applyForceTopLevelAsyncOverride; + + + + +function applyForceTopLevelAsyncOverride( +params, +depth, +forceTopLevelAsync) +{ + if (!(depth === 0 && forceTopLevelAsync)) return params; + return { ...params, async: true, clarify: false }; +} /* v9-477ca4de2563a7d0 */ diff --git a/pip-tmp/jiti/extension-control-notices.20f5052e.mjs b/pip-tmp/jiti/extension-control-notices.20f5052e.mjs new file mode 100644 index 0000000000000000000000000000000000000000..a739ebece51b8cc91f17304ce4f5546d596d074b --- /dev/null +++ b/pip-tmp/jiti/extension-control-notices.20f5052e.mjs @@ -0,0 +1,92 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.SUBAGENT_CONTROL_MESSAGE_TYPE = void 0;exports.clearPendingForegroundControlNotices = clearPendingForegroundControlNotices;exports.controlNoticeTarget = controlNoticeTarget;exports.formatSubagentControlNotice = formatSubagentControlNotice;exports.handleSubagentControlNotice = handleSubagentControlNotice; +var _subagentControl = await jitiImport("../runs/shared/subagent-control.ts"); + + +const SUBAGENT_CONTROL_MESSAGE_TYPE = exports.SUBAGENT_CONTROL_MESSAGE_TYPE = "subagent_control_notice"; + + + + + + + + + +function controlNoticeTarget(details) { + return details.childIntercomTarget; +} + +function formatSubagentControlNotice(details, content) { + return details.noticeText ?? content ?? (0, _subagentControl.formatControlNoticeMessage)(details.event, controlNoticeTarget(details)); +} + +function noticeTimerKey(details) { + const childIntercomTarget = controlNoticeTarget(details); + return `${details.event.runId}:${(0, _subagentControl.controlNotificationKey)(details.event, childIntercomTarget)}`; +} + +function clearPendingForegroundControlNotices(state, runId) { + const pending = state.pendingForegroundControlNotices; + if (!pending) return; + for (const [key, timer] of pending) { + if (runId !== undefined && !key.startsWith(`${runId}:`)) continue; + clearTimeout(timer); + pending.delete(key); + } +} + +function deliverControlNotice(input) + + + +{ + const childIntercomTarget = controlNoticeTarget(input.details); + const key = (0, _subagentControl.controlNotificationKey)(input.details.event, childIntercomTarget); + if (input.visibleControlNotices.has(key)) return; + input.visibleControlNotices.add(key); + const noticeText = input.details.noticeText ?? (0, _subagentControl.formatControlNoticeMessage)(input.details.event, childIntercomTarget); + input.pi.sendMessage( + { + customType: SUBAGENT_CONTROL_MESSAGE_TYPE, + content: noticeText, + display: true, + details: { ...input.details, childIntercomTarget, noticeText } + }, + { triggerTurn: input.details.source !== "foreground" } + ); +} + +function isForegroundNoticeStillActionable(state, details) { + const control = state.foregroundControls.get(details.event.runId); + if (!control) return false; + if (control.currentAgent && control.currentAgent !== details.event.agent) return false; + if (details.event.index !== undefined && control.currentIndex !== details.event.index) return false; + return control.currentActivityState === "needs_attention"; +} + +function handleSubagentControlNotice(input) + + + + + +{ + if (!input.details?.event || input.details.event.type === "active_long_running") return; + if (input.details.source !== "foreground") { + deliverControlNotice(input); + return; + } + + const pending = input.state.pendingForegroundControlNotices ?? new Map(); + input.state.pendingForegroundControlNotices = pending; + const timerKey = noticeTimerKey(input.details); + const existing = pending.get(timerKey); + if (existing) clearTimeout(existing); + const timer = setTimeout(() => { + pending.delete(timerKey); + if (!isForegroundNoticeStillActionable(input.state, input.details)) return; + deliverControlNotice(input); + }, input.foregroundDelayMs ?? 1000); + timer.unref?.(); + pending.set(timerKey, timer); +} /* v9-d318b884735a249e */ diff --git a/pip-tmp/jiti/extension-doctor.43600346.mjs b/pip-tmp/jiti/extension-doctor.43600346.mjs new file mode 100644 index 0000000000000000000000000000000000000000..f5faded3d1e75360f505cab8eba965c28dbc97d8 --- /dev/null +++ b/pip-tmp/jiti/extension-doctor.43600346.mjs @@ -0,0 +1,199 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.buildDoctorReport = buildDoctorReport;var fs = _interopRequireWildcard(await jitiImport("node:fs")); +var path = _interopRequireWildcard(await jitiImport("node:path")); +var _agents = await jitiImport("../agents/agents.ts"); +var _asyncExecution = await jitiImport("../runs/background/async-execution.ts"); +var _intercomBridge = await jitiImport("../intercom/intercom-bridge.ts"); +var _skills = await jitiImport("../agents/skills.ts"); +var _types = await jitiImport("../shared/types.ts");function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +const DEFAULT_PATHS = { + tempRootDir: _types.TEMP_ROOT_DIR, + asyncDir: _types.ASYNC_DIR, + resultsDir: _types.RESULTS_DIR, + chainRunsDir: _types.CHAIN_RUNS_DIR +}; + +const DEFAULT_DEPS = { + isAsyncAvailable: _asyncExecution.isAsyncAvailable, + discoverAgentsAll: _agents.discoverAgentsAll, + discoverAvailableSkills: _skills.discoverAvailableSkills, + diagnoseIntercomBridge: _intercomBridge.diagnoseIntercomBridge +}; + +function errorText(error) { + return error instanceof Error ? `${error.name}: ${error.message}` : String(error); +} + +function lineFromCheck(label, check) { + try { + return check(); + } catch (error) { + return `- ${label}: failed — ${errorText(error)}`; + } +} + +function formatExistingDirectory(label, dirPath) { + try { + if (!fs.existsSync(dirPath)) return `- ${label}: missing (${dirPath})`; + const stats = fs.statSync(dirPath); + if (!stats.isDirectory()) throw new Error(`not a directory: ${dirPath}`); + fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK); + return `- ${label}: ok (${dirPath})`; + } catch (error) { + return `- ${label}: failed (${dirPath}) — ${errorText(error)}`; + } +} + +function formatSourceCounts(counts) { + return `builtin ${counts.builtin}, user ${counts.user}, project ${counts.project}`; +} + +function formatSkillSourceCounts(skills) { + const counts = new Map(); + for (const skill of skills) counts.set(skill.source, (counts.get(skill.source) ?? 0) + 1); + const ordered = [ + "project", + "project-settings", + "project-package", + "user", + "user-settings", + "user-package", + "extension", + "builtin", + "unknown"]; + + const parts = ordered. + map((source) => `${source} ${counts.get(source) ?? 0}`). + filter((part) => !part.endsWith(" 0")); + return parts.length > 0 ? parts.join(", ") : "none"; +} + +function formatConfiguredSessionDir(input) { + if (input.requestedSessionDir) { + return path.resolve(input.expandTilde?.(input.requestedSessionDir) ?? input.requestedSessionDir); + } + if (input.config.defaultSessionDir) { + return path.resolve(input.expandTilde?.(input.config.defaultSessionDir) ?? input.config.defaultSessionDir); + } + return "not configured"; +} + +function formatSessionLines(input) { + const sessionFile = input.currentSessionFile ?? null; + const lines = [ + lineFromCheck("configured session dir", () => `- configured session dir: ${formatConfiguredSessionDir(input)}`), + `- current session file: ${sessionFile ?? "not available"}`, + `- current session dir: ${sessionFile ? path.dirname(sessionFile) : "not available"}`, + `- current session id: ${input.currentSessionId ?? input.state.currentSessionId ?? "not available"}`]; + + if (input.sessionError) lines.push(`- session manager: failed — ${input.sessionError}`); + return lines; +} + +function formatDiscovery(input, deps) { + return [ + lineFromCheck("agents/chains", () => { + const discovered = deps.discoverAgentsAll(input.cwd); + const agentCounts = { + builtin: discovered.builtin.length, + user: discovered.user.length, + project: discovered.project.length + }; + const chainCounts = discovered.chains.reduce((counts, chain) => { + counts[chain.source] += 1; + return counts; + }, { builtin: 0, user: 0, project: 0 }); + return [ + `- agents: total ${agentCounts.builtin + agentCounts.user + agentCounts.project} (${formatSourceCounts(agentCounts)})`, + `- chains: total ${discovered.chains.length} (${formatSourceCounts(chainCounts)})`]. + join("\n"); + }), + lineFromCheck("skills", () => { + const skills = deps.discoverAvailableSkills(input.cwd); + return `- skills: total ${skills.length} (${formatSkillSourceCounts(skills)})`; + })]; + +} + +function formatIntercomDiagnostic(diagnostic, context) { + const lines = [ + `- bridge: ${diagnostic.active ? "active" : "inactive"}${diagnostic.reason ? ` (${diagnostic.reason})` : ""}`, + `- mode: ${diagnostic.mode}; context: ${context ?? "unspecified"}`, + `- orchestrator target: ${diagnostic.orchestratorTarget ?? "not available"}`, + `- pi-intercom: ${diagnostic.piIntercomAvailable ? "available" : "unavailable"} at ${diagnostic.extensionDir}`]; + + if (diagnostic.configPath && diagnostic.intercomConfigEnabled !== undefined) { + lines.push(`- intercom config: ${diagnostic.intercomConfigEnabled === false ? "disabled" : "enabled or absent"} (${diagnostic.configPath})`); + } + if (diagnostic.intercomConfigError) { + lines.push(`- intercom config warning: ${diagnostic.intercomConfigError}; runtime assumes enabled`); + } + return lines; +} + +function buildDoctorReport(input) { + const paths = input.paths ?? DEFAULT_PATHS; + const deps = { ...DEFAULT_DEPS, ...input.deps }; + const lines = [ + "Subagents doctor report", + "", + "Runtime", + `- cwd: ${input.cwd}`, + lineFromCheck("async support", () => `- async support: ${deps.isAsyncAvailable() ? "available" : "unavailable"}`), + ...formatSessionLines(input), + "", + "Filesystem", + formatExistingDirectory("temp root", paths.tempRootDir), + formatExistingDirectory("async runs", paths.asyncDir), + formatExistingDirectory("results", paths.resultsDir), + formatExistingDirectory("chain runs", paths.chainRunsDir), + "", + "Discovery", + ...formatDiscovery(input, deps), + "", + "Intercom bridge", + ...lineFromCheck("intercom bridge", () => formatIntercomDiagnostic(deps.diagnoseIntercomBridge({ + config: input.config.intercomBridge, + context: input.context, + orchestratorTarget: input.orchestratorTarget, + cwd: input.cwd + }), input.context).join("\n")).split("\n")]; + + return lines.join("\n"); +} /* v9-0e2e3df69035f087 */ diff --git a/pip-tmp/jiti/extension-index.80330c2b.mjs b/pip-tmp/jiti/extension-index.80330c2b.mjs new file mode 100644 index 0000000000000000000000000000000000000000..ab3f58a0d5ce070eb5fb8d6e87e910e1370cfa45 --- /dev/null +++ b/pip-tmp/jiti/extension-index.80330c2b.mjs @@ -0,0 +1,585 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.default = registerSubagentExtension; + + + + + + + + + + + + + +var fs = _interopRequireWildcard(await jitiImport("node:fs")); +var os = _interopRequireWildcard(await jitiImport("node:os")); +var path = _interopRequireWildcard(await jitiImport("node:path")); + + +var _piTui = await jitiImport("@mariozechner/pi-tui"); +var _agents = await jitiImport("../agents/agents.ts"); +var _artifacts = await jitiImport("../shared/artifacts.ts"); +var _sessionIdentity = await jitiImport("../shared/session-identity.ts"); +var _settings = await jitiImport("../shared/settings.ts"); +var _render = await jitiImport("../tui/render.ts"); +var _schemas = await jitiImport("./schemas.ts"); +var _subagentExecutor = await jitiImport("../runs/foreground/subagent-executor.ts"); +var _asyncJobTracker = await jitiImport("../runs/background/async-job-tracker.ts"); +var _resultWatcher = await jitiImport("../runs/background/result-watcher.ts"); +var _slashCommands = await jitiImport("../slash/slash-commands.ts"); +var _promptTemplateBridge = await jitiImport("../slash/prompt-template-bridge.ts"); +var _slashBridge = await jitiImport("../slash/slash-bridge.ts"); +var _slashLiveState = await jitiImport("../slash/slash-live-state.ts"); + +var _notify = _interopRequireDefault(await jitiImport("../runs/background/notify.ts")); +var _piArgs = await jitiImport("../runs/shared/pi-args.ts"); +var _formatters = await jitiImport("../shared/formatters.ts"); +var _types = await jitiImport("../shared/types.ts"); + + + + + + + + + + + + +var _controlNotices = await jitiImport("./control-notices.ts");function _interopRequireDefault(e) {return e && e.__esModule ? e : { default: e };}function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);} /** + * Subagent Tool + * + * Full-featured subagent with sync and async modes. + * - Sync (default): Streams output, renders markdown, tracks usage + * - Async: Background execution, emits events when done + * + * Modes: single (agent + task), parallel (tasks[]), chain (chain[] with {previous}) + * Toggle: async parameter (default: false, configurable via config.json) + * + * Config file: ~/.pi/agent/extensions/subagent/config.json + * { "asyncByDefault": true, "forceTopLevelAsync": true, "maxSubagentDepth": 1, "intercomBridge": { "mode": "always", "instructionFile": "./intercom-bridge.md" }, "worktreeSetupHook": "./scripts/setup-worktree.mjs" } + */ /** + * Derive subagent session base directory from parent session file. + * If parent session is ~/.pi/agent/sessions/abc123.jsonl, + * returns ~/.pi/agent/sessions/abc123/ as the base. + * Callers add runId to create the actual session root: abc123/{runId}/ + * Falls back to a unique temp directory if no parent session. + */function getSubagentSessionRoot(parentSessionFile) {if (parentSessionFile) {const baseName = path.basename(parentSessionFile, ".jsonl");const sessionsDir = path.dirname(parentSessionFile); + return path.join(sessionsDir, baseName); + } + return fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-session-")); +} + +function loadConfig() { + const configPath = path.join(os.homedir(), ".pi", "agent", "extensions", "subagent", "config.json"); + try { + if (fs.existsSync(configPath)) { + return JSON.parse(fs.readFileSync(configPath, "utf-8")); + } + } catch (error) { + console.error(`Failed to load subagent config from '${configPath}':`, error); + } + return {}; +} + +function expandTilde(p) { + return p.startsWith("~/") ? path.join(os.homedir(), p.slice(2)) : p; +} + +/** + * Create a directory and verify it is actually accessible. + * On Windows with Azure AD/Entra ID, directories created shortly after + * wake-from-sleep can end up with broken NTFS ACLs (null DACL) when the + * cloud SID cannot be resolved without network connectivity. This leaves + * the directory completely inaccessible to the creating user. + */ +function ensureAccessibleDir(dirPath) { + fs.mkdirSync(dirPath, { recursive: true }); + try { + fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK); + } catch { + try { + fs.rmSync(dirPath, { recursive: true, force: true }); + } catch { + + // Best effort: retry mkdir/access even if cleanup fails. + }fs.mkdirSync(dirPath, { recursive: true }); + fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK); + } +} + +function isSlashResultRunning(result) { + return result.details?.progress?.some((entry) => entry.status === "running") || + result.details?.results.some((entry) => entry.progress?.status === "running") || + false; +} + +function isSlashResultError(result) { + return result.details?.results.some((entry) => entry.exitCode !== 0 && entry.progress?.status !== "running") || false; +} + +function isStaleExtensionContextError(error) { + return error instanceof Error && error.message.includes("Extension context no longer active"); +} + +function rebuildSlashResultContainer( +container, +result, +options, +theme) +{ + container.clear(); + container.addChild(new _piTui.Spacer(1)); + const boxTheme = isSlashResultRunning(result) ? "toolPendingBg" : isSlashResultError(result) ? "toolErrorBg" : "toolSuccessBg"; + const box = new _piTui.Box(1, 1, (text) => theme.bg(boxTheme, text)); + box.addChild((0, _render.renderSubagentResult)(result, options, theme)); + container.addChild(box); +} + +function createSlashResultComponent( +details, +options, +theme, +requestRender) +{ + const container = new _piTui.Container(); + const animationState = {}; + let lastVersion = -1; + container.render = (width) => { + const snapshot = (0, _slashLiveState.getSlashRenderableSnapshot)(details); + (0, _render.syncResultAnimation)(snapshot.result, { state: animationState, invalidate: requestRender }); + if (snapshot.version !== lastVersion || isSlashResultRunning(snapshot.result)) { + lastVersion = snapshot.version; + rebuildSlashResultContainer(container, snapshot.result, options, theme); + } + return _piTui.Container.prototype.render.call(container, width); + }; + return container; +} + +function parseSubagentNotifyContent(content) { + const lines = content.split("\n"); + const header = lines[0] ?? ""; + const match = header.match(/^Background task (completed|failed|paused): \*\*(.+?)\*\*(?:\s+(\([^)]*\)))?$/); + if (!match) return undefined; + const body = lines.slice(2); + let sessionIndex = -1; + for (let i = body.length - 1; i >= 1; i--) { + if (body[i - 1]?.trim() === "" && /^(Session|Session file|Session share error):\s+/.test(body[i])) { + sessionIndex = i; + break; + } + } + const sessionLine = sessionIndex >= 0 ? body[sessionIndex] : undefined; + const resultLines = sessionIndex >= 0 ? body.slice(0, sessionIndex) : body; + const resultPreview = resultLines.join("\n").trim() || "(no output)"; + let sessionLabel; + let sessionValue; + if (sessionLine) { + const separator = sessionLine.indexOf(":"); + sessionLabel = sessionLine.slice(0, separator).toLowerCase(); + sessionValue = sessionLine.slice(separator + 1).trim(); + } + return { + agent: match[2], + status: match[1], + ...(match[3] ? { taskInfo: match[3] } : {}), + resultPreview, + ...(sessionLabel && sessionValue ? { sessionLabel, sessionValue } : {}) + }; +} + +class SubagentControlNoticeComponent { + constructor( + details, + theme) + {this.details = details;this.theme = theme;} + + invalidate() {} + + render(width) { + const eventLabel = this.details.event.type.replaceAll("_", " "); + if (width < 3) return [(0, _piTui.truncateToWidth)(`Subagent ${eventLabel}`, width)]; + const bodyWidth = Math.max(1, width - 2); + const borderChar = "─"; + const header = ` ⚠ Subagent ${eventLabel}: ${this.details.event.agent} `; + const headerText = (0, _piTui.truncateToWidth)(header, bodyWidth, ""); + const headerPadding = Math.max(0, bodyWidth - (0, _piTui.visibleWidth)(headerText)); + const lines = [this.theme.fg("accent", `╭${headerText}${borderChar.repeat(headerPadding)}╮`)]; + + for (const line of (0, _piTui.wrapTextWithAnsi)((0, _controlNotices.formatSubagentControlNotice)(this.details), bodyWidth)) { + const text = (0, _piTui.truncateToWidth)(line, bodyWidth, ""); + const padding = Math.max(0, bodyWidth - (0, _piTui.visibleWidth)(text)); + lines.push(this.theme.fg("accent", `│${text}${" ".repeat(padding)}│`)); + } + lines.push(this.theme.fg("accent", `╰${borderChar.repeat(bodyWidth)}╯`)); + return lines; + } +} + +function registerSubagentExtension(pi) { + if (process.env[_piArgs.SUBAGENT_CHILD_ENV] === "1") return; + const globalStore = globalThis; + const runtimeCleanupStoreKey = "__piSubagentRuntimeCleanup"; + const previousRuntimeCleanup = globalStore[runtimeCleanupStoreKey]; + if (typeof previousRuntimeCleanup === "function") { + try { + previousRuntimeCleanup(); + } catch { + + // Best effort cleanup for stale timers from an older reload. + }} + + ensureAccessibleDir(_types.RESULTS_DIR); + ensureAccessibleDir(_types.ASYNC_DIR); + (0, _settings.cleanupOldChainDirs)(); + + const config = loadConfig(); + const asyncByDefault = config.asyncByDefault === true; + const tempArtifactsDir = (0, _artifacts.getArtifactsDir)(null); + (0, _artifacts.cleanupAllArtifactDirs)(_types.DEFAULT_ARTIFACT_CONFIG.cleanupDays); + + const state = { + baseCwd: process.cwd(), + currentSessionId: null, + asyncJobs: new Map(), + foregroundRuns: new Map(), + foregroundControls: new Map(), + lastForegroundControlId: null, + pendingForegroundControlNotices: new Map(), + cleanupTimers: new Map(), + lastUiContext: null, + poller: null, + completionSeen: new Map(), + watcher: null, + watcherRestartTimer: null, + resultFileCoalescer: { + schedule: () => false, + clear: () => {} + } + }; + + const { startResultWatcher, primeExistingResults, stopResultWatcher } = (0, _resultWatcher.createResultWatcher)( + pi, + state, + _types.RESULTS_DIR, + 10 * 60 * 1000 + ); + startResultWatcher(); + primeExistingResults(); + + const runtimeCleanup = () => { + (0, _render.stopWidgetAnimation)(); + (0, _render.stopResultAnimations)(); + stopResultWatcher(); + (0, _controlNotices.clearPendingForegroundControlNotices)(state); + if (state.poller) { + clearInterval(state.poller); + state.poller = null; + } + }; + globalStore[runtimeCleanupStoreKey] = runtimeCleanup; + + const { ensurePoller, handleStarted, handleComplete, resetJobs } = (0, _asyncJobTracker.createAsyncJobTracker)(pi, state, _types.ASYNC_DIR); + const executor = (0, _subagentExecutor.createSubagentExecutor)({ + pi, + state, + config, + asyncByDefault, + tempArtifactsDir, + getSubagentSessionRoot, + expandTilde, + discoverAgents: _agents.discoverAgents + }); + + pi.registerMessageRenderer(_types.SLASH_RESULT_TYPE, (message, options, theme) => { + const details = (0, _slashLiveState.resolveSlashMessageDetails)(message.details); + if (!details) return undefined; + return createSlashResultComponent(details, options, theme, () => state.lastUiContext?.ui.requestRender?.()); + }); + + pi.registerMessageRenderer("subagent-notify", (message, options, theme) => { + const content = typeof message.content === "string" ? message.content : ""; + const details = message.details ?? parseSubagentNotifyContent(content); + if (!details) return new _piTui.Text(content, 0, 0); + const icon = details.status === "completed" ? + theme.fg("success", "✓") : + details.status === "paused" ? + theme.fg("warning", "■") : + theme.fg("error", "✗"); + const parts = []; + if (details.taskInfo) parts.push(details.taskInfo); + if (details.durationMs !== undefined) parts.push((0, _formatters.formatDuration)(details.durationMs)); + let text = `${icon} ${theme.bold(details.agent)} ${theme.fg("dim", details.status)}`; + if (parts.length > 0) text += ` ${theme.fg("dim", "·")} ${parts.map((part) => theme.fg("dim", part)).join(` ${theme.fg("dim", "·")} `)}`; + const trimmedPreview = details.resultPreview.trim(); + const previewLines = options.expanded ? + trimmedPreview.split("\n").filter((line) => line.trim()) : + [trimmedPreview.split("\n", 1)[0] ?? ""].filter((line) => line.trim()); + for (const line of previewLines.length > 0 ? previewLines : ["(no output)"]) { + text += `\n ${theme.fg("dim", `⎿ ${line}`)}`; + } + if (!options.expanded && trimmedPreview.includes("\n")) { + text += `\n ${theme.fg("dim", "Ctrl+O full notification")}`; + } + if (details.sessionLabel && details.sessionValue) { + text += `\n ${theme.fg("muted", `${details.sessionLabel}: ${(0, _formatters.shortenPath)(details.sessionValue)}`)}`; + } + return new _piTui.Text(text, 0, 0); + }); + + pi.registerMessageRenderer(_controlNotices.SUBAGENT_CONTROL_MESSAGE_TYPE, (message, _options, theme) => { + const details = message.details; + if (!details?.event) return undefined; + const content = typeof message.content === "string" ? message.content : undefined; + return new SubagentControlNoticeComponent({ ...details, noticeText: (0, _controlNotices.formatSubagentControlNotice)(details, content) }, theme); + }); + + const executeSubagentCollapsed = (id, params, signal, onUpdate, ctx) => { + if (ctx.hasUI) ctx.ui.setToolsExpanded(false); + return executor.execute(id, params, signal, onUpdate, ctx); + }; + + const slashBridge = (0, _slashBridge.registerSlashSubagentBridge)({ + events: pi.events, + getContext: () => state.lastUiContext, + execute: (id, params, signal, onUpdate, ctx) => + executeSubagentCollapsed(id, params, signal, onUpdate, ctx) + }); + + const promptTemplateBridge = (0, _promptTemplateBridge.registerPromptTemplateDelegationBridge)({ + events: pi.events, + getContext: () => state.lastUiContext, + execute: async (requestId, request, signal, ctx, onUpdate) => { + if (request.tasks && request.tasks.length > 0) { + return executeSubagentCollapsed( + requestId, + { + tasks: request.tasks, + context: request.context, + cwd: request.cwd, + worktree: request.worktree, + async: false, + clarify: false + }, + signal, + onUpdate, + ctx + ); + } + return executeSubagentCollapsed( + requestId, + { + agent: request.agent, + task: request.task, + context: request.context, + cwd: request.cwd, + model: request.model, + async: false, + clarify: false + }, + signal, + onUpdate, + ctx + ); + } + }); + + function effectiveParallelTaskCount(tasks) { + if (!tasks || tasks.length === 0) return 0; + return tasks.reduce((total, task) => { + const count = typeof task.count === "number" && Number.isInteger(task.count) && task.count >= 1 ? task.count : 1; + return total + count; + }, 0); + } + + const tool = { + name: "subagent", + label: "Subagent", + description: `Delegate to subagents or manage agent definitions. + +EXECUTION (use exactly ONE mode): +• Before executing, use { action: "list" } to inspect configured agents/chains. Only execute agents listed as executable/non-disabled. +• SINGLE: { agent, task? } - one task; omit task for self-contained agents +• CHAIN: { chain: [{agent:"agent-a"}, {parallel:[{agent:"agent-b",count:3}]}] } - sequential pipeline with optional parallel fan-out +• PARALLEL: { tasks: [{agent,task,count?,output?,reads?,progress?}, ...], concurrency?: number, worktree?: true } - concurrent execution (worktree: isolate each task in a git worktree) +• Optional context: { context: "fresh" | "fork" } (default: if any requested agent has defaultContext: "fork", the whole invocation uses fork; otherwise "fresh"; inspect agent defaults via { action: "list" }) + +CHAIN TEMPLATE VARIABLES (use in task strings): +• {task} - The original task/request from the user +• {previous} - Text response from the previous step (empty for first step) +• {chain_dir} - Shared directory for chain files (e.g., /pi-subagents-/chain-runs/abc123/) + +Example: { chain: [{agent:"agent-a", task:"Analyze {task}"}, {agent:"agent-b", task:"Plan based on {previous}"}] } + +MANAGEMENT (use action field, omit agent/task/chain/tasks): +• { action: "list" } - discover executable agents/chains +• { action: "get", agent: "name" } - full detail; packaged agents use dotted runtime names like "package.agent" +• { action: "create", config: { name: "custom-agent", package: "code-analysis", systemPrompt, systemPromptMode, inheritProjectContext, inheritSkills, defaultContext, ... } } +• { action: "update", agent: "code-analysis.custom-agent", config: { package: "analysis", ... } } - merge +• { action: "delete", agent: "code-analysis.custom-agent" } +• Use chainName for chain operations; packaged chains also use dotted runtime names + +CONTROL: +• { action: "status", id: "..." } - inspect an async/background run by id or prefix +• { action: "interrupt", id?: "..." } - soft-interrupt the current child turn and leave the run paused +• { action: "resume", id: "...", message: "...", index?: 0 } - follow up with a live async child or revive a completed async/foreground child from its session + +DIAGNOSTICS: +• { action: "doctor" } - read-only report for runtime paths, discovery, sessions, and intercom`, + parameters: _schemas.SubagentParams, + + execute(id, params, signal, onUpdate, ctx) { + return executeSubagentCollapsed(id, params, signal, onUpdate, ctx); + }, + + renderCall(args, theme) { + if (args.action) { + const target = args.agent || args.chainName || ""; + return new _piTui.Text( + `${theme.fg("toolTitle", theme.bold("subagent "))}${args.action}${target ? ` ${theme.fg("accent", target)}` : ""}`, + 0, 0 + ); + } + const isParallel = (args.tasks?.length ?? 0) > 0; + const parallelCount = effectiveParallelTaskCount(args.tasks); + const asyncLabel = args.async === true && !isParallel ? theme.fg("warning", " [async]") : ""; + if (args.chain?.length) + return new _piTui.Text( + `${theme.fg("toolTitle", theme.bold("subagent "))}chain (${args.chain.length})${asyncLabel}`, + 0, + 0 + ); + if (isParallel) + return new _piTui.Text( + `${theme.fg("toolTitle", theme.bold("subagent "))}parallel (${parallelCount})`, + 0, + 0 + ); + return new _piTui.Text( + `${theme.fg("toolTitle", theme.bold("subagent "))}${theme.fg("accent", args.agent || "?")}${asyncLabel}`, + 0, + 0 + ); + }, + + renderResult(result, options, theme, context) { + (0, _render.syncResultAnimation)(result, context); + return (0, _render.renderSubagentResult)(result, options, theme); + } + + }; + + pi.registerTool(tool); + (0, _slashCommands.registerSlashCommands)(pi, state); + + const eventUnsubscribeStoreKey = "__piSubagentEventUnsubscribes"; + const controlNoticeSeenStoreKey = "__piSubagentVisibleControlNotices"; + const previousEventUnsubscribes = globalStore[eventUnsubscribeStoreKey]; + if (Array.isArray(previousEventUnsubscribes)) { + for (const unsubscribe of previousEventUnsubscribes) { + if (typeof unsubscribe !== "function") continue; + try { + unsubscribe(); + } catch { + + // Best effort cleanup for stale handlers from an older reload. + }} + } + (0, _notify.default)(pi); + + const existingVisibleControlNotices = globalStore[controlNoticeSeenStoreKey]; + const visibleControlNotices = existingVisibleControlNotices instanceof Set ? existingVisibleControlNotices : new Set(); + globalStore[controlNoticeSeenStoreKey] = visibleControlNotices; + const controlEventHandler = (payload) => { + (0, _controlNotices.handleSubagentControlNotice)({ + pi, + state, + visibleControlNotices, + details: payload + }); + }; + const eventUnsubscribes = [ + pi.events.on(_types.SUBAGENT_ASYNC_STARTED_EVENT, handleStarted), + pi.events.on(_types.SUBAGENT_ASYNC_COMPLETE_EVENT, handleComplete), + pi.events.on(_types.SUBAGENT_CONTROL_EVENT, controlEventHandler)]; + + globalStore[eventUnsubscribeStoreKey] = eventUnsubscribes; + + pi.on("tool_result", (event, ctx) => { + if (event.toolName !== "subagent") return; + if (!ctx.hasUI) return; + state.lastUiContext = ctx; + if (state.asyncJobs.size > 0) { + (0, _render.renderWidget)(ctx, Array.from(state.asyncJobs.values())); + ensurePoller(); + } + }); + + const cleanupSessionArtifacts = (ctx) => { + try { + const sessionFile = ctx.sessionManager.getSessionFile(); + if (sessionFile) { + (0, _artifacts.cleanupOldArtifacts)((0, _artifacts.getArtifactsDir)(sessionFile), _types.DEFAULT_ARTIFACT_CONFIG.cleanupDays); + } + } catch { + + // Cleanup failures should not block session lifecycle events. + }}; + + const resetSessionState = (ctx) => { + state.baseCwd = ctx.cwd; + state.currentSessionId = (0, _sessionIdentity.resolveCurrentSessionId)(ctx.sessionManager); + state.lastUiContext = ctx; + cleanupSessionArtifacts(ctx); + (0, _controlNotices.clearPendingForegroundControlNotices)(state); + resetJobs(ctx); + (0, _slashLiveState.restoreSlashFinalSnapshots)(ctx.sessionManager.getEntries()); + primeExistingResults(); + }; + + pi.on("session_start", (_event, ctx) => { + resetSessionState(ctx); + }); + + pi.on("session_shutdown", () => { + for (const unsubscribe of eventUnsubscribes) { + try { + unsubscribe(); + } catch { + + // Best effort cleanup during shutdown. + }} + if (globalStore[eventUnsubscribeStoreKey] === eventUnsubscribes) { + delete globalStore[eventUnsubscribeStoreKey]; + } + stopResultWatcher(); + if (state.poller) clearInterval(state.poller); + state.poller = null; + (0, _controlNotices.clearPendingForegroundControlNotices)(state); + for (const timer of state.cleanupTimers.values()) { + clearTimeout(timer); + } + state.cleanupTimers.clear(); + state.asyncJobs.clear(); + (0, _slashLiveState.clearSlashSnapshots)(); + slashBridge.cancelAll(); + slashBridge.dispose(); + promptTemplateBridge.cancelAll(); + promptTemplateBridge.dispose(); + (0, _render.stopWidgetAnimation)(); + (0, _render.stopResultAnimations)(); + if (globalStore[runtimeCleanupStoreKey] === runtimeCleanup) { + delete globalStore[runtimeCleanupStoreKey]; + } + try { + if (state.lastUiContext?.hasUI) { + state.lastUiContext.ui.setWidget(_types.WIDGET_KEY, undefined); + } + } catch (error) { + if (!isStaleExtensionContextError(error)) throw error; + } + }); +} /* v9-ae8021cc1ece3974 */ diff --git a/pip-tmp/jiti/extension-schemas.9e6ee9e3.mjs b/pip-tmp/jiti/extension-schemas.9e6ee9e3.mjs new file mode 100644 index 0000000000000000000000000000000000000000..87c1a6f08cd521a4d837dc91beff444c25de262e --- /dev/null +++ b/pip-tmp/jiti/extension-schemas.9e6ee9e3.mjs @@ -0,0 +1,168 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.SubagentParams = void 0; + + + +var _typebox = await jitiImport("typebox"); +var _types = await jitiImport("../shared/types.ts"); /** + * TypeBox schemas for subagent tool parameters + */const SkillOverride = _typebox.Type.Unsafe({ + anyOf: [ + { type: "array", items: { type: "string" } }, + { type: "boolean" }, + { type: "string" }], + + description: "Skill name(s) to inject (comma-separated), array of strings, or boolean (false disables, true uses default)" +}); + +const OutputOverride = _typebox.Type.Unsafe({ + anyOf: [ + { type: "string" }, + { type: "boolean" }], + + description: "Output filename/path (string), or false to disable file output" +}); + +const OutputModeOverride = _typebox.Type.String({ + enum: ["inline", "file-only"], + description: "Return saved output inline (default) or only a concise file reference. file-only requires output to be a path." +}); + +const ReadsOverride = _typebox.Type.Unsafe({ + anyOf: [ + { type: "array", items: { type: "string" } }, + { type: "boolean" }], + + description: "Files to read before running (array of filenames), or false to disable" +}); + +const TaskItem = _typebox.Type.Object({ + agent: _typebox.Type.String(), + task: _typebox.Type.String(), + cwd: _typebox.Type.Optional(_typebox.Type.String()), + count: _typebox.Type.Optional(_typebox.Type.Integer({ minimum: 1, description: "Repeat this parallel task N times with the same settings." })), + output: _typebox.Type.Optional(OutputOverride), + outputMode: _typebox.Type.Optional(OutputModeOverride), + reads: _typebox.Type.Optional(ReadsOverride), + progress: _typebox.Type.Optional(_typebox.Type.Boolean({ description: "Enable progress.md tracking for this task" })), + model: _typebox.Type.Optional(_typebox.Type.String({ description: "Override model for this task (e.g. 'google/gemini-3-pro')" })), + skill: _typebox.Type.Optional(SkillOverride) +}); + +// Parallel task item (within a parallel step) +const ParallelTaskSchema = _typebox.Type.Object({ + agent: _typebox.Type.String(), + task: _typebox.Type.Optional(_typebox.Type.String({ description: "Task template with {task}, {previous}, {chain_dir} variables. Defaults to {previous}." })), + cwd: _typebox.Type.Optional(_typebox.Type.String()), + count: _typebox.Type.Optional(_typebox.Type.Integer({ minimum: 1, description: "Repeat this parallel task N times with the same settings." })), + output: _typebox.Type.Optional(OutputOverride), + outputMode: _typebox.Type.Optional(OutputModeOverride), + reads: _typebox.Type.Optional(ReadsOverride), + progress: _typebox.Type.Optional(_typebox.Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })), + skill: _typebox.Type.Optional(SkillOverride), + model: _typebox.Type.Optional(_typebox.Type.String({ description: "Override model for this task" })) +}); + +// Flattened so chain steps do not need an object-shape anyOf/oneOf union. +const ChainItem = _typebox.Type.Object({ + agent: _typebox.Type.Optional(_typebox.Type.String({ description: "Sequential step agent name" })), + task: _typebox.Type.Optional(_typebox.Type.String({ + description: "Task template with variables: {task}=original request, {previous}=prior step's text response, {chain_dir}=shared folder. Required for first step, defaults to '{previous}' for subsequent steps." + })), + cwd: _typebox.Type.Optional(_typebox.Type.String()), + output: _typebox.Type.Optional(OutputOverride), + outputMode: _typebox.Type.Optional(OutputModeOverride), + reads: _typebox.Type.Optional(ReadsOverride), + progress: _typebox.Type.Optional(_typebox.Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })), + skill: _typebox.Type.Optional(SkillOverride), + model: _typebox.Type.Optional(_typebox.Type.String({ description: "Override model for this step" })), + parallel: _typebox.Type.Optional(_typebox.Type.Array(ParallelTaskSchema, { minItems: 1, description: "Tasks to run in parallel" })), + concurrency: _typebox.Type.Optional(_typebox.Type.Number({ description: "Max concurrent tasks (default: 4)" })), + failFast: _typebox.Type.Optional(_typebox.Type.Boolean({ description: "Stop on first failure (default: false)" })), + worktree: _typebox.Type.Optional(_typebox.Type.Boolean({ + description: "Create isolated git worktrees for each parallel task." + })) +}, { description: "Chain step: use {agent, task?, ...} for sequential or {parallel: [...]} for concurrent execution" }); + +const ControlOverrides = _typebox.Type.Object({ + enabled: _typebox.Type.Optional(_typebox.Type.Boolean({ description: "Enable/disable subagent control attention tracking for this run" })), + needsAttentionAfterMs: _typebox.Type.Optional(_typebox.Type.Integer({ minimum: 1, description: "No-observed-activity window before a run needs attention" })), + activeNoticeAfterMs: _typebox.Type.Optional(_typebox.Type.Integer({ minimum: 1, description: "Active-long-running notice threshold by elapsed ms (default: 240000)" })), + activeNoticeAfterTurns: _typebox.Type.Optional(_typebox.Type.Integer({ minimum: 1, description: "Optional active-long-running notice threshold by assistant turns (disabled by default)" })), + activeNoticeAfterTokens: _typebox.Type.Optional(_typebox.Type.Integer({ minimum: 1, description: "Optional active-long-running notice threshold by total tokens (disabled by default)" })), + failedToolAttemptsBeforeAttention: _typebox.Type.Optional(_typebox.Type.Integer({ minimum: 1, description: "Consecutive mutating-tool failures before escalating to needs_attention (default: 3)" })), + notifyOn: _typebox.Type.Optional(_typebox.Type.Array(_typebox.Type.String({ enum: ["active_long_running", "needs_attention"] }), { + description: "Control event types that should notify the parent/orchestrator. Defaults to active_long_running and needs_attention." + })), + notifyChannels: _typebox.Type.Optional(_typebox.Type.Array(_typebox.Type.String({ enum: ["event", "async", "intercom"] }), { + description: "Notification channels to use when available. Defaults to event, async, and intercom." + })) +}); + +const SubagentParams = exports.SubagentParams = _typebox.Type.Object({ + agent: _typebox.Type.Optional(_typebox.Type.String({ description: "Agent name (SINGLE mode) or target for management get/update/delete" })), + task: _typebox.Type.Optional(_typebox.Type.String({ description: "Task (SINGLE mode, optional for self-contained agents)" })), + // Management action (when present, tool operates in management mode) + action: _typebox.Type.Optional(_typebox.Type.String({ + enum: [..._types.SUBAGENT_ACTIONS], + description: "Management/control action. Omit for execution mode." + })), + id: _typebox.Type.Optional(_typebox.Type.String({ + description: "Run id or prefix for action='status', action='interrupt', or action='resume'." + })), + runId: _typebox.Type.Optional(_typebox.Type.String({ + description: "Target run ID for action='interrupt' or action='resume'. Defaults to the most recently active controllable run for interrupt. Prefer id for new calls." + })), + dir: _typebox.Type.Optional(_typebox.Type.String({ + description: "Async run directory for action='status' or action='resume'." + })), + index: _typebox.Type.Optional(_typebox.Type.Integer({ minimum: 0, description: "Zero-based child index for actions that target a specific child." })), + message: _typebox.Type.Optional(_typebox.Type.String({ description: "Follow-up message for action='resume'. Use index to choose a child from multi-child runs." })), + // Chain identifier for management (can't reuse 'chain' — that's the execution array) + chainName: _typebox.Type.Optional(_typebox.Type.String({ + description: "Chain name for get/update/delete management actions" + })), + // Agent/chain configuration for create/update (nested to avoid conflicts with execution fields) + config: _typebox.Type.Optional(_typebox.Type.Unsafe({ + anyOf: [ + { type: "object", additionalProperties: true }, + { type: "string" }], + + description: "Agent or chain config for create/update. Agent: name, package (optional namespace; runtime name becomes package.name), description, scope ('user'|'project', default 'user'), systemPrompt, systemPromptMode, inheritProjectContext, inheritSkills, defaultContext ('fresh'|'fork'), model, tools (comma-separated), extensions (comma-separated), skills (comma-separated), thinking, output, reads, progress, maxSubagentDepth. Chain: name, package, description, scope, steps (array of {agent, task?, output?, outputMode?, reads?, model?, skill?, progress?}). Presence of 'steps' creates a chain instead of an agent. String values must be valid JSON." + })), + tasks: _typebox.Type.Optional(_typebox.Type.Array(TaskItem, { description: "PARALLEL mode: [{agent, task, count?, output?, outputMode?, reads?, progress?}, ...]" })), + concurrency: _typebox.Type.Optional(_typebox.Type.Integer({ minimum: 1, description: "Top-level PARALLEL mode only: max concurrent tasks. Defaults to config.parallel.concurrency or 4." })), + worktree: _typebox.Type.Optional(_typebox.Type.Boolean({ + description: "Create isolated git worktrees for each parallel task. " + + "Prevents filesystem conflicts. Requires clean git state. " + + "Per-worktree diffs included in output." + })), + chain: _typebox.Type.Optional(_typebox.Type.Array(ChainItem, { description: "CHAIN mode: sequential pipeline where each step's response becomes {previous} for the next. Use {task}, {previous}, {chain_dir} in task templates." })), + context: _typebox.Type.Optional(_typebox.Type.String({ + enum: ["fresh", "fork"], + description: "'fresh' or 'fork' to branch from parent session. If omitted, any requested agent with defaultContext: 'fork' makes the whole invocation forked; otherwise the default is 'fresh'." + })), + chainDir: _typebox.Type.Optional(_typebox.Type.String({ description: "Persistent directory for chain artifacts. Default: a user-scoped temp directory under / (auto-cleaned after 24h)" })), + async: _typebox.Type.Optional(_typebox.Type.Boolean({ description: "Run in background (default: false, or per config)" })), + agentScope: _typebox.Type.Optional(_typebox.Type.String({ description: "Agent discovery scope: 'user', 'project', or 'both' (default: 'both'; project wins on name collisions)" })), + cwd: _typebox.Type.Optional(_typebox.Type.String()), + artifacts: _typebox.Type.Optional(_typebox.Type.Boolean({ description: "Write debug artifacts (default: true)" })), + includeProgress: _typebox.Type.Optional(_typebox.Type.Boolean({ description: "Include full progress in result (default: false)" })), + share: _typebox.Type.Optional(_typebox.Type.Boolean({ description: "Upload session to GitHub Gist for sharing (default: false)" })), + sessionDir: _typebox.Type.Optional( + _typebox.Type.String({ description: "Directory to store session logs (default: temp; enables sessions even if share=false)" }) + ), + // Clarification TUI + clarify: _typebox.Type.Optional(_typebox.Type.Boolean({ description: "Show TUI to preview/edit before execution (default: true for chains, false for single/parallel). Implies sync mode." })), + control: _typebox.Type.Optional(ControlOverrides), + // Solo agent overrides + output: _typebox.Type.Optional(_typebox.Type.Unsafe({ + anyOf: [ + { type: "string" }, + { type: "boolean" }], + + description: "Output file for single agent (string), or false to disable. Relative paths resolve against cwd." + })), + outputMode: _typebox.Type.Optional(OutputModeOverride), + skill: _typebox.Type.Optional(SkillOverride), + model: _typebox.Type.Optional(_typebox.Type.String({ description: "Override model for single agent (e.g. 'anthropic/claude-sonnet-4')" })) +}); /* v9-1f42c6bb141b9221 */ diff --git a/pip-tmp/jiti/foreground-chain-clarify.558277eb.mjs b/pip-tmp/jiti/foreground-chain-clarify.558277eb.mjs new file mode 100644 index 0000000000000000000000000000000000000000..81335a34abe83753059c10c3fa55f3e5fb4c7d81 --- /dev/null +++ b/pip-tmp/jiti/foreground-chain-clarify.558277eb.mjs @@ -0,0 +1,1333 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.ChainClarifyComponent = void 0; + + + + + + + + +var _piTui = await jitiImport("@mariozechner/pi-tui"); + + +var _modelFallback = await jitiImport("../shared/model-fallback.ts"); +var _modelInfo = await jitiImport("../../shared/model-info.ts"); /** + * Chain Clarification TUI Component + * + * Shows templates and resolved behaviors for each step in a chain. + * Supports runtime editing of templates, output paths, reads lists, and progress toggle. + */ + + + + + + + + + + + + + + + + + + + + + + +function createEditorState(initial = "") { + return { buffer: initial, cursor: 0, viewportOffset: 0 }; +} + +function wrapText(text, width) { + if (width <= 0) return { lines: [text], starts: [0] }; + if (text.length === 0) return { lines: [""], starts: [0] }; + + const lines = []; + const starts = []; + let offset = 0; + const segments = text.split("\n"); + for (const [index, segment] of segments.entries()) { + if (segment.length === 0) { + starts.push(offset); + lines.push(""); + } else { + let lineStart = 0; + let pos = 0; + let lineWidth = 0; + while (pos < segment.length) { + const char = String.fromCodePoint(segment.codePointAt(pos)); + const charWidth = (0, _piTui.visibleWidth)(char); + if (lineWidth > 0 && lineWidth + charWidth > width) { + starts.push(offset + lineStart); + lines.push(segment.slice(lineStart, pos)); + lineStart = pos; + lineWidth = 0; + continue; + } + pos += char.length; + lineWidth += charWidth; + } + starts.push(offset + lineStart); + lines.push(segment.slice(lineStart)); + } + offset += segment.length + (index < segments.length - 1 ? 1 : 0); + } + if (!text.endsWith("\n") && text.length > 0 && (0, _piTui.visibleWidth)(lines[lines.length - 1] ?? "") === width) { + starts.push(text.length); + lines.push(""); + } + return { lines, starts }; +} + +function getCursorDisplayPos(cursor, starts) { + for (let i = starts.length - 1; i >= 0; i--) { + if (cursor >= starts[i]) return { line: i, col: cursor - starts[i] }; + } + return { line: 0, col: 0 }; +} + +function ensureCursorVisible(cursorLine, viewportHeight, currentOffset) { + if (cursorLine < currentOffset) return Math.max(0, cursorLine); + if (cursorLine >= currentOffset + viewportHeight) return Math.max(0, cursorLine - viewportHeight + 1); + return Math.max(0, currentOffset); +} + +function isWordChar(ch) { + const code = ch.charCodeAt(0); + return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95; +} + +function wordBackward(buffer, cursor) { + let pos = cursor; + while (pos > 0 && !isWordChar(buffer[pos - 1])) pos--; + while (pos > 0 && isWordChar(buffer[pos - 1])) pos--; + return pos; +} + +function wordForward(buffer, cursor) { + let pos = cursor; + while (pos < buffer.length && isWordChar(buffer[pos])) pos++; + while (pos < buffer.length && !isWordChar(buffer[pos])) pos++; + return pos; +} + +function normalizeInsertText(data) { + let text = data.split("\x1b[200~").join("").split("\x1b[201~").join(""); + text = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); + const newline = text.indexOf("\n"); + if (newline !== -1) text = text.slice(0, newline); + text = text.replace(/\t/g, " "); + if (text.length === 0) return null; + for (let i = 0; i < text.length; i++) { + if (text.charCodeAt(i) < 32) return null; + } + return text; +} + +function handleEditorInput(state, data, textWidth) { + if ((0, _piTui.matchesKey)(data, "escape") || (0, _piTui.matchesKey)(data, "ctrl+c") || (0, _piTui.matchesKey)(data, "return")) return null; + + const { lines: wrapped, starts } = wrapText(state.buffer, textWidth); + const cursorPos = getCursorDisplayPos(state.cursor, starts); + + if ((0, _piTui.matchesKey)(data, "alt+left") || (0, _piTui.matchesKey)(data, "ctrl+left")) return { ...state, cursor: wordBackward(state.buffer, state.cursor) }; + if ((0, _piTui.matchesKey)(data, "alt+right") || (0, _piTui.matchesKey)(data, "ctrl+right")) return { ...state, cursor: wordForward(state.buffer, state.cursor) }; + if ((0, _piTui.matchesKey)(data, "left")) return state.cursor > 0 ? { ...state, cursor: state.cursor - 1 } : state; + if ((0, _piTui.matchesKey)(data, "right")) return state.cursor < state.buffer.length ? { ...state, cursor: state.cursor + 1 } : state; + if ((0, _piTui.matchesKey)(data, "up") && cursorPos.line > 0) { + const targetLine = cursorPos.line - 1; + return { ...state, cursor: starts[targetLine] + Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0) }; + } + if ((0, _piTui.matchesKey)(data, "down") && cursorPos.line < wrapped.length - 1) { + const targetLine = cursorPos.line + 1; + return { ...state, cursor: starts[targetLine] + Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0) }; + } + if ((0, _piTui.matchesKey)(data, "home")) return { ...state, cursor: starts[cursorPos.line] }; + if ((0, _piTui.matchesKey)(data, "end")) return { ...state, cursor: starts[cursorPos.line] + (wrapped[cursorPos.line]?.length ?? 0) }; + if ((0, _piTui.matchesKey)(data, "ctrl+home")) return { ...state, cursor: 0 }; + if ((0, _piTui.matchesKey)(data, "ctrl+end")) return { ...state, cursor: state.buffer.length }; + if ((0, _piTui.matchesKey)(data, "alt+backspace")) { + const target = wordBackward(state.buffer, state.cursor); + return target === state.cursor ? state : { ...state, buffer: state.buffer.slice(0, target) + state.buffer.slice(state.cursor), cursor: target }; + } + if ((0, _piTui.matchesKey)(data, "backspace")) { + return state.cursor > 0 ? + { ...state, buffer: state.buffer.slice(0, state.cursor - 1) + state.buffer.slice(state.cursor), cursor: state.cursor - 1 } : + state; + } + if ((0, _piTui.matchesKey)(data, "delete")) { + return state.cursor < state.buffer.length ? + { ...state, buffer: state.buffer.slice(0, state.cursor) + state.buffer.slice(state.cursor + 1) } : + state; + } + + const insert = normalizeInsertText(data); + return insert ? + { ...state, buffer: state.buffer.slice(0, state.cursor) + insert + state.buffer.slice(state.cursor), cursor: state.cursor + insert.length } : + null; +} + +function renderWithCursor(text, cursorPos) { + const before = text.slice(0, cursorPos); + const cursorChar = text[cursorPos] ?? " "; + const after = text.slice(cursorPos + 1); + return `${before}\x1b[7m${cursorChar}\x1b[27m${after}`; +} + +function renderEditor(state, width, viewportHeight) { + const { lines: wrapped, starts } = wrapText(state.buffer, width); + const cursorPos = getCursorDisplayPos(state.cursor, starts); + const lines = []; + for (let i = 0; i < viewportHeight; i++) { + const lineIdx = state.viewportOffset + i; + let content = lineIdx < wrapped.length ? wrapped[lineIdx] ?? "" : ""; + if (lineIdx === cursorPos.line) content = renderWithCursor(content, cursorPos.col); + lines.push(content); + } + return lines; +} + +/** + * TUI component for chain clarification. + * Factory signature matches ctx.ui.custom: (tui, theme, kb, done) => Component + */ +class ChainClarifyComponent { + width = 84; + + selectedStep = 0; + editingStep = null; + editMode = "template"; + editState = createEditorState(); + + EDIT_VIEWPORT_HEIGHT = 12; + behaviorOverrides = new Map(); + modelSearchQuery = ""; + modelSelectedIndex = 0; + filteredModels = []; + MODEL_SELECTOR_HEIGHT = 10; + thinkingSelectedIndex = 0; + skillSearchQuery = ""; + skillSelectedNames = new Set(); + skillCursorIndex = 0; + filteredSkills = []; + noticeMessage = null; + noticeMessageTimer = null; + /** Run in background (async) mode */ + runInBackground = false; + tui; + theme; + agentConfigs; + templates; + originalTask; + chainDir; + resolvedBehaviors; + availableModels; + preferredProvider; + availableSkills; + done; + mode; + + constructor( + tui, + theme, + agentConfigs, + templates, + originalTask, + chainDir, + resolvedBehaviors, + availableModels, + preferredProvider, + availableSkills, + done, + mode = 'chain') + { + this.tui = tui; + this.theme = theme; + this.agentConfigs = agentConfigs; + this.templates = templates; + this.originalTask = originalTask; + this.chainDir = chainDir; + this.resolvedBehaviors = resolvedBehaviors; + this.availableModels = availableModels; + this.preferredProvider = preferredProvider; + this.availableSkills = availableSkills; + this.done = done; + this.mode = mode; + this.filteredModels = [...availableModels]; + this.filteredSkills = [...availableSkills]; + } + + // ───────────────────────────────────────────────────────────────────────────── + // Helper methods for rendering + // ───────────────────────────────────────────────────────────────────────────── + + /** Pad string to specified visible width */ + pad(s, len) { + const vis = (0, _piTui.visibleWidth)(s); + return s + " ".repeat(Math.max(0, len - vis)); + } + + /** Create a row with border characters */ + row(content) { + const innerW = this.width - 2; + return this.theme.fg("border", "│") + this.pad(content, innerW) + this.theme.fg("border", "│"); + } + + /** Render centered header line with border */ + renderHeader(text) { + const innerW = this.width - 2; + const padLen = Math.max(0, innerW - (0, _piTui.visibleWidth)(text)); + const padLeft = Math.floor(padLen / 2); + const padRight = padLen - padLeft; + return ( + this.theme.fg("border", "╭" + "─".repeat(padLeft)) + + this.theme.fg("accent", text) + + this.theme.fg("border", "─".repeat(padRight) + "╮")); + + } + + /** Render centered footer line with border */ + renderFooter(text) { + const innerW = this.width - 2; + const padLen = Math.max(0, innerW - (0, _piTui.visibleWidth)(text)); + const padLeft = Math.floor(padLen / 2); + const padRight = padLen - padLeft; + return ( + this.theme.fg("border", "╰" + "─".repeat(padLeft)) + + this.theme.fg("dim", text) + + this.theme.fg("border", "─".repeat(padRight) + "╯")); + + } + + /** Exit edit mode and reset state */ + exitEditMode() { + this.editingStep = null; + this.editState = createEditorState(); + this.tui.requestRender(); + } + + // ───────────────────────────────────────────────────────────────────────────── + // Full edit mode methods + // ───────────────────────────────────────────────────────────────────────────── + + /** Render the full-edit takeover view */ + renderFullEditMode() { + const innerW = this.width - 2; + const textWidth = innerW - 2; // 1 char padding on each side + const lines = []; + + const { lines: wrapped, starts } = wrapText(this.editState.buffer, textWidth); + const cursorPos = getCursorDisplayPos(this.editState.cursor, starts); + this.editState = { + ...this.editState, + viewportOffset: ensureCursorVisible( + cursorPos.line, + this.EDIT_VIEWPORT_HEIGHT, + this.editState.viewportOffset + ) + }; + + // Header (truncate agent name to prevent overflow) + const fieldName = this.editMode === "template" ? "task" : this.editMode; + const rawAgentName = this.agentConfigs[this.editingStep]?.name ?? "unknown"; + const maxAgentLen = innerW - 30; // Reserve space for " Editing X (Step/Task N: ) " + const agentName = rawAgentName.length > maxAgentLen ? + rawAgentName.slice(0, maxAgentLen - 1) + "…" : + rawAgentName; + // Use mode-appropriate terminology + const stepLabel = this.mode === 'single' ? + agentName : + this.mode === 'parallel' ? + `Task ${this.editingStep + 1}: ${agentName}` : + `Step ${this.editingStep + 1}: ${agentName}`; + const headerText = ` Editing ${fieldName} (${stepLabel}) `; + lines.push(this.renderHeader(headerText)); + lines.push(this.row("")); + + const editorLines = renderEditor(this.editState, textWidth, this.EDIT_VIEWPORT_HEIGHT); + for (const line of editorLines) { + lines.push(this.row(` ${line}`)); + } + + const linesBelow = wrapped.length - this.editState.viewportOffset - this.EDIT_VIEWPORT_HEIGHT; + const hasMore = linesBelow > 0; + const hasLess = this.editState.viewportOffset > 0; + let scrollInfo = ""; + if (hasLess) scrollInfo += "↑"; + if (hasMore) scrollInfo += `↓ ${linesBelow}+`; + + lines.push(this.row("")); + + const footerText = scrollInfo ? + ` [Esc] Done • [Ctrl+C] Discard • ${scrollInfo} ` : + " [Esc] Done • [Ctrl+C] Discard "; + lines.push(this.renderFooter(footerText)); + + return lines; + } + + // ───────────────────────────────────────────────────────────────────────────── + // Behavior helpers + // ───────────────────────────────────────────────────────────────────────────── + + /** Get effective behavior for a step (with user overrides applied) */ + getEffectiveBehavior(stepIndex) { + const base = this.resolvedBehaviors[stepIndex]; + const override = this.behaviorOverrides.get(stepIndex); + if (!override) return base; + + return { + output: override.output !== undefined ? override.output : base.output, + outputMode: base.outputMode, + reads: override.reads !== undefined ? override.reads : base.reads, + progress: override.progress !== undefined ? override.progress : base.progress, + skills: override.skills !== undefined ? override.skills : base.skills, + model: override.model !== undefined ? override.model : base.model + }; + } + + /** Get the effective model for a step (override or agent default) */ + getEffectiveModel(stepIndex) { + const override = this.behaviorOverrides.get(stepIndex); + if (override?.model) return this.resolveModelFullId(override.model); + + const baseModel = this.resolvedBehaviors[stepIndex]?.model; + if (baseModel) return this.resolveModelFullId(baseModel); + return "default"; + } + + /** Resolve a model name to its full provider/model format */ + resolveModelFullId(modelName) { + return (0, _modelFallback.resolveModelCandidate)(modelName, this.availableModels, this.preferredProvider) ?? modelName; + } + + /** Update a behavior override for a step */ + updateBehavior(stepIndex, field, value) { + const existing = this.behaviorOverrides.get(stepIndex) ?? {}; + this.behaviorOverrides.set(stepIndex, { ...existing, [field]: value }); + } + + showNotice(text, type) { + this.noticeMessage = { text, type }; + if (this.noticeMessageTimer) clearTimeout(this.noticeMessageTimer); + this.noticeMessageTimer = setTimeout(() => { + this.noticeMessage = null; + this.noticeMessageTimer = null; + this.tui.requestRender(); + }, 2000); + this.tui.requestRender(); + } + + handleInput(data) { + if (this.editingStep !== null) { + if (this.editMode === "model") { + this.handleModelSelectorInput(data); + } else if (this.editMode === "thinking") { + this.handleThinkingSelectorInput(data); + } else if (this.editMode === "skills") { + this.handleSkillSelectorInput(data); + } else { + this.handleEditInput(data); + } + return; + } + + if ((0, _piTui.matchesKey)(data, "escape") || (0, _piTui.matchesKey)(data, "ctrl+c")) { + this.done({ confirmed: false, templates: [], behaviorOverrides: [] }); + return; + } + + if ((0, _piTui.matchesKey)(data, "return")) { + const overrides = []; + for (let i = 0; i < this.agentConfigs.length; i++) { + overrides.push(this.behaviorOverrides.get(i)); + } + this.done({ confirmed: true, templates: this.templates, behaviorOverrides: overrides, runInBackground: this.runInBackground }); + return; + } + + if ((0, _piTui.matchesKey)(data, "up")) { + this.selectedStep = Math.max(0, this.selectedStep - 1); + this.tui.requestRender(); + return; + } + + if ((0, _piTui.matchesKey)(data, "down")) { + const maxStep = Math.max(0, this.agentConfigs.length - 1); + this.selectedStep = Math.min(maxStep, this.selectedStep + 1); + this.tui.requestRender(); + return; + } + + if (data === "e") { + this.enterEditMode("template"); + return; + } + + if (data === "m") { + this.enterModelSelector(); + return; + } + + if (data === "t") { + this.enterThinkingSelector(); + return; + } + + if (data === "s") { + this.editingStep = this.selectedStep; + this.editMode = "skills"; + this.skillSearchQuery = ""; + this.skillCursorIndex = 0; + this.filteredSkills = [...this.availableSkills]; + const current = this.getEffectiveBehavior(this.selectedStep).skills; + this.skillSelectedNames.clear(); + if (current !== false && current.length > 0) { + current.forEach((skillName) => this.skillSelectedNames.add(skillName)); + } + this.tui.requestRender(); + return; + } + + if (data === "w" && this.mode !== 'parallel') { + this.enterEditMode("output"); + return; + } + + if (data === "r" && this.mode === 'chain') { + this.enterEditMode("reads"); + return; + } + + if (data === "p" && this.mode === 'chain') { + const anyEnabled = this.agentConfigs.some((_, i) => this.getEffectiveBehavior(i).progress); + const newState = !anyEnabled; + for (let i = 0; i < this.agentConfigs.length; i++) { + this.updateBehavior(i, "progress", newState); + } + this.tui.requestRender(); + return; + } + + if (data === "b") { + this.runInBackground = !this.runInBackground; + this.tui.requestRender(); + return; + } + + } + + enterEditMode(mode) { + this.editingStep = this.selectedStep; + this.editMode = mode; + let buffer = ""; + + if (mode === "template") { + const template = this.templates[this.selectedStep] ?? ""; + buffer = template.split("\n")[0] ?? ""; + } else if (mode === "output") { + const behavior = this.getEffectiveBehavior(this.selectedStep); + buffer = behavior.output === false ? "" : behavior.output || ""; + } else if (mode === "reads") { + const behavior = this.getEffectiveBehavior(this.selectedStep); + buffer = behavior.reads === false ? "" : behavior.reads?.join(", ") || ""; + } + + this.editState = createEditorState(buffer); + this.tui.requestRender(); + } + + /** Enter model selector mode */ + enterModelSelector() { + this.editingStep = this.selectedStep; + this.editMode = "model"; + this.modelSearchQuery = ""; + this.modelSelectedIndex = 0; + this.filteredModels = [...this.availableModels]; + const currentModel = (0, _modelFallback.splitThinkingSuffix)(this.getEffectiveModel(this.selectedStep)).baseModel; + const currentIndex = this.filteredModels.findIndex((m) => m.fullId === currentModel || m.id === currentModel); + if (currentIndex >= 0) { + this.modelSelectedIndex = currentIndex; + } + + this.tui.requestRender(); + } + + /** Filter models based on search query */ + filterModels() { + const query = this.modelSearchQuery.toLowerCase(); + if (!query) { + this.filteredModels = [...this.availableModels]; + } else { + this.filteredModels = this.availableModels.filter((m) => + m.fullId.toLowerCase().includes(query) || + m.id.toLowerCase().includes(query) || + m.provider.toLowerCase().includes(query) + ); + } + this.modelSelectedIndex = Math.min(this.modelSelectedIndex, Math.max(0, this.filteredModels.length - 1)); + } + + handleModelSelectorInput(data) { + if ((0, _piTui.matchesKey)(data, "escape") || (0, _piTui.matchesKey)(data, "ctrl+c")) { + this.exitEditMode(); + return; + } + + if ((0, _piTui.matchesKey)(data, "return")) { + const selected = this.filteredModels[this.modelSelectedIndex]; + if (selected) { + const { thinkingSuffix } = (0, _modelFallback.splitThinkingSuffix)(this.getEffectiveModel(this.editingStep)); + const requestedLevel = thinkingSuffix.slice(1); + const selectedModel = (0, _modelInfo.findModelInfo)(selected.fullId, this.availableModels, this.preferredProvider); + const suffix = (0, _modelInfo.getSupportedThinkingLevels)(selectedModel).some((level) => level === requestedLevel) ? thinkingSuffix : ""; + this.updateBehavior(this.editingStep, "model", `${selected.fullId}${suffix}`); + } + this.exitEditMode(); + return; + } + + if ((0, _piTui.matchesKey)(data, "up")) { + if (this.filteredModels.length > 0) { + this.modelSelectedIndex = this.modelSelectedIndex === 0 ? + this.filteredModels.length - 1 : + this.modelSelectedIndex - 1; + } + this.tui.requestRender(); + return; + } + + if ((0, _piTui.matchesKey)(data, "down")) { + if (this.filteredModels.length > 0) { + this.modelSelectedIndex = this.modelSelectedIndex === this.filteredModels.length - 1 ? + 0 : + this.modelSelectedIndex + 1; + } + this.tui.requestRender(); + return; + } + + if ((0, _piTui.matchesKey)(data, "backspace")) { + if (this.modelSearchQuery.length > 0) { + this.modelSearchQuery = this.modelSearchQuery.slice(0, -1); + this.filterModels(); + } + this.tui.requestRender(); + return; + } + + if (data.length === 1 && data.charCodeAt(0) >= 32) { + this.modelSearchQuery += data; + this.filterModels(); + this.tui.requestRender(); + return; + } + } + + getAvailableThinkingLevels(stepIndex) { + return (0, _modelInfo.getSupportedThinkingLevels)((0, _modelInfo.findModelInfo)(this.getEffectiveModel(stepIndex), this.availableModels, this.preferredProvider)); + } + + /** Enter thinking level selector mode */ + enterThinkingSelector() { + if (!this.getEffectiveBehavior(this.selectedStep).model) { + this.showNotice("Select a model first", "error"); + return; + } + this.editingStep = this.selectedStep; + this.editMode = "thinking"; + + const levels = this.getAvailableThinkingLevels(this.selectedStep); + const { thinkingSuffix } = (0, _modelFallback.splitThinkingSuffix)(this.getEffectiveModel(this.selectedStep)); + const suffix = thinkingSuffix.slice(1); + const levelIdx = levels.findIndex((level) => level === suffix); + this.thinkingSelectedIndex = levelIdx >= 0 ? levelIdx : Math.max(0, levels.indexOf("off")); + + this.tui.requestRender(); + } + + handleThinkingSelectorInput(data) { + if ((0, _piTui.matchesKey)(data, "escape") || (0, _piTui.matchesKey)(data, "ctrl+c")) { + this.exitEditMode(); + return; + } + + const levels = this.getAvailableThinkingLevels(this.editingStep); + if (levels.length === 0) return; + + if ((0, _piTui.matchesKey)(data, "return")) { + const selectedLevel = levels[this.thinkingSelectedIndex] ?? "off"; + this.applyThinkingLevel(selectedLevel); + this.exitEditMode(); + return; + } + + if ((0, _piTui.matchesKey)(data, "up")) { + this.thinkingSelectedIndex = this.thinkingSelectedIndex === 0 ? + levels.length - 1 : + this.thinkingSelectedIndex - 1; + this.tui.requestRender(); + return; + } + + if ((0, _piTui.matchesKey)(data, "down")) { + this.thinkingSelectedIndex = this.thinkingSelectedIndex === levels.length - 1 ? + 0 : + this.thinkingSelectedIndex + 1; + this.tui.requestRender(); + return; + } + } + + /** Apply thinking level to the current step's model */ + applyThinkingLevel(level) { + const stepIndex = this.editingStep; + const currentModel = this.getEffectiveBehavior(stepIndex).model; + if (!currentModel) return; + + const { baseModel } = (0, _modelFallback.splitThinkingSuffix)(currentModel); + const newModel = level === "off" ? baseModel : `${baseModel}:${level}`; + this.updateBehavior(stepIndex, "model", newModel); + } + + filterSkills() { + const query = this.skillSearchQuery.toLowerCase(); + if (!query) { + this.filteredSkills = [...this.availableSkills]; + } else { + this.filteredSkills = this.availableSkills.filter((s) => + s.name.toLowerCase().includes(query) || ( + s.description?.toLowerCase().includes(query) ?? false) + ); + } + this.skillCursorIndex = Math.min(this.skillCursorIndex, Math.max(0, this.filteredSkills.length - 1)); + } + + handleSkillSelectorInput(data) { + if ((0, _piTui.matchesKey)(data, "escape") || (0, _piTui.matchesKey)(data, "ctrl+c")) { + this.exitEditMode(); + return; + } + + if ((0, _piTui.matchesKey)(data, "return")) { + const selected = [...this.skillSelectedNames]; + this.updateBehavior(this.editingStep, "skills", selected); + this.exitEditMode(); + return; + } + + if (data === " ") { + if (this.filteredSkills.length > 0) { + const skill = this.filteredSkills[this.skillCursorIndex]; + if (skill) { + if (this.skillSelectedNames.has(skill.name)) { + this.skillSelectedNames.delete(skill.name); + } else { + this.skillSelectedNames.add(skill.name); + } + } + } + this.tui.requestRender(); + return; + } + + if ((0, _piTui.matchesKey)(data, "up")) { + if (this.filteredSkills.length > 0) { + this.skillCursorIndex = this.skillCursorIndex === 0 ? + this.filteredSkills.length - 1 : + this.skillCursorIndex - 1; + } + this.tui.requestRender(); + return; + } + + if ((0, _piTui.matchesKey)(data, "down")) { + if (this.filteredSkills.length > 0) { + this.skillCursorIndex = this.skillCursorIndex === this.filteredSkills.length - 1 ? + 0 : + this.skillCursorIndex + 1; + } + this.tui.requestRender(); + return; + } + + if ((0, _piTui.matchesKey)(data, "backspace")) { + if (this.skillSearchQuery.length > 0) { + this.skillSearchQuery = this.skillSearchQuery.slice(0, -1); + this.filterSkills(); + } + this.tui.requestRender(); + return; + } + + if (data.length === 1 && data.charCodeAt(0) >= 32) { + this.skillSearchQuery += data; + this.filterSkills(); + this.tui.requestRender(); + return; + } + } + + handleEditInput(data) { + const textWidth = this.width - 4; // Must match render: innerW - 2 = (width - 2) - 2 + if ((0, _piTui.matchesKey)(data, "shift+up") || (0, _piTui.matchesKey)(data, "pageup")) { + const { lines: wrapped, starts } = wrapText(this.editState.buffer, textWidth); + const cursorPos = getCursorDisplayPos(this.editState.cursor, starts); + const targetLine = Math.max(0, cursorPos.line - this.EDIT_VIEWPORT_HEIGHT); + const targetCol = Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0); + this.editState = { ...this.editState, cursor: starts[targetLine] + targetCol }; + this.tui.requestRender(); + return; + } + + if ((0, _piTui.matchesKey)(data, "shift+down") || (0, _piTui.matchesKey)(data, "pagedown")) { + const { lines: wrapped, starts } = wrapText(this.editState.buffer, textWidth); + const cursorPos = getCursorDisplayPos(this.editState.cursor, starts); + const targetLine = Math.min(wrapped.length - 1, cursorPos.line + this.EDIT_VIEWPORT_HEIGHT); + const targetCol = Math.min(cursorPos.col, wrapped[targetLine]?.length ?? 0); + this.editState = { ...this.editState, cursor: starts[targetLine] + targetCol }; + this.tui.requestRender(); + return; + } + + if ((0, _piTui.matchesKey)(data, "tab")) return; + + const nextState = handleEditorInput(this.editState, data, textWidth); + if (nextState) { + this.editState = nextState; + this.tui.requestRender(); + return; + } + + if ((0, _piTui.matchesKey)(data, "escape")) { + this.saveEdit(); + this.exitEditMode(); + return; + } + + if ((0, _piTui.matchesKey)(data, "ctrl+c")) { + this.exitEditMode(); + return; + } + } + + saveEdit() { + const stepIndex = this.editingStep; + + if (this.editMode === "template") { + // For template, preserve other lines if they existed + const original = this.templates[stepIndex] ?? ""; + const originalLines = original.split("\n"); + originalLines[0] = this.editState.buffer; + this.templates[stepIndex] = originalLines.join("\n"); + } else if (this.editMode === "output") { + // Capture OLD output before updating (for downstream propagation) + const oldBehavior = this.getEffectiveBehavior(stepIndex); + const oldOutput = typeof oldBehavior.output === "string" ? oldBehavior.output : null; + + // Empty string or whitespace means disable output + const trimmed = this.editState.buffer.trim(); + const newOutput = trimmed === "" ? false : trimmed; + this.updateBehavior(stepIndex, "output", newOutput); + + // Propagate output filename change to downstream steps' reads + if (oldOutput && typeof newOutput === "string" && oldOutput !== newOutput) { + this.propagateOutputChange(stepIndex, oldOutput, newOutput); + } + } else if (this.editMode === "reads") { + // Parse comma-separated list, empty means disable reads + const trimmed = this.editState.buffer.trim(); + if (trimmed === "") { + this.updateBehavior(stepIndex, "reads", false); + } else { + const files = trimmed.split(",").map((f) => f.trim()).filter((f) => f !== ""); + this.updateBehavior(stepIndex, "reads", files.length > 0 ? files : false); + } + } + } + + /** + * When a step's output filename changes, update downstream steps that read from it. + * This maintains the chain dependency automatically. + */ + propagateOutputChange(changedStepIndex, oldOutput, newOutput) { + // Check all downstream steps (steps that come after the changed step) + for (let i = changedStepIndex + 1; i < this.agentConfigs.length; i++) { + const behavior = this.getEffectiveBehavior(i); + + // Skip if reads is disabled or empty + if (behavior.reads === false || !behavior.reads || behavior.reads.length === 0) { + continue; + } + + // Check if this step reads the old output file + const readsArray = behavior.reads; + const oldIndex = readsArray.indexOf(oldOutput); + + if (oldIndex !== -1) { + // Replace old filename with new filename in reads + const newReads = [...readsArray]; + newReads[oldIndex] = newOutput; + this.updateBehavior(i, "reads", newReads); + } + } + } + + render(_width) { + if (this.editingStep !== null) { + if (this.editMode === "model") { + return this.renderModelSelector(); + } + if (this.editMode === "thinking") { + return this.renderThinkingSelector(); + } + if (this.editMode === "skills") { + return this.renderSkillSelector(); + } + return this.renderFullEditMode(); + } + // Mode-based navigation rendering + switch (this.mode) { + case 'single':return this.renderSingleMode(); + case 'parallel':return this.renderParallelMode(); + case 'chain':return this.renderChainMode(); + } + } + + /** Render the model selector view */ + renderModelSelector() { + const th = this.theme; + const lines = []; + + // Header (mode-aware terminology) + const agentName = this.agentConfigs[this.editingStep]?.name ?? "unknown"; + const stepLabel = this.mode === 'single' ? + agentName : + this.mode === 'parallel' ? + `Task ${this.editingStep + 1}: ${agentName}` : + `Step ${this.editingStep + 1}: ${agentName}`; + const headerText = ` Select Model (${stepLabel}) `; + lines.push(this.renderHeader(headerText)); + lines.push(this.row("")); + + const searchPrefix = th.fg("dim", "Search: "); + const cursor = "\x1b[7m \x1b[27m"; // Reverse video space for cursor + const searchDisplay = this.modelSearchQuery + cursor; + lines.push(this.row(` ${searchPrefix}${searchDisplay}`)); + lines.push(this.row("")); + + const currentModel = this.getEffectiveModel(this.editingStep); + const currentModelBase = (0, _modelFallback.splitThinkingSuffix)(currentModel).baseModel; + const currentLabel = th.fg("dim", "Current: "); + lines.push(this.row(` ${currentLabel}${th.fg("warning", currentModel)}`)); + lines.push(this.row("")); + + if (this.filteredModels.length === 0) { + lines.push(this.row(` ${th.fg("dim", "No matching models")}`)); + } else { + const maxVisible = this.MODEL_SELECTOR_HEIGHT; + let startIdx = 0; + + if (this.filteredModels.length > maxVisible) { + startIdx = Math.max(0, this.modelSelectedIndex - Math.floor(maxVisible / 2)); + startIdx = Math.min(startIdx, this.filteredModels.length - maxVisible); + } + + const endIdx = Math.min(startIdx + maxVisible, this.filteredModels.length); + + if (startIdx > 0) { + lines.push(this.row(` ${th.fg("dim", ` ↑ ${startIdx} more`)}`)); + } + + for (let i = startIdx; i < endIdx; i++) { + const model = this.filteredModels[i]; + const isSelected = i === this.modelSelectedIndex; + const isCurrent = model.fullId === currentModelBase || model.id === currentModelBase; + const prefix = isSelected ? th.fg("accent", "→ ") : " "; + const modelText = isSelected ? th.fg("accent", model.id) : model.id; + const providerBadge = th.fg("dim", ` [${model.provider}]`); + const currentBadge = isCurrent ? th.fg("success", " current") : ""; + + lines.push(this.row(` ${prefix}${modelText}${providerBadge}${currentBadge}`)); + } + + const remaining = this.filteredModels.length - endIdx; + if (remaining > 0) { + lines.push(this.row(` ${th.fg("dim", ` ↓ ${remaining} more`)}`)); + } + } + + const contentLines = lines.length; + const targetHeight = 18; + for (let i = contentLines; i < targetHeight; i++) { + lines.push(this.row("")); + } + + const footerText = " [Enter] Select • [Esc] Cancel • Type to search "; + lines.push(this.renderFooter(footerText)); + + return lines; + } + + /** Render the thinking level selector view */ + renderThinkingSelector() { + const th = this.theme; + const lines = []; + + const agentName = this.agentConfigs[this.editingStep]?.name ?? "unknown"; + const stepLabel = this.mode === 'single' ? + agentName : + this.mode === 'parallel' ? + `Task ${this.editingStep + 1}: ${agentName}` : + `Step ${this.editingStep + 1}: ${agentName}`; + const headerText = ` Thinking Level (${stepLabel}) `; + lines.push(this.renderHeader(headerText)); + lines.push(this.row("")); + + const currentModel = this.getEffectiveModel(this.editingStep); + const currentLabel = th.fg("dim", "Model: "); + lines.push(this.row(` ${currentLabel}${th.fg("accent", currentModel)}`)); + lines.push(this.row("")); + + lines.push(this.row(` ${th.fg("dim", "Select thinking level (extended thinking budget):")}`)); + lines.push(this.row("")); + + const levelDescriptions = { + "off": "No extended thinking", + "minimal": "Brief reasoning", + "low": "Light reasoning", + "medium": "Moderate reasoning", + "high": "Deep reasoning", + "xhigh": "Maximum reasoning (ultrathink)" + }; + + const levels = this.getAvailableThinkingLevels(this.editingStep); + if (levels.length === 0) { + lines.push(this.row(` ${th.fg("dim", "No supported thinking levels")}`)); + } else { + for (let i = 0; i < levels.length; i++) { + const level = levels[i]; + const isSelected = i === this.thinkingSelectedIndex; + const prefix = isSelected ? th.fg("accent", "→ ") : " "; + const levelText = isSelected ? th.fg("accent", level) : level; + const desc = th.fg("dim", ` - ${levelDescriptions[level]}`); + lines.push(this.row(` ${prefix}${levelText}${desc}`)); + } + } + + const contentLines = lines.length; + const targetHeight = 16; + for (let i = contentLines; i < targetHeight; i++) { + lines.push(this.row("")); + } + + const footerText = levels.length === 0 ? + " [Esc] Cancel " : + " [Enter] Select • [Esc] Cancel • ↑↓ Navigate "; + lines.push(this.renderFooter(footerText)); + + return lines; + } + + renderSkillSelector() { + const innerW = this.width - 2; + const th = this.theme; + const lines = []; + + const agentName = this.agentConfigs[this.editingStep]?.name ?? "unknown"; + const stepLabel = this.mode === 'single' ? + agentName : + this.mode === 'parallel' ? + `Task ${this.editingStep + 1}: ${agentName}` : + `Step ${this.editingStep + 1}: ${agentName}`; + lines.push(this.renderHeader(` Select Skills (${stepLabel}) `)); + lines.push(this.row("")); + + const cursor = "\x1b[7m \x1b[27m"; + lines.push(this.row(` ${th.fg("dim", "Search: ")}${this.skillSearchQuery}${cursor}`)); + lines.push(this.row("")); + + const selected = [...this.skillSelectedNames].join(", ") || th.fg("dim", "(none)"); + lines.push(this.row(` ${th.fg("dim", "Selected: ")}${(0, _piTui.truncateToWidth)(selected, innerW - 12)}`)); + lines.push(this.row("")); + + const selectorHeight = 10; + if (this.filteredSkills.length === 0) { + lines.push(this.row(` ${th.fg("dim", "No matching skills")}`)); + } else { + let startIdx = 0; + if (this.filteredSkills.length > selectorHeight) { + startIdx = Math.max(0, this.skillCursorIndex - Math.floor(selectorHeight / 2)); + startIdx = Math.min(startIdx, this.filteredSkills.length - selectorHeight); + } + const endIdx = Math.min(startIdx + selectorHeight, this.filteredSkills.length); + + if (startIdx > 0) { + lines.push(this.row(` ${th.fg("dim", ` ↑ ${startIdx} more`)}`)); + } + + for (let i = startIdx; i < endIdx; i++) { + const skill = this.filteredSkills[i]; + const isCursor = i === this.skillCursorIndex; + const isSelected = this.skillSelectedNames.has(skill.name); + + const prefix = isCursor ? th.fg("accent", "→ ") : " "; + const checkbox = isSelected ? th.fg("success", "[x]") : "[ ]"; + const nameText = isCursor ? th.fg("accent", skill.name) : skill.name; + const sourceBadge = th.fg("dim", ` [${skill.source}]`); + const desc = skill.description ? + th.fg("dim", ` - ${(0, _piTui.truncateToWidth)(skill.description, 25)}`) : + ""; + + lines.push(this.row(` ${prefix}${checkbox} ${nameText}${sourceBadge}${desc}`)); + } + + const remaining = this.filteredSkills.length - endIdx; + if (remaining > 0) { + lines.push(this.row(` ${th.fg("dim", ` ↓ ${remaining} more`)}`)); + } + } + + const targetHeight = 18; + for (let i = lines.length; i < targetHeight; i++) { + lines.push(this.row("")); + } + + lines.push(this.renderFooter(" [Enter] Confirm • [Space] Toggle • [Esc] Cancel ")); + return lines; + } + + getFooterText() { + const bgLabel = this.runInBackground ? '[b]g:ON' : '[b]g'; + switch (this.mode) { + case 'single': + return ` [Enter] Run • [Esc] Cancel • e m t w s ${bgLabel} `; + case 'parallel': + return ` [Enter] Run • [Esc] Cancel • e m t s ${bgLabel} • ↑↓ Nav `; + case 'chain': + return ` [Enter] Run • [Esc] Cancel • e m t w r p s ${bgLabel} • ↑↓ Nav `; + } + } + + appendNotice(lines) { + if (!this.noticeMessage) return; + const color = this.noticeMessage.type === "error" ? "error" : "success"; + lines.push(this.row(` ${this.theme.fg(color, this.noticeMessage.text)}`)); + } + + renderSingleMode() { + const innerW = this.width - 2; + const th = this.theme; + const lines = []; + + const agentName = this.agentConfigs[0]?.name ?? "unknown"; + const maxHeaderLen = innerW - 4; + const headerText = ` Agent: ${(0, _piTui.truncateToWidth)(agentName, maxHeaderLen - 9)} `; + lines.push(this.renderHeader(headerText)); + lines.push(this.row("")); + + const config = this.agentConfigs[0]; + const behavior = this.getEffectiveBehavior(0); + + const stepLabel = config.name; + lines.push(this.row(` ${th.fg("accent", "▶ " + stepLabel)}`)); + + const template = (this.templates[0] ?? "").split("\n")[0] ?? ""; + const taskLabel = th.fg("dim", "task: "); + lines.push(this.row(` ${taskLabel}${(0, _piTui.truncateToWidth)(template, innerW - 12)}`)); + + const effectiveModel = this.getEffectiveModel(0); + const override = this.behaviorOverrides.get(0); + const isOverridden = override?.model !== undefined; + const modelValue = isOverridden ? + th.fg("warning", effectiveModel) + th.fg("dim", " ✎") : + effectiveModel; + const modelLabel = th.fg("dim", "model: "); + lines.push(this.row(` ${modelLabel}${(0, _piTui.truncateToWidth)(modelValue, innerW - 13)}`)); + + const writesValue = behavior.output === false ? + th.fg("dim", "(disabled)") : + behavior.output || th.fg("dim", "(none)"); + const writesLabel = th.fg("dim", "writes: "); + lines.push(this.row(` ${writesLabel}${(0, _piTui.truncateToWidth)(writesValue, innerW - 14)}`)); + + const skillsValue = behavior.skills === false ? + th.fg("dim", "(disabled)") : + behavior.skills?.length ? behavior.skills.join(", ") : th.fg("dim", "(none)"); + const skillsLabel = th.fg("dim", "skills: "); + lines.push(this.row(` ${skillsLabel}${(0, _piTui.truncateToWidth)(skillsValue, innerW - 14)}`)); + + lines.push(this.row("")); + + this.appendNotice(lines); + lines.push(this.renderFooter(this.getFooterText())); + + return lines; + } + + renderParallelMode() { + const innerW = this.width - 2; + const th = this.theme; + const lines = []; + + const headerText = ` Parallel Tasks (${this.agentConfigs.length}) `; + lines.push(this.renderHeader(headerText)); + lines.push(this.row("")); + + for (let i = 0; i < this.agentConfigs.length; i++) { + const config = this.agentConfigs[i]; + const isSelected = i === this.selectedStep; + + const color = isSelected ? "accent" : "dim"; + const prefix = isSelected ? "▶ " : " "; + const taskPrefix = `Task ${i + 1}: `; + const maxNameLen = innerW - 4 - prefix.length - taskPrefix.length; + const agentName = config.name.length > maxNameLen ? + config.name.slice(0, maxNameLen - 1) + "…" : + config.name; + const taskLabel = `${taskPrefix}${agentName}`; + lines.push(this.row(` ${th.fg(color, prefix + taskLabel)}`)); + + const template = (this.templates[i] ?? "").split("\n")[0] ?? ""; + const taskTextLabel = th.fg("dim", "task: "); + lines.push(this.row(` ${taskTextLabel}${(0, _piTui.truncateToWidth)(template, innerW - 12)}`)); + + const effectiveModel = this.getEffectiveModel(i); + const override = this.behaviorOverrides.get(i); + const isOverridden = override?.model !== undefined; + const modelValue = isOverridden ? + th.fg("warning", effectiveModel) + th.fg("dim", " ✎") : + effectiveModel; + const modelLabel = th.fg("dim", "model: "); + lines.push(this.row(` ${modelLabel}${(0, _piTui.truncateToWidth)(modelValue, innerW - 13)}`)); + + const behavior = this.getEffectiveBehavior(i); + const skillsValue = behavior.skills === false ? + th.fg("dim", "(disabled)") : + behavior.skills?.length ? behavior.skills.join(", ") : th.fg("dim", "(none)"); + const skillsLabel = th.fg("dim", "skills: "); + lines.push(this.row(` ${skillsLabel}${(0, _piTui.truncateToWidth)(skillsValue, innerW - 14)}`)); + + lines.push(this.row("")); + } + + this.appendNotice(lines); + lines.push(this.renderFooter(this.getFooterText())); + + return lines; + } + + renderChainMode() { + const innerW = this.width - 2; + const th = this.theme; + const lines = []; + + const chainLabel = this.agentConfigs.map((c) => c.name).join(" → "); + const maxHeaderLen = innerW - 4; + const headerText = ` Chain: ${(0, _piTui.truncateToWidth)(chainLabel, maxHeaderLen - 9)} `; + lines.push(this.renderHeader(headerText)); + + lines.push(this.row("")); + + const taskPreview = (0, _piTui.truncateToWidth)(this.originalTask, innerW - 16); + lines.push(this.row(` Original Task: ${taskPreview}`)); + const chainDirPreview = (0, _piTui.truncateToWidth)(this.chainDir ?? "", innerW - 12); + lines.push(this.row(` Chain Dir: ${th.fg("dim", chainDirPreview)}`)); + + const progressEnabled = this.agentConfigs.some((_, i) => this.getEffectiveBehavior(i).progress); + const progressValue = progressEnabled ? th.fg("success", "enabled") : th.fg("dim", "disabled"); + lines.push(this.row(` Progress: ${progressValue} ${th.fg("dim", "(press [p] to toggle)")}`)); + lines.push(this.row("")); + + for (let i = 0; i < this.agentConfigs.length; i++) { + const config = this.agentConfigs[i]; + const isSelected = i === this.selectedStep; + const behavior = this.getEffectiveBehavior(i); + + const color = isSelected ? "accent" : "dim"; + const prefix = isSelected ? "▶ " : " "; + const stepPrefix = `Step ${i + 1}: `; + const maxNameLen = innerW - 4 - prefix.length - stepPrefix.length; + const agentName = config.name.length > maxNameLen ? + config.name.slice(0, maxNameLen - 1) + "…" : + config.name; + const stepLabel = `${stepPrefix}${agentName}`; + lines.push( + this.row(` ${th.fg(color, prefix + stepLabel)}`) + ); + + const template = (this.templates[i] ?? "").split("\n")[0] ?? ""; + const highlighted = template. + replace(/\{task\}/g, th.fg("success", "{task}")). + replace(/\{previous\}/g, th.fg("warning", "{previous}")). + replace(/\{chain_dir\}/g, th.fg("accent", "{chain_dir}")); + + const templateLabel = th.fg("dim", "task: "); + lines.push(this.row(` ${templateLabel}${(0, _piTui.truncateToWidth)(highlighted, innerW - 12)}`)); + + const effectiveModel = this.getEffectiveModel(i); + const override = this.behaviorOverrides.get(i); + const isOverridden = override?.model !== undefined; + const modelValue = isOverridden ? + th.fg("warning", effectiveModel) + th.fg("dim", " ✎") : + effectiveModel; + const modelLabel = th.fg("dim", "model: "); + lines.push(this.row(` ${modelLabel}${(0, _piTui.truncateToWidth)(modelValue, innerW - 13)}`)); + + const writesValue = behavior.output === false ? + th.fg("dim", "(disabled)") : + behavior.output || th.fg("dim", "(none)"); + const writesLabel = th.fg("dim", "writes: "); + lines.push(this.row(` ${writesLabel}${(0, _piTui.truncateToWidth)(writesValue, innerW - 14)}`)); + + const readsValue = behavior.reads === false ? + th.fg("dim", "(disabled)") : + behavior.reads && behavior.reads.length > 0 ? + behavior.reads.join(", ") : + th.fg("dim", "(none)"); + const readsLabel = th.fg("dim", "reads: "); + lines.push(this.row(` ${readsLabel}${(0, _piTui.truncateToWidth)(readsValue, innerW - 13)}`)); + + const skillsValue = behavior.skills === false ? + th.fg("dim", "(disabled)") : + behavior.skills?.length ? behavior.skills.join(", ") : th.fg("dim", "(none)"); + const skillsLabel = th.fg("dim", "skills: "); + lines.push(this.row(` ${skillsLabel}${(0, _piTui.truncateToWidth)(skillsValue, innerW - 14)}`)); + + if (progressEnabled) { + const isFirstStep = i === 0; + const progressAction = isFirstStep ? + th.fg("success", "writes progress.md") : + th.fg("accent", "reads progress.md"); + const progressLabel = th.fg("dim", "progress: "); + lines.push(this.row(` ${progressLabel}${progressAction}`)); + } + + if (i < this.agentConfigs.length - 1) { + const nextStepUsePrevious = (this.templates[i + 1] ?? "").includes("{previous}"); + if (nextStepUsePrevious) { + const indicator = th.fg("dim", " ↳ response → ") + th.fg("warning", "{previous}"); + lines.push(this.row(indicator)); + } + } + + lines.push(this.row("")); + } + + this.appendNotice(lines); + lines.push(this.renderFooter(this.getFooterText())); + + return lines; + } + + invalidate() {} + dispose() { + if (this.noticeMessageTimer) clearTimeout(this.noticeMessageTimer); + this.noticeMessageTimer = null; + } +}exports.ChainClarifyComponent = ChainClarifyComponent; /* v9-86a1ae2c0ca16164 */ diff --git a/pip-tmp/jiti/foreground-chain-execution.e441430b.mjs b/pip-tmp/jiti/foreground-chain-execution.e441430b.mjs new file mode 100644 index 0000000000000000000000000000000000000000..a9f52d7c811ccdc354a9aaeb18b9459f6ebafd42 --- /dev/null +++ b/pip-tmp/jiti/foreground-chain-execution.e441430b.mjs @@ -0,0 +1,932 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.executeChain = executeChain; + + + +var fs = _interopRequireWildcard(await jitiImport("node:fs")); +var path = _interopRequireWildcard(await jitiImport("node:path")); + + + +var _chainClarify = await jitiImport("./chain-clarify.ts"); +var _modelInfo = await jitiImport("../../shared/model-info.ts"); +var _settings = await jitiImport("../../shared/settings.ts"); + + + + + + + + + + + + + + + + + + +var _skills = await jitiImport("../../agents/skills.ts"); +var _intercomBridge = await jitiImport("../../intercom/intercom-bridge.ts"); +var _execution = await jitiImport("./execution.ts"); +var _formatters = await jitiImport("../../shared/formatters.ts"); +var _utils = await jitiImport("../../shared/utils.ts"); +var _runHistory = await jitiImport("../shared/run-history.ts"); +var _worktree = await jitiImport("../shared/worktree.ts"); + + + + + + + + +var _types = await jitiImport("../../shared/types.ts"); + + + + + + + + + + + + +var _modelFallback = await jitiImport("../shared/model-fallback.ts"); +var _singleOutput = await jitiImport("../shared/single-output.ts");function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);} /** + * Chain execution logic for subagent tool + */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +function buildChainExecutionDetails(input) { + return (0, _utils.compactForegroundDetails)({ + mode: "chain", + results: input.results, + progress: input.includeProgress ? input.allProgress : undefined, + artifacts: input.allArtifactPaths.length ? { dir: input.artifactsDir, files: input.allArtifactPaths } : undefined, + chainAgents: input.chainAgents, + totalSteps: input.totalSteps, + currentStepIndex: input.currentStepIndex + }); +} + +function buildChainExecutionErrorResult(message, input) { + return { + content: [{ type: "text", text: message }], + isError: true, + details: buildChainExecutionDetails(input) + }; +} + +function ensureParallelProgressFile( +chainDir, +progressCreated, +parallelBehaviors) +{ + if (progressCreated || !parallelBehaviors.some((behavior) => behavior.progress)) { + return progressCreated; + } + (0, _settings.writeInitialProgressFile)(chainDir); + return true; +} + +function appendParallelWorktreeSummary( +output, +worktreeSetup, +diffsDir, +agents) +{ + if (!worktreeSetup) return output; + const diffs = (0, _worktree.diffWorktrees)(worktreeSetup, agents, diffsDir); + const diffSummary = (0, _worktree.formatWorktreeDiffSummary)(diffs); + if (!diffSummary) return output; + return `${output}\n\n${diffSummary}`; +} + +async function runParallelChainTasks(input) { + const concurrency = input.step.concurrency ?? _types.MAX_CONCURRENCY; + const failFast = input.step.failFast ?? false; + let aborted = false; + + const parallelResults = await (0, _utils.mapConcurrent)( + input.step.parallel, + concurrency, + async (task, taskIndex) => { + if (aborted && failFast) { + return { + agent: task.agent, + task: "(skipped)", + exitCode: -1, + messages: [], + usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 }, + error: "Skipped due to fail-fast" + }; + } + + const taskTemplate = input.parallelTemplates[taskIndex] ?? "{previous}"; + const behavior = (0, _settings.suppressProgressForReadOnlyTask)(input.parallelBehaviors[taskIndex], taskTemplate, input.originalTask); + const templateHasPrevious = taskTemplate.includes("{previous}"); + const { prefix, suffix } = (0, _settings.buildChainInstructions)( + behavior, + input.chainDir, + false, + templateHasPrevious ? undefined : input.prev + ); + + let taskStr = taskTemplate; + taskStr = taskStr.replace(/\{task\}/g, input.originalTask); + taskStr = taskStr.replace(/\{previous\}/g, input.prev); + taskStr = taskStr.replace(/\{chain_dir\}/g, input.chainDir); + const cleanTask = taskStr; + taskStr = prefix + taskStr + suffix; + + const taskAgentConfig = input.agents.find((agent) => agent.name === task.agent); + const effectiveModel = + (task.model ? (0, _modelFallback.resolveModelCandidate)(task.model, input.availableModels, input.ctx.model?.provider) : null) ?? + (0, _modelFallback.resolveModelCandidate)(taskAgentConfig?.model, input.availableModels, input.ctx.model?.provider); + const maxSubagentDepth = (0, _types.resolveChildMaxSubagentDepth)(input.maxSubagentDepth, taskAgentConfig?.maxSubagentDepth); + + const taskCwd = input.worktreeSetup ? + input.worktreeSetup.worktrees[taskIndex].agentCwd : + (0, _utils.resolveChildCwd)(input.cwd ?? input.ctx.cwd, task.cwd); + + const outputPath = typeof behavior.output === "string" ? + path.isAbsolute(behavior.output) ? behavior.output : path.join(input.chainDir, behavior.output) : + undefined; + const interruptController = new AbortController(); + if (input.foregroundControl) { + input.foregroundControl.currentAgent = task.agent; + input.foregroundControl.currentIndex = input.globalTaskIndex + taskIndex; + input.foregroundControl.currentActivityState = undefined; + input.foregroundControl.updatedAt = Date.now(); + input.foregroundControl.interrupt = () => { + if (interruptController.signal.aborted) return false; + interruptController.abort(); + input.foregroundControl.currentActivityState = undefined; + input.foregroundControl.updatedAt = Date.now(); + return true; + }; + } + + const result = await (0, _execution.runSync)(input.ctx.cwd, input.agents, task.agent, taskStr, { + cwd: taskCwd, + signal: input.signal, + interruptSignal: interruptController.signal, + allowIntercomDetach: taskAgentConfig?.systemPrompt?.includes(_intercomBridge.INTERCOM_BRIDGE_MARKER) === true, + intercomEvents: input.intercomEvents, + runId: input.runId, + index: input.globalTaskIndex + taskIndex, + sessionDir: input.sessionDirForIndex(input.globalTaskIndex + taskIndex), + sessionFile: input.sessionFileForIndex?.(input.globalTaskIndex + taskIndex), + share: input.shareEnabled, + artifactsDir: input.artifactConfig.enabled ? input.artifactsDir : undefined, + artifactConfig: input.artifactConfig, + outputPath, + outputMode: behavior.outputMode, + maxSubagentDepth, + controlConfig: input.controlConfig, + onControlEvent: input.onControlEvent, + intercomSessionName: input.childIntercomTarget?.(task.agent, input.globalTaskIndex + taskIndex), + orchestratorIntercomTarget: input.orchestratorIntercomTarget, + modelOverride: effectiveModel, + availableModels: input.availableModels, + preferredModelProvider: input.ctx.model?.provider, + skills: behavior.skills === false ? [] : behavior.skills, + onUpdate: input.onUpdate ? + (progressUpdate) => { + const stepResults = progressUpdate.details?.results || []; + const stepProgress = progressUpdate.details?.progress || []; + if (input.foregroundControl && stepProgress.length > 0) { + const current = stepProgress[0]; + input.foregroundControl.currentAgent = task.agent; + input.foregroundControl.currentIndex = input.globalTaskIndex + taskIndex; + input.foregroundControl.currentActivityState = current?.activityState; + input.foregroundControl.lastActivityAt = current?.lastActivityAt; + input.foregroundControl.currentTool = current?.currentTool; + input.foregroundControl.currentToolStartedAt = current?.currentToolStartedAt; + input.foregroundControl.currentPath = current?.currentPath; + input.foregroundControl.turnCount = current?.turnCount; + input.foregroundControl.tokens = current?.tokens; + input.foregroundControl.toolCount = current?.toolCount; + input.foregroundControl.updatedAt = Date.now(); + } + input.onUpdate?.({ + ...progressUpdate, + details: { + mode: "chain", + results: input.results.concat(stepResults), + progress: input.allProgress.concat(stepProgress), + controlEvents: progressUpdate.details?.controlEvents, + chainAgents: input.chainAgents, + totalSteps: input.totalSteps, + currentStepIndex: input.stepIndex + } + }); + } : + undefined + }); + if (input.foregroundControl?.currentIndex === input.globalTaskIndex + taskIndex) { + input.foregroundControl.interrupt = undefined; + input.foregroundControl.updatedAt = Date.now(); + } + + if (result.exitCode !== 0 && failFast) { + aborted = true; + } + (0, _runHistory.recordRun)(task.agent, cleanTask, result.exitCode, result.progressSummary?.durationMs ?? 0); + return result; + } + ); + + return parallelResults; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/** + * Execute a chain of subagent steps + */ +async function executeChain(params) { + const { + chain: chainSteps, + agents, + ctx, + signal, + runId, + cwd, + shareEnabled, + sessionDirForIndex, + sessionFileForIndex, + artifactsDir, + artifactConfig, + includeProgress, + clarify, + onUpdate, + onControlEvent, + controlConfig, + childIntercomTarget, + orchestratorIntercomTarget, + foregroundControl, + intercomEvents, + chainSkills: chainSkillsParam, + chainDir: chainDirBase + } = params; + const chainSkills = chainSkillsParam ?? []; + + const allProgress = []; + const allArtifactPaths = []; + + const chainAgents = chainSteps.map((step) => + (0, _settings.isParallelStep)(step) ? + `[${step.parallel.map((t) => t.agent).join("+")}]` : + step.agent + ); + const totalSteps = chainSteps.length; + + const firstStep = chainSteps[0]; + const originalTask = params.task ?? ( + (0, _settings.isParallelStep)(firstStep) ? firstStep.parallel[0].task : firstStep.task); + + const chainDir = (0, _settings.createChainDir)(runId, chainDirBase); + const hasParallelSteps = chainSteps.some(_settings.isParallelStep); + let templates = (0, _settings.resolveChainTemplates)(chainSteps); + const shouldClarify = clarify !== false && ctx.hasUI && !hasParallelSteps; + let tuiBehaviorOverrides; + const availableModels = ctx.modelRegistry.getAvailable().map(_modelInfo.toModelInfo); + const availableSkills = (0, _skills.discoverAvailableSkills)(cwd ?? ctx.cwd); + + if (shouldClarify) { + const seqSteps = chainSteps; + const agentConfigs = []; + for (const step of seqSteps) { + const config = agents.find((a) => a.name === step.agent); + if (!config) { + (0, _settings.removeChainDir)(chainDir); + return { + content: [{ type: "text", text: `Unknown agent: ${step.agent}` }], + isError: true, + details: { mode: "chain", results: [] } + }; + } + agentConfigs.push(config); + } + + const stepOverrides = seqSteps.map((step) => ({ + output: step.output, + outputMode: step.outputMode, + reads: step.reads, + progress: step.progress, + skills: (0, _skills.normalizeSkillInput)(step.skill), + model: step.model + })); + + const resolvedBehaviors = agentConfigs.map((config, i) => + (0, _settings.resolveStepBehavior)(config, stepOverrides[i], chainSkills) + ); + const flatTemplates = templates; + + const result = await ctx.ui.custom( + (tui, theme, _kb, done) => + new _chainClarify.ChainClarifyComponent( + tui, + theme, + agentConfigs, + flatTemplates, + originalTask, + chainDir, + resolvedBehaviors, + availableModels, + ctx.model?.provider, + availableSkills, + done + ), + { + overlay: true, + overlayOptions: { anchor: "center", width: 84, maxHeight: "80%" } + } + ); + + if (!result || !result.confirmed) { + (0, _settings.removeChainDir)(chainDir); + return { + content: [{ type: "text", text: "Chain cancelled" }], + details: { mode: "chain", results: [] } + }; + } + + if (result.runInBackground) { + (0, _settings.removeChainDir)(chainDir); + const updatedChain = chainSteps.map((step, i) => { + if ((0, _settings.isParallelStep)(step)) return step; + const override = result.behaviorOverrides[i]; + return { + ...step, + task: result.templates[i], + ...(override?.model ? { model: override.model } : {}), + ...(override?.output !== undefined ? { output: override.output } : {}), + ...("outputMode" in step && step.outputMode !== undefined ? { outputMode: step.outputMode } : {}), + ...(override?.reads !== undefined ? { reads: override.reads } : {}), + ...(override?.progress !== undefined ? { progress: override.progress } : {}), + ...(override?.skills !== undefined ? { skill: override.skills } : {}) + }; + }); + return { + content: [{ type: "text", text: "Launching in background..." }], + details: { mode: "chain", results: [] }, + requestedAsync: { chain: updatedChain, chainSkills } + }; + } + + templates = result.templates; + tuiBehaviorOverrides = result.behaviorOverrides; + } + + const results = []; + let prev = ""; + let globalTaskIndex = 0; + let progressCreated = false; + + for (let stepIndex = 0; stepIndex < chainSteps.length; stepIndex++) { + const step = chainSteps[stepIndex]; + const stepTemplates = templates[stepIndex]; + + if ((0, _settings.isParallelStep)(step)) { + const parallelTemplates = stepTemplates; + const parallelCwd = (0, _utils.resolveChildCwd)(cwd ?? ctx.cwd, step.cwd); + let worktreeSetup; + if (step.worktree) { + const worktreeTaskCwdConflict = (0, _worktree.findWorktreeTaskCwdConflict)(step.parallel, parallelCwd); + if (worktreeTaskCwdConflict) { + return buildChainExecutionErrorResult( + `parallel chain step ${stepIndex + 1}: ${(0, _worktree.formatWorktreeTaskCwdConflict)(worktreeTaskCwdConflict, parallelCwd)}`, + { + results, + includeProgress, + allProgress, + allArtifactPaths, + artifactsDir, + chainAgents, + totalSteps, + currentStepIndex: stepIndex + } + ); + } + try { + worktreeSetup = (0, _worktree.createWorktrees)(parallelCwd, `${runId}-s${stepIndex}`, step.parallel.length, { + agents: step.parallel.map((task) => task.agent), + setupHook: params.worktreeSetupHook ? + { hookPath: params.worktreeSetupHook, timeoutMs: params.worktreeSetupHookTimeoutMs } : + undefined + }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return buildChainExecutionErrorResult(message, { + results, + includeProgress, + allProgress, + allArtifactPaths, + artifactsDir, + chainAgents, + totalSteps, + currentStepIndex: stepIndex + }); + } + } + + try { + const agentNames = step.parallel.map((task) => task.agent); + const parallelBehaviors = (0, _settings.resolveParallelBehaviors)(step.parallel, agents, stepIndex, chainSkills). + map((behavior, taskIndex) => (0, _settings.suppressProgressForReadOnlyTask)(behavior, parallelTemplates[taskIndex] ?? step.parallel[taskIndex]?.task, originalTask)); + for (let taskIndex = 0; taskIndex < step.parallel.length; taskIndex++) { + const behavior = parallelBehaviors[taskIndex]; + const outputPath = typeof behavior.output === "string" ? + path.isAbsolute(behavior.output) ? behavior.output : path.join(chainDir, behavior.output) : + undefined; + const validationError = (0, _singleOutput.validateFileOnlyOutputMode)(behavior.outputMode, outputPath, `Parallel chain step ${stepIndex + 1} task ${taskIndex + 1} (${step.parallel[taskIndex].agent})`); + if (validationError) return buildChainExecutionErrorResult(validationError, { + results, + includeProgress, + allProgress, + allArtifactPaths, + artifactsDir, + chainAgents, + totalSteps, + currentStepIndex: stepIndex + }); + } + progressCreated = ensureParallelProgressFile(chainDir, progressCreated, parallelBehaviors); + (0, _settings.createParallelDirs)(chainDir, stepIndex, step.parallel.length, agentNames); + + const parallelResults = await runParallelChainTasks({ + step, + parallelTemplates, + parallelBehaviors, + agents, + stepIndex, + availableModels, + chainDir, + prev, + originalTask, + ctx, + intercomEvents, + cwd, + runId, + globalTaskIndex, + sessionDirForIndex, + sessionFileForIndex, + shareEnabled, + artifactConfig, + artifactsDir, + signal, + onUpdate, + results, + allProgress, + chainAgents, + totalSteps, + controlConfig, + onControlEvent, + childIntercomTarget, + orchestratorIntercomTarget, + foregroundControl, + worktreeSetup, + maxSubagentDepth: params.maxSubagentDepth + }); + globalTaskIndex += step.parallel.length; + + for (const result of parallelResults) { + results.push(result); + if (result.progress) allProgress.push(result.progress); + if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths); + } + + const interrupted = parallelResults.find((result) => result.interrupted); + if (interrupted) { + return { + content: [{ type: "text", text: `Chain paused after interrupt at step ${stepIndex + 1} (${interrupted.agent}). Waiting for explicit next action.` }], + details: buildChainExecutionDetails({ + results, + includeProgress, + allProgress, + allArtifactPaths, + artifactsDir, + chainAgents, + totalSteps, + currentStepIndex: stepIndex + }) + }; + } + const detachedIndexInStep = parallelResults.findIndex((result) => result.detached); + const detached = detachedIndexInStep >= 0 ? parallelResults[detachedIndexInStep] : undefined; + if (detached) { + return { + content: [{ type: "text", text: `Chain detached for intercom coordination at step ${stepIndex + 1} (${detached.agent}). Reply to the supervisor request first. After the child exits, start a fresh follow-up if needed.` }], + details: buildChainExecutionDetails({ + results, + includeProgress, + allProgress, + allArtifactPaths, + artifactsDir, + chainAgents, + totalSteps, + currentStepIndex: stepIndex + }) + }; + } + + const failures = parallelResults. + map((result, originalIndex) => ({ ...result, originalIndex })). + filter((result) => result.exitCode !== 0 && result.exitCode !== -1); + if (failures.length > 0) { + const failureSummary = failures. + map((failure) => `- Task ${failure.originalIndex + 1} (${failure.agent}): ${failure.error || "failed"}`). + join("\n"); + const errorMsg = `Parallel step ${stepIndex + 1} failed:\n${failureSummary}`; + const summary = (0, _formatters.buildChainSummary)(chainSteps, results, chainDir, "failed", { + index: stepIndex, + error: errorMsg + }); + return { + content: [{ type: "text", text: summary }], + isError: true, + details: buildChainExecutionDetails({ + results, + includeProgress, + allProgress, + allArtifactPaths, + artifactsDir, + chainAgents, + totalSteps, + currentStepIndex: stepIndex + }) + }; + } + + const taskResults = parallelResults.map((result, i) => { + const outputTarget = parallelBehaviors[i]?.output; + const outputTargetPath = typeof outputTarget === "string" ? + path.isAbsolute(outputTarget) ? outputTarget : path.join(chainDir, outputTarget) : + undefined; + return { + agent: result.agent, + taskIndex: i, + output: (0, _utils.getSingleResultOutput)(result), + exitCode: result.exitCode, + error: result.error, + outputTargetPath, + outputTargetExists: outputTargetPath ? fs.existsSync(outputTargetPath) : undefined + }; + }); + prev = (0, _settings.aggregateParallelOutputs)(taskResults); + prev = appendParallelWorktreeSummary( + prev, + worktreeSetup, + path.join(chainDir, "worktree-diffs", `step-${stepIndex}`), + agentNames + ); + } finally { + if (worktreeSetup) (0, _worktree.cleanupWorktrees)(worktreeSetup); + } + } else { + const seqStep = step; + const stepTemplate = stepTemplates; + + const agentConfig = agents.find((a) => a.name === seqStep.agent); + if (!agentConfig) { + (0, _settings.removeChainDir)(chainDir); + return { + content: [{ type: "text", text: `Unknown agent: ${seqStep.agent}` }], + isError: true, + details: { mode: "chain", results: [] } + }; + } + + const tuiOverride = tuiBehaviorOverrides?.[stepIndex]; + const stepOverride = { + output: tuiOverride?.output !== undefined ? tuiOverride.output : seqStep.output, + outputMode: seqStep.outputMode, + reads: tuiOverride?.reads !== undefined ? tuiOverride.reads : seqStep.reads, + progress: tuiOverride?.progress !== undefined ? tuiOverride.progress : seqStep.progress, + skills: + tuiOverride?.skills !== undefined ? + tuiOverride.skills : + (0, _skills.normalizeSkillInput)(seqStep.skill) + }; + const behavior = (0, _settings.suppressProgressForReadOnlyTask)((0, _settings.resolveStepBehavior)(agentConfig, stepOverride, chainSkills), stepTemplate, originalTask); + + const isFirstProgress = behavior.progress && !progressCreated; + if (isFirstProgress) { + progressCreated = true; + } + + const templateHasPrevious = stepTemplate.includes("{previous}"); + const { prefix, suffix } = (0, _settings.buildChainInstructions)( + behavior, + chainDir, + isFirstProgress, + templateHasPrevious ? undefined : prev + ); + + let stepTask = stepTemplate; + stepTask = stepTask.replace(/\{task\}/g, originalTask); + stepTask = stepTask.replace(/\{previous\}/g, prev); + stepTask = stepTask.replace(/\{chain_dir\}/g, chainDir); + const cleanTask = stepTask; + stepTask = prefix + stepTask + suffix; + + const effectiveModel = + tuiOverride?.model ?? ( + seqStep.model ? (0, _modelFallback.resolveModelCandidate)(seqStep.model, availableModels, ctx.model?.provider) : null) ?? + (0, _modelFallback.resolveModelCandidate)(agentConfig.model, availableModels, ctx.model?.provider); + + const outputPath = typeof behavior.output === "string" ? + path.isAbsolute(behavior.output) ? behavior.output : path.join(chainDir, behavior.output) : + undefined; + const validationError = (0, _singleOutput.validateFileOnlyOutputMode)(behavior.outputMode, outputPath, `Chain step ${stepIndex + 1} (${seqStep.agent})`); + if (validationError) { + return buildChainExecutionErrorResult(validationError, { + results, + includeProgress, + allProgress, + allArtifactPaths, + artifactsDir, + chainAgents, + totalSteps, + currentStepIndex: stepIndex + }); + } + const maxSubagentDepth = (0, _types.resolveChildMaxSubagentDepth)(params.maxSubagentDepth, agentConfig.maxSubagentDepth); + const interruptController = new AbortController(); + if (foregroundControl) { + foregroundControl.currentAgent = seqStep.agent; + foregroundControl.currentIndex = globalTaskIndex; + foregroundControl.currentActivityState = undefined; + foregroundControl.updatedAt = Date.now(); + foregroundControl.interrupt = () => { + if (interruptController.signal.aborted) return false; + interruptController.abort(); + foregroundControl.currentActivityState = undefined; + foregroundControl.updatedAt = Date.now(); + return true; + }; + } + + const r = await (0, _execution.runSync)(ctx.cwd, agents, seqStep.agent, stepTask, { + cwd: (0, _utils.resolveChildCwd)(cwd ?? ctx.cwd, seqStep.cwd), + signal, + interruptSignal: interruptController.signal, + allowIntercomDetach: agentConfig.systemPrompt?.includes(_intercomBridge.INTERCOM_BRIDGE_MARKER) === true, + intercomEvents, + runId, + index: globalTaskIndex, + sessionDir: sessionDirForIndex(globalTaskIndex), + sessionFile: sessionFileForIndex?.(globalTaskIndex), + share: shareEnabled, + artifactsDir: artifactConfig.enabled ? artifactsDir : undefined, + artifactConfig, + outputPath, + outputMode: behavior.outputMode, + maxSubagentDepth, + controlConfig, + onControlEvent, + intercomSessionName: childIntercomTarget?.(seqStep.agent, globalTaskIndex), + orchestratorIntercomTarget, + modelOverride: effectiveModel, + availableModels, + preferredModelProvider: ctx.model?.provider, + skills: behavior.skills === false ? [] : behavior.skills, + onUpdate: onUpdate ? + (p) => { + const stepResults = p.details?.results || []; + const stepProgress = p.details?.progress || []; + if (foregroundControl && stepProgress.length > 0) { + const current = stepProgress[0]; + foregroundControl.currentAgent = seqStep.agent; + foregroundControl.currentIndex = globalTaskIndex; + foregroundControl.currentActivityState = current?.activityState; + foregroundControl.lastActivityAt = current?.lastActivityAt; + foregroundControl.currentTool = current?.currentTool; + foregroundControl.currentToolStartedAt = current?.currentToolStartedAt; + foregroundControl.currentPath = current?.currentPath; + foregroundControl.turnCount = current?.turnCount; + foregroundControl.tokens = current?.tokens; + foregroundControl.toolCount = current?.toolCount; + foregroundControl.updatedAt = Date.now(); + } + onUpdate({ + ...p, + details: { + mode: "chain", + results: results.concat(stepResults), + progress: allProgress.concat(stepProgress), + controlEvents: p.details?.controlEvents, + chainAgents, + totalSteps, + currentStepIndex: stepIndex + } + }); + } : + undefined + }); + if (foregroundControl?.currentIndex === globalTaskIndex) { + foregroundControl.interrupt = undefined; + foregroundControl.updatedAt = Date.now(); + } + (0, _runHistory.recordRun)(seqStep.agent, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0); + + globalTaskIndex++; + results.push(r); + if (r.progress) allProgress.push(r.progress); + if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths); + + if (r.interrupted) { + return { + content: [{ type: "text", text: `Chain paused after interrupt at step ${stepIndex + 1} (${r.agent}). Waiting for explicit next action.` }], + details: buildChainExecutionDetails({ + results, + includeProgress, + allProgress, + allArtifactPaths, + artifactsDir, + chainAgents, + totalSteps, + currentStepIndex: stepIndex + }) + }; + } + if (r.detached) { + return { + content: [{ type: "text", text: `Chain detached for intercom coordination at step ${stepIndex + 1} (${r.agent}). Reply to the supervisor request first. After the child exits, start a fresh follow-up if needed.` }], + details: buildChainExecutionDetails({ + results, + includeProgress, + allProgress, + allArtifactPaths, + artifactsDir, + chainAgents, + totalSteps, + currentStepIndex: stepIndex + }) + }; + } + + if (r.exitCode !== 0) { + const summary = (0, _formatters.buildChainSummary)(chainSteps, results, chainDir, "failed", { + index: stepIndex, + error: r.error || "Chain failed" + }); + return { + content: [{ type: "text", text: summary }], + details: buildChainExecutionDetails({ + results, + includeProgress, + allProgress, + allArtifactPaths, + artifactsDir, + chainAgents, + totalSteps, + currentStepIndex: stepIndex + }), + isError: true + }; + } + + if (behavior.output) { + try { + const expectedPath = path.isAbsolute(behavior.output) ? + behavior.output : + path.join(chainDir, behavior.output); + if (!fs.existsSync(expectedPath)) { + const dirFiles = fs.readdirSync(chainDir); + const mdFiles = dirFiles.filter((file) => file.endsWith(".md") && file !== "progress.md"); + const warning = mdFiles.length > 0 ? + `Agent wrote to different file(s): ${mdFiles.join(", ")} instead of ${behavior.output}` : + `Agent did not create expected output file: ${behavior.output}`; + r.error = r.error ? `${r.error}\n${warning}` : warning; + } + } catch { + + // Ignore validation errors; this diagnostic should not mask successful chain output. + }} + + prev = (0, _utils.getSingleResultOutput)(r); + } + } + + const summary = (0, _formatters.buildChainSummary)(chainSteps, results, chainDir, "completed"); + + return { + content: [{ type: "text", text: summary }], + details: buildChainExecutionDetails({ + results, + includeProgress, + allProgress, + allArtifactPaths, + artifactsDir, + chainAgents, + totalSteps + }) + }; +} /* v9-cf844499abcaf24e */ diff --git a/pip-tmp/jiti/foreground-execution.7bdac23f.mjs b/pip-tmp/jiti/foreground-execution.7bdac23f.mjs new file mode 100644 index 0000000000000000000000000000000000000000..ba052146212c09983a9ec0f26276987aefbaaf45 --- /dev/null +++ b/pip-tmp/jiti/foreground-execution.7bdac23f.mjs @@ -0,0 +1,902 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.runSync = runSync; + + + +var _nodeChild_process = await jitiImport("node:child_process"); +var _nodeFs = await jitiImport("node:fs"); + + +var _artifacts = await jitiImport("../../shared/artifacts.ts"); + + + + + +var _types = await jitiImport("../../shared/types.ts"); + + + + + + + + + + + + + +var _subagentControl = await jitiImport("../shared/subagent-control.ts"); + + + + + + +var _utils = await jitiImport("../../shared/utils.ts"); + + + + + + +var _skills = await jitiImport("../../agents/skills.ts"); +var _completionGuard = await jitiImport("../shared/completion-guard.ts"); +var _piSpawn = await jitiImport("../shared/pi-spawn.ts"); +var _jsonlWriter = await jitiImport("../../shared/jsonl-writer.ts"); +var _postExitStdioGuard = await jitiImport("../../shared/post-exit-stdio-guard.ts"); +var _piArgs = await jitiImport("../shared/pi-args.ts"); +var _singleOutput = await jitiImport("../shared/single-output.ts"); +var _modelFallback = await jitiImport("../shared/model-fallback.ts"); + + + + +var _longRunningGuard = await jitiImport("../shared/long-running-guard.ts"); /** + * Core execution logic for running subagents + */ + + + + + + + + + +const artifactOutputByResult = new WeakMap(); + +function emptyUsage() { + return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 }; +} + +function sumUsage(target, source) { + target.input += source.input; + target.output += source.output; + target.cacheRead += source.cacheRead; + target.cacheWrite += source.cacheWrite; + target.cost += source.cost; + target.turns += source.turns; +} + +function appendRecentOutput(progress, lines) { + if (lines.length === 0) return; + progress.recentOutput.push(...lines.filter((line) => line.trim())); + if (progress.recentOutput.length > 50) { + progress.recentOutput.splice(0, progress.recentOutput.length - 50); + } +} + +function snapshotProgress(progress) { + return { + ...progress, + skills: progress.skills ? [...progress.skills] : undefined, + recentTools: progress.recentTools.map((tool) => ({ ...tool })), + recentOutput: [...progress.recentOutput] + }; +} + +function snapshotResult(result, progress) { + return { + ...result, + messages: result.outputMode === "file-only" && result.savedOutputPath ? undefined : result.messages ? [...result.messages] : undefined, + usage: { ...result.usage }, + skills: result.skills ? [...result.skills] : undefined, + attemptedModels: result.attemptedModels ? [...result.attemptedModels] : undefined, + modelAttempts: result.modelAttempts ? + result.modelAttempts.map((attempt) => ({ + ...attempt, + usage: attempt.usage ? { ...attempt.usage } : undefined + })) : + undefined, + controlEvents: result.controlEvents ? result.controlEvents.map((event) => ({ ...event })) : undefined, + progress, + progressSummary: result.progressSummary ? { ...result.progressSummary } : undefined, + artifactPaths: result.artifactPaths ? { ...result.artifactPaths } : undefined, + truncation: result.truncation ? { ...result.truncation } : undefined, + outputReference: result.outputReference ? { ...result.outputReference } : undefined + }; +} + +async function runSingleAttempt( +runtimeCwd, +agent, +task, +model, +options, +shared) + + + + + + + + + +{ + const modelArg = (0, _piArgs.applyThinkingSuffix)(model, agent.thinking); + const { args, env: sharedEnv, tempDir } = (0, _piArgs.buildPiArgs)({ + baseArgs: ["--mode", "json", "-p"], + task, + sessionEnabled: shared.sessionEnabled, + sessionDir: options.sessionDir, + sessionFile: options.sessionFile, + model, + thinking: agent.thinking, + systemPromptMode: agent.systemPromptMode, + inheritProjectContext: agent.inheritProjectContext, + inheritSkills: agent.inheritSkills, + tools: agent.tools, + extensions: agent.extensions, + systemPrompt: shared.systemPrompt, + mcpDirectTools: agent.mcpDirectTools, + promptFileStem: agent.name, + intercomSessionName: options.intercomSessionName, + orchestratorIntercomTarget: options.orchestratorIntercomTarget, + runId: options.runId, + childAgentName: agent.name, + childIndex: options.index ?? 0 + }); + + const result = { + agent: agent.name, + task, + exitCode: 0, + messages: [], + usage: emptyUsage(), + model: modelArg, + artifactPaths: shared.artifactPaths, + skills: shared.resolvedSkillNames, + skillsWarning: shared.skillsWarning + }; + const startTime = Date.now(); + const controlConfig = options.controlConfig ?? _subagentControl.DEFAULT_CONTROL_CONFIG; + let interruptedByControl = false; + const allControlEvents = []; + let pendingControlEvents = []; + const emittedControlEventKeys = new Set(); + const emitControlEvent = (event) => { + if (!(0, _subagentControl.shouldNotifyControlEvent)(controlConfig, event)) return; + if (!(0, _subagentControl.claimControlNotification)(controlConfig, event, emittedControlEventKeys)) return; + allControlEvents.push(event); + pendingControlEvents.push(event); + options.onControlEvent?.(event); + }; + + const progress = { + index: options.index ?? 0, + agent: agent.name, + status: "running", + task, + skills: shared.resolvedSkillNames, + recentTools: [], + recentOutput: [...shared.attemptNotes], + toolCount: 0, + tokens: 0, + durationMs: 0, + lastActivityAt: startTime + }; + result.progress = progress; + const spawnEnv = { ...process.env, ...sharedEnv, ...(0, _types.getSubagentDepthEnv)(options.maxSubagentDepth) }; + let observedMutationAttempt = false; + + const exitCode = await new Promise((resolve) => { + const spawnSpec = (0, _piSpawn.getPiSpawnCommand)(args); + const proc = (0, _nodeChild_process.spawn)(spawnSpec.command, spawnSpec.args, { + cwd: options.cwd ?? runtimeCwd, + env: spawnEnv, + stdio: ["ignore", "pipe", "pipe"] + }); + const jsonlWriter = (0, _jsonlWriter.createJsonlWriter)(shared.jsonlPath, proc.stdout); + let buf = ""; + let processClosed = false; + let settled = false; + let detached = false; + let intercomStarted = false; + let removeAbortListener; + let removeInterruptListener; + let activityTimer; + + const detachForIntercom = () => { + detached = true; + processClosed = true; + result.detached = true; + result.detachedReason = "intercom coordination"; + progress.status = "detached"; + progress.durationMs = Date.now() - startTime; + result.progressSummary = { + toolCount: progress.toolCount, + tokens: progress.tokens, + durationMs: progress.durationMs + }; + finish(-2); + }; + + // If the child emits a terminal assistant stop but never exits, + // give it a short grace period to flush naturally, then clean it up. + const FINAL_STOP_GRACE_MS = 1000; + const HARD_KILL_MS = 3000; + let childExited = false; + let forcedTerminationSignal = false; + let cleanTerminalAssistantStopReceived = false; + let finalDrainTimer; + let finalHardKillTimer; + const clearFinalDrainTimers = () => { + if (finalDrainTimer) { + clearTimeout(finalDrainTimer); + finalDrainTimer = undefined; + } + if (finalHardKillTimer) { + clearTimeout(finalHardKillTimer); + finalHardKillTimer = undefined; + } + }; + const startFinalDrain = () => { + if (childExited || finalDrainTimer || settled || processClosed || detached) return; + finalDrainTimer = setTimeout(() => { + if (settled || processClosed || detached) return; + const termSent = (0, _postExitStdioGuard.trySignalChild)(proc, "SIGTERM"); + if (!termSent) return; + forcedTerminationSignal = true; + if (!cleanTerminalAssistantStopReceived) { + result.error = result.error ?? `Subagent process did not exit within ${FINAL_STOP_GRACE_MS}ms after its final message. Forcing termination.`; + } + finalHardKillTimer = setTimeout(() => { + if (settled || processClosed || detached) return; + forcedTerminationSignal = (0, _postExitStdioGuard.trySignalChild)(proc, "SIGKILL") || forcedTerminationSignal; + }, HARD_KILL_MS); + finalHardKillTimer.unref?.(); + }, FINAL_STOP_GRACE_MS); + finalDrainTimer.unref?.(); + }; + + const unsubscribeIntercomDetach = options.intercomEvents?.on?.(_types.INTERCOM_DETACH_REQUEST_EVENT, (payload) => { + if (!options.allowIntercomDetach || detached || processClosed || !intercomStarted) return; + if (!payload || typeof payload !== "object") return; + const requestId = payload.requestId; + if (typeof requestId !== "string" || requestId.length === 0) return; + options.intercomEvents?.emit(_types.INTERCOM_DETACH_RESPONSE_EVENT, { requestId, accepted: true }); + detachForIntercom(); + }); + + const finish = (code) => { + if (settled) return; + settled = true; + clearFinalDrainTimers(); + clearStdioGuard(); + if (activityTimer) { + clearInterval(activityTimer); + activityTimer = undefined; + } + unsubscribeIntercomDetach?.(); + removeAbortListener?.(); + removeInterruptListener?.(); + resolve(code); + }; + + const drainPendingControlEvents = () => { + if (pendingControlEvents.length === 0) return undefined; + const events = pendingControlEvents; + pendingControlEvents = []; + return events; + }; + + let activeLongRunningNotified = false; + let pendingToolResult; + const mutatingFailures = (0, _longRunningGuard.createMutatingFailureState)(); + const mutatingFailureWindowMs = 5 * 60_000; + const currentToolDurationMs = (now) => progress.currentToolStartedAt ? Math.max(0, now - progress.currentToolStartedAt) : undefined; + const emitNeedsAttention = (now, input = {}) => { + if (!controlConfig.enabled) return false; + const previous = progress.activityState; + progress.activityState = "needs_attention"; + const event = (0, _subagentControl.buildControlEvent)({ + type: "needs_attention", + from: previous, + to: "needs_attention", + runId: options.runId, + agent: agent.name, + index: options.index, + ts: now, + lastActivityAt: progress.lastActivityAt, + message: input.message, + reason: input.reason ?? "idle", + turns: result.usage.turns, + tokens: progress.tokens, + toolCount: progress.toolCount, + currentTool: input.currentTool ?? progress.currentTool, + currentToolDurationMs: input.currentToolDurationMs ?? currentToolDurationMs(now), + currentPath: input.currentPath ?? progress.currentPath, + recentFailureSummary: input.recentFailureSummary + }); + emitControlEvent(event); + return previous !== "needs_attention"; + }; + const emitActiveLongRunning = (now, reason) => { + if (!controlConfig.enabled || activeLongRunningNotified || progress.activityState === "needs_attention") return false; + activeLongRunningNotified = true; + const previous = progress.activityState; + progress.activityState = "active_long_running"; + emitControlEvent((0, _subagentControl.buildControlEvent)({ + type: "active_long_running", + from: previous, + to: "active_long_running", + runId: options.runId, + agent: agent.name, + index: options.index, + ts: now, + message: `${agent.name} is still active but long-running`, + reason, + turns: result.usage.turns, + tokens: progress.tokens, + toolCount: progress.toolCount, + currentTool: progress.currentTool, + currentToolDurationMs: currentToolDurationMs(now), + currentPath: progress.currentPath, + elapsedMs: now - startTime + })); + return true; + }; + const updateActivityState = (now) => { + if (!controlConfig.enabled) return false; + const idleState = (0, _subagentControl.deriveActivityState)({ + config: controlConfig, + startedAt: startTime, + lastActivityAt: progress.lastActivityAt, + now + }); + if (idleState === "needs_attention") { + return progress.activityState === "needs_attention" ? false : emitNeedsAttention(now); + } + const activeReason = (0, _longRunningGuard.nextLongRunningTrigger)(controlConfig, { + startedAt: startTime, + now, + turns: result.usage.turns, + tokens: progress.tokens + }); + return activeReason ? emitActiveLongRunning(now, activeReason) : false; + }; + + + const emitUpdateSnapshot = (text) => { + if (!options.onUpdate || processClosed) return; + const progressSnapshot = snapshotProgress(progress); + const resultSnapshot = snapshotResult(result, progressSnapshot); + const controlEvents = drainPendingControlEvents(); + options.onUpdate({ + content: [{ type: "text", text }], + details: { + mode: "single", + results: [resultSnapshot], + progress: [progressSnapshot], + controlEvents + } + }); + }; + + const fireUpdate = () => { + if (!options.onUpdate || processClosed) return; + progress.durationMs = Date.now() - startTime; + emitUpdateSnapshot((0, _utils.getFinalOutput)(result.messages) || "(running...)"); + }; + + const processLine = (line) => { + if (!line.trim()) return; + jsonlWriter.writeLine(line); + let evt; + try { + evt = JSON.parse(line); + } catch { + // Non-JSON stdout lines are expected; only structured events are parsed. + return; + } + + const now = Date.now(); + progress.durationMs = now - startTime; + progress.lastActivityAt = now; + updateActivityState(now); + + if (evt.type === "tool_execution_start") { + const toolArgs = evt.args && typeof evt.args === "object" && !Array.isArray(evt.args) ? + evt.args : + {}; + if (options.allowIntercomDetach && (evt.toolName === "intercom" || evt.toolName === "contact_supervisor")) { + intercomStarted = true; + } + progress.toolCount++; + progress.currentTool = evt.toolName; + progress.currentToolArgs = (0, _utils.extractToolArgsPreview)(toolArgs); + progress.currentToolStartedAt = now; + progress.currentPath = (0, _longRunningGuard.resolveCurrentPath)(evt.toolName, toolArgs); + const mutates = (0, _longRunningGuard.isMutatingTool)(evt.toolName, toolArgs); + observedMutationAttempt = observedMutationAttempt || mutates; + pendingToolResult = { tool: evt.toolName ?? "tool", path: progress.currentPath, mutates, startedAt: now }; + fireUpdate(); + } + + if (evt.type === "tool_execution_end") { + if (progress.currentTool) { + progress.recentTools.push({ + tool: progress.currentTool, + args: progress.currentToolArgs || "", + endMs: now + }); + } + progress.currentTool = undefined; + progress.currentToolArgs = undefined; + progress.currentToolStartedAt = undefined; + progress.currentPath = undefined; + fireUpdate(); + } + + if (evt.type === "message_end" && evt.message) { + result.messages.push(evt.message); + if (evt.message.role === "assistant") { + result.usage.turns++; + progress.turnCount = result.usage.turns; + const u = evt.message.usage; + if (u) { + result.usage.input += u.input || 0; + result.usage.output += u.output || 0; + result.usage.cacheRead += u.cacheRead || 0; + result.usage.cacheWrite += u.cacheWrite || 0; + result.usage.cost += u.cost?.total || 0; + progress.tokens = result.usage.input + result.usage.output; + } + if (!result.model && evt.message.model) result.model = evt.message.model; + if (evt.message.errorMessage) result.error = evt.message.errorMessage; + appendRecentOutput(progress, (0, _utils.extractTextFromContent)(evt.message.content).split("\n").slice(-10)); + // Final assistant message: start the exit drain window. + const stopReason = evt.message.stopReason; + const hasToolCall = Array.isArray(evt.message.content) && + evt.message.content.some((part) => part.type === "toolCall"); + if (stopReason === "stop" && !hasToolCall) { + cleanTerminalAssistantStopReceived ||= !evt.message.errorMessage; + startFinalDrain(); + } + } + updateActivityState(now); + fireUpdate(); + } + + if (evt.type === "tool_result_end" && evt.message) { + result.messages.push(evt.message); + const resultText = (0, _utils.extractTextFromContent)(evt.message.content); + appendRecentOutput(progress, resultText.split("\n").slice(-10)); + const toolSnapshot = pendingToolResult; + pendingToolResult = undefined; + if (toolSnapshot?.mutates && (0, _longRunningGuard.didMutatingToolFail)(resultText)) { + (0, _longRunningGuard.recordMutatingFailure)(mutatingFailures, { + tool: toolSnapshot.tool, + path: toolSnapshot.path, + error: resultText.split("\n").find((line) => line.trim())?.trim().slice(0, 180) ?? "mutating tool failed", + ts: now + }, mutatingFailureWindowMs); + if ((0, _longRunningGuard.shouldEscalateMutatingFailures)(mutatingFailures, controlConfig.failedToolAttemptsBeforeAttention)) { + emitNeedsAttention(now, { + message: `${agent.name} needs attention after repeated mutating tool failures`, + reason: "tool_failures", + currentTool: toolSnapshot.tool, + currentPath: toolSnapshot.path, + currentToolDurationMs: toolSnapshot.startedAt ? Math.max(0, now - toolSnapshot.startedAt) : undefined, + recentFailureSummary: (0, _longRunningGuard.summarizeRecentMutatingFailures)(mutatingFailures) + }); + } + } else if (toolSnapshot?.mutates) { + (0, _longRunningGuard.resetMutatingFailureState)(mutatingFailures); + } + fireUpdate(); + } + }; + + if (controlConfig.enabled) { + activityTimer = setInterval(() => { + if (processClosed || settled || detached) return; + const now = Date.now(); + if (updateActivityState(now)) { + progress.durationMs = now - startTime; + fireUpdate(); + } + }, 1000); + activityTimer.unref?.(); + } + + let stderrBuf = ""; + + const clearStdioGuard = (0, _postExitStdioGuard.attachPostExitStdioGuard)(proc, { idleMs: 2000, hardMs: 8000 }); + proc.stdout.on("data", (d) => { + buf += d.toString(); + const lines = buf.split("\n"); + buf = lines.pop() || ""; + lines.forEach(processLine); + }); + proc.stderr.on("data", (d) => { + stderrBuf += d.toString(); + }); + proc.on("exit", () => { + childExited = true; + clearFinalDrainTimers(); + }); + proc.on("close", (code, signal) => { + clearFinalDrainTimers(); + clearStdioGuard(); + void jsonlWriter.close().catch(() => { + + // JSONL artifact flush is best effort. + });(0, _piArgs.cleanupTempDir)(tempDir); + if (detached) { + finish(-2); + return; + } + processClosed = true; + if (buf.trim()) processLine(buf); + const forcedDrainAfterFinalSuccess = forcedTerminationSignal && cleanTerminalAssistantStopReceived && !result.error; + if (code !== 0 && stderrBuf.trim() && !result.error && !forcedDrainAfterFinalSuccess) { + result.error = stderrBuf.trim(); + } + const finalCode = forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? code ?? 1 : code ?? 0; + finish(finalCode); + }); + proc.on("error", (error) => { + clearFinalDrainTimers(); + clearStdioGuard(); + void jsonlWriter.close().catch(() => { + + // JSONL artifact flush is best effort. + });(0, _piArgs.cleanupTempDir)(tempDir); + if (!result.error) { + result.error = error instanceof Error ? error.message : String(error); + } + finish(1); + }); + + if (options.signal) { + const kill = () => { + if (processClosed || detached) return; + if (options.allowIntercomDetach && intercomStarted && !detached) { + detachForIntercom(); + return; + } + proc.kill("SIGTERM"); + setTimeout(() => !proc.killed && proc.kill("SIGKILL"), 3000); + }; + if (options.signal.aborted) kill();else + { + options.signal.addEventListener("abort", kill, { once: true }); + removeAbortListener = () => options.signal?.removeEventListener("abort", kill); + } + } + + if (options.interruptSignal) { + const interrupt = () => { + if (processClosed || detached || settled) return; + interruptedByControl = true; + progress.status = "running"; + progress.durationMs = Date.now() - startTime; + result.interrupted = true; + result.finalOutput = "Interrupted. Waiting for explicit next action."; + progress.activityState = undefined; + fireUpdate(); + (0, _postExitStdioGuard.trySignalChild)(proc, "SIGINT"); + setTimeout(() => { + if (settled || processClosed || detached) return; + (0, _postExitStdioGuard.trySignalChild)(proc, "SIGTERM"); + }, 1000).unref?.(); + }; + if (options.interruptSignal.aborted) interrupt();else + { + options.interruptSignal.addEventListener("abort", interrupt, { once: true }); + removeInterruptListener = () => options.interruptSignal?.removeEventListener("abort", interrupt); + } + } + }); + result.exitCode = exitCode; + if (interruptedByControl) { + result.exitCode = 0; + result.interrupted = true; + result.error = undefined; + result.finalOutput = result.finalOutput || "Interrupted. Waiting for explicit next action."; + result.controlEvents = allControlEvents.length ? allControlEvents : undefined; + progress.activityState = undefined; + progress.durationMs = Date.now() - startTime; + result.progressSummary = { + toolCount: progress.toolCount, + tokens: progress.tokens, + durationMs: progress.durationMs + }; + return result; + } + if (result.detached) { + result.exitCode = 0; + result.finalOutput = "Detached for intercom coordination."; + return result; + } + + if (result.error && result.exitCode === 0) { + result.exitCode = 1; + } + if (result.exitCode === 0 && !result.error) { + const errInfo = (0, _utils.detectSubagentError)(result.messages); + if (errInfo.hasError) { + result.exitCode = errInfo.exitCode ?? 1; + result.error = errInfo.details ? + `${errInfo.errorType} failed (exit ${errInfo.exitCode}): ${errInfo.details}` : + `${errInfo.errorType} failed with exit code ${errInfo.exitCode}`; + } + } + + progress.status = result.exitCode === 0 ? "completed" : "failed"; + progress.durationMs = Date.now() - startTime; + if (result.error) { + progress.error = result.error; + if (progress.currentTool) { + progress.failedTool = progress.currentTool; + } + } + + result.progressSummary = { + toolCount: progress.toolCount, + tokens: progress.tokens, + durationMs: progress.durationMs + }; + + let fullOutput = (0, _utils.getFinalOutput)(result.messages); + const completionGuard = result.exitCode === 0 && !result.error ? + (0, _completionGuard.evaluateCompletionMutationGuard)({ agent: agent.name, task, messages: result.messages }) : + undefined; + if (completionGuard?.triggered && !observedMutationAttempt) { + result.exitCode = 1; + result.error = "Subagent completed without making edits for an implementation task.\nIt appears to have returned planning or scratchpad output instead of applying changes."; + progress.status = "failed"; + progress.error = result.error; + emitControlEvent((0, _subagentControl.buildControlEvent)({ + from: progress.activityState, + to: "needs_attention", + runId: options.runId ?? agent.name, + agent: agent.name, + index: options.index, + ts: Date.now(), + message: `${agent.name} completed without making edits for an implementation task`, + reason: "completion_guard" + })); + } + if (options.outputPath && result.exitCode === 0) { + const resolvedOutput = (0, _singleOutput.resolveSingleOutput)(options.outputPath, fullOutput, shared.outputSnapshot); + fullOutput = resolvedOutput.fullOutput; + result.savedOutputPath = resolvedOutput.savedPath; + result.outputSaveError = resolvedOutput.saveError; + if (resolvedOutput.savedPath) { + result.outputReference = (0, _singleOutput.formatSavedOutputReference)(resolvedOutput.savedPath, fullOutput); + } + } + artifactOutputByResult.set(result, fullOutput); + result.outputMode = options.outputMode ?? "inline"; + result.finalOutput = options.outputMode === "file-only" && result.savedOutputPath && result.outputReference ? + result.outputReference.message : + fullOutput; + result.controlEvents = allControlEvents.length ? allControlEvents : undefined; + if (options.onUpdate) { + const finalText = result.finalOutput || result.error || "(no output)"; + const progressSnapshot = snapshotProgress(progress); + const resultSnapshot = snapshotResult(result, progressSnapshot); + options.onUpdate({ + content: [{ type: "text", text: finalText }], + details: { + mode: "single", + results: [resultSnapshot], + progress: [progressSnapshot], + controlEvents: allControlEvents.length ? allControlEvents : undefined + } + }); + } + return result; +} + +/** + * Run a subagent synchronously (blocking until complete) + */ +async function runSync( +runtimeCwd, +agents, +agentName, +task, +options) +{ + const agent = agents.find((a) => a.name === agentName); + if (!agent) { + return { + agent: agentName, + task, + exitCode: 1, + messages: [], + usage: emptyUsage(), + error: `Unknown agent: ${agentName}` + }; + } + const outputModeValidationError = (0, _singleOutput.validateFileOnlyOutputMode)(options.outputMode, options.outputPath, `Single run (${agentName})`); + if (outputModeValidationError) { + return { + agent: agentName, + task, + exitCode: 1, + messages: [], + usage: emptyUsage(), + outputMode: options.outputMode, + error: outputModeValidationError + }; + } + + const shareEnabled = options.share === true; + const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled; + const skillNames = options.skills ?? agent.skills ?? []; + const skillCwd = options.cwd ?? runtimeCwd; + const { resolved: resolvedSkills, missing: missingSkills } = (0, _skills.resolveSkillsWithFallback)(skillNames, skillCwd, runtimeCwd); + if (skillNames.some((skill) => skill.trim() === "pi-subagents") && missingSkills.includes("pi-subagents")) { + return { + agent: agentName, + task, + exitCode: 1, + messages: [], + usage: emptyUsage(), + error: "Skills not found: pi-subagents" + }; + } + let systemPrompt = agent.systemPrompt?.trim() || ""; + if (resolvedSkills.length > 0) { + const skillInjection = (0, _skills.buildSkillInjection)(resolvedSkills); + systemPrompt = systemPrompt ? `${systemPrompt}\n\n${skillInjection}` : skillInjection; + } + + const candidates = (0, _modelFallback.buildModelCandidates)( + options.modelOverride ?? agent.model, + agent.fallbackModels, + options.availableModels, + options.preferredModelProvider + ); + const attemptedModels = []; + const modelAttempts = []; + const aggregateUsage = emptyUsage(); + const attemptNotes = []; + let totalToolCount = 0; + let totalDurationMs = 0; + + let artifactPathsResult; + let jsonlPath; + if (options.artifactsDir && options.artifactConfig?.enabled !== false) { + artifactPathsResult = (0, _artifacts.getArtifactPaths)(options.artifactsDir, options.runId, agentName, options.index); + (0, _artifacts.ensureArtifactsDir)(options.artifactsDir); + if (options.artifactConfig?.includeInput !== false) { + (0, _artifacts.writeArtifact)(artifactPathsResult.inputPath, `# Task for ${agentName}\n\n${task}`); + } + if (options.artifactConfig?.includeJsonl !== false) { + jsonlPath = artifactPathsResult.jsonlPath; + } + } + + let lastResult; + const modelsToTry = candidates.length > 0 ? candidates : [undefined]; + for (let i = 0; i < modelsToTry.length; i++) { + const candidate = modelsToTry[i]; + if (candidate) attemptedModels.push(candidate); + const outputSnapshot = (0, _singleOutput.captureSingleOutputSnapshot)(options.outputPath); + const result = await runSingleAttempt(runtimeCwd, agent, task, candidate, options, { + sessionEnabled, + systemPrompt, + resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined, + skillsWarning: missingSkills.length > 0 ? `Skills not found: ${missingSkills.join(", ")}` : undefined, + jsonlPath, + artifactPaths: artifactPathsResult, + attemptNotes, + outputSnapshot + }); + lastResult = result; + sumUsage(aggregateUsage, result.usage); + totalToolCount += result.progressSummary?.toolCount ?? 0; + totalDurationMs += result.progressSummary?.durationMs ?? 0; + const attemptSucceeded = result.exitCode === 0 && !result.error; + const attempt = { + model: candidate ?? result.model ?? agent.model ?? "default", + success: attemptSucceeded, + exitCode: result.exitCode, + error: result.error, + usage: { ...result.usage } + }; + modelAttempts.push(attempt); + if (attemptSucceeded) { + break; + } + if (!(0, _modelFallback.isRetryableModelFailure)(result.error) || i === modelsToTry.length - 1) { + break; + } + attemptNotes.push((0, _modelFallback.formatModelAttemptNote)(attempt, modelsToTry[i + 1])); + } + + const result = lastResult ?? { + agent: agentName, + task, + exitCode: 1, + messages: [], + usage: emptyUsage(), + error: "Subagent did not produce a result." + }; + + result.usage = aggregateUsage; + result.attemptedModels = attemptedModels.length > 0 ? attemptedModels : undefined; + result.modelAttempts = modelAttempts.length > 0 ? modelAttempts : undefined; + result.progressSummary = { + toolCount: totalToolCount, + tokens: aggregateUsage.input + aggregateUsage.output, + durationMs: totalDurationMs + }; + if (attemptNotes.length > 0 && result.progress) { + result.progress.recentOutput = [...attemptNotes, ...result.progress.recentOutput]; + if (result.progress.recentOutput.length > 50) { + result.progress.recentOutput.splice(50); + } + } + + if (artifactPathsResult && options.artifactConfig?.enabled !== false) { + result.artifactPaths = artifactPathsResult; + if (options.artifactConfig?.includeOutput !== false) { + (0, _artifacts.writeArtifact)(artifactPathsResult.outputPath, artifactOutputByResult.get(result) ?? result.finalOutput ?? ""); + } + if (options.artifactConfig?.includeMetadata !== false) { + (0, _artifacts.writeMetadata)(artifactPathsResult.metadataPath, { + runId: options.runId, + agent: agentName, + task, + exitCode: result.exitCode, + usage: result.usage, + model: result.model, + attemptedModels: result.attemptedModels, + modelAttempts: result.modelAttempts, + durationMs: result.progressSummary?.durationMs, + toolCount: result.progressSummary?.toolCount, + error: result.error, + skills: result.skills, + skillsWarning: result.skillsWarning, + timestamp: Date.now() + }); + } + + if (options.maxOutput) { + const config = { ..._types.DEFAULT_MAX_OUTPUT, ...options.maxOutput }; + const truncationResult = (0, _types.truncateOutput)(result.finalOutput ?? "", config, artifactPathsResult.outputPath); + if (truncationResult.truncated) result.truncation = truncationResult; + } + } else if (options.maxOutput) { + const config = { ..._types.DEFAULT_MAX_OUTPUT, ...options.maxOutput }; + const truncationResult = (0, _types.truncateOutput)(result.finalOutput ?? "", config); + if (truncationResult.truncated) result.truncation = truncationResult; + } + + if (options.sessionFile && ((0, _nodeFs.existsSync)(options.sessionFile) || result.messages?.length)) { + result.sessionFile = options.sessionFile; + } else if (shareEnabled && options.sessionDir) { + const sessionFile = (0, _utils.findLatestSessionFile)(options.sessionDir); + if (sessionFile) result.sessionFile = sessionFile; + } + + return result; +} /* v9-3f2610b246127400 */ diff --git a/pip-tmp/jiti/foreground-subagent-executor.425806a0.mjs b/pip-tmp/jiti/foreground-subagent-executor.425806a0.mjs new file mode 100644 index 0000000000000000000000000000000000000000..894bdebb77296d3f8b55dec8cfe5a43702c614f7 --- /dev/null +++ b/pip-tmp/jiti/foreground-subagent-executor.425806a0.mjs @@ -0,0 +1,2232 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.createSubagentExecutor = createSubagentExecutor;var _nodeCrypto = await jitiImport("node:crypto"); +var fs = _interopRequireWildcard(await jitiImport("node:fs")); +var path = _interopRequireWildcard(await jitiImport("node:path")); + + + +var _artifacts = await jitiImport("../../shared/artifacts.ts"); +var _chainClarify = await jitiImport("./chain-clarify.ts"); +var _modelInfo = await jitiImport("../../shared/model-info.ts"); +var _chainExecution = await jitiImport("./chain-execution.ts"); +var _agentScope = await jitiImport("../../agents/agent-scope.ts"); +var _agentManagement = await jitiImport("../../agents/agent-management.ts"); +var _doctor = await jitiImport("../../extension/doctor.ts"); +var _controlNotices = await jitiImport("../../extension/control-notices.ts"); +var _execution = await jitiImport("./execution.ts"); +var _modelFallback = await jitiImport("../shared/model-fallback.ts"); +var _parallelUtils = await jitiImport("../shared/parallel-utils.ts"); +var _runHistory = await jitiImport("../shared/run-history.ts"); +var _settings = await jitiImport("../../shared/settings.ts"); + + + + + + + + + + + + +var _skills = await jitiImport("../../agents/skills.ts"); +var _asyncExecution = await jitiImport("../background/async-execution.ts"); +var _forkContext = await jitiImport("../../shared/fork-context.ts"); +var _sessionIdentity = await jitiImport("../../shared/session-identity.ts"); +var _intercomBridge = await jitiImport("../../intercom/intercom-bridge.ts"); +var _subagentControl = await jitiImport("../shared/subagent-control.ts"); +var _singleOutput = await jitiImport("../shared/single-output.ts"); +var _utils = await jitiImport("../../shared/utils.ts"); +var _resultIntercom = await jitiImport("../../intercom/result-intercom.ts"); + + + + + + + +var _asyncResume = await jitiImport("../background/async-resume.ts"); +var _runStatus = await jitiImport("../background/run-status.ts"); +var _topLevelAsync = await jitiImport("../background/top-level-async.ts"); +var _worktree = await jitiImport("../shared/worktree.ts"); + + + + + + + + +var _types = await jitiImport("../../shared/types.ts");function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);} + + + + + + + + + + + + + + + + + + + + + + + + + +const ASYNC_INTERRUPT_SIGNAL = process.platform === "win32" ? "SIGBREAK" : "SIGUSR2"; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +function resolveRequestedCwd(runtimeCwd, requestedCwd) { + return requestedCwd ? path.resolve(runtimeCwd, requestedCwd) : runtimeCwd; +} + +function getForegroundControl(state, runId) { + if (runId) return state.foregroundControls.get(runId); + if (state.lastForegroundControlId) { + const latest = state.foregroundControls.get(state.lastForegroundControlId); + if (latest) return latest; + } + let newest; + for (const control of state.foregroundControls.values()) { + if (!newest || control.updatedAt > newest.updatedAt) newest = control; + } + return newest; +} + +function formatForegroundActivity(control) { + const facts = []; + if (control.currentTool && control.currentToolStartedAt) facts.push(`tool ${control.currentTool} for ${Math.floor(Math.max(0, Date.now() - control.currentToolStartedAt) / 1000)}s`);else + if (control.currentTool) facts.push(`tool ${control.currentTool}`); + if (control.currentPath) facts.push(`path ${control.currentPath}`); + if (control.turnCount !== undefined) facts.push(`${control.turnCount} turns`); + if (control.tokens !== undefined) facts.push(`${control.tokens} tokens`); + if (control.toolCount !== undefined) facts.push(`${control.toolCount} tools`); + if (!control.lastActivityAt) { + if (control.currentActivityState === "needs_attention") return ["needs attention", ...facts].join(" | "); + if (control.currentActivityState === "active_long_running") return ["active but long-running", ...facts].join(" | "); + return facts.length ? facts.join(" | ") : undefined; + } + const seconds = Math.floor(Math.max(0, Date.now() - control.lastActivityAt) / 1000); + if (control.currentActivityState === "needs_attention") return [`no activity for ${seconds}s`, ...facts].join(" | "); + if (control.currentActivityState === "active_long_running") return [`active but long-running; last activity ${seconds}s ago`, ...facts].join(" | "); + return [`active ${seconds}s ago`, ...facts].join(" | "); +} + +function foregroundStatusResult(control) { + const activity = formatForegroundActivity(control); + const lines = [ + `Run: ${control.runId}`, + "State: running", + `Mode: ${control.mode}`, + control.currentAgent ? `Current: ${control.currentAgent}${control.currentIndex !== undefined ? ` step ${control.currentIndex + 1}` : ""}` : undefined, + activity ? `Activity: ${activity}` : undefined]. + filter((line) => Boolean(line)); + return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "management", results: [] } }; +} + +function rememberForegroundRun(state, input) { + state.foregroundRuns ??= new Map(); + state.foregroundRuns.set(input.runId, { + runId: input.runId, + mode: input.mode, + cwd: input.cwd, + updatedAt: Date.now(), + children: input.results.map((result, index) => ({ + agent: result.agent, + index, + status: (0, _resultIntercom.resolveSubagentResultStatus)({ exitCode: result.exitCode, interrupted: result.interrupted, detached: result.detached }), + ...(result.sessionFile ? { sessionFile: result.sessionFile } : {}) + })) + }); + while (state.foregroundRuns.size > 50) { + const oldest = [...state.foregroundRuns.values()].sort((left, right) => left.updatedAt - right.updatedAt)[0]; + if (!oldest) break; + state.foregroundRuns.delete(oldest.runId); + } +} + +function resolveForegroundResumeTarget(params, state) { + const requested = (params.id ?? params.runId)?.trim(); + if (!requested || !state.foregroundRuns?.size) return undefined; + const direct = state.foregroundRuns.get(requested); + const matches = direct ? [direct] : [...state.foregroundRuns.values()].filter((run) => run.runId.startsWith(requested)); + if (matches.length === 0) return undefined; + if (matches.length > 1) throw new Error(`Ambiguous foreground run id prefix '${requested}' matched: ${matches.map((run) => run.runId).join(", ")}. Provide a longer id.`); + const run = matches[0]; + if (run.children.length > 1 && params.index === undefined) throw new Error(`Foreground run '${run.runId}' has ${run.children.length} children. Provide index to choose one.`); + const index = params.index ?? 0; + if (!Number.isInteger(index)) throw new Error(`Foreground run '${run.runId}' index must be an integer.`); + if (index < 0 || index >= run.children.length) throw new Error(`Foreground run '${run.runId}' has ${run.children.length} children. Index ${index} is out of range.`); + const child = run.children[index]; + if (child.status === "detached") throw new Error(`Foreground run '${run.runId}' child ${index} is detached for intercom coordination and cannot be revived safely from the remembered foreground state. Reply to the supervisor request first; after the child exits, start a fresh follow-up if needed.`); + if (!child.sessionFile) throw new Error(`Foreground run '${run.runId}' child ${index} does not have a persisted session file to resume from.`); + if (path.extname(child.sessionFile) !== ".jsonl") throw new Error(`Foreground run '${run.runId}' child ${index} session file must be a .jsonl file: ${child.sessionFile}`); + const sessionFile = path.resolve(child.sessionFile); + if (!fs.existsSync(sessionFile)) throw new Error(`Foreground run '${run.runId}' child ${index} session file does not exist: ${child.sessionFile}`); + return { runId: run.runId, mode: run.mode, state: "complete", agent: child.agent, index, intercomTarget: (0, _intercomBridge.resolveSubagentIntercomTarget)(run.runId, child.agent, index), cwd: run.cwd, sessionFile }; +} + + + + + +function isAsyncRunNotFound(error) { + return error instanceof Error && error.message.startsWith("Async run not found."); +} + +function isResumeAmbiguity(error) { + return error instanceof Error && /Ambiguous .*run id prefix/.test(error.message); +} + +function resumeTargetExact(target, requested) { + return target?.runId === requested; +} + +function escapeRegExp(value) { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +function isExactResumeError(error, source, requested) { + if (!(error instanceof Error) || !requested) return false; + return new RegExp(`\\b${source} run '${escapeRegExp(requested)}'`, "i").test(error.message); +} + +function resolveResumeTarget(params, state) { + const requested = (params.id ?? params.runId)?.trim() ?? ""; + let foregroundTarget; + let foregroundError; + let asyncTarget; + let asyncError; + + try { + const target = resolveForegroundResumeTarget(params, state); + if (target) foregroundTarget = { kind: "revive", source: "foreground", ...target }; + } catch (error) { + foregroundError = error; + } + try { + asyncTarget = { source: "async", ...(0, _asyncResume.resolveAsyncResumeTarget)(params) }; + } catch (error) { + asyncError = error; + } + + if (foregroundTarget && asyncTarget) { + const foregroundExact = resumeTargetExact(foregroundTarget, requested); + const asyncExact = resumeTargetExact(asyncTarget, requested); + if (foregroundExact && !asyncExact) return foregroundTarget; + if (asyncExact && !foregroundExact) return asyncTarget; + throw new Error(`Resume id '${requested}' is ambiguous between foreground run '${foregroundTarget.runId}' and async run '${asyncTarget.runId}'. Provide a full run id.`); + } + if (foregroundTarget) { + if (isExactResumeError(asyncError, "async", requested)) throw asyncError; + if (isResumeAmbiguity(asyncError) && !resumeTargetExact(foregroundTarget, requested)) throw asyncError; + return foregroundTarget; + } + if (asyncTarget) { + if (isExactResumeError(foregroundError, "foreground", requested)) throw foregroundError; + if (isResumeAmbiguity(foregroundError) && !resumeTargetExact(asyncTarget, requested)) throw foregroundError; + return asyncTarget; + } + if (foregroundError && !isAsyncRunNotFound(asyncError)) throw foregroundError; + if (foregroundError) throw foregroundError; + if (asyncError) throw asyncError; + throw new Error("Run not found. Provide id or runId."); +} + +function getAsyncInterruptTarget(state, runId) { + if (runId) { + const direct = state.asyncJobs.get(runId); + if (direct) return { asyncId: direct.asyncId, asyncDir: direct.asyncDir }; + } + let newest; + for (const job of state.asyncJobs.values()) { + if (job.status !== "running") continue; + if (!newest || (job.updatedAt ?? 0) > newest.updatedAt) { + newest = { asyncId: job.asyncId, asyncDir: job.asyncDir, updatedAt: job.updatedAt ?? 0 }; + } + } + return newest ? { asyncId: newest.asyncId, asyncDir: newest.asyncDir } : undefined; +} + +function emitControlNotification(input) + + + + +{ + if (!(0, _subagentControl.shouldNotifyControlEvent)(input.controlConfig, input.event)) return; + const childIntercomTarget = input.intercomBridge.active ? + (0, _intercomBridge.resolveSubagentIntercomTarget)(input.event.runId, input.event.agent, input.event.index) : + undefined; + const payload = { + event: input.event, + source: "foreground", + childIntercomTarget, + noticeText: (0, _subagentControl.formatControlNoticeMessage)(input.event, childIntercomTarget) + }; + if (input.controlConfig.notifyChannels.includes("event")) { + input.pi.events.emit(_types.SUBAGENT_CONTROL_EVENT, payload); + } + if (input.event.type !== "active_long_running" && input.controlConfig.notifyChannels.includes("intercom") && input.intercomBridge.active && input.intercomBridge.orchestratorTarget) { + input.pi.events.emit(_types.SUBAGENT_CONTROL_INTERCOM_EVENT, { + ...payload, + to: input.intercomBridge.orchestratorTarget, + message: (0, _subagentControl.formatControlIntercomMessage)(input.event, childIntercomTarget) + }); + } +} + +function interruptAsyncRun(state, runId) { + const target = getAsyncInterruptTarget(state, runId); + if (!target) return null; + const status = (0, _utils.readStatus)(target.asyncDir); + if (!status || status.state !== "running" || typeof status.pid !== "number") { + return { + content: [{ type: "text", text: `No running async run with an interrupt-capable pid was found for '${runId ?? "current"}'.` }], + isError: true, + details: { mode: "management", results: [] } + }; + } + try { + process.kill(status.pid, ASYNC_INTERRUPT_SIGNAL); + const tracked = state.asyncJobs.get(target.asyncId); + if (tracked) { + tracked.activityState = undefined; + tracked.updatedAt = Date.now(); + } + return { + content: [{ type: "text", text: `Interrupt requested for async run ${target.asyncId}.` }], + details: { mode: "management", results: [] } + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { + content: [{ type: "text", text: `Failed to interrupt async run ${target.asyncId}: ${message}` }], + isError: true, + details: { mode: "management", results: [] } + }; + } +} + +async function resumeAsyncRun(input) + + + + +{ + const followUp = (input.params.message ?? input.params.task ?? "").trim(); + if (!followUp) { + return { + content: [{ type: "text", text: "action='resume' requires message." }], + isError: true, + details: { mode: "management", results: [] } + }; + } + + let target; + try { + target = resolveResumeTarget(input.params, input.deps.state); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } }; + } + + if (target.kind === "live") { + const delivered = await (0, _resultIntercom.deliverSubagentIntercomMessageEvent)( + input.deps.pi.events, + target.intercomTarget, + `Follow-up for async run ${target.runId} (${target.agent}):\n\n${followUp}`, + 500, + { source: "async-resume", runId: target.runId, agent: target.agent, index: target.index } + ); + if (delivered) { + return { + content: [{ type: "text", text: [`Delivered follow-up to live async child.`, `Run: ${target.runId}`, `Intercom target: ${target.intercomTarget}`].join("\n") }], + details: { mode: "management", results: [] } + }; + } + return { + content: [{ type: "text", text: [`Async child appears live but its intercom target is not registered.`, `Run: ${target.runId}`, `Intercom target: ${target.intercomTarget}`, `Wait for completion, then retry action='resume'.`].join("\n") }], + isError: true, + details: { mode: "management", results: [] } + }; + } + + const { blocked, depth, maxDepth } = (0, _types.checkSubagentDepth)(input.deps.config.maxSubagentDepth); + if (blocked) { + return { + content: [{ type: "text", text: `Nested subagent resume blocked (depth=${depth}, max=${maxDepth}). Complete the follow-up directly instead.` }], + isError: true, + details: { mode: "management", results: [] } + }; + } + + const parentSessionFile = input.ctx.sessionManager.getSessionFile() ?? null; + input.deps.state.currentSessionId = (0, _sessionIdentity.resolveCurrentSessionId)(input.ctx.sessionManager); + const effectiveCwd = target.cwd ?? input.requestCwd; + const scope = (0, _agentScope.resolveExecutionAgentScope)(input.params.agentScope); + const discoveredAgents = input.deps.discoverAgents(effectiveCwd, scope).agents; + const sessionName = (0, _intercomBridge.resolveIntercomSessionTarget)(input.deps.pi.getSessionName(), input.ctx.sessionManager.getSessionId()); + const intercomBridge = (0, _intercomBridge.resolveIntercomBridge)({ + config: input.deps.config.intercomBridge, + context: input.params.context, + orchestratorTarget: sessionName, + cwd: effectiveCwd + }); + const agents = intercomBridge.active ? + discoveredAgents.map((agent) => (0, _intercomBridge.applyIntercomBridgeToAgent)(agent, intercomBridge)) : + discoveredAgents; + const agentConfig = agents.find((agent) => agent.name === target.agent); + if (!agentConfig) { + return { + content: [{ type: "text", text: `Unknown agent for resume: ${target.agent}` }], + isError: true, + details: { mode: "management", results: [] } + }; + } + + const runId = (0, _nodeCrypto.randomUUID)().slice(0, 8); + const artifactConfig = { ..._types.DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false }; + const availableModels = input.ctx.modelRegistry.getAvailable().map(_modelInfo.toModelInfo); + const result = (0, _asyncExecution.executeAsyncSingle)(runId, { + agent: target.agent, + task: (0, _asyncResume.buildRevivedAsyncTask)(target, followUp), + agentConfig, + ctx: { + pi: input.deps.pi, + cwd: input.requestCwd, + currentSessionId: input.deps.state.currentSessionId, + currentModelProvider: input.ctx.model?.provider + }, + cwd: effectiveCwd, + maxOutput: input.params.maxOutput, + artifactsDir: input.deps.tempArtifactsDir, + artifactConfig, + shareEnabled: input.params.share === true, + sessionRoot: input.deps.getSubagentSessionRoot(parentSessionFile), + sessionFile: target.sessionFile, + maxSubagentDepth: (0, _types.resolveCurrentMaxSubagentDepth)(input.deps.config.maxSubagentDepth), + worktreeSetupHook: input.deps.config.worktreeSetupHook, + worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs, + controlConfig: (0, _subagentControl.resolveControlConfig)(input.deps.config.control, input.params.control), + controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined, + childIntercomTarget: intercomBridge.active ? (agent, index) => (0, _intercomBridge.resolveSubagentIntercomTarget)(runId, agent, index) : undefined, + availableModels + }); + if (result.isError) return result; + + const revivedId = result.details.asyncId ?? runId; + const revivedTarget = intercomBridge.active ? (0, _intercomBridge.resolveSubagentIntercomTarget)(revivedId, target.agent, 0) : undefined; + const sourceLabel = target.source === "foreground" ? "foreground" : "async"; + const lines = [ + `Revived ${sourceLabel} subagent from ${target.runId}.`, + `Revived run: ${revivedId}`, + `Agent: ${target.agent}`, + `Session: ${target.sessionFile}`, + result.details.asyncDir ? `Async dir: ${result.details.asyncDir}` : undefined, + revivedTarget ? `Intercom target: ${revivedTarget} (if registered)` : undefined, + `Status if needed: subagent({ action: "status", id: "${revivedId}" })`]. + filter((line) => Boolean(line)); + return { content: [{ type: "text", text: (0, _asyncExecution.formatAsyncStartedMessage)(lines.join("\n")) }], details: result.details }; +} + +function resultSummaryForIntercom(result) { + const output = (0, _utils.getSingleResultOutput)(result); + if (result.exitCode !== 0 && result.error) { + return output ? `${result.error}\n\nOutput:\n${output}` : result.error; + } + return output || result.error || "(no output)"; +} + +function createForegroundControlNotifier(data, deps) { + return (event) => emitControlNotification({ + pi: deps.pi, + controlConfig: data.controlConfig, + intercomBridge: data.intercomBridge, + event + }); +} + +async function emitForegroundResultIntercom(input) + + + + + + +{ + if (!input.intercomBridge.active || !input.intercomBridge.orchestratorTarget) return null; + const children = input.results.flatMap((result, index) => result.detached ? [] : [{ + agent: result.agent, + status: (0, _resultIntercom.resolveSubagentResultStatus)({ + exitCode: result.exitCode, + interrupted: result.interrupted, + detached: result.detached + }), + summary: resultSummaryForIntercom(result), + index, + artifactPath: result.artifactPaths?.outputPath, + sessionPath: result.sessionFile, + intercomTarget: (0, _intercomBridge.resolveSubagentIntercomTarget)(input.runId, result.agent, index) + }]); + if (children.length === 0) return null; + const payload = (0, _resultIntercom.buildSubagentResultIntercomPayload)({ + to: input.intercomBridge.orchestratorTarget, + runId: input.runId, + mode: input.mode, + source: "foreground", + children, + ...(typeof input.chainSteps === "number" ? { chainSteps: input.chainSteps } : {}) + }); + const delivered = await (0, _resultIntercom.deliverSubagentResultIntercomEvent)(input.pi.events, payload); + if (!delivered) return null; + return payload; +} + +async function maybeBuildForegroundIntercomReceipt(input) + + + + + +{ + const payload = await emitForegroundResultIntercom({ + pi: input.pi, + intercomBridge: input.intercomBridge, + runId: input.runId, + mode: input.mode, + results: input.details.results, + ...(typeof input.details.totalSteps === "number" ? { chainSteps: input.details.totalSteps } : {}) + }); + if (!payload) return null; + return { + text: (0, _resultIntercom.formatSubagentResultReceipt)({ mode: input.mode, runId: input.runId, payload }), + details: (0, _resultIntercom.stripDetailsOutputsForIntercomReceipt)(input.details) + }; +} + +function validateExecutionInput( +params, +agents, +hasChain, +hasTasks, +hasSingle, +allowClarifyTaskPrompt) +{ + if (Number(hasChain) + Number(hasTasks) + Number(hasSingle) !== 1) { + return { + content: [ + { + type: "text", + text: `Provide exactly one mode. Agents: ${agents.map((a) => a.name).join(", ") || "none"}` + }], + + isError: true, + details: { mode: "single", results: [] } + }; + } + + if (hasSingle && params.agent && !agents.find((agent) => agent.name === params.agent)) { + return { + content: [{ type: "text", text: `Unknown agent: ${params.agent}` }], + isError: true, + details: { mode: "single", results: [] } + }; + } + + if (hasTasks && params.tasks) { + for (let i = 0; i < params.tasks.length; i++) { + const task = params.tasks[i]; + if (!agents.find((agent) => agent.name === task.agent)) { + return { + content: [{ type: "text", text: `Unknown agent: ${task.agent} (task ${i + 1})` }], + isError: true, + details: { mode: "parallel", results: [] } + }; + } + } + } + + if (hasChain && params.chain) { + if (params.chain.length === 0) { + return { + content: [{ type: "text", text: "Chain must have at least one step" }], + isError: true, + details: { mode: "chain", results: [] } + }; + } + const firstStep = params.chain[0]; + if ((0, _settings.isParallelStep)(firstStep)) { + const missingTaskIndex = firstStep.parallel.findIndex((t) => !t.task); + if (missingTaskIndex !== -1) { + return { + content: [{ type: "text", text: `First parallel step: task ${missingTaskIndex + 1} must have a task (no previous output to reference)` }], + isError: true, + details: { mode: "chain", results: [] } + }; + } + } else if (!firstStep.task && !params.task && !allowClarifyTaskPrompt) { + return { + content: [{ type: "text", text: "First step in chain must have a task" }], + isError: true, + details: { mode: "chain", results: [] } + }; + } + for (let i = 0; i < params.chain.length; i++) { + const step = params.chain[i]; + const stepAgents = (0, _settings.getStepAgents)(step); + for (const agentName of stepAgents) { + if (!agents.find((a) => a.name === agentName)) { + return { + content: [{ type: "text", text: `Unknown agent: ${agentName} (step ${i + 1})` }], + isError: true, + details: { mode: "chain", results: [] } + }; + } + } + if ((0, _settings.isParallelStep)(step) && step.parallel.length === 0) { + return { + content: [{ type: "text", text: `Parallel step ${i + 1} must have at least one task` }], + isError: true, + details: { mode: "chain", results: [] } + }; + } + } + } + + return null; +} + +function getRequestedModeLabel(params) { + if ((params.chain?.length ?? 0) > 0) return "chain"; + if ((params.tasks?.length ?? 0) > 0) return "parallel"; + if (params.agent) return "single"; + return "single"; +} + +function applyAgentDefaultContext(params, agents) { + if (params.context !== undefined) return params; + const byName = new Map(agents.map((agent) => [agent.name, agent])); + const names = []; + if (params.agent) names.push(params.agent); + for (const task of params.tasks ?? []) names.push(task.agent); + for (const step of params.chain ?? []) names.push(...(0, _settings.getStepAgents)(step)); + return names.some((name) => byName.get(name)?.defaultContext === "fork") ? + { ...params, context: "fork" } : + params; +} + +function buildRequestedModeError(params, message) { + return withForkContext( + { + content: [{ type: "text", text: message }], + isError: true, + details: { mode: getRequestedModeLabel(params), results: [] } + }, + params.context + ); +} + +function expandTopLevelTaskCounts(tasks) { + const expanded = []; + for (let taskIndex = 0; taskIndex < tasks.length; taskIndex++) { + const task = tasks[taskIndex]; + const rawCount = task.count; + if (rawCount !== undefined && (typeof rawCount !== "number" || !Number.isInteger(rawCount) || rawCount < 1)) { + return { error: `tasks[${taskIndex}].count must be an integer >= 1` }; + } + const { count, ...concreteTask } = task; + for (let repeat = 0; repeat < (rawCount ?? 1); repeat++) { + expanded.push({ ...concreteTask }); + } + } + return { tasks: expanded }; +} + +function expandChainParallelCounts(chain) { + const expandedChain = []; + for (let stepIndex = 0; stepIndex < chain.length; stepIndex++) { + const step = chain[stepIndex]; + if (!(0, _settings.isParallelStep)(step)) { + expandedChain.push(step); + continue; + } + const expandedParallel = []; + for (let taskIndex = 0; taskIndex < step.parallel.length; taskIndex++) { + const task = step.parallel[taskIndex]; + const rawCount = task.count; + if (rawCount !== undefined && (typeof rawCount !== "number" || !Number.isInteger(rawCount) || rawCount < 1)) { + return { error: `chain[${stepIndex}].parallel[${taskIndex}].count must be an integer >= 1` }; + } + const { count, ...concreteTask } = task; + for (let repeat = 0; repeat < (rawCount ?? 1); repeat++) { + expandedParallel.push({ ...concreteTask }); + } + } + expandedChain.push({ ...step, parallel: expandedParallel }); + } + return { chain: expandedChain }; +} + +function normalizeRepeatedParallelCounts(params) { + if (params.tasks) { + const expandedTasks = expandTopLevelTaskCounts(params.tasks); + if (expandedTasks.error) { + return { error: buildRequestedModeError(params, expandedTasks.error) }; + } + return { params: { ...params, tasks: expandedTasks.tasks } }; + } + if (params.chain) { + const expandedChain = expandChainParallelCounts(params.chain); + if (expandedChain.error) { + return { error: buildRequestedModeError(params, expandedChain.error) }; + } + return { params: { ...params, chain: expandedChain.chain } }; + } + return { params }; +} + +function withForkContext( +result, +context) +{ + if (context !== "fork" || !result.details) return result; + return { + ...result, + details: { + ...result.details, + context: "fork" + } + }; +} + +function toExecutionErrorResult(params, error) { + const message = error instanceof Error ? error.message : String(error); + return withForkContext( + { + content: [{ type: "text", text: message }], + isError: true, + details: { mode: getRequestedModeLabel(params), results: [] } + }, + params.context + ); +} + +function collectChainSessionFiles( +chain, +sessionFileForIndex) +{ + const sessionFiles = []; + let flatIndex = 0; + for (const step of chain) { + if ((0, _settings.isParallelStep)(step)) { + for (let i = 0; i < step.parallel.length; i++) { + sessionFiles.push(sessionFileForIndex(flatIndex)); + flatIndex++; + } + continue; + } + sessionFiles.push(sessionFileForIndex(flatIndex)); + flatIndex++; + } + return sessionFiles; +} + +function wrapChainTasksForFork(chain, context) { + if (context !== "fork") return chain; + return chain.map((step, stepIndex) => { + if ((0, _settings.isParallelStep)(step)) { + return { + ...step, + parallel: step.parallel.map((task) => ({ + ...task, + task: (0, _types.wrapForkTask)(task.task ?? "{previous}") + })) + }; + } + const sequential = step; + return { + ...sequential, + task: (0, _types.wrapForkTask)(sequential.task ?? (stepIndex === 0 ? "{task}" : "{previous}")) + }; + }); +} + +function runAsyncPath(data, deps) { + const { + params, + effectiveCwd, + agents, + ctx, + shareEnabled, + sessionRoot, + sessionFileForIndex, + artifactConfig, + artifactsDir, + effectiveAsync, + controlConfig, + intercomBridge + } = data; + const hasChain = (params.chain?.length ?? 0) > 0; + const hasTasks = (params.tasks?.length ?? 0) > 0; + const hasSingle = !hasChain && !hasTasks && Boolean(params.agent); + if (!effectiveAsync) return null; + + if (hasChain && params.chain) { + const chainWorktreeTaskCwdError = buildChainWorktreeTaskCwdError(params.chain, effectiveCwd); + if (chainWorktreeTaskCwdError) { + return { + content: [{ type: "text", text: chainWorktreeTaskCwdError }], + isError: true, + details: { mode: "chain", results: [] } + }; + } + } + + if (hasTasks && params.tasks) { + const maxParallelTasks = (0, _types.resolveTopLevelParallelMaxTasks)(deps.config.parallel?.maxTasks); + if (params.tasks.length > maxParallelTasks) { + return buildParallelModeError(`Max ${maxParallelTasks} tasks`); + } + if (params.worktree) { + const worktreeTaskCwdError = buildParallelWorktreeTaskCwdError(params.tasks, effectiveCwd); + if (worktreeTaskCwdError) return buildParallelModeError(worktreeTaskCwdError); + } + } + + if (!(0, _asyncExecution.isAsyncAvailable)()) { + return { + content: [{ type: "text", text: "Async mode requires jiti for TypeScript execution but it could not be found. Install globally: npm install -g jiti" }], + isError: true, + details: { mode: "single", results: [] } + }; + } + const id = (0, _nodeCrypto.randomUUID)(); + const asyncCtx = { + pi: deps.pi, + cwd: ctx.cwd, + currentSessionId: deps.state.currentSessionId, + currentModelProvider: ctx.model?.provider + }; + const availableModels = ctx.modelRegistry.getAvailable().map(_modelInfo.toModelInfo); + const currentMaxSubagentDepth = (0, _types.resolveCurrentMaxSubagentDepth)(deps.config.maxSubagentDepth); + const currentProvider = ctx.model?.provider; + const controlIntercomTarget = intercomBridge.active ? intercomBridge.orchestratorTarget : undefined; + const childIntercomTarget = intercomBridge.active ? (agent, index) => (0, _intercomBridge.resolveSubagentIntercomTarget)(id, agent, index) : undefined; + + if (hasTasks && params.tasks) { + const agentConfigs = params.tasks.map((task) => agents.find((agent) => agent.name === task.agent)); + const modelOverrides = params.tasks.map((task, index) => + (0, _modelFallback.resolveModelCandidate)(task.model ?? agentConfigs[index]?.model, availableModels, currentProvider) + ); + const skillOverrides = params.tasks.map((task) => (0, _skills.normalizeSkillInput)(task.skill)); + const parallelTasks = params.tasks.map((task, index) => ({ + agent: task.agent, + task: params.context === "fork" ? (0, _types.wrapForkTask)(task.task) : task.task, + cwd: task.cwd, + ...(modelOverrides[index] ? { model: modelOverrides[index] } : {}), + ...(skillOverrides[index] !== undefined ? { skill: skillOverrides[index] } : {}), + ...(task.output === true ? agentConfigs[index]?.output ? { output: agentConfigs[index].output } : {} : task.output !== undefined ? { output: task.output } : {}), + ...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}), + ...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}), + ...(task.progress !== undefined ? { progress: task.progress } : {}) + })); + return (0, _asyncExecution.executeAsyncChain)(id, { + chain: [{ + parallel: parallelTasks, + concurrency: (0, _types.resolveTopLevelParallelConcurrency)(params.concurrency, deps.config.parallel?.concurrency), + worktree: params.worktree + }], + resultMode: "parallel", + agents, + ctx: asyncCtx, + availableModels, + cwd: effectiveCwd, + maxOutput: params.maxOutput, + artifactsDir: artifactConfig.enabled ? artifactsDir : undefined, + artifactConfig, + shareEnabled, + sessionRoot, + chainSkills: [], + sessionFilesByFlatIndex: params.tasks.map((_, index) => sessionFileForIndex(index)), + maxSubagentDepth: currentMaxSubagentDepth, + worktreeSetupHook: deps.config.worktreeSetupHook, + worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs, + controlConfig, + controlIntercomTarget, + childIntercomTarget + }); + } + + if (hasChain && params.chain) { + const normalized = (0, _skills.normalizeSkillInput)(params.skill); + const chainSkills = normalized === false ? [] : normalized ?? []; + const chain = wrapChainTasksForFork(params.chain, params.context); + return (0, _asyncExecution.executeAsyncChain)(id, { + chain, + task: params.task, + agents, + ctx: asyncCtx, + availableModels, + cwd: effectiveCwd, + maxOutput: params.maxOutput, + artifactsDir: artifactConfig.enabled ? artifactsDir : undefined, + artifactConfig, + shareEnabled, + sessionRoot, + chainSkills, + sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForIndex), + maxSubagentDepth: currentMaxSubagentDepth, + worktreeSetupHook: deps.config.worktreeSetupHook, + worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs, + controlConfig, + controlIntercomTarget, + childIntercomTarget + }); + } + + if (hasSingle) { + const a = agents.find((x) => x.name === params.agent); + if (!a) { + return { + content: [{ type: "text", text: `Unknown agent: ${params.agent}` }], + isError: true, + details: { mode: "single", results: [] } + }; + } + const rawOutput = params.output !== undefined ? params.output : a.output; + const effectiveOutput = rawOutput === true ? a.output : rawOutput; + const effectiveOutputMode = params.outputMode ?? "inline"; + const normalizedSkills = (0, _skills.normalizeSkillInput)(params.skill); + const skills = normalizedSkills === false ? [] : normalizedSkills; + const maxSubagentDepth = (0, _types.resolveChildMaxSubagentDepth)(currentMaxSubagentDepth, a.maxSubagentDepth); + const modelOverride = (0, _modelFallback.resolveModelCandidate)(params.model ?? a.model, availableModels, currentProvider); + return (0, _asyncExecution.executeAsyncSingle)(id, { + agent: params.agent, + task: params.context === "fork" ? (0, _types.wrapForkTask)(params.task ?? "") : params.task ?? "", + agentConfig: a, + ctx: asyncCtx, + availableModels, + cwd: effectiveCwd, + maxOutput: params.maxOutput, + artifactsDir: artifactConfig.enabled ? artifactsDir : undefined, + artifactConfig, + shareEnabled, + sessionRoot, + sessionFile: sessionFileForIndex(0), + skills, + output: effectiveOutput, + outputMode: effectiveOutputMode, + modelOverride, + maxSubagentDepth, + worktreeSetupHook: deps.config.worktreeSetupHook, + worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs, + controlConfig, + controlIntercomTarget, + childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined + }); + } + + return null; +} + +async function runChainPath(data, deps) { + const { + params, + effectiveCwd, + agents, + ctx, + signal, + runId, + shareEnabled, + sessionDirForIndex, + sessionFileForIndex, + artifactsDir, + artifactConfig, + onUpdate, + sessionRoot, + controlConfig + } = data; + const onControlEvent = createForegroundControlNotifier(data, deps); + const childIntercomTarget = data.intercomBridge.active ? _intercomBridge.resolveSubagentIntercomTarget : undefined; + const foregroundControl = deps.state.foregroundControls.get(runId); + const normalized = (0, _skills.normalizeSkillInput)(params.skill); + const chainSkills = normalized === false ? [] : normalized ?? []; + const chain = wrapChainTasksForFork(params.chain, params.context); + const currentMaxSubagentDepth = (0, _types.resolveCurrentMaxSubagentDepth)(deps.config.maxSubagentDepth); + const chainResult = await (0, _chainExecution.executeChain)({ + chain, + task: params.task, + agents, + ctx, + intercomEvents: deps.pi.events, + signal, + runId, + cwd: effectiveCwd, + shareEnabled, + sessionDirForIndex, + sessionFileForIndex, + artifactsDir, + artifactConfig, + includeProgress: params.includeProgress, + clarify: params.clarify, + onUpdate, + onControlEvent, + controlConfig, + childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined, + orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined, + foregroundControl, + chainSkills, + chainDir: params.chainDir, + maxSubagentDepth: currentMaxSubagentDepth, + worktreeSetupHook: deps.config.worktreeSetupHook, + worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs + }); + + if (chainResult.requestedAsync) { + if (!(0, _asyncExecution.isAsyncAvailable)()) { + return { + content: [{ type: "text", text: "Background mode requires jiti for TypeScript execution but it could not be found." }], + isError: true, + details: { mode: "chain", results: [] } + }; + } + const id = (0, _nodeCrypto.randomUUID)(); + const asyncCtx = { + pi: deps.pi, + cwd: ctx.cwd, + currentSessionId: deps.state.currentSessionId, + currentModelProvider: ctx.model?.provider + }; + const asyncChain = wrapChainTasksForFork(chainResult.requestedAsync.chain, params.context); + return (0, _asyncExecution.executeAsyncChain)(id, { + chain: asyncChain, + task: params.task, + agents, + ctx: asyncCtx, + availableModels: ctx.modelRegistry.getAvailable().map(_modelInfo.toModelInfo), + cwd: effectiveCwd, + maxOutput: params.maxOutput, + artifactsDir: artifactConfig.enabled ? artifactsDir : undefined, + artifactConfig, + shareEnabled, + sessionRoot, + chainSkills: chainResult.requestedAsync.chainSkills, + sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForIndex), + maxSubagentDepth: currentMaxSubagentDepth, + worktreeSetupHook: deps.config.worktreeSetupHook, + worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs, + controlConfig, + controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined, + childIntercomTarget: data.intercomBridge.active ? (agent, index) => (0, _intercomBridge.resolveSubagentIntercomTarget)(id, agent, index) : undefined + }); + } + + const chainDetails = chainResult.details ? (0, _utils.compactForegroundDetails)({ ...chainResult.details, runId }) : undefined; + if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, results: chainDetails.results }); + const intercomReceipt = chainDetails && !chainDetails.results.some((result) => result.interrupted || result.detached) ? + await maybeBuildForegroundIntercomReceipt({ + pi: deps.pi, + intercomBridge: data.intercomBridge, + runId, + mode: "chain", + details: chainDetails + }) : + null; + if (intercomReceipt) { + return { + ...chainResult, + content: [{ type: "text", text: intercomReceipt.text }], + details: intercomReceipt.details + }; + } + + return chainDetails ? { ...chainResult, details: chainDetails } : chainResult; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +function buildParallelModeError(message) { + return { + content: [{ type: "text", text: message }], + isError: true, + details: { mode: "parallel", results: [] } + }; +} + +function createParallelWorktreeSetup( +enabled, +cwd, +runId, +tasks, +setupHook, +setupHookTimeoutMs) +{ + if (!enabled) return {}; + try { + return { + setup: (0, _worktree.createWorktrees)(cwd, runId, tasks.length, { + agents: tasks.map((task) => task.agent), + setupHook: setupHook ? + { hookPath: setupHook, timeoutMs: setupHookTimeoutMs } : + undefined + }) + }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { errorResult: buildParallelModeError(message) }; + } +} + +function buildParallelWorktreeTaskCwdError( +tasks, +sharedCwd) +{ + const conflict = (0, _worktree.findWorktreeTaskCwdConflict)(tasks, sharedCwd); + if (!conflict) return undefined; + return (0, _worktree.formatWorktreeTaskCwdConflict)(conflict, sharedCwd); +} + +function buildChainWorktreeTaskCwdError(chain, sharedCwd) { + for (let stepIndex = 0; stepIndex < chain.length; stepIndex++) { + const step = chain[stepIndex]; + if (!(0, _settings.isParallelStep)(step) || !step.worktree) continue; + const stepCwd = (0, _utils.resolveChildCwd)(sharedCwd, step.cwd); + const conflict = (0, _worktree.findWorktreeTaskCwdConflict)(step.parallel, stepCwd); + if (!conflict) continue; + const detail = (0, _worktree.formatWorktreeTaskCwdConflict)(conflict, stepCwd); + return `parallel chain step ${stepIndex + 1}: ${detail}`; + } + return undefined; +} + +function resolveParallelTaskCwd( +task, +paramsCwd, +worktreeSetup, +index) +{ + if (worktreeSetup) return worktreeSetup.worktrees[index].agentCwd; + return (0, _utils.resolveChildCwd)(paramsCwd, task.cwd); +} + +function buildParallelWorktreeSuffix( +worktreeSetup, +artifactsDir, +tasks) +{ + if (!worktreeSetup) return ""; + const diffsDir = path.join(artifactsDir, "worktree-diffs"); + const diffs = (0, _worktree.diffWorktrees)(worktreeSetup, tasks.map((task) => task.agent), diffsDir); + return (0, _worktree.formatWorktreeDiffSummary)(diffs); +} + +function findDuplicateParallelOutputPath(input) + + + + + +{ + const seen = new Map(); + for (let index = 0; index < input.tasks.length; index++) { + const behavior = input.behaviors[index]; + if (!behavior?.output) continue; + const task = input.tasks[index]; + const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index); + const outputPath = (0, _singleOutput.resolveSingleOutputPath)(behavior.output, input.ctxCwd, taskCwd); + if (!outputPath) continue; + const previous = seen.get(outputPath); + if (previous) { + return `Parallel tasks ${previous.index + 1} (${previous.agent}) and ${index + 1} (${task.agent}) resolve output to the same path: ${outputPath}. Use distinct output paths.`; + } + seen.set(outputPath, { index, agent: task.agent }); + } + return undefined; +} + +async function runForegroundParallelTasks(input) { + return (0, _utils.mapConcurrent)(input.tasks, input.concurrencyLimit, async (task, index) => { + const behavior = input.behaviors[index]; + const effectiveSkills = behavior?.skills; + const taskCwd = resolveParallelTaskCwd(task, input.paramsCwd, input.worktreeSetup, index); + const readInstructions = behavior ? + (0, _settings.buildChainInstructions)({ ...behavior, output: false, progress: false }, taskCwd, false) : + { prefix: "", suffix: "" }; + const progressInstructions = behavior ? + (0, _settings.buildChainInstructions)({ ...behavior, output: false, reads: false }, input.paramsCwd, index === input.firstProgressIndex) : + { prefix: "", suffix: "" }; + const outputPath = (0, _singleOutput.resolveSingleOutputPath)(behavior?.output, input.ctx.cwd, taskCwd); + const taskText = (0, _singleOutput.injectSingleOutputInstruction)( + `${readInstructions.prefix}${input.taskTexts[index]}${progressInstructions.suffix}`, + outputPath + ); + const interruptController = new AbortController(); + if (input.foregroundControl) { + input.foregroundControl.currentAgent = task.agent; + input.foregroundControl.currentIndex = index; + input.foregroundControl.currentActivityState = undefined; + input.foregroundControl.updatedAt = Date.now(); + input.foregroundControl.interrupt = () => { + if (interruptController.signal.aborted) return false; + interruptController.abort(); + input.foregroundControl.currentActivityState = undefined; + input.foregroundControl.updatedAt = Date.now(); + return true; + }; + } + const agentConfig = input.agents.find((agent) => agent.name === task.agent); + return (0, _execution.runSync)(input.ctx.cwd, input.agents, task.agent, taskText, { + cwd: taskCwd, + signal: input.signal, + interruptSignal: interruptController.signal, + allowIntercomDetach: agentConfig?.systemPrompt?.includes(_intercomBridge.INTERCOM_BRIDGE_MARKER) === true, + intercomEvents: input.intercomEvents, + runId: input.runId, + index, + sessionDir: input.sessionDirForIndex(index), + sessionFile: input.sessionFileForIndex(index), + share: input.shareEnabled, + artifactsDir: input.artifactConfig.enabled ? input.artifactsDir : undefined, + artifactConfig: input.artifactConfig, + maxOutput: input.maxOutput, + outputPath, + outputMode: behavior?.outputMode, + maxSubagentDepth: input.maxSubagentDepths[index], + controlConfig: input.controlConfig, + onControlEvent: input.onControlEvent, + intercomSessionName: input.childIntercomTarget?.(task.agent, index), + orchestratorIntercomTarget: input.orchestratorIntercomTarget, + modelOverride: input.modelOverrides[index], + availableModels: input.availableModels, + preferredModelProvider: input.ctx.model?.provider, + skills: effectiveSkills === false ? [] : effectiveSkills, + onUpdate: input.onUpdate ? + (progressUpdate) => { + const stepResults = progressUpdate.details?.results || []; + const stepProgress = progressUpdate.details?.progress || []; + if (input.foregroundControl && stepProgress.length > 0) { + const current = stepProgress[0]; + input.foregroundControl.currentAgent = task.agent; + input.foregroundControl.currentIndex = index; + input.foregroundControl.currentActivityState = current?.activityState; + input.foregroundControl.lastActivityAt = current?.lastActivityAt; + input.foregroundControl.currentTool = current?.currentTool; + input.foregroundControl.currentToolStartedAt = current?.currentToolStartedAt; + input.foregroundControl.currentPath = current?.currentPath; + input.foregroundControl.turnCount = current?.turnCount; + input.foregroundControl.tokens = current?.tokens; + input.foregroundControl.toolCount = current?.toolCount; + input.foregroundControl.updatedAt = Date.now(); + } + if (stepResults.length > 0) input.liveResults[index] = stepResults[0]; + if (stepProgress.length > 0) input.liveProgress[index] = stepProgress[0]; + const mergedResults = input.liveResults.filter((result) => result !== undefined); + const mergedProgress = input.liveProgress.filter((progress) => progress !== undefined); + input.onUpdate?.({ + content: progressUpdate.content, + details: { + mode: "parallel", + results: mergedResults, + progress: mergedProgress, + controlEvents: progressUpdate.details?.controlEvents, + totalSteps: input.tasks.length + } + }); + } : + undefined + }).finally(() => { + if (input.foregroundControl?.currentIndex === index) { + input.foregroundControl.interrupt = undefined; + input.foregroundControl.updatedAt = Date.now(); + } + }); + }); +} + +async function runParallelPath(data, deps) { + const { + params, + effectiveCwd, + agents, + ctx, + signal, + runId, + sessionDirForIndex, + sessionFileForIndex, + shareEnabled, + artifactConfig, + artifactsDir, + backgroundRequestedWhileClarifying, + onUpdate, + sessionRoot, + controlConfig + } = data; + const onControlEvent = createForegroundControlNotifier(data, deps); + const childIntercomTarget = data.intercomBridge.active ? _intercomBridge.resolveSubagentIntercomTarget : undefined; + const allProgress = []; + const allArtifactPaths = []; + const tasks = params.tasks; + const maxParallelTasks = (0, _types.resolveTopLevelParallelMaxTasks)(deps.config.parallel?.maxTasks); + const parallelConcurrency = (0, _types.resolveTopLevelParallelConcurrency)(params.concurrency, deps.config.parallel?.concurrency); + + if (tasks.length > maxParallelTasks) + return { + content: [{ type: "text", text: `Max ${maxParallelTasks} tasks` }], + isError: true, + details: { mode: "parallel", results: [] } + }; + + const agentConfigs = []; + for (const t of tasks) { + const config = agents.find((a) => a.name === t.agent); + if (!config) { + return { + content: [{ type: "text", text: `Unknown agent: ${t.agent}` }], + isError: true, + details: { mode: "parallel", results: [] } + }; + } + agentConfigs.push(config); + } + + const currentMaxSubagentDepth = (0, _types.resolveCurrentMaxSubagentDepth)(deps.config.maxSubagentDepth); + const maxSubagentDepths = agentConfigs.map((config) => + (0, _types.resolveChildMaxSubagentDepth)(currentMaxSubagentDepth, config.maxSubagentDepth) + ); + + if (params.worktree) { + const worktreeTaskCwdError = buildParallelWorktreeTaskCwdError(tasks, effectiveCwd); + if (worktreeTaskCwdError) return buildParallelModeError(worktreeTaskCwdError); + } + + const currentProvider = ctx.model?.provider; + const availableModels = ctx.modelRegistry.getAvailable().map(_modelInfo.toModelInfo); + let taskTexts = tasks.map((t) => t.task); + const skillOverrides = tasks.map((t) => + (0, _skills.normalizeSkillInput)(t.skill) + ); + const behaviorOverrides = tasks.map((task, index) => ({ + ...(task.output !== undefined ? { output: task.output === true ? agentConfigs[index]?.output ?? false : task.output } : {}), + ...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}), + ...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}), + ...(task.progress !== undefined ? { progress: task.progress } : {}), + ...(skillOverrides[index] !== undefined ? { skills: skillOverrides[index] } : {}), + ...(task.model ? { model: task.model } : {}) + })); + const modelOverrides = tasks.map((_, i) => + (0, _modelFallback.resolveModelCandidate)(behaviorOverrides[i]?.model ?? agentConfigs[i]?.model, availableModels, currentProvider) + ); + + if (params.clarify === true && ctx.hasUI) { + const behaviors = agentConfigs.map((c, i) => + (0, _settings.resolveStepBehavior)(c, behaviorOverrides[i]) + ); + const availableSkills = (0, _skills.discoverAvailableSkills)(effectiveCwd); + + const result = await ctx.ui.custom( + (tui, theme, _kb, done) => + new _chainClarify.ChainClarifyComponent( + tui, theme, + agentConfigs, + taskTexts, + "", + undefined, + behaviors, + availableModels, + currentProvider, + availableSkills, + done, + "parallel" + ), + { overlay: true, overlayOptions: { anchor: "center", width: 84, maxHeight: "80%" } } + ); + + if (!result || !result.confirmed) { + return { content: [{ type: "text", text: "Cancelled" }], details: { mode: "parallel", results: [] } }; + } + + taskTexts = result.templates; + for (let i = 0; i < result.behaviorOverrides.length; i++) { + const override = result.behaviorOverrides[i]; + if (override?.model) { + modelOverrides[i] = override.model; + behaviorOverrides[i].model = override.model; + } + if (override?.output !== undefined) behaviorOverrides[i].output = override.output; + if (override?.reads !== undefined) behaviorOverrides[i].reads = override.reads; + if (override?.progress !== undefined) behaviorOverrides[i].progress = override.progress; + if (override?.skills !== undefined) { + skillOverrides[i] = override.skills; + behaviorOverrides[i].skills = override.skills; + } + } + + if (result.runInBackground) { + if (!(0, _asyncExecution.isAsyncAvailable)()) { + return { + content: [{ type: "text", text: "Background mode requires jiti for TypeScript execution but it could not be found." }], + isError: true, + details: { mode: "parallel", results: [] } + }; + } + const id = (0, _nodeCrypto.randomUUID)(); + const asyncCtx = { + pi: deps.pi, + cwd: ctx.cwd, + currentSessionId: deps.state.currentSessionId, + currentModelProvider: ctx.model?.provider + }; + const parallelTasks = tasks.map((t, i) => { + const taskText = params.context === "fork" ? (0, _types.wrapForkTask)(taskTexts[i]) : taskTexts[i]; + const progress = (0, _settings.taskDisallowsFileUpdates)(taskText) ? false : behaviorOverrides[i]?.progress; + return { + agent: t.agent, + task: taskText, + cwd: t.cwd, + ...(modelOverrides[i] ? { model: modelOverrides[i] } : {}), + ...(skillOverrides[i] !== undefined ? { skill: skillOverrides[i] } : {}), + ...(behaviorOverrides[i]?.output !== undefined ? { output: behaviorOverrides[i].output } : {}), + ...(behaviorOverrides[i]?.outputMode !== undefined ? { outputMode: behaviorOverrides[i].outputMode } : {}), + ...(behaviorOverrides[i]?.reads !== undefined ? { reads: behaviorOverrides[i].reads } : {}), + ...(progress !== undefined ? { progress } : {}) + }; + }); + return (0, _asyncExecution.executeAsyncChain)(id, { + chain: [{ parallel: parallelTasks, concurrency: parallelConcurrency, worktree: params.worktree }], + resultMode: "parallel", + agents, + ctx: asyncCtx, + availableModels, + cwd: effectiveCwd, + maxOutput: params.maxOutput, + artifactsDir: artifactConfig.enabled ? artifactsDir : undefined, + artifactConfig, + shareEnabled, + sessionRoot, + chainSkills: [], + sessionFilesByFlatIndex: tasks.map((_, index) => sessionFileForIndex(index)), + maxSubagentDepth: currentMaxSubagentDepth, + worktreeSetupHook: deps.config.worktreeSetupHook, + worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs, + controlConfig, + controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined, + childIntercomTarget: data.intercomBridge.active ? (agent, index) => (0, _intercomBridge.resolveSubagentIntercomTarget)(id, agent, index) : undefined + }); + } + } + + const behaviors = agentConfigs.map((config, index) => (0, _settings.suppressProgressForReadOnlyTask)((0, _settings.resolveStepBehavior)(config, behaviorOverrides[index]), taskTexts[index])); + const firstProgressIndex = behaviors.findIndex((behavior) => behavior.progress); + const liveResults = new Array(tasks.length).fill(undefined); + const liveProgress = new Array(tasks.length).fill(undefined); + const foregroundControl = deps.state.foregroundControls.get(runId); + const { setup: worktreeSetup, errorResult } = createParallelWorktreeSetup( + params.worktree, + effectiveCwd, + runId, + tasks, + deps.config.worktreeSetupHook, + deps.config.worktreeSetupHookTimeoutMs + ); + if (errorResult) return errorResult; + + try { + const duplicateOutputError = findDuplicateParallelOutputPath({ + tasks, + behaviors, + paramsCwd: effectiveCwd, + ctxCwd: ctx.cwd, + worktreeSetup + }); + if (duplicateOutputError) return buildParallelModeError(duplicateOutputError); + for (let index = 0; index < tasks.length; index++) { + const taskCwd = resolveParallelTaskCwd(tasks[index], effectiveCwd, worktreeSetup, index); + const outputPath = (0, _singleOutput.resolveSingleOutputPath)(behaviors[index]?.output, ctx.cwd, taskCwd); + const validationError = (0, _singleOutput.validateFileOnlyOutputMode)(behaviors[index]?.outputMode, outputPath, `Parallel task ${index + 1} (${tasks[index].agent})`); + if (validationError) return buildParallelModeError(validationError); + } + + const parallelProgressPrecreated = firstProgressIndex !== -1; + if (parallelProgressPrecreated) (0, _settings.writeInitialProgressFile)(effectiveCwd); + + if (params.context === "fork") { + for (let i = 0; i < taskTexts.length; i++) { + taskTexts[i] = (0, _types.wrapForkTask)(taskTexts[i]); + } + } + + const results = await runForegroundParallelTasks({ + tasks, + taskTexts, + agents, + ctx, + intercomEvents: deps.pi.events, + signal, + runId, + sessionDirForIndex, + sessionFileForIndex, + shareEnabled, + artifactConfig, + artifactsDir, + maxOutput: params.maxOutput, + paramsCwd: effectiveCwd, + availableModels, + modelOverrides, + behaviors, + firstProgressIndex: parallelProgressPrecreated ? -1 : firstProgressIndex, + controlConfig, + onControlEvent, + childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined, + orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined, + foregroundControl, + concurrencyLimit: parallelConcurrency, + maxSubagentDepths, + liveResults, + liveProgress, + onUpdate, + worktreeSetup + }); + for (let i = 0; i < results.length; i++) { + const run = results[i]; + (0, _runHistory.recordRun)(run.agent, taskTexts[i], run.exitCode, run.progressSummary?.durationMs ?? 0); + } + + for (const result of results) { + if (result.progress) allProgress.push(result.progress); + if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths); + } + + const interrupted = results.find((result) => result.interrupted); + const details = (0, _utils.compactForegroundDetails)({ + mode: "parallel", + runId, + results, + progress: params.includeProgress ? allProgress : undefined, + artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined + }); + rememberForegroundRun(deps.state, { runId, mode: "parallel", cwd: effectiveCwd, results: details.results }); + if (interrupted) { + return { + content: [{ type: "text", text: `Parallel run paused after interrupt (${interrupted.agent}). Waiting for explicit next action.` }], + details + }; + } + const detachedIndex = results.findIndex((result) => result.detached); + const detached = detachedIndex >= 0 ? results[detachedIndex] : undefined; + if (detached) { + return { + content: [{ type: "text", text: `Parallel run detached for intercom coordination (${detached.agent}). Reply to the supervisor request first. After the child exits, start a fresh follow-up if needed.` }], + details + }; + } + + const intercomReceipt = await maybeBuildForegroundIntercomReceipt({ + pi: deps.pi, + intercomBridge: data.intercomBridge, + runId, + mode: "parallel", + details + }); + if (intercomReceipt) { + return { + content: [{ type: "text", text: intercomReceipt.text }], + details: intercomReceipt.details + }; + } + + const worktreeSuffix = buildParallelWorktreeSuffix(worktreeSetup, artifactsDir, tasks); + const ok = results.filter((result) => result.exitCode === 0).length; + const downgradeNote = backgroundRequestedWhileClarifying ? " (background requested, but clarify kept this run foreground)" : ""; + const aggregatedOutput = (0, _parallelUtils.aggregateParallelOutputs)( + results.map((result) => ({ + agent: result.agent, + output: result.truncation?.text || (0, _utils.getSingleResultOutput)(result), + exitCode: result.exitCode, + error: result.error + })), + (i, agent) => `=== Task ${i + 1}: ${agent} ===` + ); + + const summary = `${ok}/${results.length} succeeded${downgradeNote}`; + const fullContent = worktreeSuffix ? + `${summary}\n\n${aggregatedOutput}\n\n${worktreeSuffix}` : + `${summary}\n\n${aggregatedOutput}`; + + return { + content: [{ type: "text", text: fullContent }], + details + }; + } finally { + if (worktreeSetup) (0, _worktree.cleanupWorktrees)(worktreeSetup); + } +} + +async function runSinglePath(data, deps) { + const { + params, + effectiveCwd, + agents, + ctx, + signal, + runId, + sessionDirForIndex, + sessionFileForIndex, + shareEnabled, + artifactConfig, + artifactsDir, + onUpdate, + sessionRoot, + controlConfig + } = data; + const onControlEvent = createForegroundControlNotifier(data, deps); + const childIntercomTarget = data.intercomBridge.active ? (0, _intercomBridge.resolveSubagentIntercomTarget)(runId, params.agent, 0) : undefined; + const allProgress = []; + const allArtifactPaths = []; + const agentConfig = agents.find((a) => a.name === params.agent); + if (!agentConfig) { + return { + content: [{ type: "text", text: `Unknown agent: ${params.agent}` }], + isError: true, + details: { mode: "single", results: [] } + }; + } + + const currentProvider = ctx.model?.provider; + const availableModels = ctx.modelRegistry.getAvailable().map(_modelInfo.toModelInfo); + let task = params.task ?? ""; + let modelOverride = (0, _modelFallback.resolveModelCandidate)( + params.model ?? agentConfig.model, + availableModels, + currentProvider + ); + let skillOverride = (0, _skills.normalizeSkillInput)(params.skill); + const rawOutput = params.output !== undefined ? params.output : agentConfig.output; + let effectiveOutput = rawOutput === true ? agentConfig.output : rawOutput; + const effectiveOutputMode = params.outputMode ?? "inline"; + const currentMaxSubagentDepth = (0, _types.resolveCurrentMaxSubagentDepth)(deps.config.maxSubagentDepth); + const maxSubagentDepth = (0, _types.resolveChildMaxSubagentDepth)(currentMaxSubagentDepth, agentConfig.maxSubagentDepth); + + if (params.clarify === true && ctx.hasUI) { + const behavior = (0, _settings.resolveStepBehavior)(agentConfig, { output: effectiveOutput, skills: skillOverride }); + const availableSkills = (0, _skills.discoverAvailableSkills)(effectiveCwd); + + const result = await ctx.ui.custom( + (tui, theme, _kb, done) => + new _chainClarify.ChainClarifyComponent( + tui, theme, + [agentConfig], + [task], + task, + undefined, + [behavior], + availableModels, + currentProvider, + availableSkills, + done, + "single" + ), + { overlay: true, overlayOptions: { anchor: "center", width: 84, maxHeight: "80%" } } + ); + + if (!result || !result.confirmed) { + return { content: [{ type: "text", text: "Cancelled" }], details: { mode: "single", results: [] } }; + } + + task = result.templates[0]; + const override = result.behaviorOverrides[0]; + if (override?.model) modelOverride = override.model; + if (override?.output !== undefined) effectiveOutput = override.output; + if (override?.skills !== undefined) skillOverride = override.skills; + + if (result.runInBackground) { + if (!(0, _asyncExecution.isAsyncAvailable)()) { + return { + content: [{ type: "text", text: "Background mode requires jiti for TypeScript execution but it could not be found." }], + isError: true, + details: { mode: "single", results: [] } + }; + } + const id = (0, _nodeCrypto.randomUUID)(); + const asyncCtx = { + pi: deps.pi, + cwd: ctx.cwd, + currentSessionId: deps.state.currentSessionId, + currentModelProvider: ctx.model?.provider + }; + return (0, _asyncExecution.executeAsyncSingle)(id, { + agent: params.agent, + task: params.context === "fork" ? (0, _types.wrapForkTask)(task) : task, + agentConfig, + ctx: asyncCtx, + availableModels, + cwd: effectiveCwd, + maxOutput: params.maxOutput, + artifactsDir: artifactConfig.enabled ? artifactsDir : undefined, + artifactConfig, + shareEnabled, + sessionRoot, + sessionFile: sessionFileForIndex(0), + skills: skillOverride === false ? [] : skillOverride, + output: effectiveOutput, + outputMode: effectiveOutputMode, + modelOverride, + maxSubagentDepth, + worktreeSetupHook: deps.config.worktreeSetupHook, + worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs, + controlConfig, + controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined, + childIntercomTarget: data.intercomBridge.active ? (agent, index) => (0, _intercomBridge.resolveSubagentIntercomTarget)(id, agent, index) : undefined + }); + } + } + + if (params.context === "fork") { + task = (0, _types.wrapForkTask)(task); + } + const cleanTask = task; + const outputPath = (0, _singleOutput.resolveSingleOutputPath)(effectiveOutput, ctx.cwd, effectiveCwd); + const validationError = (0, _singleOutput.validateFileOnlyOutputMode)(effectiveOutputMode, outputPath, `Single run (${params.agent})`); + if (validationError) { + return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [] } }; + } + task = (0, _singleOutput.injectSingleOutputInstruction)(task, outputPath); + + let effectiveSkills; + if (skillOverride === false) { + effectiveSkills = []; + } else { + effectiveSkills = skillOverride; + } + const interruptController = new AbortController(); + const foregroundControl = deps.state.foregroundControls.get(runId); + if (foregroundControl) { + foregroundControl.currentAgent = params.agent; + foregroundControl.currentIndex = 0; + foregroundControl.currentActivityState = undefined; + foregroundControl.updatedAt = Date.now(); + foregroundControl.interrupt = () => { + if (interruptController.signal.aborted) return false; + interruptController.abort(); + foregroundControl.currentActivityState = undefined; + foregroundControl.updatedAt = Date.now(); + return true; + }; + } + + const forwardSingleUpdate = onUpdate ? + (update) => { + if (foregroundControl) { + const firstProgress = update.details?.progress?.[0]; + foregroundControl.currentAgent = params.agent; + foregroundControl.currentIndex = firstProgress?.index ?? 0; + foregroundControl.currentActivityState = firstProgress?.activityState; + foregroundControl.lastActivityAt = firstProgress?.lastActivityAt; + foregroundControl.currentTool = firstProgress?.currentTool; + foregroundControl.currentToolStartedAt = firstProgress?.currentToolStartedAt; + foregroundControl.currentPath = firstProgress?.currentPath; + foregroundControl.turnCount = firstProgress?.turnCount; + foregroundControl.tokens = firstProgress?.tokens; + foregroundControl.toolCount = firstProgress?.toolCount; + foregroundControl.updatedAt = Date.now(); + } + onUpdate(update); + } : + undefined; + + const r = await (0, _execution.runSync)(ctx.cwd, agents, params.agent, task, { + cwd: effectiveCwd, + signal, + interruptSignal: interruptController.signal, + allowIntercomDetach: agentConfig.systemPrompt?.includes(_intercomBridge.INTERCOM_BRIDGE_MARKER) === true, + intercomEvents: deps.pi.events, + runId, + sessionDir: sessionDirForIndex(0), + sessionFile: sessionFileForIndex(0), + share: shareEnabled, + artifactsDir: artifactConfig.enabled ? artifactsDir : undefined, + artifactConfig, + maxOutput: params.maxOutput, + outputPath, + outputMode: effectiveOutputMode, + maxSubagentDepth, + onUpdate: forwardSingleUpdate, + controlConfig, + onControlEvent, + intercomSessionName: childIntercomTarget, + orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined, + index: 0, + modelOverride, + availableModels, + preferredModelProvider: currentProvider, + skills: effectiveSkills + }); + if (foregroundControl?.currentIndex === 0) { + foregroundControl.interrupt = undefined; + foregroundControl.currentActivityState = r.progress?.activityState; + foregroundControl.lastActivityAt = r.progress?.lastActivityAt; + foregroundControl.currentTool = r.progress?.currentTool; + foregroundControl.currentToolStartedAt = r.progress?.currentToolStartedAt; + foregroundControl.currentPath = r.progress?.currentPath; + foregroundControl.turnCount = r.progress?.turnCount; + foregroundControl.tokens = r.progress?.tokens; + foregroundControl.toolCount = r.progress?.toolCount; + foregroundControl.updatedAt = Date.now(); + } + (0, _runHistory.recordRun)(params.agent, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0); + + if (r.progress) allProgress.push(r.progress); + if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths); + + const fullOutput = (0, _utils.getSingleResultOutput)(r); + const finalizedOutput = (0, _singleOutput.finalizeSingleOutput)({ + fullOutput, + truncatedOutput: r.truncation?.text, + outputPath, + outputMode: r.outputMode, + exitCode: r.exitCode, + savedPath: r.savedOutputPath, + outputReference: r.outputReference, + saveError: r.outputSaveError + }); + const details = (0, _utils.compactForegroundDetails)({ + mode: "single", + runId, + results: [r], + progress: params.includeProgress ? allProgress : undefined, + artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined, + truncation: r.truncation + }); + rememberForegroundRun(deps.state, { runId, mode: "single", cwd: effectiveCwd, results: details.results }); + + if (!r.detached && !r.interrupted) { + const intercomReceipt = await maybeBuildForegroundIntercomReceipt({ + pi: deps.pi, + intercomBridge: data.intercomBridge, + runId, + mode: "single", + details + }); + if (intercomReceipt) { + return { + content: [{ type: "text", text: intercomReceipt.text }], + details: intercomReceipt.details, + ...(r.exitCode !== 0 ? { isError: true } : {}) + }; + } + } + + if (r.detached) { + return { + content: [{ type: "text", text: `Detached for intercom coordination: ${params.agent}. Reply to the supervisor request first. After the child exits, start a fresh follow-up if needed.` }], + details + }; + } + + if (r.interrupted) { + return { + content: [{ type: "text", text: `Run paused after interrupt (${params.agent}). Waiting for explicit next action.` }], + details + }; + } + + if (r.exitCode !== 0) + return { + content: [{ type: "text", text: r.error || "Failed" }], + details, + isError: true + }; + return { + content: [{ type: "text", text: finalizedOutput.displayOutput || "(no output)" }], + details + }; +} + +function createSubagentExecutor(deps) + + + + + + + +{ + const execute = async ( + _id, + params, + signal, + onUpdate, + ctx) => + { + deps.state.baseCwd = ctx.cwd; + deps.state.foregroundRuns ??= new Map(); + deps.state.foregroundControls ??= new Map(); + deps.state.lastForegroundControlId ??= null; + const requestCwd = resolveRequestedCwd(ctx.cwd, params.cwd); + const paramsWithResolvedCwd = params.cwd === undefined ? params : { ...params, cwd: requestCwd }; + if (params.action) { + if (params.action === "doctor") { + let currentSessionFile = null; + let currentSessionId = deps.state.currentSessionId; + let sessionError; + try { + currentSessionFile = ctx.sessionManager.getSessionFile() ?? null; + currentSessionId = ctx.sessionManager.getSessionId(); + } catch (error) { + sessionError = error instanceof Error ? `${error.name}: ${error.message}` : String(error); + } + let orchestratorTarget; + try { + orchestratorTarget = (0, _intercomBridge.resolveIntercomSessionTarget)(deps.pi.getSessionName(), ctx.sessionManager.getSessionId()); + } catch {} + return { + content: [{ + type: "text", + text: (0, _doctor.buildDoctorReport)({ + cwd: requestCwd, + config: deps.config, + state: deps.state, + context: paramsWithResolvedCwd.context, + requestedSessionDir: paramsWithResolvedCwd.sessionDir, + currentSessionFile, + currentSessionId, + orchestratorTarget, + sessionError, + expandTilde: deps.expandTilde + }) + }], + details: { mode: "management", results: [] } + }; + } + if (params.action === "status") { + const foreground = getForegroundControl(deps.state, paramsWithResolvedCwd.id ?? paramsWithResolvedCwd.runId); + if (foreground) return foregroundStatusResult(foreground); + return (0, _runStatus.inspectSubagentStatus)(paramsWithResolvedCwd); + } + if (params.action === "resume") { + return resumeAsyncRun({ params: paramsWithResolvedCwd, requestCwd, ctx, deps }); + } + if (params.action === "interrupt") { + const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id; + const foreground = getForegroundControl(deps.state, targetRunId); + if (foreground?.interrupt) { + const interrupted = foreground.interrupt(); + if (interrupted) { + foreground.updatedAt = Date.now(); + foreground.currentActivityState = undefined; + return { + content: [{ type: "text", text: `Interrupt requested for foreground run ${foreground.runId}.` }], + details: { mode: "management", results: [] } + }; + } + return { + content: [{ type: "text", text: `Foreground run ${foreground.runId} has no active child step to interrupt.` }], + isError: true, + details: { mode: "management", results: [] } + }; + } + const asyncInterruptResult = interruptAsyncRun(deps.state, targetRunId); + if (asyncInterruptResult) return asyncInterruptResult; + return { + content: [{ type: "text", text: "No interrupt-capable run found in this session." }], + isError: true, + details: { mode: "management", results: [] } + }; + } + if (!_types.SUBAGENT_ACTIONS.includes(params.action)) { + return { + content: [{ type: "text", text: `Unknown action: ${params.action}. Valid: ${_types.SUBAGENT_ACTIONS.join(", ")}` }], + isError: true, + details: { mode: "management", results: [] } + }; + } + return (0, _agentManagement.handleManagementAction)(params.action, paramsWithResolvedCwd, { ...ctx, cwd: requestCwd }); + } + + const { blocked, depth, maxDepth } = (0, _types.checkSubagentDepth)(deps.config.maxSubagentDepth); + if (blocked) { + return { + content: [ + { + type: "text", + text: + `Nested subagent call blocked (depth=${depth}, max=${maxDepth}). ` + + "You are running at the maximum subagent nesting depth. " + + "Complete your current task directly without delegating to further subagents." + }], + + isError: true, + details: { mode: "single", results: [] } + }; + } + + const normalized = normalizeRepeatedParallelCounts(paramsWithResolvedCwd); + if (normalized.error) return normalized.error; + const normalizedParams = normalized.params; + + let effectiveParams = (0, _topLevelAsync.applyForceTopLevelAsyncOverride)( + normalizedParams, + depth, + deps.config.forceTopLevelAsync === true + ); + + const scope = (0, _agentScope.resolveExecutionAgentScope)(effectiveParams.agentScope); + const effectiveCwd = effectiveParams.cwd ?? ctx.cwd; + const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null; + deps.state.currentSessionId = (0, _sessionIdentity.resolveCurrentSessionId)(ctx.sessionManager); + const discoveredAgents = deps.discoverAgents(effectiveCwd, scope).agents; + effectiveParams = applyAgentDefaultContext(effectiveParams, discoveredAgents); + const sessionName = (0, _intercomBridge.resolveIntercomSessionTarget)(deps.pi.getSessionName(), ctx.sessionManager.getSessionId()); + const intercomBridge = (0, _intercomBridge.resolveIntercomBridge)({ + config: deps.config.intercomBridge, + context: effectiveParams.context, + orchestratorTarget: sessionName, + cwd: effectiveCwd + }); + const agents = intercomBridge.active ? + discoveredAgents.map((agent) => (0, _intercomBridge.applyIntercomBridgeToAgent)(agent, intercomBridge)) : + discoveredAgents; + const runId = (0, _nodeCrypto.randomUUID)().slice(0, 8); + const shareEnabled = effectiveParams.share === true; + const hasChain = (effectiveParams.chain?.length ?? 0) > 0; + const hasTasks = (effectiveParams.tasks?.length ?? 0) > 0; + const hasSingle = !hasChain && !hasTasks && Boolean(effectiveParams.agent); + const allowClarifyTaskPrompt = hasChain && + effectiveParams.clarify === true && + ctx.hasUI && + !(effectiveParams.chain?.some(_settings.isParallelStep) ?? false); + + const validationError = validateExecutionInput( + effectiveParams, + agents, + hasChain, + hasTasks, + hasSingle, + allowClarifyTaskPrompt + ); + if (validationError) return validationError; + + let sessionFileForIndex = () => undefined; + try { + sessionFileForIndex = (0, _forkContext.createForkContextResolver)(ctx.sessionManager, effectiveParams.context).sessionFileForIndex; + } catch (error) { + return toExecutionErrorResult(effectiveParams, error); + } + const requestedAsync = effectiveParams.async ?? deps.asyncByDefault; + const backgroundRequestedWhileClarifying = hasTasks && requestedAsync && effectiveParams.clarify === true; + const effectiveAsync = requestedAsync && ( + hasChain ? effectiveParams.clarify === false : effectiveParams.clarify !== true); + const controlConfig = (0, _subagentControl.resolveControlConfig)(deps.config.control, effectiveParams.control); + + const artifactConfig = { + ..._types.DEFAULT_ARTIFACT_CONFIG, + enabled: effectiveParams.artifacts !== false + }; + const artifactsDir = effectiveAsync ? deps.tempArtifactsDir : (0, _artifacts.getArtifactsDir)(parentSessionFile); + + let sessionRoot; + if (effectiveParams.sessionDir) { + sessionRoot = path.resolve(deps.expandTilde(effectiveParams.sessionDir)); + } else { + const baseSessionRoot = deps.config.defaultSessionDir ? + path.resolve(deps.expandTilde(deps.config.defaultSessionDir)) : + deps.getSubagentSessionRoot(parentSessionFile); + sessionRoot = path.join(baseSessionRoot, runId); + } + try { + fs.mkdirSync(sessionRoot, { recursive: true }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return toExecutionErrorResult( + effectiveParams, + new Error(`Failed to create session directory '${sessionRoot}': ${message}`) + ); + } + const sessionDirForIndex = (idx) => + path.join(sessionRoot, `run-${idx ?? 0}`); + const childSessionFileForIndex = (idx) => + sessionFileForIndex(idx) ?? path.join(sessionDirForIndex(idx), "session.jsonl"); + + const onUpdateWithContext = onUpdate ? + (r) => onUpdate(withForkContext(r, effectiveParams.context)) : + undefined; + + const execData = { + params: effectiveParams, + effectiveCwd, + ctx, + signal, + onUpdate: onUpdateWithContext, + agents, + runId, + shareEnabled, + sessionRoot, + sessionDirForIndex, + sessionFileForIndex: childSessionFileForIndex, + artifactConfig, + artifactsDir, + backgroundRequestedWhileClarifying, + effectiveAsync, + controlConfig, + intercomBridge + }; + + const foregroundMode = hasChain ? "chain" : hasTasks ? "parallel" : "single"; + const foregroundControl = effectiveAsync ? + undefined : + { + runId, + mode: foregroundMode, + startedAt: Date.now(), + updatedAt: Date.now(), + currentAgent: undefined, + currentIndex: undefined, + currentActivityState: undefined, + interrupt: undefined + }; + if (foregroundControl) { + deps.state.foregroundControls.set(runId, foregroundControl); + deps.state.lastForegroundControlId = runId; + } + + try { + const asyncResult = runAsyncPath(execData, deps); + if (asyncResult) return withForkContext(asyncResult, effectiveParams.context); + if (hasChain && effectiveParams.chain) return withForkContext(await runChainPath(execData, deps), effectiveParams.context); + if (hasTasks && effectiveParams.tasks) return withForkContext(await runParallelPath(execData, deps), effectiveParams.context); + if (hasSingle) return withForkContext(await runSinglePath(execData, deps), effectiveParams.context); + } catch (error) { + return toExecutionErrorResult(effectiveParams, error); + } finally { + if (foregroundControl) { + (0, _controlNotices.clearPendingForegroundControlNotices)(deps.state, runId); + deps.state.foregroundControls.delete(runId); + if (deps.state.lastForegroundControlId === runId) { + deps.state.lastForegroundControlId = null; + } + } + } + + return withForkContext({ + content: [{ type: "text", text: "Invalid params" }], + isError: true, + details: { mode: "single", results: [] } + }, effectiveParams.context); + }; + + return { execute }; +} /* v9-c7a1289a50ce9121 */ diff --git a/pip-tmp/jiti/intercom-intercom-bridge.88630197.mjs b/pip-tmp/jiti/intercom-intercom-bridge.88630197.mjs new file mode 100644 index 0000000000000000000000000000000000000000..d04a4e23af4d7c6336e1bcd96f64fffa2282663e --- /dev/null +++ b/pip-tmp/jiti/intercom-intercom-bridge.88630197.mjs @@ -0,0 +1,379 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.INTERCOM_BRIDGE_MARKER = void 0;exports.applyIntercomBridgeToAgent = applyIntercomBridgeToAgent;exports.diagnoseIntercomBridge = diagnoseIntercomBridge;exports.resolveIntercomBridge = resolveIntercomBridge;exports.resolveIntercomBridgeMode = resolveIntercomBridgeMode;exports.resolveIntercomSessionTarget = resolveIntercomSessionTarget;exports.resolveSubagentIntercomTarget = resolveSubagentIntercomTarget;var _nodeChild_process = await jitiImport("node:child_process"); +var fs = _interopRequireWildcard(await jitiImport("node:fs")); +var os = _interopRequireWildcard(await jitiImport("node:os")); +var path = _interopRequireWildcard(await jitiImport("node:path"));function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);} + + + +const PI_INTERCOM_PACKAGE_NAME = "pi-intercom"; +const CONFIG_DIR = ".pi"; + +function defaultAgentDir() { + return path.join(os.homedir(), ".pi", "agent"); +} + +function defaultIntercomExtensionDir(agentDir = defaultAgentDir()) { + return path.join(agentDir, "extensions", PI_INTERCOM_PACKAGE_NAME); +} + +function defaultIntercomConfigPath(agentDir = defaultAgentDir()) { + return path.join(agentDir, "intercom", "config.json"); +} + +function defaultSubagentConfigDir(agentDir = defaultAgentDir()) { + return path.join(agentDir, "extensions", "subagent"); +} + +const DEFAULT_INTERCOM_TARGET_PREFIX = "subagent-chat"; +const INTERCOM_BRIDGE_MARKER = exports.INTERCOM_BRIDGE_MARKER = "Intercom orchestration channel:"; +const DEFAULT_INTERCOM_BRIDGE_TEMPLATE = `The inherited thread is reference-only. Do not continue that conversation or send questions, status updates, or completion handoffs to the supervisor in normal assistant text. + +Use contact_supervisor first. It resolves the supervisor session "{orchestratorTarget}" and run metadata automatically. +- Need a decision, blocked, approval, or product/API/scope ambiguity: contact_supervisor({ reason: "need_decision", message: "" }) +- After contact_supervisor with reason "need_decision", stay alive and continue only after the reply arrives. Do not finish your final response with a choose-one question. +- Do not ask for clarification when the only conflict is review-only/no-edit versus progress-writing or artifact-writing instructions. Review-only/no-edit wins; leave files unchanged and mention the conflict in your final result only if it matters. +- Meaningful progress or unexpected discoveries that change the plan: contact_supervisor({ reason: "progress_update", message: "UPDATE: " }) +- Generic intercom is lower-level plumbing/fallback only: intercom({ action: "ask", to: "{orchestratorTarget}", message: "" }) + +Do not use contact_supervisor or intercom for routine completion handoffs. If no coordination is needed, return a focused task result.`; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +function resolveIntercomSessionTarget(sessionName, sessionId) { + const trimmedName = sessionName?.trim(); + if (trimmedName) return trimmedName; + const normalizedSessionId = sessionId.startsWith("session-") ? sessionId.slice("session-".length) : sessionId; + return `${DEFAULT_INTERCOM_TARGET_PREFIX}-${normalizedSessionId.slice(0, 8)}`; +} + +function sanitizeIntercomTargetPart(value) { + return value.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "agent"; +} + +function resolveSubagentIntercomTarget(runId, agent, index) { + const stepSuffix = index !== undefined ? `-${index + 1}` : ""; + return `subagent-${sanitizeIntercomTargetPart(agent)}-${sanitizeIntercomTargetPart(runId)}${stepSuffix}`; +} + +function resolveIntercomBridgeMode(value) { + if (value === "off" || value === "always" || value === "fork-only") return value; + return "always"; +} + +function resolveIntercomBridgeConfig(value) { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return { + mode: "always", + instructionFile: "" + }; + } + return { + mode: resolveIntercomBridgeMode(value.mode), + instructionFile: typeof value.instructionFile === "string" ? value.instructionFile : "" + }; +} + +function intercomConfigStatus(configPath) { + if (!fs.existsSync(configPath)) return { enabled: true }; + try { + const parsed = JSON.parse(fs.readFileSync(configPath, "utf-8")); + return { enabled: parsed.enabled !== false }; + } catch (error) { + return { enabled: true, error }; + } +} + +function readJsonBestEffort(filePath) { + try { + return JSON.parse(fs.readFileSync(filePath, "utf-8")); + } catch (error) { + const code = error && typeof error === "object" && "code" in error ? error.code : undefined; + if (code !== "ENOENT") console.warn(`Failed to read JSON from '${filePath}'.`, error); + return null; + } +} + +function packageHasPiExtension(packageRoot) { + if (!fs.existsSync(packageRoot)) return false; + const pkg = readJsonBestEffort(path.join(packageRoot, "package.json")); + if (pkg && typeof pkg === "object" && !Array.isArray(pkg)) { + const pi = pkg.pi; + if (pi && typeof pi === "object" && !Array.isArray(pi)) { + const extensions = pi.extensions; + return Array.isArray(extensions) && extensions.some((entry) => typeof entry === "string" && entry.trim() !== ""); + } + } + return fs.existsSync(path.join(packageRoot, "extensions")); +} + +function isSafePackagePath(value) { + return value.length > 0 && + !path.isAbsolute(value) && + value.split(/[\\/]/).every((part) => part.length > 0 && part !== "." && part !== ".."); +} + +function parseNpmPackageName(source) { + const spec = source.slice(4).trim(); + if (!spec) return undefined; + const match = spec.match(/^(@?[^@]+(?:\/[^@]+)?)(?:@(.+))?$/); + const packageName = match?.[1] ?? spec; + return isSafePackagePath(packageName) ? packageName : undefined; +} + +function packageEntrySource(entry) { + if (typeof entry === "string") return entry; + if (entry && typeof entry === "object" && !Array.isArray(entry) && typeof entry.source === "string") { + return entry.source; + } + return undefined; +} + +function packageEntryAllowsExtensions(entry) { + if (!entry || typeof entry !== "object" || Array.isArray(entry)) return true; + const extensions = entry.extensions; + return !Array.isArray(extensions) || extensions.length > 0; +} + +function findNearestProjectConfigDir(cwd) { + let current = path.resolve(cwd); + while (true) { + const configDir = path.join(current, CONFIG_DIR); + if (fs.existsSync(path.join(configDir, "settings.json"))) return configDir; + const parent = path.dirname(current); + if (parent === current) return undefined; + current = parent; + } +} + +let cachedGlobalNpmRoot; + +function getGlobalNpmRoot() { + if (cachedGlobalNpmRoot !== undefined) return cachedGlobalNpmRoot; + try { + cachedGlobalNpmRoot = (0, _nodeChild_process.execSync)("npm root -g", { encoding: "utf-8", timeout: 5000 }).trim(); + return cachedGlobalNpmRoot; + } catch { + cachedGlobalNpmRoot = null; + return null; + } +} + +function configuredPiIntercomPackageDir(input, agentDir) { + const cwd = path.resolve(input.cwd ?? process.cwd()); + const projectConfigDir = findNearestProjectConfigDir(cwd); + const settingsFiles = [ + ...(projectConfigDir ? [{ file: path.join(projectConfigDir, "settings.json"), configDir: projectConfigDir, scope: "project" }] : []), + { file: path.join(agentDir, "settings.json"), configDir: agentDir, scope: "user" }]; + + const globalNpmRoot = input.globalNpmRoot === undefined ? getGlobalNpmRoot() : input.globalNpmRoot; + + for (const { file, configDir, scope } of settingsFiles) { + const settings = readJsonBestEffort(file); + if (!settings || typeof settings !== "object" || Array.isArray(settings)) continue; + const packages = settings.packages; + if (!Array.isArray(packages)) continue; + + for (const entry of packages) { + if (!packageEntryAllowsExtensions(entry)) continue; + const source = packageEntrySource(entry)?.trim(); + if (!source?.startsWith("npm:")) continue; + const packageName = parseNpmPackageName(source); + if (packageName !== PI_INTERCOM_PACKAGE_NAME) continue; + const candidates = scope === "project" ? + [path.join(configDir, "npm", "node_modules", packageName)] : + [ + ...(globalNpmRoot ? [path.join(globalNpmRoot, packageName)] : []), + path.join(agentDir, "npm", "node_modules", packageName)]; + + const packageRoot = candidates.find(packageHasPiExtension); + if (packageRoot) return path.resolve(packageRoot); + } + } + return undefined; +} + +function resolveIntercomExtensionDir(input, agentDir) { + const legacyDir = path.resolve(input.extensionDir ?? defaultIntercomExtensionDir(agentDir)); + if (fs.existsSync(legacyDir)) return legacyDir; + return configuredPiIntercomPackageDir(input, agentDir) ?? legacyDir; +} + +function extensionSandboxAllowsIntercom(extensions, extensionDir) { + if (extensions === undefined) return true; + + const intercomDir = path.resolve(extensionDir).replaceAll("\\", "/").toLowerCase(); + for (const entry of extensions) { + const normalized = entry.trim().replaceAll("\\", "/").toLowerCase(); + if (normalized === "pi-intercom") return true; + if (normalized === intercomDir) return true; + if (normalized.startsWith(`${intercomDir}/`)) return true; + if (normalized.endsWith("/pi-intercom")) return true; + if (normalized.includes("/pi-intercom/")) return true; + } + return false; +} + +function expandTilde(filePath) { + return filePath.startsWith("~/") ? path.join(os.homedir(), filePath.slice(2)) : filePath; +} + +function resolveInstructionTemplate(instructionFile, settingsDir) { + if (!instructionFile) return DEFAULT_INTERCOM_BRIDGE_TEMPLATE; + const expandedPath = expandTilde(instructionFile); + const resolvedPath = path.isAbsolute(expandedPath) ? + expandedPath : + path.resolve(settingsDir, expandedPath); + try { + return fs.readFileSync(resolvedPath, "utf-8"); + } catch (error) { + console.warn(`Failed to read intercom bridge instructionFile at '${resolvedPath}'. Using default instructions.`, error); + return DEFAULT_INTERCOM_BRIDGE_TEMPLATE; + } +} + +function buildIntercomBridgeInstruction(orchestratorTarget, template) { + const instruction = template.replaceAll("{orchestratorTarget}", orchestratorTarget).trim(); + if (instruction.startsWith(INTERCOM_BRIDGE_MARKER)) return instruction; + return `${INTERCOM_BRIDGE_MARKER} +${instruction}`; +} + +function diagnoseIntercomBridge(input) { + const config = resolveIntercomBridgeConfig(input.config); + const mode = config.mode; + const agentDir = path.resolve(input.agentDir ?? defaultAgentDir()); + const extensionDir = resolveIntercomExtensionDir(input, agentDir); + const orchestratorTarget = input.orchestratorTarget?.trim(); + const configPath = path.resolve(input.configPath ?? defaultIntercomConfigPath(agentDir)); + const wantsIntercom = mode !== "off" && !(mode === "fork-only" && input.context !== "fork"); + const piIntercomAvailable = fs.existsSync(extensionDir); + let configStatus; + let reason; + if (mode === "off") reason = "bridge mode is off";else + if (mode === "fork-only" && input.context !== "fork") reason = "bridge mode is fork-only and context is not fork";else + if (!orchestratorTarget) reason = "orchestrator target is not available";else + if (!piIntercomAvailable) reason = "pi-intercom extension was not found";else + { + configStatus = intercomConfigStatus(configPath); + if (!configStatus.enabled) reason = "intercom config is disabled"; + } + let intercomConfigError; + if (configStatus?.error) { + const error = configStatus.error; + intercomConfigError = error instanceof Error ? `${error.name}: ${error.message}` : String(error); + } + + return { + active: reason === undefined, + mode, + wantsIntercom, + piIntercomAvailable, + extensionDir, + configPath, + ...(orchestratorTarget ? { orchestratorTarget } : {}), + ...(reason ? { reason } : {}), + ...(configStatus ? { intercomConfigEnabled: configStatus.enabled } : {}), + ...(intercomConfigError ? { intercomConfigError } : {}) + }; +} + +function resolveIntercomBridge(input) { + const config = resolveIntercomBridgeConfig(input.config); + const mode = config.mode; + const agentDir = path.resolve(input.agentDir ?? defaultAgentDir()); + const extensionDir = resolveIntercomExtensionDir(input, agentDir); + const orchestratorTarget = input.orchestratorTarget?.trim(); + const settingsDir = path.resolve(input.settingsDir ?? defaultSubagentConfigDir(agentDir)); + const defaultInstruction = buildIntercomBridgeInstruction( + orchestratorTarget || "{orchestratorTarget}", + DEFAULT_INTERCOM_BRIDGE_TEMPLATE + ); + + if (mode === "off") { + return { active: false, mode, extensionDir, instruction: defaultInstruction }; + } + if (mode === "fork-only" && input.context !== "fork") { + return { active: false, mode, extensionDir, instruction: defaultInstruction }; + } + if (!orchestratorTarget) { + return { active: false, mode, extensionDir, instruction: defaultInstruction }; + } + if (!fs.existsSync(extensionDir)) { + return { active: false, mode, extensionDir, instruction: defaultInstruction }; + } + + const configPath = path.resolve(input.configPath ?? defaultIntercomConfigPath(agentDir)); + const intercomStatus = intercomConfigStatus(configPath); + if (intercomStatus.error) console.warn(`Failed to parse intercom config at '${configPath}'. Assuming enabled.`, intercomStatus.error); + if (!intercomStatus.enabled) { + return { active: false, mode, extensionDir, instruction: defaultInstruction }; + } + + const instruction = buildIntercomBridgeInstruction( + orchestratorTarget, + resolveInstructionTemplate(config.instructionFile, settingsDir) + ); + + return { + active: true, + mode, + orchestratorTarget, + extensionDir, + instruction + }; +} + +function applyIntercomBridgeToAgent(agent, bridge) { + if (!bridge.active || !bridge.orchestratorTarget) return agent; + if (!extensionSandboxAllowsIntercom(agent.extensions, bridge.extensionDir)) return agent; + + const bridgeTools = ["intercom", "contact_supervisor"]; + const tools = agent.tools ? + [...agent.tools, ...bridgeTools.filter((tool) => !agent.tools?.includes(tool))] : + agent.tools; + const instruction = bridge.instruction; + const trimmedPrompt = agent.systemPrompt?.trim() || ""; + const systemPrompt = trimmedPrompt.includes(INTERCOM_BRIDGE_MARKER) ? + trimmedPrompt : + trimmedPrompt ? + `${trimmedPrompt}\n\n${instruction}` : + instruction; + + if (tools === agent.tools && systemPrompt === agent.systemPrompt) return agent; + return { + ...agent, + tools, + systemPrompt + }; +} /* v9-8debbb1836ab02f2 */ diff --git a/pip-tmp/jiti/intercom-result-intercom.32a45520.mjs b/pip-tmp/jiti/intercom-result-intercom.32a45520.mjs new file mode 100644 index 0000000000000000000000000000000000000000..fc4c372d6e9ad7efb978bb91b8985fbac6173b07 --- /dev/null +++ b/pip-tmp/jiti/intercom-result-intercom.32a45520.mjs @@ -0,0 +1,269 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.buildSubagentResultIntercomPayload = buildSubagentResultIntercomPayload;exports.deliverSubagentIntercomMessageEvent = deliverSubagentIntercomMessageEvent;exports.deliverSubagentResultIntercomEvent = deliverSubagentResultIntercomEvent;exports.formatSubagentResultReceipt = formatSubagentResultReceipt;exports.resolveSubagentResultStatus = resolveSubagentResultStatus;exports.stripDetailsOutputsForIntercomReceipt = stripDetailsOutputsForIntercomReceipt;var _nodeCrypto = await jitiImport("node:crypto"); +var fs = _interopRequireWildcard(await jitiImport("node:fs")); +var _types = await jitiImport("../shared/types.ts");function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);} + + + + + + + + + + + +function resolveSubagentResultStatus(input) + + + + + +{ + if (input.detached) return "detached"; + if (input.interrupted || input.state === "paused") return "paused"; + if (typeof input.success === "boolean") return input.success ? "completed" : "failed"; + if (input.state === "complete") return "completed"; + if (input.state === "failed") return "failed"; + if (typeof input.exitCode === "number") return input.exitCode === 0 ? "completed" : "failed"; + return "failed"; +} + +function countStatuses(children) { + const counts = { + completed: 0, + failed: 0, + paused: 0, + detached: 0 + }; + for (const child of children) { + counts[child.status] += 1; + } + return counts; +} + +function formatStatusCounts(counts) { + const parts = [ + counts.completed ? `${counts.completed} completed` : undefined, + counts.failed ? `${counts.failed} failed` : undefined, + counts.paused ? `${counts.paused} paused` : undefined, + counts.detached ? `${counts.detached} detached` : undefined]. + filter((part) => Boolean(part)); + return parts.length ? parts.join(", ") : "0 results"; +} + +function resolveGroupedStatus(children) { + const counts = countStatuses(children); + if (counts.failed > 0) return "failed"; + if (counts.paused > 0) return "paused"; + if (counts.completed > 0) return "completed"; + if (counts.detached > 0) return "detached"; + return "failed"; +} + + + + + + + + + + + + +function asyncResumeGuidance(input) + + + +{ + if (input.source !== "async" || !input.asyncId) return undefined; + const resumable = input.children.filter((child) => typeof child.sessionPath === "string" && fs.existsSync(child.sessionPath)); + if (input.children.length === 1 && resumable.length === 1) { + return `Revive: subagent({ action: "resume", id: "${input.asyncId}", message: "..." })`; + } + if (resumable.length > 0) { + const firstIndex = resumable[0]?.index ?? input.children.indexOf(resumable[0]); + return `Revive child: subagent({ action: "resume", id: "${input.asyncId}", index: ${firstIndex}, message: "..." })`; + } + return "Resume: unavailable; no child session file was persisted."; +} + +function formatSubagentResultIntercomMessage(input) + + + + + + + + +{ + const counts = countStatuses(input.children); + const lines = [ + "subagent results", + "", + `Run: ${input.runId}`, + `Mode: ${input.mode}`, + `Status: ${input.status}`, + `Children: ${formatStatusCounts(counts)}`]; + + if (input.mode === "chain" && typeof input.chainSteps === "number") { + lines.push(`Chain steps: ${input.chainSteps}`); + } + if (input.asyncId) lines.push(`Async id: ${input.asyncId}`); + if (input.asyncDir) lines.push(`Async dir: ${input.asyncDir}`); + const resumeGuidance = asyncResumeGuidance(input); + if (resumeGuidance) lines.push(resumeGuidance); + if (input.children.some((child) => child.intercomTarget)) { + lines.push(""); + lines.push(input.source === "async" ? + "Previous intercom targets below identify child sessions used while they were running. Inspect artifacts or session logs if resume is unavailable." : + "Intercom targets below identify child sessions used while they were running; completed child sessions may no longer be reachable. Inspect artifacts or session logs for follow-up."); + } + + for (let index = 0; index < input.children.length; index++) { + const child = input.children[index]; + lines.push(""); + lines.push(`${index + 1}. ${child.agent} — ${child.status}`); + if (child.intercomTarget) lines.push(`${input.source === "async" ? "Previous intercom target" : "Run intercom target"}: ${child.intercomTarget}`); + if (child.artifactPath) lines.push(`Output artifact: ${child.artifactPath}`); + if (child.sessionPath) lines.push(`Session: ${child.sessionPath}`); + lines.push("Summary:"); + lines.push(child.summary); + } + + return lines.join("\n"); +} + +function buildSubagentResultIntercomPayload(input) { + const children = input.children.map((child) => ({ + ...child, + summary: child.summary.trim() || "(no output)" + })); + const status = resolveGroupedStatus(children); + const summary = formatStatusCounts(countStatuses(children)); + const firstChild = children[0]; + const payload = { + to: input.to, + runId: input.runId, + mode: input.mode, + status, + summary, + source: input.source, + children, + ...(input.asyncId ? { asyncId: input.asyncId } : {}), + ...(input.asyncDir ? { asyncDir: input.asyncDir } : {}), + ...(typeof input.chainSteps === "number" ? { chainSteps: input.chainSteps } : {}), + ...(firstChild?.agent ? { agent: firstChild.agent } : {}), + ...(firstChild?.index !== undefined ? { index: firstChild.index } : {}), + ...(firstChild?.artifactPath ? { artifactPath: firstChild.artifactPath } : {}), + ...(firstChild?.sessionPath ? { sessionPath: firstChild.sessionPath } : {}), + message: "" + }; + payload.message = formatSubagentResultIntercomMessage(payload); + return payload; +} + +async function deliverSubagentResultIntercomEvent( +events, +payload, +timeoutMs = 500) +{ + return deliverSubagentIntercomMessageEvent(events, payload.to, payload.message, timeoutMs, payload); +} + +async function deliverSubagentIntercomMessageEvent( +events, +to, +message, +timeoutMs = 500, +extra = {}) +{ + if (typeof events.on !== "function" || typeof events.emit !== "function") return false; + const requestId = typeof extra.requestId === "string" ? extra.requestId : (0, _nodeCrypto.randomUUID)(); + return new Promise((resolve) => { + let settled = false; + let unsubscribe; + let timer; + const finish = (delivered) => { + if (settled) return; + settled = true; + if (timer) clearTimeout(timer); + unsubscribe?.(); + resolve(delivered); + }; + unsubscribe = events.on(_types.SUBAGENT_RESULT_INTERCOM_DELIVERY_EVENT, (data) => { + if (!data || typeof data !== "object") return; + const delivery = data; + if (delivery.requestId !== requestId) return; + finish(delivery.delivered === true); + }); + timer = setTimeout(() => finish(false), timeoutMs); + try { + events.emit(_types.SUBAGENT_RESULT_INTERCOM_EVENT, { ...extra, to, message, requestId }); + } catch { + finish(false); + } + }); +} + +function stripSingleResultOutputs(result) { + return { + ...result, + messages: undefined, + finalOutput: undefined, + truncation: undefined + }; +} + +function stripDetailsOutputsForIntercomReceipt(details) { + return { + ...details, + results: details.results.map(stripSingleResultOutputs) + }; +} + +function formatSubagentResultReceipt(input) + + + +{ + const counts = countStatuses(input.payload.children); + const modeLabel = input.mode === "single" ? + "single subagent result" : + input.mode === "parallel" ? + "parallel subagent results" : + "chain subagent results"; + const lines = [ + `Delivered ${modeLabel} via intercom.`, + `Run: ${input.runId}`, + `Children: ${formatStatusCounts(counts)}`]; + + + const artifacts = input.payload.children.filter((child) => typeof child.artifactPath === "string"); + if (artifacts.length > 0) { + lines.push("Artifacts:"); + for (const child of artifacts) { + lines.push(`- ${child.agent} [${child.status}]: ${child.artifactPath}`); + } + } + + const intercomTargets = input.payload.children.filter((child) => typeof child.intercomTarget === "string"); + if (intercomTargets.length > 0) { + lines.push("Run intercom targets (may be inactive after completion):"); + for (const child of intercomTargets) { + lines.push(`- ${child.agent} [${child.status}]: ${child.intercomTarget}`); + } + } + + const sessions = input.payload.children.filter((child) => typeof child.sessionPath === "string"); + if (sessions.length > 0) { + lines.push("Sessions:"); + for (const child of sessions) { + lines.push(`- ${child.agent} [${child.status}]: ${child.sessionPath}`); + } + } + + lines.push("Full grouped output was sent over intercom."); + return lines.join("\n"); +} /* v9-427c8e4be18cce04 */ diff --git a/pip-tmp/jiti/pi-web-access-activity.4dfe2849.mjs b/pip-tmp/jiti/pi-web-access-activity.4dfe2849.mjs new file mode 100644 index 0000000000000000000000000000000000000000..cb2c2a4860315c4c9d41f04ec6825be8aae6b657 --- /dev/null +++ b/pip-tmp/jiti/pi-web-access-activity.4dfe2849.mjs @@ -0,0 +1,101 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.activityMonitor = exports.ActivityMonitor = void 0; // Types + + + + + + + + + + + + + + + + + + + + + + + + +class ActivityMonitor { + entries = []; + maxEntries = 10; + listeners = new Set(); + rateLimitInfo = { used: 0, max: 10, oldestTimestamp: null, windowMs: 60000 }; + nextId = 1; + + logStart(partial) { + const id = `act-${this.nextId++}`; + const entry = { + ...partial, + id, + startTime: Date.now(), + status: null + }; + this.entries.push(entry); + if (this.entries.length > this.maxEntries) { + this.entries.shift(); + } + this.notify(); + return id; + } + + logComplete(id, status) { + const entry = this.entries.find((e) => e.id === id); + if (entry) { + entry.endTime = Date.now(); + entry.status = status; + this.notify(); + } + } + + logError(id, error) { + const entry = this.entries.find((e) => e.id === id); + if (entry) { + entry.endTime = Date.now(); + entry.error = error; + this.notify(); + } + } + + getEntries() { + return this.entries; + } + + getRateLimitInfo() { + return this.rateLimitInfo; + } + + updateRateLimit(info) { + this.rateLimitInfo = info; + this.notify(); + } + + onUpdate(callback) { + this.listeners.add(callback); + return () => this.listeners.delete(callback); + } + + clear() { + this.entries = []; + this.rateLimitInfo = { used: 0, max: 10, oldestTimestamp: null, windowMs: 60000 }; + this.notify(); + } + + notify() { + for (const cb of this.listeners) { + try { + cb(); + } catch { + } + } + } +}exports.ActivityMonitor = ActivityMonitor; + +const activityMonitor = exports.activityMonitor = new ActivityMonitor(); /* v9-be7b38195f5d2adb */ diff --git a/pip-tmp/jiti/pi-web-access-chrome-cookies.ad56e10d.mjs b/pip-tmp/jiti/pi-web-access-chrome-cookies.ad56e10d.mjs new file mode 100644 index 0000000000000000000000000000000000000000..3bb7688d573df870afbce530774eae1d90dca7d8 --- /dev/null +++ b/pip-tmp/jiti/pi-web-access-chrome-cookies.ad56e10d.mjs @@ -0,0 +1,322 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.getGoogleCookies = getGoogleCookies;var _nodeChild_process = await jitiImport("node:child_process"); +var _nodeCrypto = await jitiImport("node:crypto"); +var _nodeFs = await jitiImport("node:fs"); +var _nodeOs = await jitiImport("node:os"); +var _nodePath = await jitiImport("node:path");function _interopRequireWildcard(e, t) {if ("function" == typeof WeakMap) var r = new WeakMap(),n = new WeakMap();return (_interopRequireWildcard = function (e, t) {if (!t && e && e.__esModule) return e;var o,i,f = { __proto__: null, default: e };if (null === e || "object" != typeof e && "function" != typeof e) return f;if (o = t ? n : r) {if (o.has(e)) return o.get(e);o.set(e, f);}for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]);return f;})(e, t);} + + + + + + + + + + + +const GOOGLE_ORIGINS = [ +"https://gemini.google.com", +"https://accounts.google.com", +"https://www.google.com"]; + + +const ALL_COOKIE_NAMES = new Set([ +"__Secure-1PSID", +"__Secure-1PSIDTS", +"__Secure-1PSIDCC", +"__Secure-1PAPISID", +"NID", +"AEC", +"SOCS", +"__Secure-BUCKET", +"__Secure-ENID", +"SID", +"HSID", +"SSID", +"APISID", +"SAPISID", +"__Secure-3PSID", +"__Secure-3PSIDTS", +"__Secure-3PAPISID", +"SIDCC"] +); + +const MACOS_BROWSER_CONFIGS = [ +{ + name: "Helium", + baseDir: "Library/Application Support/net.imput.helium", + keychainService: "Helium Storage Key", + keychainAccount: "Helium" +}, +{ + name: "Chrome", + baseDir: "Library/Application Support/Google/Chrome", + keychainService: "Chrome Safe Storage", + keychainAccount: "Chrome" +}, +{ + name: "Arc", + baseDir: "Library/Application Support/Arc/User Data", + keychainService: "Arc Safe Storage", + keychainAccount: "Arc" +}]; + + +const LINUX_BROWSER_CONFIGS = [ +{ name: "Chromium", baseDir: ".config/chromium", secretToolApp: "chromium" }, +{ name: "Chrome", baseDir: ".config/google-chrome", secretToolApp: "chrome" }]; + + +async function getGoogleCookies( +options) +{ + const currentPlatform = (0, _nodeOs.platform)(); + const configs = currentPlatform === "darwin" ? + MACOS_BROWSER_CONFIGS : + currentPlatform === "linux" ? + LINUX_BROWSER_CONFIGS : + []; + if (configs.length === 0) return null; + + const warnings = []; + const profile = options?.profile ?? "Default"; + const hosts = GOOGLE_ORIGINS.map((origin) => new URL(origin).hostname); + + for (const config of configs) { + const cookiesPath = (0, _nodePath.join)((0, _nodeOs.homedir)(), config.baseDir, profile, "Cookies"); + if (!(0, _nodeFs.existsSync)(cookiesPath)) continue; + + const password = await readBrowserPassword(config, currentPlatform); + if (!password) { + warnings.push(`Could not read ${config.name} cookie encryption password`); + continue; + } + + const key = (0, _nodeCrypto.pbkdf2Sync)(password, "saltysalt", currentPlatform === "darwin" ? 1003 : 1, 16, "sha1"); + const tempDir = (0, _nodeFs.mkdtempSync)((0, _nodePath.join)((0, _nodeOs.tmpdir)(), "pi-chrome-cookies-")); + + try { + const tempDb = (0, _nodePath.join)(tempDir, "Cookies"); + (0, _nodeFs.copyFileSync)(cookiesPath, tempDb); + copySidecar(cookiesPath, tempDb, "-wal"); + copySidecar(cookiesPath, tempDb, "-shm"); + + const metaVersion = await readMetaVersion(tempDb); + const stripHash = metaVersion >= 24; + const rows = await queryCookieRows(tempDb, hosts); + if (!rows) { + warnings.push(`Failed to query ${config.name} cookie database`); + continue; + } + + const cookies = {}; + for (const row of rows) { + const name = row.name; + if (!ALL_COOKIE_NAMES.has(name)) continue; + if (cookies[name]) continue; + + let value = typeof row.value === "string" && row.value.length > 0 ? row.value : null; + if (!value) { + const encrypted = row.encrypted_value; + if (encrypted instanceof Uint8Array) { + value = decryptCookieValue(encrypted, key, stripHash); + } + } + if (value) cookies[name] = value; + } + + if (options?.requiredCookies?.length && !options.requiredCookies.every((name) => Boolean(cookies[name]))) { + continue; + } + + return { cookies, warnings }; + } finally { + (0, _nodeFs.rmSync)(tempDir, { recursive: true, force: true }); + } + } + + return null; +} + +function decryptCookieValue(encrypted, key, stripHash) { + const buf = Buffer.from(encrypted); + if (buf.length < 3) return null; + + const prefix = buf.subarray(0, 3).toString("utf8"); + if (!/^v\d\d$/.test(prefix)) return null; + + const ciphertext = buf.subarray(3); + if (!ciphertext.length) return ""; + + try { + const iv = Buffer.alloc(16, 0x20); + const decipher = (0, _nodeCrypto.createDecipheriv)("aes-128-cbc", key, iv); + decipher.setAutoPadding(false); + const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]); + const unpadded = removePkcs7Padding(plaintext); + const bytes = stripHash && unpadded.length >= 32 ? unpadded.subarray(32) : unpadded; + const decoded = new TextDecoder("utf-8", { fatal: true }).decode(bytes); + let i = 0; + while (i < decoded.length && decoded.charCodeAt(i) < 0x20) i++; + return decoded.slice(i); + } catch { + return null; + } +} + +function removePkcs7Padding(buf) { + if (!buf.length) return buf; + const padding = buf[buf.length - 1]; + if (!padding || padding > 16) return buf; + return buf.subarray(0, buf.length - padding); +} + +function readBrowserPassword( +config, +currentPlatform) +{ + if (currentPlatform === "darwin") { + if (!config.keychainAccount || !config.keychainService) return Promise.resolve(null); + return readKeychainPassword(config.keychainAccount, config.keychainService); + } + if (currentPlatform === "linux") { + return readLinuxPassword(config.secretToolApp); + } + return Promise.resolve(null); +} + +function readKeychainPassword(account, service) { + return new Promise((resolve) => { + (0, _nodeChild_process.execFile)( + "security", + ["find-generic-password", "-w", "-a", account, "-s", service], + { timeout: 5000 }, + (err, stdout) => { + if (err) {resolve(null);return;} + resolve(stdout.trim() || null); + } + ); + }); +} + +function readLinuxPassword(secretToolApp) { + if (!secretToolApp) return Promise.resolve("peanuts"); + + return new Promise((resolve) => { + (0, _nodeChild_process.execFile)( + "secret-tool", + ["lookup", "application", secretToolApp], + { timeout: 5000 }, + (err, stdout) => { + if (err) { + // KDE Wallet users fall through to peanuts intentionally. + resolve("peanuts"); + return; + } + resolve(stdout.trim() || "peanuts"); + } + ); + }); +} + +let sqliteModule = null; + +async function importSqlite() { + if (sqliteModule) return sqliteModule; + const orig = process.emitWarning.bind(process); + process.emitWarning = (warning, ...args) => { + const msg = typeof warning === "string" ? warning : warning?.message ?? ""; + if (msg.includes("SQLite is an experimental feature")) return; + return orig(warning, ...args); + }; + try { + sqliteModule = await Promise.resolve().then(() => jitiImport("node:sqlite").then((m) => _interopRequireWildcard(m))); + return sqliteModule; + } catch { + return null; + } finally { + process.emitWarning = orig; + } +} + +function supportsReadBigInts() { + const [major, minor] = process.versions.node.split(".").map(Number); + if (major > 24) return true; + if (major < 24) return false; + return minor >= 4; +} + +async function readMetaVersion(dbPath) { + const sqlite = await importSqlite(); + if (!sqlite) return 0; + const opts = { readOnly: true }; + if (supportsReadBigInts()) opts.readBigInts = true; + const db = new sqlite.DatabaseSync(dbPath, opts); + try { + const rows = db.prepare("SELECT value FROM meta WHERE key = 'version'").all(); + const val = rows[0]?.value; + if (typeof val === "number") return Math.floor(val); + if (typeof val === "bigint") return Number(val); + if (typeof val === "string") return parseInt(val, 10) || 0; + return 0; + } catch { + return 0; + } finally { + db.close(); + } +} + +async function queryCookieRows( +dbPath, +hosts) +{ + const sqlite = await importSqlite(); + if (!sqlite) return null; + + const clauses = []; + for (const host of hosts) { + for (const candidate of expandHosts(host)) { + const esc = candidate.replaceAll("'", "''"); + clauses.push(`host_key = '${esc}'`); + clauses.push(`host_key = '.${esc}'`); + clauses.push(`host_key LIKE '%.${esc}'`); + } + } + const where = clauses.join(" OR "); + + const opts = { readOnly: true }; + if (supportsReadBigInts()) opts.readBigInts = true; + const db = new sqlite.DatabaseSync(dbPath, opts); + try { + return db. + prepare( + `SELECT name, value, host_key, encrypted_value FROM cookies WHERE (${where}) ORDER BY expires_utc DESC` + ). + all(); + } catch { + return null; + } finally { + db.close(); + } +} + +function expandHosts(host) { + const parts = host.split(".").filter(Boolean); + if (parts.length <= 1) return [host]; + const candidates = new Set(); + candidates.add(host); + for (let i = 1; i <= parts.length - 2; i++) { + const c = parts.slice(i).join("."); + if (c) candidates.add(c); + } + return Array.from(candidates); +} + +function copySidecar(srcDb, targetDb, suffix) { + const sidecar = `${srcDb}${suffix}`; + if (!(0, _nodeFs.existsSync)(sidecar)) return; + try { + (0, _nodeFs.copyFileSync)(sidecar, `${targetDb}${suffix}`); + } catch { + } +} /* v9-cf2975e0a9fd33bd */ diff --git a/pip-tmp/jiti/pi-web-access-code-search.f93c7d37.mjs b/pip-tmp/jiti/pi-web-access-code-search.f93c7d37.mjs new file mode 100644 index 0000000000000000000000000000000000000000..df6d4a280a61af12534883f621ed9185b91c6b04 --- /dev/null +++ b/pip-tmp/jiti/pi-web-access-code-search.f93c7d37.mjs @@ -0,0 +1,107 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.executeCodeSearch = executeCodeSearch;var _activity = await jitiImport("./activity.js"); +var _exa = await jitiImport("./exa.js"); + +const CODE_CONTEXT_TOOL = "get_code_context_exa"; +const WEB_SEARCH_TOOL = "web_search_exa"; +const DEFAULT_MAX_TOKENS = 5000; + +let codeContextToolMissing = false; + +function isMissingMcpToolError(message) { + const normalized = message.toLowerCase(); + return normalized.includes("tool") && normalized.includes("not found"); +} + +function buildFallbackQuery(query) { + const normalized = query.toLowerCase(); + const hasCodeTerms = /\b(api|code|docs?|documentation|example|github|implementation|library|source|stackoverflow|stack overflow)\b/.test(normalized); + return hasCodeTerms ? query : `${query} code examples documentation GitHub Stack Overflow official docs`; +} + +function maxTokensToResultCount(maxTokens) { + return Math.min(20, Math.max(5, Math.ceil(maxTokens / 1000))); +} + +function trimApproxTokens(text, maxTokens) { + const maxCharacters = Math.max(1000, maxTokens * 4); + if (text.length <= maxCharacters) return text; + return `${text.slice(0, maxCharacters).trimEnd()}\n\n[Truncated by code_search to approximately ${maxTokens} tokens.]`; +} + +async function executeFallbackSearch(query, maxTokens, signal) { + const text = await (0, _exa.callExaMcp)( + WEB_SEARCH_TOOL, + { + query: buildFallbackQuery(query), + numResults: maxTokensToResultCount(maxTokens), + livecrawl: "fallback", + type: "auto", + contextMaxCharacters: Math.min(50000, Math.max(1000, maxTokens * 4)) + }, + signal + ); + return trimApproxTokens(text, maxTokens); +} + +async function executeCodeSearch( +_toolCallId, +params, +signal) + + + +{ + const query = params.query.trim(); + if (!query) { + return { + content: [{ type: "text", text: "Error: No query provided." }], + details: { query: "", maxTokens: params.maxTokens ?? DEFAULT_MAX_TOKENS, error: "No query provided" } + }; + } + + const maxTokens = params.maxTokens ?? DEFAULT_MAX_TOKENS; + const activityId = _activity.activityMonitor.logStart({ type: "api", query }); + + try { + let mode = "web-search-fallback"; + let text; + + if (codeContextToolMissing) { + text = await executeFallbackSearch(query, maxTokens, signal); + } else { + try { + text = await (0, _exa.callExaMcp)( + CODE_CONTEXT_TOOL, + { + query, + tokensNum: maxTokens + }, + signal + ); + mode = "code-context"; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + if (!isMissingMcpToolError(message)) throw err; + codeContextToolMissing = true; + text = await executeFallbackSearch(query, maxTokens, signal); + } + } + + _activity.activityMonitor.logComplete(activityId, 200); + return { + content: [{ type: "text", text }], + details: { query, maxTokens, mode } + }; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + if (message.toLowerCase().includes("abort")) { + _activity.activityMonitor.logComplete(activityId, 0); + throw err; + } + _activity.activityMonitor.logError(activityId, message); + return { + content: [{ type: "text", text: `Error: ${message}` }], + details: { query, maxTokens, error: message } + }; + } +} /* v9-7cb6dc1a8d8e5070 */ diff --git a/pip-tmp/jiti/pi-web-access-curator-page.f382e376.mjs b/pip-tmp/jiti/pi-web-access-curator-page.f382e376.mjs new file mode 100644 index 0000000000000000000000000000000000000000..0a4cda91a4f5ed82c5cd871736b6c83dfba6c22f --- /dev/null +++ b/pip-tmp/jiti/pi-web-access-curator-page.f382e376.mjs @@ -0,0 +1,3359 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.generateCuratorPage = generateCuratorPage;function safeInlineJSON(data) { + return JSON.stringify(data). + replace(//g, "\\u003e"). + replace(/&/g, "\\u0026"). + replace(/\u2028/g, "\\u2028"). + replace(/\u2029/g, "\\u2029"); +} + +function buildProviderButtons( +available, +selected, +hasInitialQueries) +{ + const providers = [ + { value: "perplexity", label: "Perplexity", available: available.perplexity }, + { value: "exa", label: "Exa", available: available.exa }, + { value: "gemini", label: "Gemini", available: available.gemini }]; + + + return providers. + filter((p) => p.available). + map((p) => { + const isDefault = p.value === selected; + const state = isDefault && hasInitialQueries ? "loading" : "idle"; + const classes = ["provider-btn", state, isDefault ? "is-default" : ""].filter(Boolean).join(" "); + const disabled = state === "loading" ? " disabled" : ""; + return ``; + }). + join(""); +} + +function generateCuratorPage( +queries, +sessionToken, +timeout, +availableProviders, +defaultProvider, +summaryModels, +defaultSummaryModel) +{ + const providerButtonsHtml = buildProviderButtons(availableProviders, defaultProvider, queries.length > 0); + const inlineData = safeInlineJSON({ queries, sessionToken, timeout, defaultProvider, summaryModels, defaultSummaryModel, availableProviders }); + + return ` + + + + +Curate Search Results + + + + + +`; +} + +const CSS = ` +*,*::before,*::after{box-sizing:border-box;margin:0;padding:0} + +:root { + --bg: #18181e; + --bg-card: #1e1e24; + --bg-elevated: #252530; + --bg-hover: #2b2b37; + --fg: #e0e0e0; + --fg-muted: #909098; + --fg-dim: #606068; + --accent: #8abeb7; + --accent-hover: #9dcec7; + --accent-muted: rgba(138, 190, 183, 0.15); + --accent-subtle: rgba(138, 190, 183, 0.08); + --border: #2a2a34; + --border-muted: #353540; + --border-checked: #8abeb7; + --check-bg: #8abeb7; + --btn-primary: #8abeb7; + --btn-primary-hover: #9dcec7; + --btn-primary-fg: #18181e; + --btn-secondary: #252530; + --btn-secondary-hover: #2b2b37; + --timer-bg: #252530; + --timer-fg: #909098; + --timer-warn-bg: rgba(240, 198, 116, 0.15); + --timer-warn-fg: #f0c674; + --timer-urgent-bg: rgba(204, 102, 102, 0.15); + --timer-urgent-fg: #cc6666; + --overlay-bg: rgba(24, 24, 30, 0.92); + --success: #b5bd68; + --warning: #f0c674; + --font: 'Outfit', system-ui, -apple-system, sans-serif; + --font-display: 'Instrument Serif', Georgia, 'Times New Roman', serif; + --font-mono: 'SF Mono', Consolas, monospace; + --radius: 10px; + --radius-sm: 6px; +} + +@media (prefers-color-scheme: light) { + :root { + --bg: #f5f5f7; + --bg-card: #ffffff; + --bg-elevated: #eeeef0; + --bg-hover: #e4e4e8; + --fg: #1a1a1e; + --fg-muted: #6c6c74; + --fg-dim: #9a9aa2; + --accent: #5f8787; + --accent-hover: #4a7272; + --accent-muted: rgba(95, 135, 135, 0.12); + --accent-subtle: rgba(95, 135, 135, 0.06); + --border: #dcdce0; + --border-muted: #c8c8d0; + --border-checked: #5f8787; + --check-bg: #5f8787; + --btn-primary: #5f8787; + --btn-primary-hover: #4a7272; + --btn-primary-fg: #ffffff; + --btn-secondary: #e4e4e8; + --btn-secondary-hover: #d4d4d8; + --timer-bg: #e4e4e8; + --timer-fg: #6c6c74; + --timer-warn-bg: rgba(217, 119, 6, 0.10); + --timer-warn-fg: #92400e; + --timer-urgent-bg: rgba(175, 95, 95, 0.10); + --timer-urgent-fg: #991b1b; + --overlay-bg: rgba(255, 255, 255, 0.92); + --success: #4d7c0f; + --warning: #b45309; + } +} + +body { + font-family: var(--font); + background: var(--bg); + background-image: radial-gradient(ellipse at 50% 0%, var(--accent-muted) 0%, transparent 60%); + color: var(--fg); + line-height: 1.5; + min-height: 100dvh; + padding-bottom: 72px; +} + +.timer-badge { + position: fixed; + top: 20px; + right: 24px; + z-index: 50; + font-family: var(--font); + font-size: 12px; + font-weight: 600; + font-variant-numeric: tabular-nums; + padding: 5px 14px; + border-radius: 999px; + background: var(--bg-elevated); + color: var(--timer-fg); + border: 1px solid var(--border); + transition: background 0.3s, color 0.3s, border-color 0.3s, opacity 0.3s; + box-shadow: 0 2px 8px rgba(0,0,0,0.2); + cursor: pointer; + user-select: none; + opacity: 0.5; +} +.timer-badge:hover { opacity: 1; } +.timer-badge.active { opacity: 1; } +.timer-badge.warn { + opacity: 1; + background: var(--timer-warn-bg); + color: var(--timer-warn-fg); + border-color: color-mix(in srgb, var(--timer-warn-fg) 30%, transparent); +} +.timer-badge.urgent { + opacity: 1; + background: var(--timer-urgent-bg); + color: var(--timer-urgent-fg); + border-color: color-mix(in srgb, var(--timer-urgent-fg) 30%, transparent); +} +.timer-adjust { + position: fixed; + top: 20px; + right: 24px; + z-index: 51; + display: none; + align-items: center; + gap: 6px; + padding: 4px 6px 4px 12px; + background: var(--bg-elevated); + border: 1px solid var(--accent); + border-radius: 999px; + box-shadow: 0 2px 12px rgba(0,0,0,0.3); +} +.timer-adjust.visible { display: flex; } +.timer-adjust input { + width: 48px; + background: transparent; + border: none; + outline: none; + color: var(--fg); + font-family: var(--font); + font-size: 13px; + font-weight: 600; + font-variant-numeric: tabular-nums; + text-align: center; +} +.timer-adjust-label { font-size: 11px; color: var(--fg-dim); } +.timer-adjust-btn { + font-family: var(--font); + font-size: 11px; + font-weight: 600; + padding: 3px 10px; + border-radius: 999px; + border: none; + background: var(--accent); + color: var(--btn-primary-fg); + cursor: pointer; +} +.timer-adjust-btn:hover { background: var(--accent-hover); } + +main { + max-width: 640px; + margin: 0 auto; + padding: 56px 24px 16px; +} + +.hero { margin-bottom: 28px; } +.hero-kicker { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--accent); + margin-bottom: 8px; +} +.hero-title { + font-family: var(--font-display); + font-size: 40px; + font-weight: 400; + font-style: italic; + letter-spacing: -0.01em; + line-height: 1.1; + color: var(--fg); + margin-bottom: 10px; + text-wrap: balance; +} +.hero-desc { + font-size: 14px; + color: var(--fg-muted); + line-height: 1.5; + margin-bottom: 12px; + max-width: 480px; +} +.hero-meta { + display: flex; + align-items: center; + gap: 10px; + font-size: 13px; + color: var(--fg-dim); +} +.hero-meta-sep { + width: 3px; + height: 3px; + border-radius: 50%; + background: var(--fg-dim); + flex-shrink: 0; +} +#hero-status:empty + .hero-meta-sep { display: none; } +.provider-buttons { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 6px; +} +.summary-model-controls { + display: flex; + align-items: center; + gap: 6px; + min-width: 0; + flex-shrink: 0; +} +.summary-model-dropdown { + font-family: var(--font); + font-size: 12px; + font-weight: 600; + color: var(--fg); + background: var(--bg-elevated); + border: 1px solid var(--border-muted); + border-radius: var(--radius-sm); + padding: 4px 8px; + max-width: 220px; +} +.summary-model-dropdown:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 18%, transparent); +} +.summary-model-dropdown:disabled { + opacity: 0.65; + cursor: default; +} +.provider-btn { + font-family: var(--font); + font-size: 12px; + font-weight: 600; + padding: 3px 10px; + border-radius: 999px; + border: 1px solid var(--border-muted); + background: transparent; + color: var(--fg-muted); + cursor: pointer; + transition: border-color 0.12s, background 0.12s, color 0.12s, opacity 0.12s; +} +.provider-btn.idle:hover { + color: var(--fg); + border-color: var(--accent); +} +.provider-btn.loading { + background: var(--accent-subtle); + color: var(--accent); + border-color: color-mix(in srgb, var(--accent) 35%, var(--border-muted)); + cursor: default; + pointer-events: none; + opacity: 0.85; +} +.provider-btn.loading::after { + content: " …"; + animation: provider-pulse 1.2s ease-in-out infinite; +} +.provider-btn.searched { + background: var(--btn-secondary); + color: var(--fg); + border-color: var(--border-muted); +} +.provider-btn.searched::after { + content: " ✓"; + color: var(--success); +} +.provider-btn.is-default { + box-shadow: inset 0 -2px 0 0 var(--accent); + border-color: var(--accent); +} +.provider-btn:disabled { + cursor: default; + opacity: 0.5; +} + +@keyframes provider-pulse { + 0%, 100% { opacity: 0.4; } + 50% { opacity: 1; } +} + +#result-cards { display: flex; flex-direction: column; gap: 8px; } + +.send-raw-row { + display: flex; + justify-content: flex-end; + padding: 4px 0; +} +.send-raw-row.hidden { display: none; } + +.result-loading { + border: 1px solid var(--border); + border-radius: var(--radius); + background: color-mix(in srgb, var(--bg-card) 86%, var(--accent-subtle)); + overflow: hidden; + box-shadow: 0 1px 2px rgba(0,0,0,0.06); +} +.result-loading-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 12px 14px 10px; + border-bottom: 1px solid var(--border); +} +.result-loading-title { + font-size: 12px; + font-weight: 600; + letter-spacing: 0.04em; + text-transform: uppercase; + color: var(--accent); +} +.result-loading-sub { + font-size: 12px; + color: var(--fg-dim); + font-variant-numeric: tabular-nums; +} +.result-loading-grid { + display: grid; + gap: 10px; + padding: 12px 14px 14px; +} +.loading-card { + border: 1px solid color-mix(in srgb, var(--border-muted) 80%, var(--accent-subtle)); + border-radius: var(--radius-sm); + background: var(--bg-card); + overflow: hidden; + position: relative; +} +.loading-card::after { + content: ""; + position: absolute; + inset: 0; + background: linear-gradient(105deg, transparent 10%, color-mix(in srgb, var(--accent) 18%, transparent) 45%, transparent 75%); + transform: translateX(-130%); + animation: loading-sweep 2s ease-in-out infinite; + pointer-events: none; +} +.loading-card-row { + height: 10px; + border-radius: 999px; + margin: 10px 12px; + background: color-mix(in srgb, var(--fg-dim) 35%, transparent); +} +.loading-card-row.short { width: 35%; } +.loading-card-row.mid { width: 58%; } +.loading-card-row.long { width: 78%; } + +.result-card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; + transition: border-color 0.12s; + box-shadow: 0 1px 2px rgba(0,0,0,0.06); +} +.result-card.checked { border-color: var(--border-checked); } +.result-card.searching { + opacity: 1; + border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); + background: linear-gradient(180deg, color-mix(in srgb, var(--accent-subtle) 70%, var(--bg-card)) 0%, var(--bg-card) 100%); + position: relative; +} +.result-card.searching::after { + content: ""; + position: absolute; + inset: 0; + background: linear-gradient(110deg, transparent 20%, color-mix(in srgb, var(--accent) 14%, transparent) 50%, transparent 80%); + transform: translateX(-130%); + animation: loading-sweep 2.2s ease-in-out infinite; + pointer-events: none; +} +.result-card.searching .result-card-header { cursor: default; } +.result-card.searching .result-card-header:hover { background: transparent; } +.result-card.error { border-color: var(--timer-urgent-fg); } + +.result-card-header { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 14px 16px; + cursor: pointer; + user-select: none; + transition: background 0.12s; +} +.result-card-header:hover { background: var(--bg-hover); } + +.result-card-header input[type="checkbox"] { + appearance: none; + width: 16px; + height: 16px; + min-width: 16px; + border: 1.5px solid var(--border-muted); + border-radius: 4px; + margin-top: 2px; + cursor: pointer; + transition: background 0.12s, border-color 0.12s; + display: grid; + place-content: center; +} +.result-card-header input[type="checkbox"]:checked { + background: var(--check-bg); + border-color: var(--check-bg); +} +.result-card-header input[type="checkbox"]:checked::after { + content: ""; + width: 9px; + height: 6px; + border-left: 2px solid var(--btn-primary-fg); + border-bottom: 2px solid var(--btn-primary-fg); + transform: rotate(-45deg); + margin-top: -1px; +} + +.result-card-info { flex: 1; min-width: 0; } + +.result-card-query-row { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; + margin-bottom: 2px; +} +.result-card-query { + font-size: 14px; + font-weight: 600; + color: var(--fg); +} +.provider-tag { + display: inline-flex; + align-items: center; + padding: 1px 7px; + border-radius: 999px; + font-size: 10px; + font-weight: 700; + letter-spacing: 0.03em; + text-transform: uppercase; + border: 1px solid transparent; +} +.provider-tag.provider-exa { + color: #8dd3ff; + background: rgba(141, 211, 255, 0.14); + border-color: rgba(141, 211, 255, 0.3); +} +.provider-tag.provider-perplexity { + color: #cba6f7; + background: rgba(203, 166, 247, 0.14); + border-color: rgba(203, 166, 247, 0.3); +} +.provider-tag.provider-gemini { + color: #f5c27b; + background: rgba(245, 194, 123, 0.14); + border-color: rgba(245, 194, 123, 0.3); +} +.provider-tag.provider-unknown { + color: var(--fg-muted); + background: var(--bg-elevated); + border-color: var(--border-muted); +} +.result-card-meta { + font-size: 12px; + color: var(--fg-dim); +} +.result-card-preview { + font-size: 12.5px; + color: var(--fg-muted); + margin-top: 6px; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + line-height: 1.45; +} + +.result-card-expand { + color: var(--fg-dim); + font-size: 11px; + margin-top: 2px; + flex-shrink: 0; + padding-top: 3px; + transition: color 0.12s; +} +.result-card-header:hover .result-card-expand { color: var(--fg-muted); } + +.result-card-body { + display: none; + border-top: 1px solid var(--border); +} +.result-card-body.open { display: block; } + +.result-card-answer { + padding: 14px 16px; + font-size: 13.5px; + color: var(--fg-muted); + line-height: 1.6; + max-height: 400px; + overflow-y: auto; +} +.result-card-answer h1, +.result-card-answer h2, +.result-card-answer h3, +.result-card-answer h4 { + color: var(--fg); + font-family: var(--font); + font-weight: 600; + margin: 16px 0 6px; + line-height: 1.3; +} +.result-card-answer h1 { font-size: 16px; } +.result-card-answer h2 { font-size: 14.5px; } +.result-card-answer h3 { font-size: 13.5px; } +.result-card-answer h4 { font-size: 13px; color: var(--fg-muted); } +.result-card-answer p { margin: 0 0 10px; } +.result-card-answer p:last-child { margin-bottom: 0; } +.result-card-answer strong { color: var(--fg); font-weight: 600; } +.result-card-answer a { color: var(--accent); text-decoration: none; } +.result-card-answer a:hover { text-decoration: underline; } +.result-card-answer ul, .result-card-answer ol { + margin: 6px 0 10px; + padding-left: 20px; +} +.result-card-answer li { margin-bottom: 4px; } +.result-card-answer li::marker { color: var(--fg-dim); } +.result-card-answer code { + font-family: var(--font-mono); + font-size: 12px; + padding: 1px 5px; + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: 3px; + color: var(--fg); +} +.result-card-answer pre { + margin: 8px 0 12px; + padding: 12px 14px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + overflow-x: auto; + line-height: 1.45; +} +.result-card-answer pre code { + padding: 0; + background: none; + border: none; + font-size: 12px; + color: var(--fg-muted); +} +.result-card-answer blockquote { + margin: 8px 0; + padding: 8px 14px; + border-left: 3px solid var(--accent); + color: var(--fg-dim); + background: var(--accent-subtle); + border-radius: 0 var(--radius-sm) var(--radius-sm) 0; +} +.result-card-answer table { + width: 100%; + border-collapse: collapse; + margin: 8px 0 12px; + font-size: 12.5px; +} +.result-card-answer th, .result-card-answer td { + padding: 6px 10px; + border: 1px solid var(--border); + text-align: left; +} +.result-card-answer th { + background: var(--bg-elevated); + color: var(--fg); + font-weight: 600; + font-size: 11.5px; + text-transform: uppercase; + letter-spacing: 0.03em; +} +.result-card-answer hr { + border: none; + border-top: 1px solid var(--border); + margin: 14px 0; +} + +.result-card-sources { + padding: 10px 16px 14px; + border-top: 1px solid var(--border); +} +.result-card-sources-title { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--fg-dim); + margin-bottom: 6px; +} +.source-link { + display: block; + padding: 4px 0; + font-size: 12.5px; + color: var(--fg-muted); + text-decoration: none; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + transition: color 0.12s; +} +.source-link:hover { color: var(--accent); } +.source-domain { + color: var(--fg-dim); + margin-left: 6px; +} + +.result-card-error-msg { + padding: 12px 16px; + font-size: 13px; + color: var(--timer-urgent-fg); +} + +.card-alt-providers { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 16px 8px 42px; + font-size: 11px; + color: var(--fg-dim); +} +.card-alt-chip { + font-family: var(--font); + font-size: 10px; + font-weight: 600; + padding: 2px 8px; + border-radius: 999px; + border: 1px solid var(--border-muted); + background: transparent; + color: var(--fg-muted); + cursor: pointer; + transition: border-color 0.12s, color 0.12s, background 0.12s; +} +.card-alt-chip:hover:not(:disabled) { + color: var(--accent); + border-color: var(--accent); +} +.card-alt-chip:disabled { + opacity: 0.4; + cursor: default; +} +.card-alt-chip.loading { + opacity: 0.6; + pointer-events: none; +} +.card-alt-chip.loading::after { + content: " …"; +} + +.searching-dots::after { + content: ""; + animation: dots 1.5s steps(4, end) infinite; +} +@keyframes dots { + 0% { content: ""; } + 25% { content: "."; } + 50% { content: ".."; } + 75% { content: "..."; } +} + +@keyframes loading-sweep { + 0% { transform: translateX(-130%); } + 100% { transform: translateX(130%); } +} + +@keyframes summary-pulse { + 0%, 100% { + transform: scale(0.9); + box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 35%, transparent); + } + 50% { + transform: scale(1.15); + box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent) 0%, transparent); + } +} + +@keyframes summary-sweep { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(120%); } +} + +@keyframes summary-panel-sweep { + 0% { transform: translateX(-115%); } + 100% { transform: translateX(115%); } +} + +.add-search { + display: flex; + align-items: center; + gap: 10px; + margin-top: 12px; + padding: 11px 14px; + border: 1px dashed var(--border); + border-radius: var(--radius); + cursor: text; + transition: border-color 0.15s, background 0.15s; +} +.add-search:hover { + border-color: var(--border-muted); + background: var(--accent-subtle); +} +.add-search:focus-within { + border-color: var(--accent); + border-style: solid; + background: var(--accent-subtle); +} +.add-search-icon { + color: var(--fg-dim); + font-size: 16px; + font-weight: 300; + line-height: 1; + flex-shrink: 0; + transition: color 0.15s; +} +.add-search:focus-within .add-search-icon { color: var(--accent); } +.add-search input { + flex: 1; + background: transparent; + border: none; + outline: none; + color: var(--fg); + font-family: var(--font); + font-size: 13.5px; + font-weight: 500; +} +.add-search input::placeholder { + color: var(--fg-dim); + font-weight: 400; +} +.add-search-wand { + flex-shrink: 0; + width: 26px; + height: 26px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid var(--border-muted); + border-radius: 6px; + background: transparent; + color: var(--fg-dim); + font-size: 14px; + cursor: pointer; + transition: color 0.12s, border-color 0.12s, background 0.12s; +} +.add-search-wand:hover:not(:disabled) { + color: var(--accent); + border-color: var(--accent); + background: var(--accent-subtle); +} +.add-search-wand:disabled { + opacity: 0.3; + cursor: default; +} +.add-search-wand.rewriting { + pointer-events: none; + animation: wand-spin 0.8s linear infinite; +} +@keyframes wand-spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.summary-panel { + margin-top: 14px; + border: 1px solid var(--border); + border-radius: var(--radius); + background: var(--bg-card); + padding: 14px; + display: flex; + flex-direction: column; + gap: 10px; +} +.summary-panel.hidden { display: none; } +.summary-header { display: flex; flex-direction: column; gap: 2px; } +.summary-header-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; } +.summary-title { + font-size: 14px; + font-weight: 600; + color: var(--fg); +} +.summary-subtitle { + font-size: 12px; + color: var(--fg-dim); +} +.summary-generating { + position: relative; + isolation: isolate; + overflow: hidden; + border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--border)); + border-radius: var(--radius-sm); + background: linear-gradient(130deg, color-mix(in srgb, var(--accent-subtle) 78%, transparent) 0%, var(--bg-elevated) 70%); + padding: 12px; + display: flex; + flex-direction: column; + gap: 10px; +} +.summary-generating::before { + content: ""; + position: absolute; + inset: 0; + background: linear-gradient(110deg, transparent 0%, color-mix(in srgb, var(--accent) 16%, transparent) 50%, transparent 100%); + transform: translateX(-115%); + animation: summary-panel-sweep 2.4s ease-in-out infinite; + pointer-events: none; +} +.summary-generating > * { + position: relative; + z-index: 1; +} +.summary-generating.hidden { display: none; } +.summary-generating-head { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + font-weight: 600; + color: var(--accent-hover); +} +.summary-generating-orb { + width: 10px; + height: 10px; + border-radius: 999px; + background: var(--accent); + box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 35%, transparent); + animation: summary-pulse 1.1s ease-in-out infinite; +} +.summary-generating-bars { + display: grid; + gap: 6px; +} +.summary-generating-bar { + position: relative; + display: block; + height: 8px; + border-radius: 999px; + background: color-mix(in srgb, var(--bg) 65%, var(--bg-elevated)); + overflow: hidden; + transition: width 220ms ease; +} +.summary-generating-bar::after { + content: ""; + position: absolute; + inset: 0; + transform: translateX(-100%); + background: linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--accent) 45%, transparent) 50%, transparent 100%); + animation: summary-sweep 1.6s ease-in-out infinite; +} +.summary-generating-bar.b1 { width: 86%; } +.summary-generating-bar.b2 { width: 68%; } +.summary-generating-bar.b3 { width: 74%; } +.summary-generating[data-phase="1"] .summary-generating-bar.b1 { width: 72%; } +.summary-generating[data-phase="1"] .summary-generating-bar.b2 { width: 82%; } +.summary-generating[data-phase="1"] .summary-generating-bar.b3 { width: 60%; } +.summary-generating[data-phase="2"] .summary-generating-bar.b1 { width: 64%; } +.summary-generating[data-phase="2"] .summary-generating-bar.b2 { width: 71%; } +.summary-generating[data-phase="2"] .summary-generating-bar.b3 { width: 90%; } +.summary-generating-bar.b2::after { animation-delay: 0.15s; } +.summary-generating-bar.b3::after { animation-delay: 0.3s; } +.summary-input { + width: 100%; + min-height: 180px; + resize: vertical; + border: 1px solid var(--border-muted); + border-radius: var(--radius-sm); + padding: 10px 12px; + font-family: var(--font); + font-size: 13px; + line-height: 1.5; + color: var(--fg); + background: var(--bg-elevated); + outline: none; +} +.summary-input.hidden { display: none; } +.summary-input:focus { + border-color: var(--accent); +} +.summary-feedback-row { + display: flex; + align-items: center; + gap: 8px; + margin-top: 6px; +} +.summary-feedback { + flex: 1; + height: 32px; + border: 1px solid var(--border-muted); + border-radius: var(--radius-sm); + padding: 4px 10px; + font-family: var(--font); + font-size: 12px; + color: var(--fg); + background: var(--bg-elevated); + outline: none; +} +.summary-feedback:focus { + border-color: var(--accent); +} +.summary-feedback::placeholder { + color: var(--fg-muted); +} +.summary-actions { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 8px; +} + +.action-bar { + position: fixed; + bottom: 0; + left: 0; + right: 0; + z-index: 10; + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 24px; + background: color-mix(in srgb, var(--bg) 90%, transparent); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border-top: 1px solid var(--border); +} +.action-shortcuts { display: flex; align-items: center; gap: 16px; } +.shortcut { display: flex; align-items: center; gap: 5px; font-size: 11px; color: var(--fg-dim); } +.shortcut kbd { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 18px; + height: 18px; + padding: 0 4px; + font-family: var(--font-mono); + font-size: 10px; + font-weight: 500; + background: var(--bg-elevated); + border: 1px solid var(--border-muted); + border-radius: 3px; + color: var(--fg-muted); +} +.action-buttons { display: flex; gap: 8px; } + +.btn { + font-family: var(--font); + font-size: 13px; + font-weight: 500; + padding: 7px 16px; + border: none; + border-radius: var(--radius-sm); + cursor: pointer; + transition: background 0.12s, opacity 0.12s; +} +.btn:disabled { opacity: 0.35; cursor: default; } +.btn-submit { background: var(--btn-primary); color: var(--btn-primary-fg); } +.btn-submit:hover:not(:disabled) { background: var(--btn-primary-hover); } +.btn-secondary { background: var(--btn-secondary); color: var(--fg-muted); border: 1px solid var(--border); } +.btn-secondary:hover:not(:disabled) { background: var(--btn-secondary-hover); color: var(--fg); } + +.success-overlay { + position: fixed; inset: 0; z-index: 200; + background: var(--overlay-bg); + display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; + transition: opacity 200ms; +} +.success-overlay.hidden { display: flex !important; opacity: 0; pointer-events: none; } +.success-icon { + width: 56px; height: 56px; border-radius: 50%; + border: 2px solid var(--success); + display: flex; align-items: center; justify-content: center; + font-size: 18px; font-weight: 700; color: var(--success); +} +.success-overlay p { margin: 0; font-size: 13px; font-weight: 600; color: var(--success); letter-spacing: 0.06em; text-transform: uppercase; } + +.expired-overlay { + position: fixed; inset: 0; + background: var(--overlay-bg); + display: flex; align-items: center; justify-content: center; + opacity: 0; transition: opacity 400ms; pointer-events: none; z-index: 200; +} +.expired-overlay.visible { opacity: 1; pointer-events: auto; } +.expired-overlay.hidden { display: flex !important; opacity: 0; pointer-events: none; } +.expired-content { + text-align: center; max-width: 480px; padding: 48px 56px; + background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; +} +.expired-overlay.visible .expired-content { animation: slide-up 400ms ease-out; } +@keyframes slide-up { from { transform: translateY(20px); } to { transform: translateY(0); } } +.expired-icon { + width: 72px; height: 72px; border-radius: 50%; border: 2px solid var(--warning); + display: flex; align-items: center; justify-content: center; + font-size: 32px; font-weight: bold; color: var(--warning); margin: 0 auto 24px; +} +.expired-content h2 { color: var(--fg); margin: 0 0 16px; font-size: 22px; font-weight: 600; } +.expired-content p { color: var(--fg-muted); margin: 0 0 24px; font-size: 14px; line-height: 1.6; } +.expired-countdown { font-size: 13px; color: var(--fg-dim); font-variant-numeric: tabular-nums; } +.expired-countdown span { color: var(--warning); font-weight: 600; } + +.preview-modal { + position: fixed; inset: 0; z-index: 250; + background: var(--overlay-bg); + display: flex; align-items: center; justify-content: center; + animation: fade-in 150ms ease-out; +} +.preview-modal.hidden { display: none; } +@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } } +.preview-modal-inner { + width: min(720px, calc(100% - 48px)); + max-height: calc(100vh - 80px); + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: 12px; + display: flex; flex-direction: column; + animation: slide-up 200ms ease-out; +} +.preview-modal-header { + display: flex; align-items: center; justify-content: space-between; + padding: 16px 20px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} +.preview-modal-title { font-size: 14px; font-weight: 600; color: var(--fg); margin: 0; } +.preview-modal-close { + background: none; border: none; cursor: pointer; + font-size: 22px; line-height: 1; color: var(--fg-muted); padding: 0 4px; + transition: color 0.12s; +} +.preview-modal-close:hover { color: var(--fg); } +.preview-modal-body { + position: relative; + padding: 24px 28px; + overflow-y: auto; + font-size: 14px; line-height: 1.7; color: var(--fg); +} +.preview-modal-body h1 { font-size: 20px; font-weight: 600; margin: 1.2em 0 0.5em; color: var(--fg); } +.preview-modal-body h2 { font-size: 16px; font-weight: 600; margin: 1.2em 0 0.4em; color: var(--fg); } +.preview-modal-body h3 { font-size: 14px; font-weight: 600; margin: 1em 0 0.3em; color: var(--fg); } +.preview-modal-body p { margin: 0.6em 0; } +.preview-modal-body a { color: var(--accent); } +.preview-modal-body pre { background: var(--bg-elevated); padding: 14px; border-radius: var(--radius-sm); overflow-x: auto; } +.preview-modal-body code { font-size: 0.9em; } +.preview-modal-body blockquote { border-left: 3px solid var(--border); padding-left: 14px; color: var(--fg-muted); margin: 0.6em 0; } +.preview-modal-body hr { border: none; border-top: 1px solid var(--border); margin: 1.5em 0; } +.preview-modal-body ul, .preview-modal-body ol { padding-left: 1.4em; } +.preview-modal-body li + li { margin-top: 0.25em; } +.preview-modal-body strong { color: var(--fg); } +.preview-modal-footer { + padding: 12px 20px; + border-top: 1px solid var(--border); + display: flex; align-items: center; gap: 8px; + flex-shrink: 0; +} +.preview-modal-model { + margin-right: auto; + font-family: var(--font); + font-size: 11px; + color: var(--fg-muted); + background: var(--bg-elevated); + border: 1px solid var(--border-muted); + border-radius: var(--radius-sm); + padding: 4px 8px; + max-width: 220px; + outline: none; +} +.preview-modal-model:focus { border-color: var(--accent); } + +.preview-popover { + position: absolute; + z-index: 260; + width: min(340px, calc(100% - 40px)); + background: var(--bg-elevated); + border: 1px solid var(--accent); + border-radius: var(--radius); + padding: 10px 12px; + display: flex; flex-direction: column; gap: 8px; + box-shadow: 0 8px 24px rgba(0,0,0,0.35); + animation: fade-in 100ms ease-out; +} +.preview-popover.hidden { display: none; } +.preview-popover-quote { + font-size: 12px; + color: var(--fg-muted); + font-style: italic; + border-left: 2px solid var(--accent); + padding-left: 8px; + max-height: 48px; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} +.preview-popover-input { + font-family: var(--font); + font-size: 13px; + line-height: 1.4; + color: var(--fg); + background: var(--bg-card); + border: 1px solid var(--border-muted); + border-radius: var(--radius-sm); + padding: 6px 10px; + outline: none; + width: 100%; + resize: vertical; +} +.preview-popover-input:focus { border-color: var(--accent); } +.preview-popover-btn { align-self: flex-end; font-size: 12px; padding: 5px 14px; } + +.error-banner { + position: fixed; bottom: 64px; left: 50%; transform: translateX(-50%); z-index: 50; + padding: 10px 20px; background: var(--timer-urgent-bg); color: var(--timer-urgent-fg); + border-radius: var(--radius); font-size: 13px; font-weight: 500; +} + +.summary-panel.updating { + border-color: color-mix(in srgb, var(--accent) 35%, var(--border)); + position: relative; + overflow: hidden; +} +.summary-panel.updating::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 30%; + height: 2px; + border-radius: var(--radius) var(--radius) 0 0; + background: linear-gradient(90deg, transparent, var(--accent), transparent); + animation: updating-bar 1.8s ease-in-out infinite; + pointer-events: none; +} +.summary-panel.updating .summary-input, +.summary-panel.updating .summary-feedback-row { + opacity: 0.45; + pointer-events: none; +} +.summary-panel.updating .summary-actions { + opacity: 0.72; +} +@keyframes updating-bar { + 0% { transform: translateX(-50%); } + 100% { transform: translateX(430%); } +} + +@media (prefers-reduced-motion: reduce) { + .loading-card::after, + .result-card.searching::after, + .provider-btn.loading::after, + .searching-dots::after, + .summary-generating::before, + .summary-generating-orb, + .summary-generating-bar::after, + .summary-panel.updating::after { + animation: none !important; + } +} + +@media (max-width: 500px) { + main { padding: 32px 16px 16px; } + .hero-title { font-size: 28px; } + .hero-desc { font-size: 13px; } + .summary-header-top { flex-direction: column; } + .summary-model-controls { flex-wrap: wrap; } + .summary-model-dropdown { max-width: 100%; } + .action-bar { padding: 10px 14px; } + .action-shortcuts { display: none; } + .result-card-header { padding: 12px 14px; } + .expired-content { padding: 32px 24px; } + .timer-badge { top: 12px; right: 16px; } +} +`; + +const SCRIPT = `(function() { + var DATA = __INLINE_DATA__; + var token = DATA.sessionToken; + var timeoutSec = DATA.timeout; + var queries = Array.isArray(DATA.queries) ? DATA.queries : []; + var providers = ["perplexity", "exa", "gemini"]; + var availProviders = DATA.availableProviders && typeof DATA.availableProviders === "object" ? DATA.availableProviders : {}; + var workflow = "summary-review"; + var initialDefaultProvider = typeof DATA.defaultProvider === "string" ? DATA.defaultProvider : "exa"; + if (providers.indexOf(initialDefaultProvider) === -1) initialDefaultProvider = "exa"; + + var summaryModels = Array.isArray(DATA.summaryModels) + ? DATA.summaryModels.filter(function(model) { + return model && typeof model === "object" && typeof model.value === "string"; + }) + : []; + var defaultSummaryModel = typeof DATA.defaultSummaryModel === "string" + ? DATA.defaultSummaryModel.trim() + : ""; + + var submitted = false; + var timerExpired = false; + var submitInFlight = false; + var searchesDone = false; + var stage = "results"; + var summaryMeta = null; + var summaryRequestSeq = 0; + var lastAutoSummarySignature = ""; + var lastInteraction = Date.now(); + var completedCount = 0; + var es = null; + + var allQueries = queries.map(function(query, slotId) { return { slotId: slotId, query: query }; }); + var nextSlotId = queries.length; + var queryIndexToSlot = new Map(); + var providerCoverage = new Map(); + + var currentProvider = initialDefaultProvider; + var initialStreamDone = queries.length === 0; + var providerBatchInFlight = false; + var batchLoadingProvider = null; + var addSearchInFlight = 0; + var isRegenerating = false; + + var timerEl = document.getElementById("timer"); + var timerAdjustEl = document.getElementById("timer-adjust"); + var timerInput = document.getElementById("timer-input"); + var timerSetBtn = document.getElementById("timer-set"); + var heroTitle = document.querySelector(".hero-title"); + var heroDesc = document.querySelector(".hero-desc"); + var resultCardsEl = document.getElementById("result-cards"); + var btnSend = document.getElementById("btn-send"); + var btnSendRaw = document.getElementById("btn-send-raw"); + var sendRawRow = document.getElementById("send-raw-row"); + var summaryPanel = document.getElementById("summary-panel"); + var summarySubtitle = document.getElementById("summary-subtitle"); + var summaryGeneratingEl = document.getElementById("summary-generating"); + var summaryGeneratingCopy = document.getElementById("summary-generating-copy"); + var summaryInput = document.getElementById("summary-input"); + var summaryFeedback = document.getElementById("summary-feedback"); + var btnSummaryBack = document.getElementById("btn-summary-back"); + var btnSummaryRegenerate = document.getElementById("btn-summary-regenerate"); + var btnSummaryPreview = document.getElementById("btn-summary-preview"); + var btnSummaryApprove = document.getElementById("btn-summary-approve"); + var successOverlay = document.getElementById("success-overlay"); + var successText = document.getElementById("success-text"); + var expiredOverlay = document.getElementById("expired-overlay"); + var expiredText = document.getElementById("expired-text"); + var closeCountdown = document.getElementById("close-countdown"); + var errorBanner = document.getElementById("error-banner"); + var addSearchInput = document.getElementById("add-search-input"); + var addSearchEl = document.getElementById("add-search"); + var addSearchWand = document.getElementById("add-search-wand"); + var heroStatus = document.getElementById("hero-status"); + var summaryProviderSelect = document.getElementById("summary-provider-select"); + var summaryModelSelect = document.getElementById("summary-model-select"); + var previewModal = document.getElementById("preview-modal"); + var previewModalBody = document.getElementById("preview-modal-body"); + var previewModalClose = document.getElementById("preview-modal-close"); + var previewModalModel = document.getElementById("preview-modal-model"); + var previewModalRegenerate = document.getElementById("preview-modal-regenerate"); + var previewModalApprove = document.getElementById("preview-modal-approve"); + var previewPopover = document.getElementById("preview-popover"); + var previewPopoverQuote = document.getElementById("preview-popover-quote"); + var previewPopoverInput = document.getElementById("preview-popover-input"); + var previewPopoverRegen = document.getElementById("preview-popover-regen"); + var providerButtons = Array.prototype.slice.call(document.querySelectorAll(".provider-btn")); + var loadingPanelEl = null; + + var summaryModelsByProvider = Object.create(null); + var summaryProviders = []; + var currentSummaryProvider = ""; + var currentSummaryModel = ""; + var summaryPendingModel = ""; + var summaryGeneratingStartedAt = 0; + var summaryGeneratingPhase = -1; + var rewriteInFlight = false; + + function escHtml(s) { + return String(s) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/\"/g, """); + } + + function sanitizeHref(url) { + var value = typeof url === "string" ? url.trim() : ""; + return /^https?:\/\//i.test(value) ? value : "#"; + } + + function sanitizeMarkdownHtml(html) { + var container = document.createElement("div"); + container.innerHTML = html; + + container.querySelectorAll("script, iframe, object, embed, form, style, link, meta, base") + .forEach(function(el) { el.remove(); }); + + var nodes = container.querySelectorAll("*"); + nodes.forEach(function(node) { + for (var i = node.attributes.length - 1; i >= 0; i--) { + var attr = node.attributes[i]; + if (/^on/i.test(attr.name)) node.removeAttribute(attr.name); + } + }); + + var anchors = container.querySelectorAll("a[href]"); + anchors.forEach(function(anchor) { + var safe = sanitizeHref(anchor.getAttribute("href") || ""); + anchor.setAttribute("href", safe); + anchor.setAttribute("rel", "noopener noreferrer"); + anchor.setAttribute("target", "_blank"); + }); + + var images = container.querySelectorAll("img[src]"); + images.forEach(function(img) { + var safe = sanitizeHref(img.getAttribute("src") || ""); + if (safe === "#") { + img.remove(); + } else { + img.setAttribute("src", safe); + } + }); + + return container.innerHTML; + } + + function post(path, body) { + return fetch(path, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(Object.assign({ token: token }, body)), + }); + } + + function extractServerError(data) { + if (!data || typeof data !== "object") return ""; + if (typeof data.error === "string" && data.error.trim()) return data.error.trim(); + return ""; + } + + function postJson(path, body) { + return post(path, body).then(function(res) { + return res.text().then(function(raw) { + var data = null; + if (raw) { + try { + data = JSON.parse(raw); + } catch (err) { + var parseMessage = err instanceof Error ? err.message : String(err); + throw new Error("Invalid JSON response from " + path + ": " + parseMessage); + } + } + + if (!res.ok) { + throw new Error(extractServerError(data) || ("HTTP " + res.status)); + } + + return data; + }); + }); + } + + function formatTime(sec) { + var m = Math.floor(sec / 60); + var s = sec % 60; + return m + ":" + (s < 10 ? "0" : "") + s; + } + + function normalizeProvider(provider, fallback) { + if (typeof provider === "string") { + var normalized = provider.toLowerCase(); + if (providers.indexOf(normalized) !== -1) return normalized; + } + if (typeof fallback === "string") { + var fallbackNormalized = fallback.toLowerCase(); + if (providers.indexOf(fallbackNormalized) !== -1) return fallbackNormalized; + } + return ""; + } + + function providerLabel(provider) { + if (provider === "perplexity") return "Perplexity"; + if (provider === "exa") return "Exa"; + if (provider === "gemini") return "Gemini"; + return "Unknown"; + } + + function providerTagHtml(provider) { + var normalized = normalizeProvider(provider, ""); + if (!normalized) return ""; + return '' + escHtml(providerLabel(normalized)) + ""; + } + + function buildAltChipsHtml(provider, queryText) { + var normalizedProv = normalizeProvider(provider, ""); + if (!normalizedProv) return ""; + var altProviders = providers.filter(function(p) { return p !== normalizedProv && availProviders[p] === true; }); + if (altProviders.length === 0) return ""; + var html = '
Also try'; + for (var ap = 0; ap < altProviders.length; ap++) { + html += ''; + } + html += "
"; + return html; + } + + function getSummaryProvider(modelValue) { + if (typeof modelValue !== "string") return ""; + var trimmed = modelValue.trim(); + var slash = trimmed.indexOf("/"); + if (slash <= 0) return ""; + return trimmed.slice(0, slash); + } + + function summaryProviderLabel(provider) { + if (!provider) return ""; + if (provider === "openai") return "OpenAI"; + if (provider === "google") return "Google"; + if (provider === "anthropic") return "Anthropic"; + return provider.charAt(0).toUpperCase() + provider.slice(1); + } + + function buildSummaryModelState() { + summaryModelsByProvider = Object.create(null); + summaryProviders = []; + var seenValues = {}; + + for (var i = 0; i < summaryModels.length; i++) { + var model = summaryModels[i]; + if (!model || typeof model.value !== "string") continue; + var value = model.value.trim(); + if (!value || seenValues[value]) continue; + var provider = getSummaryProvider(value); + if (!provider) continue; + seenValues[value] = true; + + if (!summaryModelsByProvider[provider]) { + summaryModelsByProvider[provider] = []; + summaryProviders.push(provider); + } + + var label = typeof model.label === "string" && model.label.trim().length > 0 + ? model.label.trim() + : value; + summaryModelsByProvider[provider].push({ value: value, label: label }); + } + } + + function renderSummaryProviderSelect() { + if (!summaryProviderSelect) return; + + summaryProviderSelect.innerHTML = ""; + for (var i = 0; i < summaryProviders.length; i++) { + var provider = summaryProviders[i]; + var option = document.createElement("option"); + option.value = provider; + option.textContent = summaryProviderLabel(provider); + summaryProviderSelect.appendChild(option); + } + } + + function populateSummaryModelSelect(provider, preferredModel) { + if (!summaryModelSelect) return; + + summaryModelSelect.innerHTML = ""; + + var autoOption = document.createElement("option"); + autoOption.value = ""; + autoOption.textContent = "Auto"; + summaryModelSelect.appendChild(autoOption); + + var models = summaryModelsByProvider[provider] || []; + for (var i = 0; i < models.length; i++) { + var option = document.createElement("option"); + option.value = models[i].value; + var shortLabel = models[i].value; + var labelSlash = shortLabel.indexOf("/"); + if (labelSlash > 0) shortLabel = shortLabel.slice(labelSlash + 1); + option.textContent = shortLabel; + summaryModelSelect.appendChild(option); + } + + var hasPreferred = false; + if (preferredModel) { + for (var j = 0; j < models.length; j++) { + if (models[j].value === preferredModel) { + hasPreferred = true; + break; + } + } + } + + if (hasPreferred) { + summaryModelSelect.value = preferredModel; + } else if (models.length > 0) { + summaryModelSelect.value = models[0].value; + } else { + summaryModelSelect.value = ""; + } + + currentSummaryModel = typeof summaryModelSelect.value === "string" + ? summaryModelSelect.value.trim() + : ""; + } + + function setSummaryProvider(provider, preferredModel) { + if (summaryProviders.indexOf(provider) === -1) return; + currentSummaryProvider = provider; + + if (summaryProviderSelect) { + summaryProviderSelect.value = provider; + } + + populateSummaryModelSelect(provider, preferredModel); + } + + function initializeSummaryModelControls() { + buildSummaryModelState(); + renderSummaryProviderSelect(); + + if (summaryProviders.length === 0) { + currentSummaryProvider = ""; + currentSummaryModel = ""; + if (summaryProviderSelect) summaryProviderSelect.innerHTML = ""; + if (summaryModelSelect) { + summaryModelSelect.innerHTML = ''; + summaryModelSelect.value = ""; + } + return; + } + + var defaultProvider = getSummaryProvider(defaultSummaryModel); + if (defaultProvider && summaryProviders.indexOf(defaultProvider) !== -1) { + setSummaryProvider(defaultProvider, defaultSummaryModel); + return; + } + + setSummaryProvider(summaryProviders[0], ""); + } + + function getSelectedSummaryModel() { + if (!summaryModelSelect) return currentSummaryModel; + if (typeof summaryModelSelect.value !== "string") return currentSummaryModel; + currentSummaryModel = summaryModelSelect.value.trim(); + return currentSummaryModel; + } + + function getFeedbackText() { + if (!summaryFeedback || typeof summaryFeedback.value !== "string") return ""; + return summaryFeedback.value; + } + + function getCoverageSet(provider) { + var set = providerCoverage.get(provider); + if (set) return set; + set = new Set(); + providerCoverage.set(provider, set); + return set; + } + + function markCoverage(provider, slotId) { + if (typeof slotId !== "number") return; + var normalized = normalizeProvider(provider, ""); + if (!normalized) return; + getCoverageSet(normalized).add(slotId); + } + + function removeSlot(slotId) { + allQueries = allQueries.filter(function(slot) { return slot.slotId !== slotId; }); + + providerCoverage.forEach(function(coveredSlots) { + coveredSlots.delete(slotId); + }); + + queryIndexToSlot.forEach(function(mappedSlotId, qi) { + if (mappedSlotId === slotId) queryIndexToSlot.delete(qi); + }); + + syncLoadingPanel(); + } + + function isResultMutationLocked() { + return submitted || timerExpired || submitInFlight; + } + + function applyProviderInterlocks() { + var disableProviders = isResultMutationLocked() || providerBatchInFlight || addSearchInFlight; + for (var i = 0; i < providerButtons.length; i++) { + var btn = providerButtons[i]; + var state = btn.dataset.state || "idle"; + btn.disabled = disableProviders || state === "loading"; + } + + var disableAddSearch = isResultMutationLocked(); + if (addSearchInput) { + addSearchInput.disabled = disableAddSearch; + } + + if (addSearchEl) { + addSearchEl.style.opacity = disableAddSearch ? "0.6" : ""; + addSearchEl.style.pointerEvents = disableAddSearch ? "none" : ""; + } + + var cards = resultCardsEl ? resultCardsEl.querySelectorAll(".result-card") : []; + cards.forEach(function(card) { + var cb = card.querySelector("input[type=checkbox]"); + if (!cb) return; + var searching = card.classList.contains("searching"); + var error = card.classList.contains("error"); + cb.disabled = searching || error || isResultMutationLocked(); + }); + } + + function recomputeProviderStates() { + for (var i = 0; i < providerButtons.length; i++) { + var btn = providerButtons[i]; + var provider = normalizeProvider(btn.dataset.provider, ""); + if (!provider) continue; + + var state = "idle"; + if (providerBatchInFlight && batchLoadingProvider === provider) { + state = "loading"; + } else if (!initialStreamDone && queries.length > 0 && provider === initialDefaultProvider) { + state = "loading"; + } else if (allQueries.length > 0) { + var coveredSlots = providerCoverage.get(provider); + if (coveredSlots && coveredSlots.size >= allQueries.length) { + state = "searched"; + } + } + + btn.dataset.state = state; + btn.classList.remove("idle", "loading", "searched"); + btn.classList.add(state); + btn.classList.toggle("is-default", provider === currentProvider); + } + + applyProviderInterlocks(); + } + + function updateSummaryText() { + if (completedCount <= 0) return; + var totalCards = resultCardsEl.querySelectorAll(".result-card").length; + var searchingCount = totalCards - completedCount; + if (searchingCount > 0) { + heroTitle.textContent = completedCount + " of " + totalCards + " Searches Complete"; + } else { + heroTitle.textContent = completedCount + " Search" + (completedCount !== 1 ? "es" : "") + " Complete"; + } + heroDesc.textContent = "Check the results to include, then generate and approve a summary."; + if (heroStatus) heroStatus.textContent = completedCount + " completed" + (searchingCount > 0 ? ", " + searchingCount + " searching" : ""); + } + + function getSummaryDraftText() { + if (!summaryInput || typeof summaryInput.value !== "string") return ""; + return summaryInput.value.trim(); + } + + function clearError() { + if (!errorBanner) return; + errorBanner.hidden = true; + errorBanner.textContent = ""; + } + + function setError(text) { + if (!errorBanner) return; + errorBanner.textContent = text; + errorBanner.hidden = false; + } + + function updateSummaryGeneratingIndicator() { + if (!summaryGeneratingCopy) return; + + if (stage !== "generating-summary") { + summaryGeneratingCopy.textContent = "Generating summary draft…"; + summaryGeneratingPhase = -1; + if (summaryGeneratingEl) { + summaryGeneratingEl.removeAttribute("data-phase"); + } + return; + } + + if (summaryGeneratingStartedAt <= 0) { + summaryGeneratingStartedAt = Date.now(); + } + + var elapsedMs = Date.now() - summaryGeneratingStartedAt; + var nextPhase = Math.min(2, Math.floor(elapsedMs / 1800)); + if (nextPhase === summaryGeneratingPhase) return; + + summaryGeneratingPhase = nextPhase; + + var phaseLabel = "Planning summary"; + if (nextPhase === 1) phaseLabel = "Drafting summary"; + if (nextPhase === 2) phaseLabel = "Polishing summary"; + + summaryGeneratingCopy.textContent = summaryPendingModel + ? phaseLabel + " with " + summaryPendingModel + "…" + : phaseLabel + "…"; + + if (summaryGeneratingEl) { + summaryGeneratingEl.dataset.phase = String(nextPhase); + } + } + + function updateStageUI() { + var showSummary = stage === "summary-review" || stage === "generating-summary" || isRegenerating; + if (summaryPanel) { + summaryPanel.classList.toggle("hidden", !showSummary); + summaryPanel.classList.toggle("updating", isRegenerating); + } + if (summarySubtitle) { + var selCount = getSelectedIndices().length; + var selLabel = selCount + " selected result" + (selCount !== 1 ? "s" : ""); + if (isRegenerating && stage === "generating-summary") { + summarySubtitle.textContent = "Selection changed — regenerating summary…"; + } else if (isRegenerating) { + summarySubtitle.textContent = "Selection changed — summary will regenerate shortly…"; + } else if (stage === "generating-summary") { + summarySubtitle.textContent = summaryPendingModel + ? "Summarizing " + selLabel + " with " + summaryPendingModel + "…" + : "Summarizing " + selLabel + "…"; + } else if (summaryMeta && summaryMeta.fallbackUsed) { + summarySubtitle.textContent = "Fallback summary of " + selLabel + "."; + } else { + summarySubtitle.textContent = "Summary of " + selLabel + ". Edit directly, regenerate with feedback, or approve."; + } + } + + if (summaryGeneratingEl) { + var showGenerating = stage === "generating-summary" && !isRegenerating; + summaryGeneratingEl.classList.toggle("hidden", !showGenerating); + } + updateSummaryGeneratingIndicator(); + + if (summaryInput) { + summaryInput.classList.toggle("hidden", stage === "generating-summary" && !isRegenerating); + summaryInput.disabled = submitted || timerExpired || stage === "generating-summary" || submitInFlight || isRegenerating; + } + if (summaryFeedback) { + summaryFeedback.disabled = submitted || timerExpired || submitInFlight || stage === "generating-summary" || isRegenerating; + } + var disableSummaryModelControls = submitted || timerExpired || stage === "generating-summary" || submitInFlight || summaryProviders.length === 0; + if (summaryProviderSelect) { + summaryProviderSelect.disabled = disableSummaryModelControls; + } + if (summaryModelSelect) { + summaryModelSelect.disabled = disableSummaryModelControls; + } + + var inResults = stage === "results"; + var hasSelection = getSelectedIndices().length > 0; + var hasCompleted = getCompletedSelectableIndices().length > 0; + var canGenerate = inResults && !submitted && !timerExpired && !submitInFlight && hasCompleted; + + if (btnSend) { + if (stage === "generating-summary") { + btnSend.textContent = "Generating summary…"; + btnSend.disabled = true; + } else if (!inResults) { + btnSend.textContent = "Summary ready"; + btnSend.disabled = true; + } else if (!hasCompleted) { + btnSend.textContent = searchesDone ? "No results yet" : "Waiting for results…"; + btnSend.disabled = true; + } else { + btnSend.textContent = hasSelection ? "Generate summary" : "Select results to summarize"; + btnSend.disabled = !canGenerate || !hasSelection; + } + } + if (sendRawRow) { + sendRawRow.classList.toggle("hidden", !hasSelection || submitted || timerExpired); + } + if (btnSendRaw) { + btnSendRaw.disabled = !hasSelection || submitted || timerExpired || submitInFlight; + } + + if (btnSummaryBack) btnSummaryBack.disabled = submitted || timerExpired || submitInFlight || (stage === "generating-summary" && !isRegenerating); + if (btnSummaryRegenerate) btnSummaryRegenerate.disabled = submitted || timerExpired || submitInFlight || stage === "generating-summary" || isRegenerating; + var hasDraft = getSummaryDraftText().length > 0; + if (btnSummaryPreview) btnSummaryPreview.disabled = !hasDraft || stage === "generating-summary"; + if (btnSummaryApprove) { + btnSummaryApprove.disabled = submitted || timerExpired || submitInFlight || stage === "generating-summary" || isRegenerating || !hasSelection || !hasDraft; + } + + applyProviderInterlocks(); + } + + function shouldShowLoadingPanel() { + if (submitted || timerExpired || searchesDone) return false; + if (completedCount > 0) return false; + return allQueries.length > 0; + } + + function ensureLoadingPanel() { + if (loadingPanelEl) return loadingPanelEl; + if (!resultCardsEl) return null; + + var panel = document.createElement("div"); + panel.className = "result-loading"; + panel.innerHTML = + '
' + + '
Searching sources
' + + '
Searching\u2026
' + + '
' + + '
' + + '
' + + '
' + + '
'; + + resultCardsEl.prepend(panel); + loadingPanelEl = panel; + return panel; + } + + function updateLoadingPanelSummary() { + if (!loadingPanelEl) return; + var sub = loadingPanelEl.querySelector(".result-loading-sub"); + if (!sub) return; + + var total = allQueries.length; + if (total <= 0) { + sub.textContent = "Searching\u2026"; + return; + } + + var done = Math.min(completedCount, total); + var noun = total === 1 ? "query" : "queries"; + sub.textContent = "Searching " + done + "/" + total + " " + noun + "\u2026"; + } + + function syncLoadingPanel() { + if (shouldShowLoadingPanel()) { + if (!ensureLoadingPanel()) return; + updateLoadingPanelSummary(); + return; + } + + if (loadingPanelEl) { + loadingPanelEl.remove(); + loadingPanelEl = null; + } + } + + function renderErrorCard(card, queryText, errorText, provider) { + var tag = providerTagHtml(provider); + card.innerHTML = + '
' + + '' + + '
' + + '
' + + '
' + escHtml(queryText) + "
" + + tag + + "
" + + '
Failed
' + + "
" + + "
" + + '
' + escHtml(errorText || "Search failed") + "
"; + } + + function populateResultCard(card, data, queryText, provider) { + var sourceCount = data.results ? data.results.length : 0; + var domains = []; + if (data.results) { + for (var i = 0; i < Math.min(data.results.length, 3); i++) { + domains.push(data.results[i].domain); + } + } + var metaText = sourceCount + " source" + (sourceCount !== 1 ? "s" : ""); + if (domains.length > 0) metaText += " \u00B7 " + domains.join(", "); + if (sourceCount > 3) metaText += ", +" + (sourceCount - 3); + + var preview = ""; + if (data.answer) { + preview = data.answer.substring(0, 200).replace(/\\n+/g, " ").replace(/[#*_\\[\\]]/g, ""); + } + + var bodyHtml = ""; + if (data.answer) { + var rendered = typeof marked !== "undefined" && marked.parse + ? marked.parse(data.answer, { breaks: true }) + : "

" + escHtml(data.answer) + "

"; + bodyHtml += '
' + sanitizeMarkdownHtml(rendered) + "
"; + } + if (data.results && data.results.length > 0) { + bodyHtml += '
Sources
'; + for (var k = 0; k < data.results.length; k++) { + var r = data.results[k]; + var label = r.title && r.title.indexOf("Source ") !== 0 ? r.title : r.url; + var href = sanitizeHref(r.url); + bodyHtml += '' + escHtml(label) + '' + escHtml(r.domain) + ""; + } + bodyHtml += "
"; + } + + var altChipsHtml = buildAltChipsHtml(provider, queryText); + + card.innerHTML = + '
' + + '' + + '
' + + '
' + + '
' + escHtml(queryText) + "
" + + providerTagHtml(provider) + + "
" + + '
' + escHtml(metaText) + "
" + + (preview ? '
' + escHtml(preview) + "
" : "") + + "
" + + '
\u25BC
' + + "
" + + altChipsHtml + + '
' + bodyHtml + "
"; + } + + function applyResponseToCard(card, data, queryText, providerHint, slotHint) { + if (!card || !data) return; + if (submitted || timerExpired) return; + + var queryIndex = typeof data.queryIndex === "number" ? data.queryIndex : null; + if (queryIndex !== null) { + card.dataset.qi = String(queryIndex); + } + + var slotId = typeof slotHint === "number" ? slotHint : (queryIndex !== null ? queryIndexToSlot.get(queryIndex) : undefined); + if (typeof slotId !== "number" && queryIndex !== null) { + slotId = queryIndex; + } + if (queryIndex !== null && typeof slotId === "number") { + queryIndexToSlot.set(queryIndex, slotId); + } + + var provider = normalizeProvider(data.provider, providerHint); + + card.classList.remove("searching", "checked", "error"); + + if (data.error) { + card.classList.add("error"); + renderErrorCard(card, queryText, data.error, provider); + } else { + card.classList.add("checked"); + populateResultCard(card, data, queryText, provider); + setupCardInteraction(card); + } + + if (card.dataset.completed !== "true") { + completedCount++; + card.dataset.completed = "true"; + } + markCoverage(provider, slotId); + updateSummaryText(); + syncLoadingPanel(); + recomputeProviderStates(); + updateStageUI(); + maybeAutoGenerateSummary(); + resetTimer(); + } + + function resetTimer() { lastInteraction = Date.now(); } + + function updateTimer() { + var idleSec = Math.floor((Date.now() - lastInteraction) / 1000); + var remaining = Math.max(0, timeoutSec - idleSec); + timerEl.textContent = formatTime(remaining); + + timerEl.classList.remove("warn", "urgent", "active"); + if (remaining <= 15) timerEl.classList.add("urgent"); + else if (remaining <= 30) timerEl.classList.add("warn"); + else if (remaining < timeoutSec) timerEl.classList.add("active"); + + updateSummaryGeneratingIndicator(); + + if (remaining <= 0 && !submitted && !timerExpired) onTimeout(); + } + + setInterval(updateTimer, 1000); + updateTimer(); + + ["click", "keydown", "input", "change"].forEach(function(evt) { + document.addEventListener(evt, resetTimer, { passive: true }); + }); + document.addEventListener("scroll", resetTimer, { passive: true }); + document.addEventListener("mousemove", resetTimer, { passive: true }); + + timerEl.addEventListener("click", function(e) { + e.stopPropagation(); + timerInput.value = timeoutSec; + timerAdjustEl.classList.add("visible"); + timerEl.style.display = "none"; + timerInput.focus(); + timerInput.select(); + }); + + function applyTimerAdjust() { + var val = parseInt(timerInput.value, 10); + if (val && val > 0) timeoutSec = Math.min(val, 600); + timerAdjustEl.classList.remove("visible"); + timerEl.style.display = ""; + resetTimer(); + } + + timerSetBtn.addEventListener("click", function(e) { e.stopPropagation(); applyTimerAdjust(); }); + timerInput.addEventListener("keydown", function(e) { + if (e.key === "Enter") { e.preventDefault(); applyTimerAdjust(); } + if (e.key === "Escape") { timerAdjustEl.classList.remove("visible"); timerEl.style.display = ""; } + e.stopPropagation(); + }); + document.addEventListener("click", function() { + if (timerAdjustEl.classList.contains("visible")) { + timerAdjustEl.classList.remove("visible"); + timerEl.style.display = ""; + } + }); + + function setDefaultProvider(provider, persist) { + var normalized = normalizeProvider(provider, currentProvider); + if (!normalized) return; + currentProvider = normalized; + recomputeProviderStates(); + if (persist) { + postJson("/provider", { provider: normalized }).then(function(data) { + if (data && data.ok === false) { + throw new Error(extractServerError(data) || "request rejected"); + } + }).catch(function(err) { + var message = err instanceof Error ? err.message : String(err); + setError("Failed to save provider preference: " + (message || "unknown error")); + }); + } + } + + providerButtons.forEach(function(btn) { + btn.addEventListener("click", function() { + if (isResultMutationLocked()) return; + if (providerBatchInFlight || addSearchInFlight) return; + + var provider = normalizeProvider(btn.dataset.provider, ""); + if (!provider) return; + + var state = btn.dataset.state || "idle"; + if (state === "loading") return; + + if (state === "searched") { + if (provider === currentProvider) return; + setDefaultProvider(provider, true); + resetTimer(); + return; + } + + setDefaultProvider(provider, true); + if (allQueries.length === 0) { + resetTimer(); + return; + } + + interruptSummaryIfNeeded(); + providerBatchInFlight = true; + batchLoadingProvider = provider; + recomputeProviderStates(); + + var batchQueries = allQueries.slice(); + var inflight = batchQueries.length; + if (inflight === 0) { + providerBatchInFlight = false; + batchLoadingProvider = null; + recomputeProviderStates(); + return; + } + + var batchCards = []; + for (var bi = 0; bi < batchQueries.length; bi++) { + var bq = batchQueries[bi]; + var card = document.createElement("div"); + card.className = "result-card searching"; + card.innerHTML = + '
' + + '' + + '
' + + '
' + + '
' + escHtml(bq.query) + "
" + + providerTagHtml(provider) + + "
" + + '
Searching
' + + "
" + + "
" + + buildAltChipsHtml(provider, bq.query); + resultCardsEl.appendChild(card); + batchCards.push(card); + } + updateSummaryText(); + + batchQueries.forEach(function(slot, si) { + var searchingCard = batchCards[si]; + postJson("/search", { query: slot.query, provider: provider }) + .then(function(data) { + if (submitted || timerExpired) return; + if (!data || data.ok === false) { + applyResponseToCard(searchingCard, { + answer: "", + results: [], + error: extractServerError(data) || "Search failed", + provider: provider, + }, slot.query, provider, slot.slotId); + return; + } + applyResponseToCard(searchingCard, data, slot.query, provider, slot.slotId); + }) + .catch(function(err) { + if (submitted || timerExpired) return; + var message = err instanceof Error ? err.message : String(err); + applyResponseToCard(searchingCard, { + answer: "", + results: [], + error: message || "Search failed", + provider: provider, + }, slot.query, provider, slot.slotId); + }) + .finally(function() { + inflight -= 1; + if (inflight <= 0) { + providerBatchInFlight = false; + batchLoadingProvider = null; + recomputeProviderStates(); + updateStageUI(); + maybeAutoGenerateSummary(); + } + }); + }); + + resetTimer(); + }); + }); + + if (resultCardsEl) { + resultCardsEl.addEventListener("click", function(e) { + if (!(e.target instanceof Element)) return; + var chip = e.target.closest(".card-alt-chip"); + if (!chip) return; + if (isResultMutationLocked()) return; + + var altProvider = chip.dataset.altProvider; + var altQuery = chip.dataset.altQuery; + if (!altProvider || !altQuery) return; + + interruptSummaryIfNeeded(); + + chip.classList.add("loading"); + chip.disabled = true; + resetTimer(); + + var slotId = nextSlotId++; + allQueries.push({ slotId: slotId, query: altQuery }); + + var parentCard = chip.closest(".result-card"); + var newCard = document.createElement("div"); + newCard.className = "result-card searching"; + newCard.innerHTML = + '
' + + '' + + '
' + + '
' + + '
' + escHtml(altQuery) + "
" + + providerTagHtml(altProvider) + + "
" + + '
Searching
' + + "
" + + "
" + + buildAltChipsHtml(altProvider, altQuery); + if (parentCard && parentCard.nextSibling) { + resultCardsEl.insertBefore(newCard, parentCard.nextSibling); + } else { + resultCardsEl.appendChild(newCard); + } + updateSummaryText(); + + postJson("/search", { query: altQuery, provider: altProvider }) + .then(function(data) { + if (submitted || timerExpired) return; + if (!data || data.ok === false) { + applyResponseToCard(newCard, { + answer: "", results: [], + error: extractServerError(data) || "Search failed", + provider: altProvider, + }, altQuery, altProvider, slotId); + return; + } + applyResponseToCard(newCard, data, altQuery, altProvider, slotId); + }) + .catch(function(err) { + removeSlot(slotId); + newCard.remove(); + var message = err instanceof Error ? err.message : String(err); + setError("Re-search failed: " + (message || "Search failed")); + updateSummaryText(); + }) + .finally(function() { + chip.classList.remove("loading"); + chip.disabled = false; + recomputeProviderStates(); + updateStageUI(); + maybeAutoGenerateSummary(); + }); + }); + } + + if (addSearchInput && addSearchWand) { + addSearchInput.addEventListener("input", function() { + addSearchWand.disabled = rewriteInFlight || !addSearchInput.value.trim() || isResultMutationLocked(); + }); + + addSearchWand.addEventListener("click", function() { + var text = addSearchInput.value.trim(); + if (!text || rewriteInFlight || isResultMutationLocked()) return; + rewriteInFlight = true; + addSearchWand.disabled = true; + addSearchWand.classList.add("rewriting"); + resetTimer(); + + postJson("/rewrite", { query: text }) + .then(function(data) { + if (!data || data.ok === false) { + throw new Error(extractServerError(data) || "Rewrite failed"); + } + var rewritten = typeof data.query === "string" ? data.query.trim() : ""; + if (rewritten) { + addSearchInput.value = rewritten; + addSearchInput.focus(); + } + }) + .catch(function(err) { + var message = err instanceof Error ? err.message : String(err); + setError("Rewrite failed: " + (message || "unknown error")); + }) + .finally(function() { + rewriteInFlight = false; + addSearchWand.classList.remove("rewriting"); + addSearchWand.disabled = !addSearchInput.value.trim() || isResultMutationLocked(); + }); + }); + } + + addSearchInput.addEventListener("keydown", function(e) { + if (e.key !== "Enter") return; + var text = addSearchInput.value.trim(); + if (!text || isResultMutationLocked()) return; + interruptSummaryIfNeeded(); + e.preventDefault(); + e.stopPropagation(); + + addSearchInFlight++; + applyProviderInterlocks(); + addSearchInput.value = ""; + + var slotId = nextSlotId++; + allQueries.push({ slotId: slotId, query: text }); + syncLoadingPanel(); + recomputeProviderStates(); + + var requestedProvider = currentProvider; + + var card = document.createElement("div"); + card.className = "result-card searching"; + card.innerHTML = + '
' + + '' + + '
' + + '
' + + '
' + escHtml(text) + "
" + + providerTagHtml(requestedProvider) + + "
" + + '
Searching
' + + "
" + + "
" + + buildAltChipsHtml(requestedProvider, text); + resultCardsEl.appendChild(card); + updateSummaryText(); + resetTimer(); + + postJson("/search", { query: text, provider: requestedProvider }) + .then(function(data) { + if (!data || data.ok === false) { + removeSlot(slotId); + card.remove(); + setError("Failed to add search: " + (extractServerError(data) || "Search failed")); + recomputeProviderStates(); + updateSummaryText(); + return; + } + + if (submitted || timerExpired) return; + + applyResponseToCard(card, data, text, requestedProvider, slotId); + }) + .catch(function(err) { + removeSlot(slotId); + card.remove(); + var message = err instanceof Error ? err.message : String(err); + setError("Failed to add search: " + (message || "Search failed")); + recomputeProviderStates(); + updateSummaryText(); + }) + .finally(function() { + addSearchInFlight--; + recomputeProviderStates(); + updateStageUI(); + maybeAutoGenerateSummary(); + }); + }); + + function showSuccess(text) { + if (es) { es.close(); es = null; } + closePreviewModal(); + successText.textContent = text; + successOverlay.classList.remove("hidden"); + setTimeout(function() { window.close(); }, 800); + } + + function showExpired(text) { + if (es) { es.close(); es = null; } + closePreviewModal(); + expiredText.textContent = text; + expiredOverlay.classList.remove("hidden"); + requestAnimationFrame(function() { expiredOverlay.classList.add("visible"); }); + } + + function startOverlayCloseCountdown(seconds) { + var count = seconds; + closeCountdown.textContent = count; + var iv = setInterval(function() { + count--; + closeCountdown.textContent = count; + if (count <= 0) { + clearInterval(iv); + window.close(); + } + }, 1000); + } + + function submitPayload(payload, successText) { + if (submitInFlight) return Promise.reject(new Error("Submit already in progress")); + submitInFlight = true; + submitted = true; + syncLoadingPanel(); + updateStageUI(); + clearError(); + + return postJson("/submit", payload) + .then(function(data) { + if (data && data.ok === false) { + throw new Error(extractServerError(data) || "submit rejected"); + } + showSuccess(successText); + }) + .catch(function(err) { + submitInFlight = false; + submitted = false; + syncLoadingPanel(); + updateStageUI(); + throw err; + }); + } + + function submitWithTimeoutFallback(payload) { + if (submitInFlight) return; + submitInFlight = true; + submitted = true; + timerExpired = true; + syncLoadingPanel(); + updateStageUI(); + clearError(); + showExpired("Time\u2019s up \u2014 submitting current summary state."); + + function finalizeClose() { + submitInFlight = false; + startOverlayCloseCountdown(5); + } + + function toErrorMessage(err) { + return err instanceof Error ? err.message : String(err); + } + + function attemptCancelFallback(submitErrorMessage) { + return postJson("/cancel", { reason: "timeout" }) + .catch(function(cancelErr) { + console.error("Timeout finalize failed after submit errors:", submitErrorMessage, "| cancel:", toErrorMessage(cancelErr)); + }) + .finally(finalizeClose); + } + + postJson("/submit", payload) + .then(function(data) { + if (data && data.ok === false) { + throw new Error(extractServerError(data) || "submit rejected"); + } + finalizeClose(); + }) + .catch(function(firstErr) { + var firstMessage = toErrorMessage(firstErr); + setTimeout(function() { + postJson("/submit", payload) + .then(function(data) { + if (data && data.ok === false) { + throw new Error(extractServerError(data) || "submit rejected"); + } + finalizeClose(); + }) + .catch(function(secondErr) { + var secondMessage = toErrorMessage(secondErr); + attemptCancelFallback(firstMessage + " | " + secondMessage); + }); + }, 250); + }); + } + + function onTimeout() { + if (submitted || timerExpired) return; + var timeoutSelected = getTimeoutSelectedIndices(); + var payload = { selected: timeoutSelected }; + var draft = getSummaryDraftText(); + if (stage === "summary-review" && draft.length > 0) { + payload.summary = draft; + if (summaryMeta) payload.summaryMeta = summaryMeta; + } + submitWithTimeoutFallback(payload); + } + + if (queries.length === 0) { + heroTitle.textContent = "What do you need?"; + heroDesc.textContent = "Search for anything below, then generate and approve a summary."; + if (heroStatus) heroStatus.textContent = ""; + btnSend.textContent = "No results yet"; + } else { + for (var i = 0; i < queries.length; i++) { + queryIndexToSlot.set(i, i); + var card = document.createElement("div"); + card.className = "result-card searching"; + card.dataset.qi = i; + card.innerHTML = + '
' + + '' + + '
' + + '
' + + '
' + escHtml(queries[i]) + "
" + + providerTagHtml(initialDefaultProvider) + + "
" + + '
Searching
' + + "
" + + "
" + + buildAltChipsHtml(initialDefaultProvider, queries[i]); + resultCardsEl.appendChild(card); + } + } + + initializeSummaryModelControls(); + syncLoadingPanel(); + recomputeProviderStates(); + updateStageUI(); + + es = new EventSource("/events?session=" + encodeURIComponent(token)); + + function parseSseEventData(eventName, e) { + try { + return JSON.parse(e.data); + } catch (err) { + var message = err instanceof Error ? err.message : String(err); + setError("Invalid " + eventName + " event payload: " + (message || "unknown parse error")); + return null; + } + } + + es.addEventListener("result", function(e) { + var data = parseSseEventData("result", e); + if (!data) return; + + var card = resultCardsEl.querySelector('.result-card[data-qi="' + data.queryIndex + '"]'); + if (!card) return; + + var slotId = queryIndexToSlot.get(data.queryIndex); + if (typeof slotId !== "number") slotId = data.queryIndex; + applyResponseToCard(card, data, data.query || queries[data.queryIndex], data.provider, slotId); + }); + + es.addEventListener("search-error", function(e) { + var data = parseSseEventData("search-error", e); + if (!data) return; + + var card = resultCardsEl.querySelector('.result-card[data-qi="' + data.queryIndex + '"]'); + if (!card) return; + + var slotId = queryIndexToSlot.get(data.queryIndex); + if (typeof slotId !== "number") slotId = data.queryIndex; + applyResponseToCard(card, { + queryIndex: data.queryIndex, + answer: "", + results: [], + error: data.error || "Search failed", + provider: data.provider, + }, data.query || queries[data.queryIndex], data.provider, slotId); + }); + + es.addEventListener("done", function() { + searchesDone = true; + initialStreamDone = true; + if (completedCount > 0) { + updateSummaryText(); + } + syncLoadingPanel(); + recomputeProviderStates(); + updateStageUI(); + maybeAutoGenerateSummary(); + resetTimer(); + }); + + es.onerror = function() { + // EventSource reconnects automatically. + }; + + function setupCardInteraction(card) { + var header = card.querySelector(".result-card-header"); + var body = card.querySelector(".result-card-body"); + var cb = card.querySelector("input[type=checkbox]"); + var expandEl = card.querySelector(".result-card-expand"); + + if (!header || !cb) return; + + header.addEventListener("click", function(e) { + if (e.target.tagName === "A") return; + if (e.target === cb) { + if (isResultMutationLocked()) { + e.preventDefault(); + return; + } + card.classList.toggle("checked", cb.checked); + if (stage === "summary-review" || stage === "generating-summary") { + interruptSummaryIfNeeded(); + } + updateStageUI(); + maybeAutoGenerateSummary(); + return; + } + var isExpanded = body && body.classList.contains("open"); + if (body) body.classList.toggle("open"); + if (expandEl) expandEl.textContent = isExpanded ? "\u25BC" : "\u25B2"; + }); + + if (body) { + body.addEventListener("click", function(e) { + e.stopPropagation(); + }); + } + } + + function getSelectedIndices() { + var indices = []; + var cards = resultCardsEl.querySelectorAll(".result-card"); + cards.forEach(function(card) { + if (card.dataset.completed !== "true") return; + if (card.classList.contains("error")) return; + var cb = card.querySelector("input[type=checkbox]"); + if (!cb || !cb.checked) return; + var qi = parseInt(card.dataset.qi, 10); + if (!Number.isNaN(qi)) indices.push(qi); + }); + return indices; + } + + function getCompletedSelectableIndices() { + var indices = []; + var cards = resultCardsEl.querySelectorAll(".result-card"); + cards.forEach(function(card) { + if (card.dataset.completed !== "true") return; + if (card.classList.contains("error")) return; + var qi = parseInt(card.dataset.qi, 10); + if (!Number.isNaN(qi)) indices.push(qi); + }); + return indices; + } + + function hasPendingSearchCards() { + var cards = resultCardsEl.querySelectorAll(".result-card"); + for (var i = 0; i < cards.length; i++) { + var card = cards[i]; + if (card.dataset.completed !== "true") return true; + } + return addSearchInFlight || providerBatchInFlight; + } + + function getTimeoutSelectedIndices() { + var selected = getSelectedIndices(); + if (selected.length > 0) return selected; + return getCompletedSelectableIndices(); + } + + function normalizeSummaryMeta(meta, edited) { + if (!meta || typeof meta !== "object") { + return { + model: null, + durationMs: 0, + tokenEstimate: 0, + fallbackUsed: false, + edited: !!edited, + }; + } + + return { + model: typeof meta.model === "string" || meta.model === null ? meta.model : null, + durationMs: typeof meta.durationMs === "number" && Number.isFinite(meta.durationMs) && meta.durationMs >= 0 ? meta.durationMs : 0, + tokenEstimate: typeof meta.tokenEstimate === "number" && Number.isFinite(meta.tokenEstimate) && meta.tokenEstimate >= 0 ? meta.tokenEstimate : 0, + fallbackUsed: meta.fallbackUsed === true, + fallbackReason: typeof meta.fallbackReason === "string" ? meta.fallbackReason : undefined, + edited: !!edited, + }; + } + + function isSummaryModelSelectionError(message) { + if (typeof message !== "string") return false; + return message.indexOf("Invalid summary model") !== -1 + || message.indexOf("Summary model not found") !== -1 + || message.indexOf("No API key available for summary model") !== -1 + || message.indexOf("Invalid provider") !== -1; + } + + function resetSummaryGeneratingState() { + summaryPendingModel = ""; + summaryGeneratingStartedAt = 0; + summaryGeneratingPhase = -1; + } + + function cancelInFlightSummaryRequest() { + summaryRequestSeq += 1; + resetSummaryGeneratingState(); + } + + function interruptSummaryIfNeeded() { + if (stage !== "generating-summary" && stage !== "summary-review") return; + if (stage === "generating-summary") { + cancelInFlightSummaryRequest(); + } + clearError(); + isRegenerating = getSummaryDraftText().length > 0; + stage = "results"; + updateStageUI(); + } + + function exitRegeneratingState() { + if (!isRegenerating) return false; + if (stage === "generating-summary") { + cancelInFlightSummaryRequest(); + } + isRegenerating = false; + clearError(); + stage = "results"; + updateStageUI(); + return true; + } + + function requestSummary(indices, feedback) { + if (submitted || timerExpired || submitInFlight) return; + + if (!Array.isArray(indices) || indices.length === 0) { + setError("Select at least one result to summarize"); + stage = "results"; + updateStageUI(); + return; + } + + if (hasPendingSearchCards()) { + setError("Wait for running searches to finish before generating summary"); + stage = "results"; + updateStageUI(); + return; + } + + clearError(); + var previousStage = stage; + var wasRegenerating = isRegenerating; + var selectedSummaryModel = getSelectedSummaryModel(); + summaryPendingModel = selectedSummaryModel; + summaryGeneratingStartedAt = Date.now(); + summaryGeneratingPhase = -1; + stage = "generating-summary"; + updateStageUI(); + + var requestId = ++summaryRequestSeq; + var feedbackText = typeof feedback === "string" ? feedback.trim() : ""; + var summarizePayload = { selected: indices }; + if (selectedSummaryModel.length > 0) { + summarizePayload.model = selectedSummaryModel; + } + if (feedbackText.length > 0) { + summarizePayload.feedback = feedbackText; + } + + postJson("/summarize", summarizePayload) + .then(function(data) { + if (requestId !== summaryRequestSeq) return data; + if (!data || data.ok === false) { + throw new Error(extractServerError(data) || "summary request rejected"); + } + return data; + }) + .catch(function(err) { + if (requestId !== summaryRequestSeq) throw err; + + var firstMessage = err instanceof Error ? err.message : String(err); + if (selectedSummaryModel.length === 0 || !isSummaryModelSelectionError(firstMessage)) { + throw err; + } + + summaryPendingModel = ""; + updateStageUI(); + + var retryPayload = { selected: indices }; + if (feedbackText.length > 0) { + retryPayload.feedback = feedbackText; + } + return postJson("/summarize", retryPayload).then(function(retryData) { + if (!retryData || retryData.ok === false) { + throw new Error(extractServerError(retryData) || "summary request rejected"); + } + return retryData; + }).catch(function(retryErr) { + var retryMessage = retryErr instanceof Error ? retryErr.message : String(retryErr); + throw new Error(firstMessage + " (auto retry failed: " + (retryMessage || "unknown error") + ")"); + }); + }) + .then(function(data) { + if (requestId !== summaryRequestSeq) return; + + var summaryText = typeof data.summary === "string" ? data.summary.trim() : ""; + if (!summaryText) { + throw new Error("Summary response was empty"); + } + + if (summaryInput) { + summaryInput.value = summaryText; + } + if (summaryFeedback) { + summaryFeedback.value = ""; + } + summaryMeta = normalizeSummaryMeta(data.meta || null, false); + lastAutoSummarySignature = selectionSignature(indices); + resetSummaryGeneratingState(); + isRegenerating = false; + stage = "summary-review"; + updateStageUI(); + }) + .catch(function(err) { + if (requestId !== summaryRequestSeq) return; + var message = err instanceof Error ? err.message : String(err); + setError("Failed to generate summary — " + (message || "unknown error")); + resetSummaryGeneratingState(); + isRegenerating = false; + if (wasRegenerating && getSummaryDraftText().length > 0) { + stage = "summary-review"; + } else { + stage = previousStage === "summary-review" ? "summary-review" : "results"; + } + updateStageUI(); + }); + } + + function selectionSignature(indices) { + return indices.slice().sort(function(a, b) { return a - b; }).join(","); + } + + function maybeAutoGenerateSummary() { + if (workflow !== "summary-review") return; + if (!searchesDone) return; + if (stage !== "results") return; + if (submitted || timerExpired || submitInFlight) return; + if (hasPendingSearchCards()) return; + + var selected = getSelectedIndices(); + if (selected.length === 0) { + if (isRegenerating) { + isRegenerating = false; + updateStageUI(); + } + return; + } + + var signature = selectionSignature(selected); + if (signature === lastAutoSummarySignature) { + if (isRegenerating) { + isRegenerating = false; + if (getSummaryDraftText().length > 0) { + stage = "summary-review"; + } + updateStageUI(); + } + return; + } + + lastAutoSummarySignature = signature; + requestSummary(selected); + } + + function doApprove() { + if (submitted || timerExpired || submitInFlight || stage !== "summary-review") return; + + var selected = getSelectedIndices(); + if (selected.length === 0) { + setError("Select at least one result before approving"); + updateStageUI(); + return; + } + + var draft = getSummaryDraftText(); + var payload = { selected: selected }; + if (draft.length > 0) { + payload.summary = draft; + payload.summaryMeta = normalizeSummaryMeta(summaryMeta, summaryMeta && summaryMeta.edited === true); + } + + submitPayload(payload, "Summary approved") + .catch(function(err) { + var message = err instanceof Error ? err.message : String(err); + setError("Failed to approve summary — " + (message || "the agent may have moved on")); + }); + } + + function doCancel() { + if (submitted || timerExpired || submitInFlight) return; + submitted = true; + submitInFlight = true; + syncLoadingPanel(); + updateStageUI(); + clearError(); + + postJson("/cancel", { reason: "user" }) + .then(function(data) { + if (data && data.ok === false) { + throw new Error(extractServerError(data) || "cancel rejected"); + } + showSuccess("Skipped"); + }) + .catch(function(err) { + submitted = false; + submitInFlight = false; + syncLoadingPanel(); + updateStageUI(); + var message = err instanceof Error ? err.message : String(err); + setError("Failed to cancel — " + (message || "the agent may have moved on")); + }); + } + + btnSend.addEventListener("click", function() { + if (stage !== "results") return; + requestSummary(getSelectedIndices()); + }); + + if (btnSendRaw) { + btnSendRaw.addEventListener("click", function() { + var selected = getSelectedIndices(); + if (selected.length === 0) return; + submitPayload({ selected: selected, rawResults: true }, "Results sent") + .catch(function(err) { + var message = err instanceof Error ? err.message : String(err); + setError("Failed to send results — " + (message || "the agent may have moved on")); + }); + }); + } + + if (btnSummaryBack) { + btnSummaryBack.addEventListener("click", function() { + if (exitRegeneratingState()) { + resetTimer(); + return; + } + if (stage !== "summary-review") return; + clearError(); + stage = "results"; + updateStageUI(); + resetTimer(); + }); + } + + if (btnSummaryRegenerate) { + btnSummaryRegenerate.addEventListener("click", function() { + requestSummary(getSelectedIndices(), getFeedbackText()); + resetTimer(); + }); + } + + function openPreviewModal() { + var draft = getSummaryDraftText(); + if (!draft || !previewModal || !previewModalBody) return; + var rendered = typeof marked !== "undefined" && marked.parse + ? marked.parse(draft, { breaks: true }) + : "
" + escHtml(draft) + "
"; + previewModalBody.innerHTML = sanitizeMarkdownHtml(rendered); + if (previewModalModel) { + previewModalModel.innerHTML = ''; + for (var i = 0; i < summaryModels.length; i++) { + var m = summaryModels[i]; + var opt = document.createElement("option"); + opt.value = m.value; + opt.textContent = m.label; + previewModalModel.appendChild(opt); + } + previewModalModel.value = getSelectedSummaryModel() || ""; + } + previewModal.classList.remove("hidden"); + resetTimer(); + } + + function closePreviewModal() { + if (previewModal) previewModal.classList.add("hidden"); + if (previewModalBody) previewModalBody.innerHTML = ""; + hidePreviewPopover(); + } + + var popoverSelectedText = ""; + + function hidePreviewPopover() { + if (previewPopover) previewPopover.classList.add("hidden"); + if (previewPopoverInput) previewPopoverInput.value = ""; + popoverSelectedText = ""; + } + + function showPreviewPopover(text, rect) { + if (!previewPopover || !previewPopoverQuote || !previewModalBody) return; + popoverSelectedText = text; + var display = text.length > 120 ? text.slice(0, 117) + "\u2026" : text; + previewPopoverQuote.textContent = "\u201c" + display + "\u201d"; + if (previewPopoverInput) previewPopoverInput.value = ""; + previewPopover.classList.remove("hidden"); + + var bodyRect = previewModalBody.getBoundingClientRect(); + var popH = previewPopover.offsetHeight; + var top = rect.bottom - bodyRect.top + previewModalBody.scrollTop + 6; + if (rect.bottom + popH + 20 > bodyRect.bottom) { + top = rect.top - bodyRect.top + previewModalBody.scrollTop - popH - 6; + } + var left = Math.max(8, Math.min(rect.left - bodyRect.left, bodyRect.width - previewPopover.offsetWidth - 8)); + previewPopover.style.top = top + "px"; + previewPopover.style.left = left + "px"; + + if (previewPopoverInput) previewPopoverInput.focus(); + } + + if (btnSummaryPreview) { + btnSummaryPreview.addEventListener("click", openPreviewModal); + } + if (previewModalClose) { + previewModalClose.addEventListener("click", closePreviewModal); + } + if (previewModalRegenerate) { + previewModalRegenerate.addEventListener("click", function() { + var selectedModel = previewModalModel ? previewModalModel.value.trim() : ""; + closePreviewModal(); + var modelProvider = getSummaryProvider(selectedModel); + if (modelProvider && modelProvider !== currentSummaryProvider) { + setSummaryProvider(modelProvider, selectedModel); + } else if (summaryModelSelect) { + summaryModelSelect.value = selectedModel; + currentSummaryModel = selectedModel; + } + requestSummary(getSelectedIndices(), getFeedbackText()); + resetTimer(); + }); + } + if (previewModalApprove) { + previewModalApprove.addEventListener("click", function() { + closePreviewModal(); + doApprove(); + }); + } + if (previewModalBody) { + previewModalBody.addEventListener("mouseup", function() { + var sel = window.getSelection(); + if (!sel || sel.isCollapsed) return; + var text = sel.toString().trim(); + if (!text) return; + var range = sel.getRangeAt(0); + showPreviewPopover(text, range.getBoundingClientRect()); + }); + previewModalBody.addEventListener("mousedown", function(e) { + if (previewPopover && !previewPopover.contains(e.target)) { + hidePreviewPopover(); + } + }); + } + + if (previewPopoverRegen) { + previewPopoverRegen.addEventListener("click", function() { + var note = previewPopoverInput ? previewPopoverInput.value.trim() : ""; + var quoted = popoverSelectedText; + hidePreviewPopover(); + + var feedback = 'Regarding: "' + quoted + '"'; + if (note) feedback += " \u2014 " + note; + + var selectedModel = previewModalModel ? previewModalModel.value.trim() : ""; + closePreviewModal(); + var modelProvider = getSummaryProvider(selectedModel); + if (modelProvider && modelProvider !== currentSummaryProvider) { + setSummaryProvider(modelProvider, selectedModel); + } else if (summaryModelSelect) { + summaryModelSelect.value = selectedModel; + currentSummaryModel = selectedModel; + } + requestSummary(getSelectedIndices(), feedback); + resetTimer(); + }); + } + + if (previewPopoverInput) { + previewPopoverInput.addEventListener("keydown", function(e) { + if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + if (previewPopoverRegen) previewPopoverRegen.click(); + } + if (e.key === "Escape") { + e.preventDefault(); + e.stopImmediatePropagation(); + hidePreviewPopover(); + } + }); + } + + if (previewModal) { + previewModal.addEventListener("click", function(e) { + if (e.target === previewModal) closePreviewModal(); + }); + document.addEventListener("keydown", function(e) { + if (e.key === "Escape" && !previewModal.classList.contains("hidden")) { + if (previewPopover && !previewPopover.classList.contains("hidden")) { + e.preventDefault(); + e.stopImmediatePropagation(); + hidePreviewPopover(); + return; + } + e.preventDefault(); + e.stopImmediatePropagation(); + closePreviewModal(); + } + }); + } + + if (btnSummaryApprove) { + btnSummaryApprove.addEventListener("click", function() { + doApprove(); + resetTimer(); + }); + } + + if (summaryInput) { + summaryInput.addEventListener("input", function() { + if (!summaryMeta || typeof summaryMeta !== "object") { + summaryMeta = normalizeSummaryMeta(null, true); + } + summaryMeta.edited = true; + clearError(); + updateStageUI(); + resetTimer(); + }); + } + + if (summaryProviderSelect) { + summaryProviderSelect.addEventListener("change", function() { + var provider = typeof summaryProviderSelect.value === "string" ? summaryProviderSelect.value : ""; + if (!provider || provider === currentSummaryProvider) return; + setSummaryProvider(provider, ""); + clearError(); + updateStageUI(); + resetTimer(); + }); + } + + if (summaryModelSelect) { + summaryModelSelect.addEventListener("change", function() { + currentSummaryModel = typeof summaryModelSelect.value === "string" + ? summaryModelSelect.value.trim() + : ""; + clearError(); + resetTimer(); + }); + } + + function isInteractiveTarget(target) { + if (!target || !target.tagName) return false; + var tag = target.tagName; + if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || tag === "BUTTON" || tag === "A") return true; + if (typeof target.isContentEditable === "boolean" && target.isContentEditable) return true; + if (typeof target.closest === "function") { + return !!target.closest('[contenteditable=""], [contenteditable="true"]'); + } + return false; + } + + document.addEventListener("keydown", function(e) { + if (submitted || timerExpired || submitInFlight) return; + + var isSummaryInput = summaryInput && e.target === summaryInput; + if (isSummaryInput && (e.metaKey || e.ctrlKey) && e.key === "Enter") { + e.preventDefault(); + if (stage === "summary-review") doApprove(); + return; + } + + if (e.key === "Escape") { + e.preventDefault(); + if (exitRegeneratingState()) { + return; + } + if (stage === "summary-review") { + stage = "results"; + clearError(); + updateStageUI(); + } else if (stage === "results") { + doCancel(); + } + return; + } + + if (isInteractiveTarget(e.target)) return; + + if (e.key === "Enter" && !e.metaKey && !e.ctrlKey) { + if (stage !== "results") return; + e.preventDefault(); + requestSummary(getSelectedIndices()); + return; + } + + if ((e.metaKey || e.ctrlKey) && e.key === "Enter") { + if (stage !== "summary-review") return; + e.preventDefault(); + doApprove(); + return; + } + + if (e.key.toLowerCase() === "a" && !e.metaKey && !e.ctrlKey) { + e.preventDefault(); + if (stage !== "results") return; + var boxes = resultCardsEl.querySelectorAll(".result-card input[type=checkbox]"); + var selectable = []; + boxes.forEach(function(cb) { + if (cb.disabled) return; + selectable.push(cb); + }); + if (selectable.length === 0) return; + var allChecked = true; + selectable.forEach(function(cb) { if (!cb.checked) allChecked = false; }); + selectable.forEach(function(cb) { + cb.checked = !allChecked; + var parentCard = typeof cb.closest === "function" ? cb.closest(".result-card") : null; + if (parentCard) parentCard.classList.toggle("checked", cb.checked); + }); + updateStageUI(); + maybeAutoGenerateSummary(); + resetTimer(); + } + }); + + setInterval(function() { + if (submitted) return; + postJson("/heartbeat", {}).catch(function() { + // Heartbeat is best-effort. + }); + }, 10000); + + var lastResizeHeight = 0; + function checkContentHeight() { + if (!window.glimpse || typeof window.glimpse.send !== "function") return; + var h = document.documentElement.scrollHeight || document.body.scrollHeight; + if (h > 0 && Math.abs(h - lastResizeHeight) > 30) { + lastResizeHeight = h; + window.glimpse.send({ type: "resize", height: h }); + } + } + setInterval(checkContentHeight, 500); + + if (queries.length === 0 && addSearchInput) { + addSearchInput.focus(); + } +})();`; /* v9-c96edf658a9d7e1a */ diff --git a/pip-tmp/jiti/pi-web-access-curator-server.2a16f6fa.mjs b/pip-tmp/jiti/pi-web-access-curator-server.2a16f6fa.mjs new file mode 100644 index 0000000000000000000000000000000000000000..f624e0939d4c87168522df16c84c4022c87216a8 --- /dev/null +++ b/pip-tmp/jiti/pi-web-access-curator-server.2a16f6fa.mjs @@ -0,0 +1,605 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.startCuratorServer = startCuratorServer;var _nodeHttp = _interopRequireDefault(await jitiImport("node:http")); +var _curatorPage = await jitiImport("./curator-page.js");function _interopRequireDefault(e) {return e && e.__esModule ? e : { default: e };} + + +const STALE_THRESHOLD_MS = 30000; +const WATCHDOG_INTERVAL_MS = 5000; +const MAX_BODY_SIZE = 64 * 1024; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +function sendJson(res, status, payload) { + res.writeHead(status, { + "Content-Type": "application/json", + "Cache-Control": "no-store" + }); + res.end(JSON.stringify(payload)); +} + +function parseJSONBody(req) { + return new Promise((resolve, reject) => { + let body = ""; + let size = 0; + req.on("data", (chunk) => { + size += chunk.length; + if (size > MAX_BODY_SIZE) { + req.destroy(); + reject(new Error("Request body too large")); + return; + } + body += chunk.toString(); + }); + req.on("end", () => { + try { + resolve(JSON.parse(body)); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + reject(new Error(`Invalid JSON: ${message}`)); + } + }); + req.on("error", reject); + }); +} + +async function parseBodyOrSend(req, res) { + try { + return await parseJSONBody(req); + } catch (err) { + const message = err instanceof Error ? err.message : "Invalid body"; + const status = message === "Request body too large" ? 413 : 400; + sendJson(res, status, { ok: false, error: message }); + return null; + } +} + +function normalizeSelectedIndices( +value, +options) +{ + if (!Array.isArray(value)) { + return { ok: false, error: "Invalid selection" }; + } + + if (!options.allowEmpty && value.length === 0) { + return { ok: false, error: "Invalid selection" }; + } + + const normalized = []; + const seen = new Set(); + for (const item of value) { + if (typeof item !== "number" || !Number.isInteger(item) || item < 0) { + return { ok: false, error: "Invalid selection" }; + } + if (item >= options.maxExclusive) { + return { ok: false, error: "Invalid selection" }; + } + if (seen.has(item)) { + continue; + } + seen.add(item); + normalized.push(item); + } + + if (!options.allowEmpty && normalized.length === 0) { + return { ok: false, error: "Invalid selection" }; + } + + return { ok: true, indices: normalized }; +} + +function normalizeSummaryMeta(value) { + if (!value || typeof value !== "object") return null; + const meta = value; + + const model = meta.model; + if (model !== null && typeof model !== "string") return null; + + const durationMs = meta.durationMs; + if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs < 0) return null; + + const tokenEstimate = meta.tokenEstimate; + if (typeof tokenEstimate !== "number" || !Number.isFinite(tokenEstimate) || tokenEstimate < 0) return null; + + const fallbackUsed = meta.fallbackUsed; + if (typeof fallbackUsed !== "boolean") return null; + + const fallbackReason = meta.fallbackReason; + if (fallbackReason !== undefined && typeof fallbackReason !== "string") return null; + + const edited = meta.edited; + if (edited !== undefined && typeof edited !== "boolean") return null; + + return { + model, + durationMs, + tokenEstimate, + fallbackUsed, + fallbackReason, + edited + }; +} + +function startCuratorServer( +options, +callbacks) +{ + const { + queries, + sessionToken, + timeout, + availableProviders, + defaultProvider, + summaryModels, + defaultSummaryModel + } = options; + let browserConnected = false; + let lastHeartbeatAt = Date.now(); + let completed = false; + let watchdog = null; + let state = "SEARCHING"; + let sseResponse = null; + const sseBuffer = []; + let nextQueryIndex = queries.length; + let summarizeAbortController = null; + let summarizeRequestSeq = 0; + + let sseKeepalive = null; + + const abortInFlightSummarize = () => { + if (!summarizeAbortController) return; + summarizeAbortController.abort(); + summarizeAbortController = null; + }; + + const markCompleted = () => { + if (completed) return false; + completed = true; + state = "COMPLETED"; + if (watchdog) { + clearInterval(watchdog); + watchdog = null; + } + if (sseKeepalive) { + clearInterval(sseKeepalive); + sseKeepalive = null; + } + abortInFlightSummarize(); + if (sseResponse) { + try {sseResponse.end();} catch {} + sseResponse = null; + } + return true; + }; + + const touchHeartbeat = () => { + lastHeartbeatAt = Date.now(); + browserConnected = true; + }; + + function validateToken(body, res) { + if (!body || typeof body !== "object") { + sendJson(res, 400, { ok: false, error: "Invalid body" }); + return false; + } + if (body.token !== sessionToken) { + sendJson(res, 403, { ok: false, error: "Invalid session" }); + return false; + } + return true; + } + + function isAvailableProvider(provider) { + if (provider === "perplexity") return availableProviders.perplexity; + if (provider === "exa") return availableProviders.exa; + if (provider === "gemini") return availableProviders.gemini; + return false; + } + + function sendSSE(event, data) { + const payload = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`; + const res = sseResponse; + if (res && !res.writableEnded && res.socket && !res.socket.destroyed) { + try {res.write(payload);return;} catch {} + } + sseBuffer.push(payload); + } + + const pageHtml = (0, _curatorPage.generateCuratorPage)( + queries, + sessionToken, + timeout, + availableProviders, + defaultProvider, + summaryModels, + defaultSummaryModel + ); + + const server = _nodeHttp.default.createServer(async (req, res) => { + try { + const method = req.method || "GET"; + const url = new URL(req.url || "/", `http://${req.headers.host || "127.0.0.1"}`); + + if (method === "GET" && url.pathname === "/") { + const token = url.searchParams.get("session"); + if (token !== sessionToken) { + res.writeHead(403, { "Content-Type": "text/plain" }); + res.end("Invalid session"); + return; + } + touchHeartbeat(); + res.writeHead(200, { + "Content-Type": "text/html; charset=utf-8", + "Cache-Control": "no-store" + }); + res.end(pageHtml); + return; + } + + if (method === "GET" && url.pathname === "/events") { + const token = url.searchParams.get("session"); + if (token !== sessionToken) { + res.writeHead(403, { "Content-Type": "text/plain" }); + res.end("Invalid session"); + return; + } + if (state === "COMPLETED") { + sendJson(res, 409, { ok: false, error: "No events available" }); + return; + } + if (sseResponse) { + try {sseResponse.end();} catch {} + } + res.writeHead(200, { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + "X-Accel-Buffering": "no" + }); + res.flushHeaders(); + if (res.socket) res.socket.setNoDelay(true); + sseResponse = res; + if (sseBuffer.length > 0) { + const pending = sseBuffer.splice(0, sseBuffer.length); + for (let i = 0; i < pending.length; i++) { + const msg = pending[i]; + try { + res.write(msg); + } catch { + sseBuffer.unshift(...pending.slice(i)); + break; + } + } + } + if (sseKeepalive) clearInterval(sseKeepalive); + sseKeepalive = setInterval(() => { + if (sseResponse) { + try {sseResponse.write(":keepalive\n\n");} catch {} + } + }, 15000); + req.on("close", () => { + if (sseResponse === res) sseResponse = null; + }); + return; + } + + if (method === "POST" && url.pathname === "/heartbeat") { + const body = await parseBodyOrSend(req, res); + if (!body) return; + if (!validateToken(body, res)) return; + touchHeartbeat(); + sendJson(res, 200, { ok: true }); + return; + } + + if (method === "POST" && url.pathname === "/provider") { + const body = await parseBodyOrSend(req, res); + if (!body) return; + if (!validateToken(body, res)) return; + const { provider } = body; + if (typeof provider !== "string" || provider.length === 0) { + sendJson(res, 400, { ok: false, error: "Invalid provider" }); + return; + } + if (!isAvailableProvider(provider)) { + sendJson(res, 400, { ok: false, error: `Provider unavailable: ${provider}` }); + return; + } + setImmediate(() => callbacks.onProviderChange(provider)); + sendJson(res, 200, { ok: true }); + return; + } + + if (method === "POST" && url.pathname === "/search") { + const body = await parseBodyOrSend(req, res); + if (!body) return; + if (!validateToken(body, res)) return; + if (state === "COMPLETED") { + sendJson(res, 409, { ok: false, error: "Session closed" }); + return; + } + const { query, provider } = body; + if (typeof query !== "string" || query.trim().length === 0) { + sendJson(res, 400, { ok: false, error: "Invalid query" }); + return; + } + if (provider !== undefined) { + if (typeof provider !== "string" || provider.length === 0) { + sendJson(res, 400, { ok: false, error: "Invalid provider" }); + return; + } + if (!isAvailableProvider(provider)) { + sendJson(res, 400, { ok: false, error: `Provider unavailable: ${provider}` }); + return; + } + } + const qi = nextQueryIndex++; + touchHeartbeat(); + try { + const result = await callbacks.onAddSearch(query.trim(), qi, provider); + sendJson(res, 200, { + ok: true, + queryIndex: qi, + answer: result.answer, + results: result.results, + provider: result.provider + }); + } catch (err) { + const message = err instanceof Error ? err.message : "Search failed"; + sendJson(res, 200, { + ok: true, + queryIndex: qi, + error: message, + provider: typeof provider === "string" && provider.length > 0 ? provider : undefined + }); + } + return; + } + + if (method === "POST" && url.pathname === "/summarize") { + const body = await parseBodyOrSend(req, res); + if (!body) return; + if (!validateToken(body, res)) return; + if (state === "COMPLETED") { + sendJson(res, 409, { ok: false, error: "Session closed" }); + return; + } + + const parsed = normalizeSelectedIndices(body.selected, { + allowEmpty: false, + maxExclusive: nextQueryIndex + }); + if (!parsed.ok) { + sendJson(res, 400, { ok: false, error: parsed.error }); + return; + } + + let model; + const bodyModel = body.model; + if (bodyModel !== undefined) { + if (typeof bodyModel !== "string") { + sendJson(res, 400, { ok: false, error: "Invalid model" }); + return; + } + const trimmedModel = bodyModel.trim(); + model = trimmedModel.length > 0 ? trimmedModel : undefined; + } + + const bodyFeedback = body.feedback; + const feedback = typeof bodyFeedback === "string" && bodyFeedback.trim().length > 0 ? + bodyFeedback.trim() : + undefined; + + abortInFlightSummarize(); + const controller = new AbortController(); + summarizeAbortController = controller; + const requestId = ++summarizeRequestSeq; + + try { + const result = await callbacks.onSummarize(parsed.indices, controller.signal, model, feedback); + if (requestId !== summarizeRequestSeq || state === "COMPLETED") { + sendJson(res, 409, { ok: false, error: "Summarize request superseded" }); + return; + } + sendJson(res, 200, { + ok: true, + summary: result.summary, + meta: result.meta + }); + } catch (err) { + const message = err instanceof Error ? err.message : "Summary generation failed"; + const status = controller.signal.aborted ? 409 : 500; + sendJson(res, status, { ok: false, error: message }); + } finally { + if (summarizeAbortController === controller) { + summarizeAbortController = null; + } + } + return; + } + + if (method === "POST" && url.pathname === "/rewrite") { + const body = await parseBodyOrSend(req, res); + if (!body) return; + if (!validateToken(body, res)) return; + if (state === "COMPLETED") { + sendJson(res, 409, { ok: false, error: "Session closed" }); + return; + } + const { query } = body; + if (typeof query !== "string" || query.trim().length === 0) { + sendJson(res, 400, { ok: false, error: "Invalid query" }); + return; + } + const controller = new AbortController(); + req.on("close", () => controller.abort()); + touchHeartbeat(); + try { + const rewritten = await callbacks.onRewriteQuery(query.trim(), controller.signal); + sendJson(res, 200, { ok: true, query: rewritten }); + } catch (err) { + const message = err instanceof Error ? err.message : "Rewrite failed"; + const status = controller.signal.aborted ? 409 : 500; + sendJson(res, status, { ok: false, error: message }); + } + return; + } + + if (method === "POST" && url.pathname === "/submit") { + const body = await parseBodyOrSend(req, res); + if (!body) return; + if (!validateToken(body, res)) return; + + const parsed = normalizeSelectedIndices(body.selected, { + allowEmpty: true, + maxExclusive: nextQueryIndex + }); + if (!parsed.ok) { + sendJson(res, 400, { ok: false, error: parsed.error }); + return; + } + + let summary; + const bodySummary = body.summary; + if (bodySummary !== undefined) { + if (typeof bodySummary !== "string") { + sendJson(res, 400, { ok: false, error: "Invalid summary" }); + return; + } + const trimmedSummary = bodySummary.trim(); + summary = trimmedSummary.length > 0 ? trimmedSummary : undefined; + } + + let summaryMeta; + const bodySummaryMeta = body.summaryMeta; + if (bodySummaryMeta !== undefined) { + const parsedSummaryMeta = normalizeSummaryMeta(bodySummaryMeta); + if (!parsedSummaryMeta) { + sendJson(res, 400, { ok: false, error: "Invalid summaryMeta" }); + return; + } + summaryMeta = parsedSummaryMeta; + } + + if (state !== "SEARCHING" && state !== "RESULT_SELECTION") { + sendJson(res, 409, { ok: false, error: "Cannot submit in current state" }); + return; + } + if (!markCompleted()) { + sendJson(res, 409, { ok: false, error: "Session closed" }); + return; + } + const rawResults = body.rawResults === true; + sendJson(res, 200, { ok: true }); + setImmediate(() => callbacks.onSubmit({ selectedQueryIndices: parsed.indices, summary, summaryMeta, rawResults })); + return; + } + + if (method === "POST" && url.pathname === "/cancel") { + const body = await parseBodyOrSend(req, res); + if (!body) return; + if (!validateToken(body, res)) return; + if (!markCompleted()) { + sendJson(res, 200, { ok: true }); + return; + } + const { reason } = body; + sendJson(res, 200, { ok: true }); + const cancelReason = reason === "timeout" ? "timeout" : "user"; + setImmediate(() => callbacks.onCancel(cancelReason)); + return; + } + + res.writeHead(404, { "Content-Type": "text/plain" }); + res.end("Not found"); + } catch (err) { + const message = err instanceof Error ? err.message : "Server error"; + sendJson(res, 500, { ok: false, error: message }); + } + }); + + return new Promise((resolve, reject) => { + const onError = (err) => { + reject(new Error(`Curator server failed to start: ${err.message}`)); + }; + + server.once("error", onError); + server.listen(0, "127.0.0.1", () => { + server.off("error", onError); + const addr = server.address(); + if (!addr || typeof addr === "string") { + reject(new Error("Curator server: invalid address")); + return; + } + const url = `http://localhost:${addr.port}/?session=${sessionToken}`; + + watchdog = setInterval(() => { + if (completed || !browserConnected) return; + if (Date.now() - lastHeartbeatAt <= STALE_THRESHOLD_MS) return; + if (!markCompleted()) return; + setImmediate(() => callbacks.onCancel("stale")); + }, WATCHDOG_INTERVAL_MS); + + resolve({ + server, + url, + close: () => { + const wasOpen = markCompleted(); + try {server.close();} catch {} + if (wasOpen) { + setImmediate(() => callbacks.onCancel("stale")); + } + }, + pushResult: (queryIndex, data) => { + if (completed) return; + sendSSE("result", { queryIndex, query: queries[queryIndex] ?? "", ...data }); + }, + pushError: (queryIndex, error, provider) => { + if (completed) return; + sendSSE("search-error", { queryIndex, query: queries[queryIndex] ?? "", error, provider }); + }, + searchesDone: () => { + if (completed) return; + sendSSE("done", {}); + state = "RESULT_SELECTION"; + } + }); + }); + }); +} /* v9-ab24f6f4c86d3473 */ diff --git a/pip-tmp/jiti/pi-web-access-exa.b5c6a885.mjs b/pip-tmp/jiti/pi-web-access-exa.b5c6a885.mjs new file mode 100644 index 0000000000000000000000000000000000000000..3cb17ee526d681d45556c2fec13dc2b4c5660db4 --- /dev/null +++ b/pip-tmp/jiti/pi-web-access-exa.b5c6a885.mjs @@ -0,0 +1,520 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.callExaMcp = callExaMcp;exports.hasExaApiKey = hasExaApiKey;exports.isExaAvailable = isExaAvailable;exports.searchWithExa = searchWithExa;var _nodeFs = await jitiImport("node:fs"); +var _nodeOs = await jitiImport("node:os"); +var _nodePath = await jitiImport("node:path"); +var _activity = await jitiImport("./activity.js"); + + + +const EXA_ANSWER_URL = "https://api.exa.ai/answer"; +const EXA_SEARCH_URL = "https://api.exa.ai/search"; +const EXA_MCP_URL = "https://mcp.exa.ai/mcp"; +const CONFIG_PATH = (0, _nodePath.join)((0, _nodeOs.homedir)(), ".pi", "web-search.json"); +const USAGE_PATH = (0, _nodePath.join)((0, _nodeOs.homedir)(), ".pi", "exa-usage.json"); + +const MONTHLY_LIMIT = 1000; +const WARNING_THRESHOLD = 800; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +let cachedConfig = null; +let warnedMonth = null; + +function loadConfig() { + if (cachedConfig) return cachedConfig; + if (!(0, _nodeFs.existsSync)(CONFIG_PATH)) { + cachedConfig = {}; + return cachedConfig; + } + + const raw = (0, _nodeFs.readFileSync)(CONFIG_PATH, "utf-8"); + try { + cachedConfig = JSON.parse(raw); + return cachedConfig; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + throw new Error(`Failed to parse ${CONFIG_PATH}: ${message}`); + } +} + +function normalizeApiKey(value) { + if (typeof value !== "string") return null; + const normalized = value.trim(); + return normalized.length > 0 ? normalized : null; +} + +function getApiKey() { + return normalizeApiKey(process.env.EXA_API_KEY) ?? normalizeApiKey(loadConfig().exaApiKey); +} + +function getCurrentMonth() { + return new Date().toISOString().slice(0, 7); +} + +function normalizeUsage(raw) { + const month = getCurrentMonth(); + if (!raw || typeof raw !== "object") return { month, count: 0 }; + const data = raw; + const parsedMonth = typeof data.month === "string" ? data.month : month; + const parsedCount = typeof data.count === "number" && Number.isFinite(data.count) ? data.count : 0; + if (parsedMonth !== month) return { month, count: 0 }; + return { month: parsedMonth, count: Math.max(0, Math.floor(parsedCount)) }; +} + +function readUsage() { + if (!(0, _nodeFs.existsSync)(USAGE_PATH)) return { month: getCurrentMonth(), count: 0 }; + const raw = (0, _nodeFs.readFileSync)(USAGE_PATH, "utf-8"); + try { + return normalizeUsage(JSON.parse(raw)); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + throw new Error(`Failed to parse ${USAGE_PATH}: ${message}`); + } +} + +function writeUsage(usage) { + const dir = (0, _nodePath.join)((0, _nodeOs.homedir)(), ".pi"); + if (!(0, _nodeFs.existsSync)(dir)) (0, _nodeFs.mkdirSync)(dir, { recursive: true }); + (0, _nodeFs.writeFileSync)(USAGE_PATH, JSON.stringify(usage, null, 2) + "\n"); +} + +function reserveRequestBudget() { + const usage = readUsage(); + + if (usage.count >= MONTHLY_LIMIT) { + return { exhausted: true }; + } + + const nextCount = usage.count + 1; + if (nextCount >= WARNING_THRESHOLD && warnedMonth !== usage.month) { + warnedMonth = usage.month; + console.error(`Exa usage warning: ${nextCount}/${MONTHLY_LIMIT} monthly requests used.`); + } + + writeUsage({ month: usage.month, count: nextCount }); + return null; +} + +function requestSignal(signal) { + const timeout = AbortSignal.timeout(60000); + return signal ? AbortSignal.any([signal, timeout]) : timeout; +} + +function recencyToStartDate(filter) { + const now = new Date(); + const offsets = { + day: 1, + week: 7, + month: 30, + year: 365 + }; + const days = offsets[filter] ?? 0; + return new Date(now.getTime() - days * 86400000).toISOString(); +} + +function mapDomainFilter(domainFilter) { + if (!domainFilter?.length) return {}; + const includeDomains = domainFilter. + filter((d) => !d.startsWith("-") && d.trim().length > 0). + map((d) => d.trim()); + const excludeDomains = domainFilter. + filter((d) => d.startsWith("-")). + map((d) => d.slice(1).trim()). + filter(Boolean); + return { + ...(includeDomains.length ? { includeDomains } : {}), + ...(excludeDomains.length ? { excludeDomains } : {}) + }; +} + +function normalizeHighlights(value) { + if (!Array.isArray(value)) return []; + return value.filter((item) => typeof item === "string" && item.trim().length > 0); +} + +function buildAnswerFromSearchResults(results) { + if (!results?.length) return ""; + const parts = []; + for (let i = 0; i < results.length; i++) { + const item = results[i]; + if (!item?.url) continue; + const highlights = normalizeHighlights(item.highlights); + const content = highlights.length > 0 ? + highlights.join(" ") : + typeof item.text === "string" ? item.text.trim().slice(0, 1000) : ""; + if (!content) continue; + const sourceTitle = item.title || `Source ${i + 1}`; + parts.push(`${content}\nSource: ${sourceTitle} (${item.url})`); + } + return parts.join("\n\n"); +} + +function mapResults(results) { + if (!Array.isArray(results)) return []; + const mapped = []; + for (let i = 0; i < results.length; i++) { + const item = results[i]; + if (!item?.url) continue; + mapped.push({ + title: item.title || `Source ${i + 1}`, + url: item.url, + snippet: "" + }); + } + return mapped; +} + +function mapInlineContent(results) { + if (!results?.length) return []; + return results. + filter((r) => + !!r?.url && typeof r.text === "string" && r.text.length > 0). + map((r) => ({ + url: r.url, + title: r.title || "", + content: r.text, + error: null + })); +} + +async function callExaMcp( +toolName, +args, +signal) +{ + const response = await fetch(EXA_MCP_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json, text/event-stream" + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method: "tools/call", + params: { + name: toolName, + arguments: args + } + }), + signal: requestSignal(signal) + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Exa MCP error ${response.status}: ${errorText.slice(0, 300)}`); + } + + const body = await response.text(); + const dataLines = body.split("\n").filter((line) => line.startsWith("data:")); + + let parsed = null; + for (const line of dataLines) { + const payload = line.slice(5).trim(); + if (!payload) continue; + try { + const candidate = JSON.parse(payload); + if (candidate?.result || candidate?.error) { + parsed = candidate; + break; + } + } catch { + } + } + + if (!parsed) { + try { + const candidate = JSON.parse(body); + if (candidate?.result || candidate?.error) { + parsed = candidate; + } + } catch { + } + } + + if (!parsed) { + throw new Error("Exa MCP returned an empty response"); + } + + if (parsed.error) { + const code = typeof parsed.error.code === "number" ? ` ${parsed.error.code}` : ""; + const message = parsed.error.message || "Unknown error"; + throw new Error(`Exa MCP error${code}: ${message}`); + } + + if (parsed.result?.isError) { + const message = parsed.result.content?. + find((item) => item.type === "text" && typeof item.text === "string")?. + text?.trim(); + throw new Error(message || "Exa MCP returned an error"); + } + + const text = parsed.result?.content?. + find((item) => item.type === "text" && typeof item.text === "string" && item.text.trim().length > 0)?. + text; + + if (!text) { + throw new Error("Exa MCP returned empty content"); + } + + return text; +} + +function parseMcpResults(text) { + const blocks = text.split(/(?=^Title: )/m).filter((block) => block.trim().length > 0); + const parsed = blocks.map((block) => { + const title = block.match(/^Title: (.+)/m)?.[1]?.trim() ?? ""; + const url = block.match(/^URL: (.+)/m)?.[1]?.trim() ?? ""; + let content = ""; + const textStart = block.indexOf("\nText: "); + if (textStart >= 0) { + content = block.slice(textStart + 7).trim(); + } else { + const hlMatch = block.match(/\nHighlights:\s*\n/); + if (hlMatch?.index != null) { + content = block.slice(hlMatch.index + hlMatch[0].length).trim(); + } + } + content = content.replace(/\n---\s*$/, "").trim(); + return { title, url, content }; + }).filter((result) => result.url.length > 0); + return parsed.length > 0 ? parsed : null; +} + +function buildAnswerFromMcpResults(results) { + if (results.length === 0) return ""; + const parts = []; + for (let i = 0; i < results.length; i++) { + const result = results[i]; + const snippet = result.content.replace(/\s+/g, " ").trim().slice(0, 500); + if (!snippet) continue; + const sourceTitle = result.title || `Source ${i + 1}`; + parts.push(`${snippet}\nSource: ${sourceTitle} (${result.url})`); + } + return parts.join("\n\n"); +} + +function mapMcpInlineContent(results) { + return results. + filter((result) => result.content.length > 0). + map((result) => ({ + url: result.url, + title: result.title, + content: result.content, + error: null + })); +} + +function buildMcpQuery(query, options) { + const parts = [query]; + if (options.domainFilter?.length) { + for (const d of options.domainFilter) { + parts.push(d.startsWith("-") ? `-site:${d.slice(1)}` : `site:${d}`); + } + } + if (options.recencyFilter) { + const now = new Date(); + switch (options.recencyFilter) { + case "day":parts.push("past 24 hours");break; + case "week":parts.push("past week");break; + case "month":parts.push(`${now.toLocaleString("en", { month: "long" })} ${now.getFullYear()}`);break; + case "year":parts.push(String(now.getFullYear()));break; + } + } + return parts.join(" "); +} + +async function searchWithExaMcp(query, options = {}) { + const enrichedQuery = buildMcpQuery(query, options); + const activityId = _activity.activityMonitor.logStart({ type: "api", query: enrichedQuery }); + + try { + const text = await callExaMcp( + "web_search_exa", + { + query: enrichedQuery, + numResults: options.numResults ?? 5, + livecrawl: "fallback", + type: "auto", + contextMaxCharacters: options.includeContent ? 50000 : 3000 + }, + options.signal + ); + const parsedResults = parseMcpResults(text); + _activity.activityMonitor.logComplete(activityId, 200); + + if (!parsedResults) return null; + + const response = { + answer: buildAnswerFromMcpResults(parsedResults), + results: parsedResults.map((result, index) => ({ + title: result.title || `Source ${index + 1}`, + url: result.url, + snippet: "" + })) + }; + + if (options.includeContent) { + const inlineContent = mapMcpInlineContent(parsedResults); + if (inlineContent.length > 0) response.inlineContent = inlineContent; + } + + return response; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + if (message.toLowerCase().includes("abort")) { + _activity.activityMonitor.logComplete(activityId, 0); + } else { + _activity.activityMonitor.logError(activityId, message); + } + throw err; + } +} + +function isExaAvailable() { + if (getApiKey()) { + const usage = readUsage(); + return usage.count < MONTHLY_LIMIT; + } + return true; +} + +function hasExaApiKey() { + return !!getApiKey(); +} + +async function searchWithExa(query, options = {}) { + const apiKey = getApiKey(); + if (!apiKey) { + return searchWithExaMcp(query, options); + } + + const budget = reserveRequestBudget(); + if (budget) return budget; + + const useSearch = options.includeContent || + !!options.recencyFilter || + !!options.domainFilter?.length || + !!(options.numResults && options.numResults !== 5); + + const activityId = _activity.activityMonitor.logStart({ type: "api", query }); + + try { + if (!useSearch) { + const response = await fetch(EXA_ANSWER_URL, { + method: "POST", + headers: { + "x-api-key": apiKey, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + query, + text: true + }), + signal: requestSignal(options.signal) + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Exa API error ${response.status}: ${errorText.slice(0, 300)}`); + } + + const data = await response.json(); + _activity.activityMonitor.logComplete(activityId, response.status); + return { + answer: data.answer || "", + results: mapResults(data.citations) + }; + } + + const startDate = options.recencyFilter ? recencyToStartDate(options.recencyFilter) : null; + const domainFilters = mapDomainFilter(options.domainFilter); + const response = await fetch(EXA_SEARCH_URL, { + method: "POST", + headers: { + "x-api-key": apiKey, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + query, + type: "auto", + numResults: options.numResults ?? 5, + ...domainFilters, + ...(startDate ? { startPublishedDate: startDate } : {}), + contents: { + text: options.includeContent ? true : { maxCharacters: 3000 }, + highlights: true + } + }), + signal: requestSignal(options.signal) + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Exa API error ${response.status}: ${errorText.slice(0, 300)}`); + } + + const data = await response.json(); + _activity.activityMonitor.logComplete(activityId, response.status); + + const mapped = { + answer: buildAnswerFromSearchResults(data.results), + results: mapResults(data.results) + }; + if (options.includeContent) { + const inlineContent = mapInlineContent(data.results); + if (inlineContent.length > 0) mapped.inlineContent = inlineContent; + } + return mapped; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + if (message.toLowerCase().includes("abort")) { + _activity.activityMonitor.logComplete(activityId, 0); + } else { + _activity.activityMonitor.logError(activityId, message); + } + throw err; + } +} /* v9-af927f0725a120a3 */ diff --git a/pip-tmp/jiti/pi-web-access-extract.baf16c13.mjs b/pip-tmp/jiti/pi-web-access-extract.baf16c13.mjs new file mode 100644 index 0000000000000000000000000000000000000000..f59245d1a209ddcdd2b495637830e2bb23c2ebe7 --- /dev/null +++ b/pip-tmp/jiti/pi-web-access-extract.baf16c13.mjs @@ -0,0 +1,641 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.extractContent = extractContent;exports.extractHeadingTitle = extractHeadingTitle;exports.fetchAllContent = fetchAllContent;var _readability = await jitiImport("@mozilla/readability"); +var _linkedom = await jitiImport("linkedom"); +var _turndown = _interopRequireDefault(await jitiImport("turndown")); +var _pLimit = _interopRequireDefault(await jitiImport("p-limit")); +var _activity = await jitiImport("./activity.js"); +var _rscExtract = await jitiImport("./rsc-extract.js"); +var _pdfExtract = await jitiImport("./pdf-extract.js"); +var _githubExtract = await jitiImport("./github-extract.js"); +var _youtubeExtract = await jitiImport("./youtube-extract.js"); +var _geminiUrlContext = await jitiImport("./gemini-url-context.js"); +var _videoExtract = await jitiImport("./video-extract.js"); +var _utils = await jitiImport("./utils.js");function _interopRequireDefault(e) {return e && e.__esModule ? e : { default: e };} + +const DEFAULT_TIMEOUT_MS = 30000; +const CONCURRENT_LIMIT = 3; + +const NON_RECOVERABLE_ERRORS = ["Unsupported content type", "Response too large"]; +const MIN_USEFUL_CONTENT = 500; + +function errorMessage(err) { + return err instanceof Error ? err.message : String(err); +} + +function isConfigParseError(err) { + return errorMessage(err).startsWith("Failed to parse "); +} + +function isAbortError(err) { + return errorMessage(err).toLowerCase().includes("abort"); +} + +function abortedResult(url) { + return { url, title: "", content: "", error: "Aborted" }; +} + +const turndown = new _turndown.default({ + headingStyle: "atx", + codeBlockStyle: "fenced" +}); + +const fetchLimit = (0, _pLimit.default)(CONCURRENT_LIMIT); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +const JINA_READER_BASE = "https://r.jina.ai/"; +const JINA_TIMEOUT_MS = 30000; + +async function extractWithJinaReader( +url, +signal) +{ + const jinaUrl = JINA_READER_BASE + url; + + const activityId = _activity.activityMonitor.logStart({ type: "api", query: `jina: ${url}` }); + + try { + const res = await fetch(jinaUrl, { + headers: { + "Accept": "text/markdown", + "X-No-Cache": "true" + }, + signal: AbortSignal.any([ + AbortSignal.timeout(JINA_TIMEOUT_MS), + ...(signal ? [signal] : [])] + ) + }); + + if (!res.ok) { + _activity.activityMonitor.logComplete(activityId, res.status); + return null; + } + + const content = await res.text(); + _activity.activityMonitor.logComplete(activityId, res.status); + + const contentStart = content.indexOf("Markdown Content:"); + if (contentStart < 0) { + return null; + } + + const markdownPart = content.slice(contentStart + 17).trim(); // 17 = "Markdown Content:".length + + // Check for failed JS rendering or minimal content + if (markdownPart.length < 100 || + markdownPart.startsWith("Loading...") || + markdownPart.startsWith("Please enable JavaScript")) { + return null; + } + + const title = extractHeadingTitle(markdownPart) ?? (new URL(url).pathname.split("/").pop() || url); + return { url, title, content: markdownPart, error: null }; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + if (message.toLowerCase().includes("abort")) { + _activity.activityMonitor.logComplete(activityId, 0); + } else { + _activity.activityMonitor.logError(activityId, message); + } + return null; + } +} + +function parseTimestamp(ts) { + const num = Number(ts); + if (!isNaN(num) && num >= 0) return Math.floor(num); + const parts = ts.split(":").map(Number); + if (parts.some((p) => isNaN(p) || p < 0)) return null; + if (parts.length === 3) return Math.floor(parts[0] * 3600 + parts[1] * 60 + parts[2]); + if (parts.length === 2) return Math.floor(parts[0] * 60 + parts[1]); + return null; +} + + + +function parseTimestampSpec(ts) { + const dashIdx = ts.indexOf("-", 1); + if (dashIdx > 0) { + const start = parseTimestamp(ts.slice(0, dashIdx)); + const end = parseTimestamp(ts.slice(dashIdx + 1)); + if (start !== null && end !== null && end > start) return { type: "range", start, end }; + } + const seconds = parseTimestamp(ts); + return seconds !== null ? { type: "single", seconds } : null; +} + +const DEFAULT_RANGE_FRAMES = 6; +const MIN_FRAME_INTERVAL = 5; + +function computeRangeTimestamps(start, end, maxFrames = DEFAULT_RANGE_FRAMES) { + if (maxFrames <= 1) return [start]; + const duration = end - start; + const idealInterval = duration / (maxFrames - 1); + if (idealInterval < MIN_FRAME_INTERVAL) { + const timestamps = []; + for (let t = start; t <= end && timestamps.length < maxFrames; t += MIN_FRAME_INTERVAL) { + timestamps.push(t); + } + return timestamps; + } + return Array.from({ length: maxFrames }, (_, i) => Math.round(start + i * idealInterval)); +} + +function buildFrameResult( +url, label, requestedCount, +frames, error, duration) +{ + if (frames.length === 0) { + const msg = error ?? "Frame extraction failed"; + return { url, title: `Frames ${label} (0/${requestedCount})`, content: msg, error: msg }; + } + return { + url, + title: `Frames ${label} (${frames.length}/${requestedCount})`, + content: `${frames.length} frames extracted from ${label}`, + error: null, + frames, + duration + }; +} + +async function extractLocalFrames( +filePath, timestamps) +{ + const results = await Promise.all(timestamps.map(async (t) => { + const frame = await (0, _videoExtract.extractVideoFrame)(filePath, t); + if ("error" in frame) return { error: frame.error }; + return { ...frame, timestamp: (0, _utils.formatSeconds)(t) }; + })); + const frames = results.filter((f) => "data" in f); + const firstError = results.find((f) => "error" in f); + return { frames, error: frames.length === 0 && firstError ? firstError.error : null }; +} + +function safeVideoInfo(url) { + try { + return { info: (0, _videoExtract.isVideoFile)(url) }; + } catch (err) { + return { info: null, error: errorMessage(err) }; + } +} + +async function extractContent( +url, +signal, +options) +{ + if (signal?.aborted) { + return { url, title: "", content: "", error: "Aborted" }; + } + + if (options?.frames && !options.timestamp) { + const frameCount = options.frames; + const ytInfo = (0, _youtubeExtract.isYouTubeURL)(url); + if (ytInfo.isYouTube && ytInfo.videoId) { + const streamInfo = await (0, _youtubeExtract.getYouTubeStreamInfo)(ytInfo.videoId); + if ("error" in streamInfo) { + return { url, title: "Frames", content: streamInfo.error, error: streamInfo.error }; + } + if (streamInfo.duration === null) { + const error = "Cannot determine video duration. Use a timestamp range instead."; + return { url, title: "Frames", content: error, error }; + } + const dur = Math.floor(streamInfo.duration); + const timestamps = computeRangeTimestamps(0, dur, frameCount); + const result = await (0, _youtubeExtract.extractYouTubeFrames)(ytInfo.videoId, timestamps, streamInfo); + const label = `${(0, _utils.formatSeconds)(0)}-${(0, _utils.formatSeconds)(dur)}`; + return buildFrameResult(url, label, timestamps.length, result.frames, result.error, streamInfo.duration); + } + + const localVideo = safeVideoInfo(url); + if (localVideo.error) { + return { url, title: "", content: "", error: localVideo.error }; + } + if (localVideo.info) { + const durationResult = await (0, _videoExtract.getLocalVideoDuration)(localVideo.info.absolutePath); + if (typeof durationResult !== "number") { + return { url, title: "Frames", content: durationResult.error, error: durationResult.error }; + } + const dur = Math.floor(durationResult); + const timestamps = computeRangeTimestamps(0, dur, frameCount); + const result = await extractLocalFrames(localVideo.info.absolutePath, timestamps); + const label = `${(0, _utils.formatSeconds)(0)}-${(0, _utils.formatSeconds)(dur)}`; + return buildFrameResult(url, label, timestamps.length, result.frames, result.error, durationResult); + } + + return { url, title: "", content: "", error: "Frame extraction only works with YouTube and local video files" }; + } + + if (options?.timestamp) { + const spec = parseTimestampSpec(options.timestamp); + if (!spec) { + return { + url, + title: "", + content: "", + error: `Invalid timestamp format: "${options.timestamp}". Use "H:MM:SS", "MM:SS", "85", or "start-end".` + }; + } + + const frameCount = options.frames; + const ytInfo = (0, _youtubeExtract.isYouTubeURL)(url); + if (ytInfo.isYouTube && ytInfo.videoId) { + const streamInfo = await (0, _youtubeExtract.getYouTubeStreamInfo)(ytInfo.videoId); + if ("error" in streamInfo) { + if (spec.type === "range") { + const label = `${(0, _utils.formatSeconds)(spec.start)}-${(0, _utils.formatSeconds)(spec.end)}`; + return { url, title: `Frames ${label}`, content: streamInfo.error, error: streamInfo.error }; + } + if (frameCount) { + const end = spec.seconds + (frameCount - 1) * MIN_FRAME_INTERVAL; + const label = `${(0, _utils.formatSeconds)(spec.seconds)}-${(0, _utils.formatSeconds)(end)}`; + return { url, title: `Frames ${label}`, content: streamInfo.error, error: streamInfo.error }; + } + return { url, title: `Frame at ${options.timestamp}`, content: streamInfo.error, error: streamInfo.error }; + } + + if (spec.type === "range") { + const label = `${(0, _utils.formatSeconds)(spec.start)}-${(0, _utils.formatSeconds)(spec.end)}`; + if (streamInfo.duration !== null && spec.end > streamInfo.duration) { + const error = `Timestamp ${(0, _utils.formatSeconds)(spec.end)} exceeds video duration (${(0, _utils.formatSeconds)(Math.floor(streamInfo.duration))})`; + return { url, title: `Frames ${label}`, content: error, error }; + } + const timestamps = frameCount ? + computeRangeTimestamps(spec.start, spec.end, frameCount) : + computeRangeTimestamps(spec.start, spec.end); + const result = await (0, _youtubeExtract.extractYouTubeFrames)(ytInfo.videoId, timestamps, streamInfo); + return buildFrameResult(url, label, timestamps.length, result.frames, result.error, result.duration ?? undefined); + } + + if (frameCount) { + const end = spec.seconds + (frameCount - 1) * MIN_FRAME_INTERVAL; + const label = `${(0, _utils.formatSeconds)(spec.seconds)}-${(0, _utils.formatSeconds)(end)}`; + if (streamInfo.duration !== null && end > streamInfo.duration) { + const error = `Timestamp ${(0, _utils.formatSeconds)(end)} exceeds video duration (${(0, _utils.formatSeconds)(Math.floor(streamInfo.duration))})`; + return { url, title: `Frames ${label}`, content: error, error }; + } + const timestamps = computeRangeTimestamps(spec.seconds, end, frameCount); + const result = await (0, _youtubeExtract.extractYouTubeFrames)(ytInfo.videoId, timestamps, streamInfo); + return buildFrameResult(url, label, timestamps.length, result.frames, result.error, result.duration ?? undefined); + } + + if (streamInfo.duration !== null && spec.seconds > streamInfo.duration) { + const error = `Timestamp ${(0, _utils.formatSeconds)(spec.seconds)} exceeds video duration (${(0, _utils.formatSeconds)(Math.floor(streamInfo.duration))})`; + return { url, title: `Frame at ${options.timestamp}`, content: error, error }; + } + const frame = await (0, _youtubeExtract.extractYouTubeFrame)(ytInfo.videoId, spec.seconds, streamInfo); + if ("error" in frame) { + return { url, title: `Frame at ${options.timestamp}`, content: frame.error, error: frame.error }; + } + return { url, title: `Frame at ${options.timestamp}`, content: `Video frame at ${options.timestamp}`, error: null, thumbnail: frame }; + } + + const localVideo = safeVideoInfo(url); + if (localVideo.error) { + return { url, title: "", content: "", error: localVideo.error }; + } + if (localVideo.info) { + if (spec.type === "range") { + const timestamps = frameCount ? + computeRangeTimestamps(spec.start, spec.end, frameCount) : + computeRangeTimestamps(spec.start, spec.end); + const result = await extractLocalFrames(localVideo.info.absolutePath, timestamps); + const label = `${(0, _utils.formatSeconds)(spec.start)}-${(0, _utils.formatSeconds)(spec.end)}`; + return buildFrameResult(url, label, timestamps.length, result.frames, result.error); + } + + if (frameCount) { + const end = spec.seconds + (frameCount - 1) * MIN_FRAME_INTERVAL; + const timestamps = computeRangeTimestamps(spec.seconds, end, frameCount); + const result = await extractLocalFrames(localVideo.info.absolutePath, timestamps); + const label = `${(0, _utils.formatSeconds)(spec.seconds)}-${(0, _utils.formatSeconds)(end)}`; + return buildFrameResult(url, label, timestamps.length, result.frames, result.error); + } + + const frame = await (0, _videoExtract.extractVideoFrame)(localVideo.info.absolutePath, spec.seconds); + if ("error" in frame) { + return { url, title: `Frame at ${options.timestamp}`, content: frame.error, error: frame.error }; + } + return { url, title: `Frame at ${options.timestamp}`, content: `Video frame at ${options.timestamp}`, error: null, thumbnail: frame }; + } + + return { url, title: "", content: "", error: "Timestamp extraction only works with YouTube and local video files" }; + } + + const localVideo = safeVideoInfo(url); + if (localVideo.error) { + return { url, title: "", content: "", error: localVideo.error }; + } + if (localVideo.info) { + try { + const result = await (0, _videoExtract.extractVideo)(localVideo.info, signal, options); + if (signal?.aborted) return abortedResult(url); + return result ?? { url, title: "", content: "", error: "Video analysis requires Gemini access. Either:\n 1. Sign into gemini.google.com in Chrome (free, uses cookies)\n 2. Set GEMINI_API_KEY in ~/.pi/web-search.json" }; + } catch (err) { + if (isAbortError(err)) return abortedResult(url); + return { url, title: "", content: "", error: errorMessage(err) }; + } + } + + try { + new URL(url); + } catch { + return { url, title: "", content: "", error: "Invalid URL" }; + } + + try { + const ghResult = await (0, _githubExtract.extractGitHub)(url, signal, options?.forceClone); + if (ghResult) return ghResult; + if (signal?.aborted) return abortedResult(url); + } catch (err) { + const message = errorMessage(err); + if (isAbortError(err)) return abortedResult(url); + if (isConfigParseError(err)) { + return { url, title: "", content: "", error: message }; + } + } + + const ytInfo = (0, _youtubeExtract.isYouTubeURL)(url); + let youtubeEnabled = false; + try { + youtubeEnabled = (0, _youtubeExtract.isYouTubeEnabled)(); + } catch (err) { + return { url, title: "", content: "", error: errorMessage(err) }; + } + if (ytInfo.isYouTube && youtubeEnabled) { + try { + const ytResult = await (0, _youtubeExtract.extractYouTube)(url, signal, options?.prompt, options?.model); + if (ytResult) return ytResult; + if (signal?.aborted) return abortedResult(url); + } catch (err) { + const message = errorMessage(err); + if (isAbortError(err)) return abortedResult(url); + if (isConfigParseError(err)) { + return { url, title: "", content: "", error: message }; + } + } + return { + url, + title: "", + content: "", + error: "Could not extract YouTube video content. Sign into Google in Chrome for automatic access, or set GEMINI_API_KEY." + }; + } + + if (signal?.aborted) return abortedResult(url); + + const httpResult = await extractViaHttp(url, signal, options); + + if (signal?.aborted) return abortedResult(url); + if (!httpResult.error) return httpResult; + if (NON_RECOVERABLE_ERRORS.some((prefix) => httpResult.error.startsWith(prefix))) return httpResult; + + const jinaResult = await extractWithJinaReader(url, signal); + if (jinaResult) return jinaResult; + if (signal?.aborted) return abortedResult(url); + + let geminiResult = null; + try { + geminiResult = (await (0, _geminiUrlContext.extractWithUrlContext)(url, signal)) ?? ( + await (0, _geminiUrlContext.extractWithGeminiWeb)(url, signal)); + } catch (err) { + if (isAbortError(err)) return abortedResult(url); + if (isConfigParseError(err)) { + return { ...httpResult, error: errorMessage(err) }; + } + } + + if (geminiResult) return geminiResult; + if (signal?.aborted) return abortedResult(url); + + const guidance = [ + httpResult.error, + "", + "Fallback options:", + " \u2022 Set GEMINI_API_KEY in ~/.pi/web-search.json", + " \u2022 Sign into gemini.google.com in Chrome", + " \u2022 Use web_search to find content about this topic"]. + join("\n"); + return { ...httpResult, error: guidance }; +} + +function isLikelyJSRendered(html) { + // Extract body content + const bodyMatch = html.match(/]*>([\s\S]*?)<\/body>/i); + if (!bodyMatch) return false; + + const bodyHtml = bodyMatch[1]; + + // Strip tags to get text content + const textContent = bodyHtml. + replace(//gi, ""). + replace(//gi, ""). + replace(/<[^>]+>/g, ""). + replace(/\s+/g, " "). + trim(); + + // Count scripts + const scriptCount = (html.match(/ + +`; + const win = open(shellHTML, { + width: 800, + height: 900, + title + }); + + let maxHeight = 1200; + win.on("ready", (info) => { + const visibleHeight = info?.screen?.visibleHeight; + if (typeof visibleHeight === "number" && visibleHeight > 0) { + maxHeight = Math.floor(visibleHeight * 0.85); + } + }); + win.on("message", (data) => { + if (!data || typeof data !== "object") return; + const msg = data; + if (msg.type !== "resize" || typeof msg.height !== "number") return; + const clamped = Math.max(400, Math.min(Math.round(msg.height), maxHeight)); + win._write({ type: "resize", width: 800, height: clamped }); + }); + + return win; +} + +function extractDomain(url) { + try {return new URL(url).hostname;} + catch {return url;} +} + +function updateWidget(ctx) { + const theme = ctx.ui.theme; + const entries = _activity.activityMonitor.getEntries(); + const lines = []; + + lines.push(theme.fg("accent", "─── Web Search Activity " + "─".repeat(36))); + + if (entries.length === 0) { + lines.push(theme.fg("muted", " No activity yet")); + } else { + for (const e of entries) { + lines.push(" " + formatEntryLine(e, theme)); + } + } + + lines.push(theme.fg("accent", "─".repeat(60))); + + const rateInfo = _activity.activityMonitor.getRateLimitInfo(); + const resetMs = rateInfo.oldestTimestamp ? Math.max(0, rateInfo.oldestTimestamp + rateInfo.windowMs - Date.now()) : 0; + const resetSec = Math.ceil(resetMs / 1000); + lines.push( + theme.fg("muted", `Rate: ${rateInfo.used}/${rateInfo.max}`) + ( + resetMs > 0 ? theme.fg("dim", ` (resets in ${resetSec}s)`) : "") + ); + + ctx.ui.setWidget("web-activity", new _piTui.Text(lines.join("\n"), 0, 0)); +} + +function formatEntryLine( +entry, +theme) +{ + const typeStr = entry.type === "api" ? "API" : "GET"; + const target = + entry.type === "api" ? + `"${(0, _piTui.truncateToWidth)(entry.query || "", 28, "")}"` : + (0, _piTui.truncateToWidth)(entry.url?.replace(/^https?:\/\//, "") || "", 30, ""); + + const duration = entry.endTime ? + `${((entry.endTime - entry.startTime) / 1000).toFixed(1)}s` : + `${((Date.now() - entry.startTime) / 1000).toFixed(1)}s`; + + let statusStr; + let indicator; + if (entry.error) { + statusStr = "err"; + indicator = theme.fg("error", "✗"); + } else if (entry.status === null) { + statusStr = "..."; + indicator = theme.fg("warning", "⋯"); + } else if (entry.status === 0) { + statusStr = "abort"; + indicator = theme.fg("muted", "○"); + } else { + statusStr = String(entry.status); + indicator = entry.status >= 200 && entry.status < 300 ? theme.fg("success", "✓") : theme.fg("error", "✗"); + } + + return `${typeStr.padEnd(4)} ${target.padEnd(32)} ${statusStr.padStart(5)} ${duration.padStart(5)} ${indicator}`; +} + +function handleSessionChange(ctx) { + abortPendingFetches(); + closeCurator(); + (0, _githubExtract.clearCloneCache)(); + sessionActive = true; + (0, _storage.restoreFromSession)(ctx); + // Unsubscribe before clear() to avoid callback with stale ctx + widgetUnsubscribe?.(); + widgetUnsubscribe = null; + _activity.activityMonitor.clear(); + if (widgetVisible) { + // Re-subscribe with new ctx + widgetUnsubscribe = _activity.activityMonitor.onUpdate(() => updateWidget(ctx)); + updateWidget(ctx); + } +} + +function _default(pi) { + const initConfig = loadConfigForExtensionInit(); + const curateKey = initConfig.shortcuts?.curate || DEFAULT_SHORTCUTS.curate; + const activityKey = initConfig.shortcuts?.activity || DEFAULT_SHORTCUTS.activity; + + function startBackgroundFetch(urls) { + if (urls.length === 0) return null; + const fetchId = (0, _storage.generateId)(); + const controller = new AbortController(); + pendingFetches.set(fetchId, controller); + (0, _extract.fetchAllContent)(urls, controller.signal). + then((fetched) => { + if (!sessionActive || !pendingFetches.has(fetchId)) return; + const data = { + id: fetchId, + type: "fetch", + timestamp: Date.now(), + urls: stripThumbnails(fetched) + }; + (0, _storage.storeResult)(fetchId, data); + pi.appendEntry("web-search-results", data); + const ok = fetched.filter((f) => !f.error).length; + pi.sendMessage( + { + customType: "web-search-content-ready", + content: `Content fetched for ${ok}/${fetched.length} URLs [${fetchId}]. Full page content now available.`, + display: true + }, + { triggerTurn: true } + ); + }). + catch((err) => { + if (!sessionActive || !pendingFetches.has(fetchId)) return; + const message = err instanceof Error ? err.message : String(err); + const isAbort = err instanceof Error && err.name === "AbortError" || message.toLowerCase().includes("abort"); + if (!isAbort) { + pi.sendMessage( + { + customType: "web-search-error", + content: `Content fetch failed [${fetchId}]: ${message}`, + display: true + }, + { triggerTurn: false } + ); + } + }). + finally(() => {pendingFetches.delete(fetchId);}); + return fetchId; + } + + function storeAndPublishSearch(results) { + const id = (0, _storage.generateId)(); + const data = { + id, type: "search", timestamp: Date.now(), queries: results + }; + (0, _storage.storeResult)(id, data); + pi.appendEntry("web-search-results", data); + return id; + } + + + + + + + + + + + + + + + function normalizeSummaryMeta(meta, summaryText) { + const normalizedText = summaryText.trim(); + if (!meta) { + return { + model: null, + durationMs: 0, + tokenEstimate: normalizedText.length > 0 ? Math.max(1, Math.ceil(normalizedText.length / 4)) : 0, + fallbackUsed: false, + edited: false + }; + } + + return { + model: meta.model, + durationMs: Number.isFinite(meta.durationMs) && meta.durationMs >= 0 ? meta.durationMs : 0, + tokenEstimate: Number.isFinite(meta.tokenEstimate) && meta.tokenEstimate >= 0 ? + meta.tokenEstimate : + normalizedText.length > 0 ? Math.max(1, Math.ceil(normalizedText.length / 4)) : 0, + fallbackUsed: meta.fallbackUsed === true, + fallbackReason: meta.fallbackReason, + edited: meta.edited === true + }; + } + + function buildCurationCancelledReturn(reason) { + const message = `Search curation cancelled (${reason}).`; + return { + content: [{ type: "text", text: message }], + details: { + error: message, + cancelled: true, + cancelReason: reason + } + }; + } + + async function resolveFirstAvailableModel( + ctx, + candidates) + { + for (const { provider, id } of candidates) { + const model = (0, _piAi.getModel)(provider, id); + if (!model) continue; + const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model); + if (auth.ok && auth.apiKey) return { model, apiKey: auth.apiKey, headers: auth.headers }; + } + throw new Error(`No model available: ${candidates.map((c) => `${c.provider}/${c.id}`).join(", ")}`); + } + + async function rewriteSearchQuery(query, ctx, signal) { + const { model, apiKey, headers } = await resolveFirstAvailableModel(ctx, [ + { provider: "anthropic", id: "claude-haiku-4-5" }, + { provider: "google", id: "gemini-2.5-flash" }, + { provider: "openai", id: "gpt-4.1-mini" }] + ); + const response = await (0, _piAi.complete)( + model, + { + messages: [{ + role: "user", + content: [{ type: "text", text: `Rewrite this web search query to get better, more specific results. Add relevant year qualifiers, precise technical terms, and specificity. Return ONLY the improved query text, nothing else.\n\nQuery: ${query}` }], + timestamp: Date.now() + }] + }, + { apiKey, headers, signal } + ); + if (response.stopReason === "aborted") throw new Error("Aborted"); + const contentParts = Array.isArray(response.content) ? response.content : []; + const text = contentParts. + map((p) => { + if (!p || typeof p !== "object") return ""; + const part = p; + return typeof part.text === "string" ? part.text : ""; + }). + join(""). + trim(); + if (!text) throw new Error("Rewrite returned empty response"); + return text; + } + + async function generateSummaryForSelectedIndices( + selectedQueryIndices, + resultsByIndex, + summaryContext, + signal, + modelOverride, + feedback) + { + const selectedResults = []; + for (const qi of selectedQueryIndices) { + const result = resultsByIndex.get(qi); + if (result) selectedResults.push(result); + } + if (selectedResults.length === 0) { + throw new Error("No selected results available for summary generation"); + } + try { + return await (0, _summaryReview.generateSummaryDraft)(selectedResults, summaryContext, signal, modelOverride, feedback); + } catch (err) { + const isEmptyResponse = err instanceof Error && err.message.includes("Summary model returned empty response"); + if (!isEmptyResponse) throw err; + const deterministic = (0, _summaryReview.buildDeterministicSummary)(selectedResults); + return { + summary: deterministic.summary, + meta: { + ...deterministic.meta, + fallbackReason: "summary-model-empty-response" + } + }; + } + } + + async function loadSummaryModelChoices( + summaryContext) + { + const summaryModels = []; + const seen = new Set(); + const availableValues = new Set(); + + const addModel = (provider, id) => { + const value = `${provider}/${id}`; + if (seen.has(value)) return; + seen.add(value); + summaryModels.push({ value, label: value }); + }; + + try { + const availableModels = summaryContext.modelRegistry.getAvailable(); + for (const model of availableModels) { + const value = `${model.provider}/${model.id}`; + availableValues.add(value); + addModel(model.provider, model.id); + } + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error(`Failed to load summary models: ${message}`); + } + + const currentModelValue = summaryContext.model ? + `${summaryContext.model.provider}/${summaryContext.model.id}` : + null; + if (summaryContext.model && currentModelValue && !seen.has(currentModelValue)) { + addModel(summaryContext.model.provider, summaryContext.model.id); + } + + const config = loadConfig(); + const configuredSummaryModel = typeof config.summaryModel === "string" ? config.summaryModel.trim() : ""; + const preferredDefaults = [ + "anthropic/claude-haiku-4-5", + "openai-codex/gpt-5.3-codex-spark"]; + + + let defaultSummaryModel = null; + if (configuredSummaryModel.length > 0 && availableValues.has(configuredSummaryModel)) { + defaultSummaryModel = configuredSummaryModel; + } + if (!defaultSummaryModel) { + for (const preferred of preferredDefaults) { + if (availableValues.has(preferred)) { + defaultSummaryModel = preferred; + break; + } + } + } + if (!defaultSummaryModel && summaryModels.length > 0) { + defaultSummaryModel = summaryModels[0].value; + } + + return { summaryModels, defaultSummaryModel }; + } + + function resolveSummaryForSubmit( + payload, + resultsByIndex) + { + const submittedSummary = typeof payload.summary === "string" ? payload.summary.trim() : ""; + if (submittedSummary.length > 0) { + return { + approvedSummary: submittedSummary, + summaryMeta: normalizeSummaryMeta(payload.summaryMeta, submittedSummary) + }; + } + + const selected = filterByQueryIndices(payload.selectedQueryIndices, resultsByIndex).results; + const fallbackResults = selected.length > 0 ? selected : [...resultsByIndex.values()]; + const deterministic = (0, _summaryReview.buildDeterministicSummary)(fallbackResults); + return { + approvedSummary: deterministic.summary, + summaryMeta: deterministic.meta + }; + } + + function buildSearchReturn(opts) { + const sc = opts.results.filter((r) => !r.error).length; + const tr = opts.results.reduce((sum, r) => sum + r.results.length, 0); + + const hasApprovedSummary = typeof opts.approvedSummary === "string" && opts.approvedSummary.trim().length > 0; + let output = ""; + if (hasApprovedSummary) { + output = opts.approvedSummary.trim(); + } else { + if (opts.curated) { + output += "[These results were manually curated by the user in the browser. Use them as-is — do not re-search or discard.]\n\n"; + } + const duplicateQueries = opts.curated ? duplicateQuerySet(opts.results) : new Set(); + for (const { query, answer, results, error, provider } of opts.results) { + if (opts.queryList.length > 1) { + output += opts.curated ? + formatQueryHeader(query, provider, duplicateQueries) : + `## Query: "${query}"\n\n`; + } + if (error) output += `Error: ${error}\n\n`;else + if (results.length === 0) output += "No results found.\n\n";else + output += formatSearchSummary(results, answer) + "\n\n"; + } + } + + const hasInlineReady = hasFullInlineCoverage(opts.urls, opts.inlineContent); + let fetchId = null; + if (hasInlineReady && opts.inlineContent) { + fetchId = (0, _storage.generateId)(); + const data = { + id: fetchId, + type: "fetch", + timestamp: Date.now(), + urls: opts.inlineContent + }; + (0, _storage.storeResult)(fetchId, data); + pi.appendEntry("web-search-results", data); + if (!hasApprovedSummary) { + output += `---\nFull content for ${opts.inlineContent.length} sources available [${fetchId}].`; + } + } else if (opts.includeContent) { + fetchId = startBackgroundFetch(opts.urls); + if (fetchId && !hasApprovedSummary) { + output += `---\nContent fetching in background [${fetchId}]. Will notify when ready.`; + } + } + + const searchId = storeAndPublishSearch(opts.results); + const isBackgroundFetch = fetchId !== null && !hasInlineReady; + + return { + content: [{ type: "text", text: output.trim() }], + details: { + queries: opts.queryList, + queryCount: opts.queryList.length, + successfulQueries: sc, + totalResults: tr, + includeContent: opts.includeContent, + fetchId, + fetchUrls: isBackgroundFetch ? opts.urls : undefined, + searchId, + ...(opts.curated ? { + curated: true, + curatedFrom: opts.curatedFrom, + curatedQueries: opts.results.map((r) => ({ + query: r.query, + provider: r.provider || null, + answer: r.answer || null, + sources: r.results.map((s) => ({ title: s.title, url: s.url })), + error: r.error + })) + } : {}), + ...(opts.workflow && hasApprovedSummary ? + { + summary: { + text: opts.approvedSummary.trim(), + workflow: opts.workflow, + model: opts.summaryMeta?.model ?? null, + durationMs: opts.summaryMeta?.durationMs ?? 0, + tokenEstimate: opts.summaryMeta?.tokenEstimate ?? 0, + fallbackUsed: opts.summaryMeta?.fallbackUsed === true, + fallbackReason: opts.summaryMeta?.fallbackReason, + edited: opts.summaryMeta?.edited === true + } + } : + {}) + } + }; + } + + function filterByQueryIndices(selectedQueryIndices, results) { + const filteredResults = []; + const filteredUrls = []; + for (const qi of selectedQueryIndices) { + const r = results.get(qi); + if (r) { + filteredResults.push(r); + for (const res of r.results) { + if (!filteredUrls.includes(res.url)) filteredUrls.push(res.url); + } + } + } + return { results: filteredResults, urls: filteredUrls }; + } + + function collectAllResultsAndUrls(resultsByIndex) { + const results = [...resultsByIndex.values()]; + const urls = []; + for (const result of results) { + for (const source of result.results) { + if (!urls.includes(source.url)) urls.push(source.url); + } + } + return { results, urls }; + } + + async function openCuratorBrowser(pc, searchesComplete = true) { + let handle = null; + try { + pc.phase = "curating"; + + const searchAbort = new AbortController(); + const addSearchSignal = pc.signal ? + AbortSignal.any([pc.signal, searchAbort.signal]) : + searchAbort.signal; + + const sessionToken = (0, _nodeCrypto.randomUUID)(); + handle = await (0, _curatorServer.startCuratorServer)( + { + queries: pc.queryList, + sessionToken, + timeout: pc.timeoutSeconds, + availableProviders: pc.availableProviders, + defaultProvider: pc.defaultProvider, + summaryModels: pc.summaryModels, + defaultSummaryModel: pc.defaultSummaryModel + }, + { + async onSummarize(selectedQueryIndices, summarizeSignal, model, feedback) { + if (pendingCurate !== pc) throw new Error("Curator session is no longer active."); + pc.onUpdate?.({ + content: [{ type: "text", text: "Generating summary draft..." }], + details: { phase: "generating-summary", progress: 0.9 } + }); + const draft = await generateSummaryForSelectedIndices( + selectedQueryIndices, + pc.searchResults, + pc.summaryContext, + summarizeSignal, + model, + feedback + ); + if (pendingCurate !== pc) throw new Error("Curator session is no longer active."); + pc.onUpdate?.({ + content: [{ type: "text", text: "Summary draft ready — waiting for approval..." }], + details: { phase: "waiting-for-approval", progress: 1 } + }); + return draft; + }, + onSubmit(payload) { + if (pendingCurate !== pc) return; + searchAbort.abort(); + const filtered = payload.selectedQueryIndices.length > 0 ? + filterByQueryIndices(payload.selectedQueryIndices, pc.searchResults) : + collectAllResultsAndUrls(pc.searchResults); + const filteredInline = pc.allInlineContent.filter((c) => filtered.urls.includes(c.url)); + const base = { + queryList: filtered.results.map((r) => r.query), + results: filtered.results, + urls: filtered.urls, + includeContent: pc.includeContent, + inlineContent: filteredInline.length > 0 ? filteredInline : undefined, + curated: true, + curatedFrom: pc.searchResults.size + }; + if (!payload.rawResults) { + const resolvedSummary = resolveSummaryForSubmit(payload, pc.searchResults); + base.workflow = pc.workflow; + base.approvedSummary = resolvedSummary.approvedSummary; + base.summaryMeta = resolvedSummary.summaryMeta; + } + pc.finish(buildSearchReturn(base)); + closeCurator(); + }, + onCancel(reason) { + if (pendingCurate !== pc) return; + searchAbort.abort(); + if (reason === "timeout") { + const resolvedSummary = resolveSummaryForSubmit({ selectedQueryIndices: [], summary: undefined, summaryMeta: undefined }, pc.searchResults); + const all = collectAllResultsAndUrls(pc.searchResults); + const filteredInline = pc.allInlineContent.filter((c) => all.urls.includes(c.url)); + pc.finish(buildSearchReturn({ + queryList: all.results.map((r) => r.query), + results: all.results, + urls: all.urls, + includeContent: pc.includeContent, + inlineContent: filteredInline.length > 0 ? filteredInline : undefined, + curated: true, + curatedFrom: pc.searchResults.size, + workflow: pc.workflow, + approvedSummary: resolvedSummary.approvedSummary, + summaryMeta: resolvedSummary.summaryMeta + })); + } else { + pc.finish(buildCurationCancelledReturn(reason)); + } + closeCurator(); + }, + onProviderChange(provider) { + if (pendingCurate !== pc) return; + const normalized = normalizeProviderInput(provider); + if (!normalized || normalized === "auto") return; + pc.defaultProvider = normalized; + try { + saveConfig({ provider: normalized }); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error(`Failed to persist default provider: ${message}`); + } + }, + async onAddSearch(query, queryIndex, provider) { + if (pendingCurate !== pc) throw new Error("Curator session is no longer active."); + const normalizedProvider = normalizeProviderInput(provider); + const requestedProvider = !normalizedProvider || normalizedProvider === "auto" ? + pc.defaultProvider : + normalizedProvider; + try { + const { answer, results, inlineContent, provider: actualProvider } = await (0, _geminiSearch.search)(query, { + provider: requestedProvider, + numResults: pc.numResults, + recencyFilter: pc.recencyFilter, + domainFilter: pc.domainFilter, + includeContent: pc.includeContent, + signal: addSearchSignal + }); + if (pendingCurate !== pc) throw new Error("Curator session is no longer active."); + pc.searchResults.set(queryIndex, { query, answer, results, error: null, provider: actualProvider }); + if (inlineContent) pc.allInlineContent.push(...inlineContent); + return { + answer, + results: results.map((r) => ({ title: r.title, url: r.url, domain: extractDomain(r.url) })), + provider: actualProvider + }; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + if (pendingCurate === pc) { + pc.searchResults.set(queryIndex, { query, answer: "", results: [], error: message, provider: requestedProvider }); + } + throw err; + } + }, + async onRewriteQuery(query, rewriteSignal) { + if (pendingCurate !== pc) throw new Error("Curator session is no longer active."); + return rewriteSearchQuery(query, pc.summaryContext, rewriteSignal); + } + } + ); + + if (pendingCurate !== pc) { + handle.close(); + return; + } + + activeCurator = handle; + + for (const [qi, data] of pc.searchResults) { + if (data.error) { + handle.pushError(qi, data.error, data.provider); + } else { + handle.pushResult(qi, { + answer: data.answer, + results: data.results.map((r) => ({ title: r.title, url: r.url, domain: extractDomain(r.url) })), + provider: data.provider || pc.defaultProvider + }); + } + } + if (searchesComplete) handle.searchesDone(); + + pc.onUpdate?.({ + content: [{ type: "text", text: searchesComplete ? "Waiting for summary approval in browser..." : "Searches streaming to browser..." }], + details: { phase: "curating", progress: searchesComplete ? 1 : 0.5 } + }); + + const open = (0, _nodeOs.platform)() === "darwin" ? await getGlimpseOpen() : null; + if (open) { + try { + const win = openInGlimpse(open, handle.url, "Search Curator"); + glimpseWin = win; + win.on("closed", () => { + if (glimpseWin === win) { + glimpseWin = null; + closeCurator(); + } + }); + return; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error(`Failed to open Glimpse curator window: ${message}`); + glimpseWin = null; + } + } + await openInBrowser(pi, handle.url); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error(`Failed to open curator UI: ${message}`); + if (pendingCurate === pc || handle && activeCurator === handle) { + closeCurator(); + } + } + } + + pi.registerShortcut(curateKey, { + description: "Review search results", + handler: async (ctx) => { + if (!pendingCurate) return; + + if (pendingCurate.phase === "searching") { + pendingCurate.browserPromise = openCuratorBrowser(pendingCurate, false); + ctx.ui.notify("Opening curator — remaining searches will stream in", "info"); + return; + } + } + }); + + pi.registerShortcut(activityKey, { + description: "Toggle web search activity", + handler: async (ctx) => { + widgetVisible = !widgetVisible; + if (widgetVisible) { + widgetUnsubscribe = _activity.activityMonitor.onUpdate(() => updateWidget(ctx)); + updateWidget(ctx); + } else { + widgetUnsubscribe?.(); + widgetUnsubscribe = null; + ctx.ui.setWidget("web-activity", null); + } + } + }); + + pi.on("session_start", async (_event, ctx) => handleSessionChange(ctx)); + pi.on("session_tree", async (_event, ctx) => handleSessionChange(ctx)); + + pi.on("session_shutdown", () => { + sessionActive = false; + abortPendingFetches(); + closeCurator(); + (0, _githubExtract.clearCloneCache)(); + (0, _storage.clearResults)(); + // Unsubscribe before clear() to avoid callback with stale ctx + widgetUnsubscribe?.(); + widgetUnsubscribe = null; + _activity.activityMonitor.clear(); + widgetVisible = false; + }); + + pi.registerTool({ + name: "web_search", + label: "Web Search", + description: + `Search the web using Perplexity AI, Exa, or Gemini. Returns an AI-synthesized answer with source citations. For comprehensive research, prefer queries (plural) with 2-4 varied angles over a single query — each query gets its own synthesized answer, so varying phrasing and scope gives much broader coverage. When includeContent is true, full page content is fetched in the background. Searches auto-open the interactive browser curator and stream results live; set workflow to "none" to skip curation. Provider auto-selects: Exa (direct API with key, MCP fallback without), else Perplexity (needs key), else Gemini API (needs key), else Gemini Web (needs a supported Chromium-based browser login).`, + promptSnippet: + "Use for web research questions. Prefer {queries:[...]} with 2-4 varied angles over a single query for broader coverage.", + parameters: _typebox.Type.Object({ + query: _typebox.Type.Optional(_typebox.Type.String({ description: "Single search query. For research tasks, prefer 'queries' with multiple varied angles instead." })), + queries: _typebox.Type.Optional(_typebox.Type.Array(_typebox.Type.String(), { description: "Multiple queries searched in sequence, each returning its own synthesized answer. Prefer this for research — vary phrasing, scope, and angle across 2-4 queries to maximize coverage. Good: ['React vs Vue performance benchmarks 2026', 'React vs Vue developer experience comparison', 'React ecosystem size vs Vue ecosystem']. Bad: ['React vs Vue', 'React vs Vue comparison', 'React vs Vue review'] (too similar, redundant results)." })), + numResults: _typebox.Type.Optional(_typebox.Type.Number({ description: "Results per query (default: 5, max: 20)" })), + includeContent: _typebox.Type.Optional(_typebox.Type.Boolean({ description: "Fetch full page content (async)" })), + recencyFilter: _typebox.Type.Optional( + (0, _piAi.StringEnum)(["day", "week", "month", "year"], { description: "Filter by recency" }) + ), + domainFilter: _typebox.Type.Optional(_typebox.Type.Array(_typebox.Type.String(), { description: "Limit to domains (prefix with - to exclude)" })), + provider: _typebox.Type.Optional( + (0, _piAi.StringEnum)(["auto", "perplexity", "gemini", "exa"], { description: "Search provider (default: auto)" }) + ), + workflow: _typebox.Type.Optional( + (0, _piAi.StringEnum)(["none", "summary-review"], { + description: "Search workflow mode: none = no curator, summary-review = open curator with auto summary draft (default)" + }) + ) + }), + + async execute(_toolCallId, params, signal, onUpdate, ctx) { + const rawQueryList = Array.isArray(params.queries) ? + params.queries : + params.query !== undefined ? [params.query] : []; + const queryList = normalizeQueryList(rawQueryList); + const configWorkflow = loadConfigForExtensionInit().workflow; + const workflow = resolveWorkflow(params.workflow ?? configWorkflow, ctx?.hasUI !== false); + const shouldCurate = workflow !== "none"; + + if (queryList.length === 0) { + return { + content: [{ type: "text", text: "Error: No query provided. Use 'query' or 'queries' parameter." }], + details: { error: "No query provided" } + }; + } + + if (shouldCurate && !ctx) { + return { + content: [{ type: "text", text: "Error: Curation requires an active extension context." }], + details: { error: "Missing extension context" } + }; + } + + if (shouldCurate) { + closeCurator(); + + let resolvePromise = () => {}; + const promise = new Promise((resolve) => { + resolvePromise = resolve; + }); + const includeContent = params.includeContent ?? false; + const searchResults = new Map(); + const allInlineContent = []; + const searchAbort = new AbortController(); + const searchSignal = signal ? + AbortSignal.any([signal, searchAbort.signal]) : + searchAbort.signal; + let cancelled = false; + + const bootstrap = await loadCuratorBootstrap(params.provider); + const availableProviders = bootstrap.availableProviders; + const defaultProvider = bootstrap.defaultProvider; + const curatorTimeoutSeconds = bootstrap.timeoutSeconds; + const curatorWorkflow = "summary-review"; + + const summaryContext = { + model: ctx.model, + modelRegistry: ctx.modelRegistry + }; + const summaryModelChoices = await loadSummaryModelChoices(summaryContext); + + const pc = { + phase: "searching", + workflow: curatorWorkflow, + summaryContext, + searchResults, + allInlineContent, + queryList, + includeContent, + numResults: params.numResults, + recencyFilter: params.recencyFilter, + domainFilter: params.domainFilter, + availableProviders, + defaultProvider, + summaryModels: summaryModelChoices.summaryModels, + defaultSummaryModel: summaryModelChoices.defaultSummaryModel, + timeoutSeconds: curatorTimeoutSeconds, + onUpdate: onUpdate, + signal, + abortSearches: () => { + if (!searchAbort.signal.aborted) searchAbort.abort(); + }, + finish: () => {}, + cancel: () => {} + }; + + const finish = (value) => { + if (cancelled) return; + cancelled = true; + pc.abortSearches(); + signal?.removeEventListener("abort", onAbort); + pendingCurate = null; + resolvePromise(value); + }; + + const cancel = (reason = "stale") => { + if (cancelled) return; + finish(buildCurationCancelledReturn(reason)); + }; + + pc.finish = finish; + pc.cancel = cancel; + + const onAbort = () => closeCurator(); + pendingCurate = pc; + signal?.addEventListener("abort", onAbort, { once: true }); + pc.browserPromise = openCuratorBrowser(pc, false); + + for (let qi = 0; qi < queryList.length; qi++) { + if (signal?.aborted || cancelled || searchAbort.signal.aborted) break; + onUpdate?.({ + content: [{ type: "text", text: `Searching ${qi + 1}/${queryList.length}: "${queryList[qi]}"...` }], + details: { phase: "searching", progress: qi / queryList.length, currentQuery: queryList[qi] } + }); + const requestedProvider = pc.defaultProvider; + try { + const { answer, results, inlineContent, provider } = await (0, _geminiSearch.search)(queryList[qi], { + provider: requestedProvider, + numResults: params.numResults, + recencyFilter: params.recencyFilter, + domainFilter: params.domainFilter, + includeContent: params.includeContent, + signal: searchSignal + }); + if (signal?.aborted || cancelled || searchAbort.signal.aborted) break; + searchResults.set(qi, { query: queryList[qi], answer, results, error: null, provider }); + if (inlineContent) allInlineContent.push(...inlineContent); + if (activeCurator) { + activeCurator.pushResult(qi, { + answer, + results: results.map((r) => ({ title: r.title, url: r.url, domain: extractDomain(r.url) })), + provider + }); + } + } catch (err) { + if (signal?.aborted || cancelled || searchAbort.signal.aborted) break; + const message = err instanceof Error ? err.message : String(err); + searchResults.set(qi, { query: queryList[qi], answer: "", results: [], error: message, provider: requestedProvider }); + if (activeCurator) { + activeCurator.pushError(qi, message, requestedProvider); + } + } + } + + if (signal?.aborted || cancelled || searchAbort.signal.aborted) { + cancel(); + return promise; + } + + await pc.browserPromise; + if (activeCurator && !cancelled) { + activeCurator.searchesDone(); + pc.onUpdate?.({ + content: [{ type: "text", text: "All searches complete — waiting for summary approval in browser..." }], + details: { phase: "curating", progress: 1 } + }); + } + + return promise; + } + + const searchResults = []; + const allUrls = []; + const allInlineContent = []; + const resolvedProvider = normalizeProviderInput(params.provider ?? loadConfig().provider); + + for (let i = 0; i < queryList.length; i++) { + const query = queryList[i]; + + onUpdate?.({ + content: [{ type: "text", text: `Searching ${i + 1}/${queryList.length}: "${query}"...` }], + details: { phase: "search", progress: i / queryList.length, currentQuery: query } + }); + + try { + const { answer, results, inlineContent, provider } = await (0, _geminiSearch.search)(query, { + provider: resolvedProvider, + numResults: params.numResults, + recencyFilter: params.recencyFilter, + domainFilter: params.domainFilter, + includeContent: params.includeContent, + signal + }); + + searchResults.push({ query, answer, results, error: null, provider }); + for (const r of results) { + if (!allUrls.includes(r.url)) { + allUrls.push(r.url); + } + } + if (inlineContent) allInlineContent.push(...inlineContent); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + const requestedProvider = typeof resolvedProvider === "string" && resolvedProvider !== "auto" ? + resolvedProvider : + undefined; + searchResults.push({ query, answer: "", results: [], error: message, provider: requestedProvider }); + } + } + + return buildSearchReturn({ + queryList, + results: searchResults, + urls: allUrls, + includeContent: params.includeContent ?? false, + inlineContent: allInlineContent.length > 0 ? allInlineContent : undefined + }); + }, + + renderCall(args, theme) { + const input = args; + const rawQueryList = Array.isArray(input.queries) ? + input.queries : + input.query !== undefined ? [input.query] : []; + const queryList = normalizeQueryList(rawQueryList); + if (queryList.length === 0) { + return new _piTui.Text(theme.fg("toolTitle", theme.bold("search ")) + theme.fg("error", "(no query)"), 0, 0); + } + if (queryList.length === 1) { + const q = queryList[0]; + const display = q.length > 60 ? q.slice(0, 57) + "..." : q; + return new _piTui.Text(theme.fg("toolTitle", theme.bold("search ")) + theme.fg("accent", `"${display}"`), 0, 0); + } + const lines = [theme.fg("toolTitle", theme.bold("search ")) + theme.fg("accent", `${queryList.length} queries`)]; + for (const q of queryList.slice(0, 5)) { + const display = q.length > 50 ? q.slice(0, 47) + "..." : q; + lines.push(theme.fg("muted", ` "${display}"`)); + } + if (queryList.length > 5) { + lines.push(theme.fg("muted", ` ... and ${queryList.length - 5} more`)); + } + return new _piTui.Text(lines.join("\n"), 0, 0); + }, + + renderResult(result, { expanded, isPartial }, theme) { + + + + + + + + const details = result.details; + + + + + + + + + + + + + + + + + + + + + + + + + + + if (isPartial) { + if (details?.phase === "curating") { + return new _piTui.Text(theme.fg("accent", "waiting for summary approval..."), 0, 0); + } + if (details?.phase === "searching") { + const progress = details?.progress ?? 0; + const bar = "\u2588".repeat(Math.floor(progress * 10)) + "\u2591".repeat(10 - Math.floor(progress * 10)); + const query = details?.currentQuery || ""; + const display = query.length > 40 ? query.slice(0, 37) + "..." : query; + return new _piTui.Text(theme.fg("accent", `[${bar}] ${display}`), 0, 0); + } + const progress = details?.progress ?? 0; + const bar = "\u2588".repeat(Math.floor(progress * 10)) + "\u2591".repeat(10 - Math.floor(progress * 10)); + return new _piTui.Text(theme.fg("accent", `[${bar}] ${details?.phase || "searching"}`), 0, 0); + } + + if (details?.error) { + return new _piTui.Text(theme.fg("error", `Error: ${details.error}`), 0, 0); + } + + let statusLine; + const queryInfo = details?.queryCount === 1 ? "" : `${details?.successfulQueries}/${details?.queryCount} queries, `; + statusLine = theme.fg("success", `${queryInfo}${details?.totalResults ?? 0} sources`); + if (details?.curated && details?.curatedFrom) { + statusLine += theme.fg("muted", ` (${details.queryCount}/${details.curatedFrom} queries curated)`); + } + if (details?.fetchId && details?.fetchUrls) { + statusLine += theme.fg("muted", ` (fetching ${details.fetchUrls.length} URLs)`); + } else if (details?.fetchId) { + statusLine += theme.fg("muted", " (content ready)"); + } + + // Build expanded lines first so collapsed view can reference total count + const lines = [statusLine]; + if (details?.summary?.text) { + lines.push(""); + lines.push(theme.fg("accent", `── Summary (${details.summary.workflow}) ` + "─".repeat(32))); + lines.push(""); + for (const line of details.summary.text.split("\n")) { + lines.push(` ${line}`); + } + lines.push(""); + const metaParts = [ + details.summary.model ? `model=${details.summary.model}` : "model=deterministic", + `duration=${details.summary.durationMs}ms`, + `tokens~${details.summary.tokenEstimate}`, + details.summary.fallbackUsed ? "fallback=true" : "fallback=false", + details.summary.edited ? "edited=true" : "edited=false"]; + + if (details.summary.fallbackReason) { + metaParts.push(`reason=${details.summary.fallbackReason}`); + } + lines.push(theme.fg("dim", " " + metaParts.join(" · "))); + } + + const queryDetails = details?.curatedQueries; + if (queryDetails?.length) { + const kept = queryDetails.length; + const from = details?.curatedFrom ?? kept; + lines.push(""); + lines.push(theme.fg("accent", `\u2500\u2500 Curated Results (${kept} of ${from} queries kept) ` + "\u2500".repeat(24))); + + for (const cq of queryDetails) { + lines.push(""); + const dq = cq.query.length > 65 ? cq.query.slice(0, 62) + "..." : cq.query; + const providerLabel = cq.provider ? ` (${cq.provider})` : ""; + lines.push(theme.fg("accent", ` "${dq}"${providerLabel}`)); + + if (cq.error) { + lines.push(theme.fg("error", ` ${cq.error}`)); + } else if (cq.answer) { + lines.push(""); + for (const line of cq.answer.split("\n")) { + lines.push(` ${line}`); + } + } + + if (cq.sources.length > 0) { + lines.push(""); + for (const s of cq.sources) { + const domain = s.url.replace(/^https?:\/\//, "").replace(/\/.*$/, ""); + const title = s.title.length > 50 ? s.title.slice(0, 47) + "..." : s.title; + lines.push(theme.fg("muted", ` \u25b8 ${title}`) + theme.fg("dim", ` \u00b7 ${domain}`)); + } + } + } + lines.push(""); + } else { + const textContent = result.content.find((c) => c.type === "text")?.text || ""; + const preview = textContent.length > 500 ? textContent.slice(0, 500) + "..." : textContent; + for (const line of preview.split("\n")) { + lines.push(theme.fg("dim", line)); + } + } + + if (details?.fetchUrls && details.fetchUrls.length > 0) { + if (details.curated) { + lines.push(theme.fg("muted", `Fetching ${details.fetchUrls.length} URLs in background`)); + } else { + lines.push(theme.fg("muted", "Fetching:")); + for (const u of details.fetchUrls.slice(0, 5)) { + const display = u.length > 60 ? u.slice(0, 57) + "..." : u; + lines.push(theme.fg("dim", " " + display)); + } + if (details.fetchUrls.length > 5) { + lines.push(theme.fg("dim", ` ... and ${details.fetchUrls.length - 5} more`)); + } + } + } + + const totalLines = lines.length; + + if (!expanded) { + const box = new _piTui.Box(1, 0, (t) => theme.bg("toolSuccessBg", t)); + box.addChild(new _piTui.Text(statusLine, 0, 0)); + + let collapsedLines = 1; // statusLine + const summaryPreview = details?.summary?.text?.trim() || ""; + if (summaryPreview) { + const preview = summaryPreview.length > 120 ? summaryPreview.slice(0, 117) + "..." : summaryPreview; + box.addChild(new _piTui.Text(theme.fg("dim", preview), 0, 0)); + collapsedLines++; + } else if (details?.curatedQueries?.length) { + for (const cq of details.curatedQueries.slice(0, 3)) { + const dq = cq.query.length > 55 ? cq.query.slice(0, 52) + "..." : cq.query; + const srcCount = cq.sources?.length ?? 0; + const suffix = cq.error ? theme.fg("error", " (error)") : theme.fg("dim", ` · ${srcCount} sources`); + box.addChild(new _piTui.Text(theme.fg("accent", ` "${dq}"`) + suffix, 0, 0)); + collapsedLines++; + } + if (details.curatedQueries.length > 3) { + box.addChild(new _piTui.Text(theme.fg("dim", ` ... and ${details.curatedQueries.length - 3} more`), 0, 0)); + collapsedLines++; + } + } else { + const textContent = result.content.find((c) => c.type === "text")?.text || ""; + const firstContentLine = textContent.split("\n").find((l) => { + const t = l.trim(); + return t && !t.startsWith("[") && !t.startsWith("#") && !t.startsWith("---"); + }); + const fallbackLine = (firstContentLine?.trim() || "").replace(/\*\*/g, ""); + if (fallbackLine) { + const preview = fallbackLine.length > 120 ? fallbackLine.slice(0, 117) + "..." : fallbackLine; + box.addChild(new _piTui.Text(theme.fg("dim", preview), 0, 0)); + collapsedLines++; + } + } + const moreLines = Math.max(0, totalLines - collapsedLines); + if (moreLines > 0) { + box.addChild(new _piTui.Text(theme.fg("muted", `\n... (${moreLines} more lines, ${totalLines} total, ctrl+o to expand)`), 0, 0)); + } + return box; + } + + return new _piTui.Text(lines.join("\n"), 0, 0); + } + }); + + pi.registerTool({ + name: "code_search", + label: "Code Search", + description: "Search for code examples, documentation, and API references. Returns relevant code snippets and docs from GitHub, Stack Overflow, and official documentation. Use for any programming question — API usage, library examples, debugging help.", + promptSnippet: + "Use for programming/API/library questions to retrieve concrete examples and docs before implementing or debugging code.", + parameters: _typebox.Type.Object({ + query: _typebox.Type.String({ description: "Programming question, API, library, or debugging topic to search for" }), + maxTokens: _typebox.Type.Optional(_typebox.Type.Integer({ + minimum: 1000, + maximum: 50000, + description: "Maximum tokens of code/documentation context to return (default: 5000)" + })) + }), + + async execute(toolCallId, params, signal) { + return (0, _codeSearch.executeCodeSearch)(toolCallId, params, signal); + }, + + renderCall(args, theme) { + const { query } = args; + const display = !query ? + "(no query)" : + query.length > 70 ? query.slice(0, 67) + "..." : query; + return new _piTui.Text(theme.fg("toolTitle", theme.bold("code_search ")) + theme.fg("accent", display), 0, 0); + }, + + renderResult(result, { expanded }, theme) { + const details = result.details; + if (details?.error) { + return new _piTui.Text(theme.fg("error", `Error: ${details.error}`), 0, 0); + } + + const summary = theme.fg("success", "code context returned") + + theme.fg("muted", ` (${details?.maxTokens ?? 5000} tokens max)`); + if (!expanded) return new _piTui.Text(summary, 0, 0); + + const textContent = result.content.find((c) => c.type === "text")?.text || ""; + const preview = textContent.length > 500 ? textContent.slice(0, 500) + "..." : textContent; + return new _piTui.Text(summary + "\n" + theme.fg("dim", preview), 0, 0); + } + }); + + pi.registerTool({ + name: "fetch_content", + label: "Fetch Content", + description: "Fetch URL(s) and extract readable content as markdown. Supports YouTube video transcripts (with thumbnail), GitHub repository contents, and local video files (with frame thumbnail). Video frames can be extracted via timestamp/range or sampled across the entire video with frames alone. Falls back to Gemini for pages that block bots or fail Readability extraction. For YouTube and video files: ALWAYS pass the user's specific question via the prompt parameter — this directs the AI to focus on that aspect of the video, producing much better results than a generic extraction. Content is always stored and can be retrieved with get_search_content.", + promptSnippet: + "Use to extract readable content from URL(s), YouTube, GitHub repos, or local videos. For video questions, pass the user's exact question in prompt.", + parameters: _typebox.Type.Object({ + url: _typebox.Type.Optional(_typebox.Type.String({ description: "Single URL to fetch" })), + urls: _typebox.Type.Optional(_typebox.Type.Array(_typebox.Type.String(), { description: "Multiple URLs (parallel)" })), + forceClone: _typebox.Type.Optional(_typebox.Type.Boolean({ + description: "Force cloning large GitHub repositories that exceed the size threshold" + })), + prompt: _typebox.Type.Optional(_typebox.Type.String({ + description: "Question or instruction for video analysis (YouTube and video files). Pass the user's specific question here — e.g. 'describe the book shown at the advice for beginners section'. Without this, a generic transcript extraction is used which may miss what the user is asking about." + })), + timestamp: _typebox.Type.Optional(_typebox.Type.String({ + description: "Extract video frame(s) at a timestamp or time range. Single: '1:23:45', '23:45', or '85' (seconds). Range: '23:41-25:00' extracts evenly-spaced frames across that span (default 6). Use frames with ranges to control density; single+frames uses a fixed 5s interval. YouTube requires yt-dlp + ffmpeg; local videos require ffmpeg. Use a range when you know the approximate area but not the exact moment — you'll get a contact sheet to visually identify the right frame." + })), + frames: _typebox.Type.Optional(_typebox.Type.Integer({ + minimum: 1, + maximum: 12, + description: "Number of frames to extract. Use with timestamp range for custom density, with single timestamp to get N frames at 5s intervals, or alone to sample across the entire video. Requires yt-dlp + ffmpeg for YouTube, ffmpeg for local video." + })), + model: _typebox.Type.Optional(_typebox.Type.String({ + description: "Override the Gemini model for video/YouTube analysis (e.g. 'gemini-2.5-flash', 'gemini-3-flash-preview'). Defaults to config or gemini-3-flash-preview." + })) + }), + + async execute(_toolCallId, params, signal, onUpdate) { + const urlList = params.urls ?? (params.url ? [params.url] : []); + if (urlList.length === 0) { + return { + content: [{ type: "text", text: "Error: No URL provided." }], + details: { error: "No URL provided" } + }; + } + + onUpdate?.({ + content: [{ type: "text", text: `Fetching ${urlList.length} URL(s)...` }], + details: { phase: "fetch", progress: 0 } + }); + + const fetchResults = await (0, _extract.fetchAllContent)(urlList, signal, { + forceClone: params.forceClone, + prompt: params.prompt, + timestamp: params.timestamp, + frames: params.frames, + model: params.model + }); + const successful = fetchResults.filter((r) => !r.error).length; + const totalChars = fetchResults.reduce((sum, r) => sum + r.content.length, 0); + + // ALWAYS store results (even for single URL) + const responseId = (0, _storage.generateId)(); + const data = { + id: responseId, + type: "fetch", + timestamp: Date.now(), + urls: stripThumbnails(fetchResults) + }; + (0, _storage.storeResult)(responseId, data); + pi.appendEntry("web-search-results", data); + + // Single URL: return content directly (possibly truncated) with responseId + if (urlList.length === 1) { + const result = fetchResults[0]; + if (result.error) { + return { + content: [{ type: "text", text: `Error: ${result.error}` }], + details: { urls: urlList, urlCount: 1, successful: 0, error: result.error, responseId, prompt: params.prompt, timestamp: params.timestamp, frames: params.frames } + }; + } + + const fullLength = result.content.length; + const truncated = fullLength > MAX_INLINE_CONTENT; + let output = truncated ? + result.content.slice(0, MAX_INLINE_CONTENT) + "\n\n[Content truncated...]" : + result.content; + + if (truncated) { + output += `\n\n---\nShowing ${MAX_INLINE_CONTENT} of ${fullLength} chars. ` + + `Use get_search_content({ responseId: "${responseId}", urlIndex: 0 }) for full content.`; + } + + const content = []; + if (result.frames?.length) { + for (const frame of result.frames) { + content.push({ type: "image", data: frame.data, mimeType: frame.mimeType }); + content.push({ type: "text", text: `Frame at ${frame.timestamp}` }); + } + } else if (result.thumbnail) { + content.push({ type: "image", data: result.thumbnail.data, mimeType: result.thumbnail.mimeType }); + } + content.push({ type: "text", text: output }); + + const imageCount = (result.frames?.length ?? 0) + (result.thumbnail ? 1 : 0); + return { + content, + details: { + urls: urlList, + urlCount: 1, + successful: 1, + totalChars: fullLength, + title: result.title, + responseId, + truncated, + hasImage: imageCount > 0, + imageCount, + prompt: params.prompt, + timestamp: params.timestamp, + frames: params.frames, + duration: result.duration + } + }; + } + + // Multi-URL: existing behavior (summary + responseId) + let output = "## Fetched URLs\n\n"; + for (const { url, title, content, error } of fetchResults) { + if (error) { + output += `- ${url}: Error - ${error}\n`; + } else { + output += `- ${title || url} (${content.length} chars)\n`; + } + } + output += `\n---\nUse get_search_content({ responseId: "${responseId}", urlIndex: 0 }) to retrieve full content.`; + + return { + content: [{ type: "text", text: output }], + details: { urls: urlList, urlCount: urlList.length, successful, totalChars, responseId } + }; + }, + + renderCall(args, theme) { + const { url, urls, prompt, timestamp, frames, model } = args; + const urlList = urls ?? (url ? [url] : []); + if (urlList.length === 0) { + return new _piTui.Text(theme.fg("toolTitle", theme.bold("fetch ")) + theme.fg("error", "(no URL)"), 0, 0); + } + const lines = []; + if (urlList.length === 1) { + const display = urlList[0].length > 60 ? urlList[0].slice(0, 57) + "..." : urlList[0]; + lines.push(theme.fg("toolTitle", theme.bold("fetch ")) + theme.fg("accent", display)); + } else { + lines.push(theme.fg("toolTitle", theme.bold("fetch ")) + theme.fg("accent", `${urlList.length} URLs`)); + for (const u of urlList.slice(0, 5)) { + const display = u.length > 60 ? u.slice(0, 57) + "..." : u; + lines.push(theme.fg("muted", " " + display)); + } + if (urlList.length > 5) { + lines.push(theme.fg("muted", ` ... and ${urlList.length - 5} more`)); + } + } + if (timestamp) { + lines.push(theme.fg("dim", " timestamp: ") + theme.fg("warning", timestamp)); + } + if (typeof frames === "number") { + lines.push(theme.fg("dim", " frames: ") + theme.fg("warning", String(frames))); + } + if (prompt) { + const display = prompt.length > 250 ? prompt.slice(0, 247) + "..." : prompt; + lines.push(theme.fg("dim", " prompt: ") + theme.fg("muted", `"${display}"`)); + } + if (model) { + lines.push(theme.fg("dim", " model: ") + theme.fg("warning", model)); + } + return new _piTui.Text(lines.join("\n"), 0, 0); + }, + + renderResult(result, { expanded, isPartial }, theme) { + const details = result.details; + + + + + + + + + + + + + + + + + + if (isPartial) { + const progress = details?.progress ?? 0; + const bar = "\u2588".repeat(Math.floor(progress * 10)) + "\u2591".repeat(10 - Math.floor(progress * 10)); + return new _piTui.Text(theme.fg("accent", `[${bar}] ${details?.phase || "fetching"}`), 0, 0); + } + + if (details?.error) { + return new _piTui.Text(theme.fg("error", `Error: ${details.error}`), 0, 0); + } + + if (details?.urlCount === 1) { + const title = details?.title || "Untitled"; + const imgCount = details?.imageCount ?? (details?.hasImage ? 1 : 0); + const imageBadge = imgCount > 1 ? + theme.fg("accent", ` [${imgCount} images]`) : + imgCount === 1 ? + theme.fg("accent", " [image]") : + ""; + let statusLine = theme.fg("success", title) + theme.fg("muted", ` (${details?.totalChars ?? 0} chars)`) + imageBadge; + if (details?.truncated) { + statusLine += theme.fg("warning", " [truncated]"); + } + if (typeof details?.duration === "number") { + statusLine += theme.fg("muted", ` | ${(0, _utils.formatSeconds)(Math.floor(details.duration))} total`); + } + const textContent = result.content.find((c) => c.type === "text")?.text || ""; + if (!expanded) { + const brief = textContent.length > 200 ? textContent.slice(0, 200) + "..." : textContent; + return new _piTui.Text(statusLine + "\n" + theme.fg("dim", brief), 0, 0); + } + const lines = [statusLine]; + if (details?.prompt) { + const display = details.prompt.length > 250 ? details.prompt.slice(0, 247) + "..." : details.prompt; + lines.push(theme.fg("dim", ` prompt: "${display}"`)); + } + if (details?.timestamp) { + lines.push(theme.fg("dim", ` timestamp: ${details.timestamp}`)); + } + if (typeof details?.frames === "number") { + lines.push(theme.fg("dim", ` frames: ${details.frames}`)); + } + const preview = textContent.length > 500 ? textContent.slice(0, 500) + "..." : textContent; + lines.push(theme.fg("dim", preview)); + return new _piTui.Text(lines.join("\n"), 0, 0); + } + + const countColor = (details?.successful ?? 0) > 0 ? "success" : "error"; + const statusLine = theme.fg(countColor, `${details?.successful}/${details?.urlCount} URLs`) + theme.fg("muted", " (content stored)"); + if (!expanded) { + return new _piTui.Text(statusLine, 0, 0); + } + const textContent = result.content.find((c) => c.type === "text")?.text || ""; + const preview = textContent.length > 500 ? textContent.slice(0, 500) + "..." : textContent; + return new _piTui.Text(statusLine + "\n" + theme.fg("dim", preview), 0, 0); + } + }); + + pi.registerTool({ + name: "get_search_content", + label: "Get Search Content", + description: "Retrieve full content from a previous web_search or fetch_content call.", + promptSnippet: + "Use after web_search/fetch_content when full stored content is needed via responseId plus query/url selectors.", + parameters: _typebox.Type.Object({ + responseId: _typebox.Type.String({ description: "The responseId from web_search or fetch_content" }), + query: _typebox.Type.Optional(_typebox.Type.String({ description: "Get content for this query (web_search)" })), + queryIndex: _typebox.Type.Optional(_typebox.Type.Number({ description: "Get content for query at index" })), + url: _typebox.Type.Optional(_typebox.Type.String({ description: "Get content for this URL" })), + urlIndex: _typebox.Type.Optional(_typebox.Type.Number({ description: "Get content for URL at index" })) + }), + + async execute(_toolCallId, params) { + const data = (0, _storage.getResult)(params.responseId); + if (!data) { + return { + content: [{ type: "text", text: `Error: No stored results for "${params.responseId}"` }], + details: { error: "Not found", responseId: params.responseId } + }; + } + + if (data.type === "search" && data.queries) { + let queryData; + + if (params.query !== undefined) { + queryData = data.queries.find((q) => q.query === params.query); + if (!queryData) { + const available = data.queries.map((q) => `"${q.query}"`).join(", "); + return { + content: [{ type: "text", text: `Query "${params.query}" not found. Available: ${available}` }], + details: { error: "Query not found" } + }; + } + } else if (params.queryIndex !== undefined) { + queryData = data.queries[params.queryIndex]; + if (!queryData) { + return { + content: [{ type: "text", text: `Index ${params.queryIndex} out of range (0-${data.queries.length - 1})` }], + details: { error: "Index out of range" } + }; + } + } else { + const available = data.queries.map((q, i) => `${i}: "${q.query}"`).join(", "); + return { + content: [{ type: "text", text: `Specify query or queryIndex. Available: ${available}` }], + details: { error: "No query specified" } + }; + } + + if (queryData.error) { + return { + content: [{ type: "text", text: `Error for "${queryData.query}": ${queryData.error}` }], + details: { error: queryData.error, query: queryData.query } + }; + } + + return { + content: [{ type: "text", text: formatFullResults(queryData) }], + details: { query: queryData.query, resultCount: queryData.results.length } + }; + } + + if (data.type === "fetch" && data.urls) { + let urlData; + + if (params.url !== undefined) { + urlData = data.urls.find((u) => u.url === params.url); + if (!urlData) { + const available = data.urls.map((u) => u.url).join("\n "); + return { + content: [{ type: "text", text: `URL not found. Available:\n ${available}` }], + details: { error: "URL not found" } + }; + } + } else if (params.urlIndex !== undefined) { + urlData = data.urls[params.urlIndex]; + if (!urlData) { + return { + content: [{ type: "text", text: `Index ${params.urlIndex} out of range (0-${data.urls.length - 1})` }], + details: { error: "Index out of range" } + }; + } + } else { + const available = data.urls.map((u, i) => `${i}: ${u.url}`).join("\n "); + return { + content: [{ type: "text", text: `Specify url or urlIndex. Available:\n ${available}` }], + details: { error: "No URL specified" } + }; + } + + if (urlData.error) { + return { + content: [{ type: "text", text: `Error for ${urlData.url}: ${urlData.error}` }], + details: { error: urlData.error, url: urlData.url } + }; + } + + return { + content: [{ type: "text", text: `# ${urlData.title}\n\n${urlData.content}` }], + details: { url: urlData.url, title: urlData.title, contentLength: urlData.content.length } + }; + } + + return { + content: [{ type: "text", text: "Invalid stored data format" }], + details: { error: "Invalid data" } + }; + }, + + renderCall(args, theme) { + const { responseId, query, queryIndex, url, urlIndex } = args; + + + + + + + let target = ""; + if (query) target = `query="${query}"`;else + if (queryIndex !== undefined) target = `queryIndex=${queryIndex}`;else + if (url) target = url.length > 30 ? url.slice(0, 27) + "..." : url;else + if (urlIndex !== undefined) target = `urlIndex=${urlIndex}`; + return new _piTui.Text(theme.fg("toolTitle", theme.bold("get_content ")) + theme.fg("accent", target || responseId.slice(0, 8)), 0, 0); + }, + + renderResult(result, { expanded }, theme) { + const details = result.details; + + + + + + + + + if (details?.error) { + return new _piTui.Text(theme.fg("error", `Error: ${details.error}`), 0, 0); + } + + let statusLine; + if (details?.query) { + statusLine = theme.fg("success", `"${details.query}"`) + theme.fg("muted", ` (${details.resultCount} results)`); + } else { + statusLine = theme.fg("success", details?.title || "Content") + theme.fg("muted", ` (${details?.contentLength ?? 0} chars)`); + } + + if (!expanded) { + return new _piTui.Text(statusLine, 0, 0); + } + + const textContent = result.content.find((c) => c.type === "text")?.text || ""; + const preview = textContent.length > 500 ? textContent.slice(0, 500) + "..." : textContent; + return new _piTui.Text(statusLine + "\n" + theme.fg("dim", preview), 0, 0); + } + }); + + pi.registerCommand("websearch", { + description: "Open web search curator", + handler: async (args, ctx) => { + closeCurator(); + const sessionToken = (0, _nodeCrypto.randomUUID)(); + + const raw = args.trim(); + const queries = raw.length > 0 ? + normalizeQueryList(raw.split(",")) : + []; + + let bootstrap; + try { + bootstrap = await loadCuratorBootstrap(undefined); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + ctx.ui.notify(`Failed to load web search config: ${message}`, "error"); + return; + } + const availableProviders = bootstrap.availableProviders; + const initialProvider = bootstrap.defaultProvider; + const curatorTimeoutSeconds = bootstrap.timeoutSeconds; + let currentProvider = initialProvider; + const summaryContext = { + model: ctx.model, + modelRegistry: ctx.modelRegistry + }; + const summaryModelChoices = await loadSummaryModelChoices(summaryContext); + + ctx.ui.notify("Opening web search curator...", "info"); + + const collected = new Map(); + const searchAbort = new AbortController(); + let aborted = false; + let commandHandle = null; + + function sendFollowUpFromReturn(payload) { + pi.sendMessage({ + customType: "web-search-results", + content: payload.content, + display: "tool", + details: payload.details + }, { triggerTurn: true, deliverAs: "followUp" }); + } + + try { + const handle = await (0, _curatorServer.startCuratorServer)( + { + queries, + sessionToken, + timeout: curatorTimeoutSeconds, + availableProviders, + defaultProvider: initialProvider, + summaryModels: summaryModelChoices.summaryModels, + defaultSummaryModel: summaryModelChoices.defaultSummaryModel + }, + { + async onSummarize(selectedQueryIndices, summarizeSignal, model, feedback) { + if (commandHandle && activeCurator !== commandHandle) { + throw new Error("Curator session is no longer active."); + } + return generateSummaryForSelectedIndices( + selectedQueryIndices, + collected, + summaryContext, + summarizeSignal, + model, + feedback + ); + }, + onSubmit(payload) { + if (commandHandle && activeCurator !== commandHandle) return; + aborted = true; + searchAbort.abort(); + const filtered = payload.selectedQueryIndices.length > 0 ? + filterByQueryIndices(payload.selectedQueryIndices, collected) : + collectAllResultsAndUrls(collected); + const base = { + queryList: filtered.results.map((r) => r.query), + results: filtered.results, + urls: filtered.urls, + includeContent: false, + curated: true, + curatedFrom: collected.size + }; + if (!payload.rawResults) { + const resolvedSummary = resolveSummaryForSubmit(payload, collected); + base.workflow = "summary-review"; + base.approvedSummary = resolvedSummary.approvedSummary; + base.summaryMeta = resolvedSummary.summaryMeta; + } + sendFollowUpFromReturn(buildSearchReturn(base)); + closeCurator(); + }, + onCancel(reason) { + if (commandHandle && activeCurator !== commandHandle) return; + aborted = true; + searchAbort.abort(); + if (reason === "timeout") { + const all = collectAllResultsAndUrls(collected); + const resolvedSummary = resolveSummaryForSubmit({ selectedQueryIndices: [], summary: undefined, summaryMeta: undefined }, collected); + sendFollowUpFromReturn(buildSearchReturn({ + queryList: all.results.map((r) => r.query), + results: all.results, + urls: all.urls, + includeContent: false, + curated: true, + curatedFrom: collected.size, + workflow: "summary-review", + approvedSummary: resolvedSummary.approvedSummary, + summaryMeta: resolvedSummary.summaryMeta + })); + } + closeCurator(); + }, + onProviderChange(provider) { + if (commandHandle && activeCurator !== commandHandle) return; + const normalized = normalizeProviderInput(provider); + if (!normalized || normalized === "auto") return; + currentProvider = normalized; + try { + saveConfig({ provider: normalized }); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error(`Failed to persist default provider: ${message}`); + } + }, + async onAddSearch(query, queryIndex, provider) { + if (commandHandle && activeCurator !== commandHandle) { + throw new Error("Curator session is no longer active."); + } + const normalizedProvider = normalizeProviderInput(provider); + const requestedProvider = !normalizedProvider || normalizedProvider === "auto" ? + currentProvider : + normalizedProvider; + try { + const { answer, results, provider: actualProvider } = await (0, _geminiSearch.search)(query, { + provider: requestedProvider, + signal: searchAbort.signal + }); + if (commandHandle && activeCurator !== commandHandle) { + throw new Error("Curator session is no longer active."); + } + collected.set(queryIndex, { query, answer, results, error: null, provider: actualProvider }); + return { + answer, + results: results.map((r) => ({ title: r.title, url: r.url, domain: extractDomain(r.url) })), + provider: actualProvider + }; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + if (!commandHandle || activeCurator === commandHandle) { + collected.set(queryIndex, { query, answer: "", results: [], error: message, provider: requestedProvider }); + } + throw err; + } + }, + async onRewriteQuery(query, rewriteSignal) { + if (commandHandle && activeCurator !== commandHandle) { + throw new Error("Curator session is no longer active."); + } + return rewriteSearchQuery(query, summaryContext, rewriteSignal); + } + } + ); + + commandHandle = handle; + activeCurator = handle; + const open = (0, _nodeOs.platform)() === "darwin" ? await getGlimpseOpen() : null; + if (open) { + try { + const win = openInGlimpse(open, handle.url, "Search Curator"); + glimpseWin = win; + win.on("closed", () => { + if (glimpseWin === win) { + glimpseWin = null; + closeCurator(); + } + }); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error(`Failed to open Glimpse curator window: ${message}`); + glimpseWin = null; + await openInBrowser(pi, handle.url); + } + } else { + await openInBrowser(pi, handle.url); + } + + if (queries.length > 0) { + (async () => { + for (let qi = 0; qi < queries.length; qi++) { + if (aborted || activeCurator !== handle) break; + const requestedProvider = currentProvider; + try { + const { answer, results, provider } = await (0, _geminiSearch.search)(queries[qi], { + provider: requestedProvider, + signal: searchAbort.signal + }); + if (aborted || activeCurator !== handle) break; + handle.pushResult(qi, { + answer, + results: results.map((r) => ({ title: r.title, url: r.url, domain: extractDomain(r.url) })), + provider + }); + collected.set(qi, { query: queries[qi], answer, results, error: null, provider }); + } catch (err) { + if (aborted || activeCurator !== handle) break; + const message = err instanceof Error ? err.message : String(err); + handle.pushError(qi, message, requestedProvider); + collected.set(qi, { query: queries[qi], answer: "", results: [], error: message, provider: requestedProvider }); + } + } + if (!aborted && activeCurator === handle) handle.searchesDone(); + })(); + } else { + if (activeCurator === handle) handle.searchesDone(); + } + } catch (err) { + closeCurator(); + const message = err instanceof Error ? err.message : String(err); + ctx.ui.notify(`Failed to open curator: ${message}`, "error"); + } + } + }); + + pi.registerCommand("curator", { + description: "Toggle or configure the search curator workflow", + handler: async (args, ctx) => { + const arg = args.trim().toLowerCase(); + + let newWorkflow; + if (arg.length === 0) { + const current = resolveWorkflow(loadConfigForExtensionInit().workflow, true); + newWorkflow = current === "none" ? "summary-review" : "none"; + } else if (arg === "on") { + newWorkflow = "summary-review"; + } else if (arg === "off") { + newWorkflow = "none"; + } else if (arg === "none" || arg === "summary-review") { + newWorkflow = arg; + } else { + ctx.ui.notify(`Unknown option: ${arg}. Use on, off, or summary-review.`, "error"); + return; + } + + try { + saveConfig({ workflow: newWorkflow }); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + ctx.ui.notify(`Failed to save config: ${message}`, "error"); + return; + } + + const label = newWorkflow === "none" ? + "Curator disabled — web_search will return raw results" : + "Curator enabled — web_search will open curator and auto-generate a summary draft"; + pi.sendMessage({ + customType: "curator-config", + content: [{ type: "text", text: label }], + display: "tool", + details: { workflow: newWorkflow } + }, { triggerTurn: false, deliverAs: "followUp" }); + } + }); + + pi.registerCommand("google-account", { + description: "Show the active Google account for Gemini Web", + handler: async () => { + if (!(0, _geminiWebConfig.isBrowserCookieAccessAllowed)()) { + pi.sendMessage({ + customType: "google-account", + content: [{ type: "text", text: "Gemini Web browser cookie access is disabled. Set allowBrowserCookies: true in ~/.pi/web-search.json to enable it." }], + display: "tool", + details: { available: false, cookieAccessAllowed: false } + }, { triggerTurn: true, deliverAs: "followUp" }); + return; + } + + const cookies = await (0, _geminiWeb.isGeminiWebAvailable)(); + if (!cookies) { + pi.sendMessage({ + customType: "google-account", + content: [{ type: "text", text: "Gemini Web is unavailable. Sign into gemini.google.com in a supported Chromium-based browser." }], + display: "tool", + details: { available: false, cookieAccessAllowed: true } + }, { triggerTurn: true, deliverAs: "followUp" }); + return; + } + + const email = await (0, _geminiWeb.getActiveGoogleEmail)(cookies); + const text = email ? + `Active Google account: ${email}` : + "Gemini Web is available, but the active Google account could not be determined."; + + pi.sendMessage({ + customType: "google-account", + content: [{ type: "text", text }], + display: "tool", + details: { available: true, email: email ?? null } + }, { triggerTurn: true, deliverAs: "followUp" }); + } + }); + + pi.registerCommand("search", { + description: "Browse stored web search results", + handler: async (_args, ctx) => { + const results = (0, _storage.getAllResults)(); + + if (results.length === 0) { + ctx.ui.notify("No stored search results", "info"); + return; + } + + const options = results.map((r) => { + const age = Math.floor((Date.now() - r.timestamp) / 60000); + const ageStr = age < 60 ? `${age}m ago` : `${Math.floor(age / 60)}h ago`; + if (r.type === "search" && r.queries) { + const query = r.queries[0]?.query || "unknown"; + return `[${r.id.slice(0, 6)}] "${query}" (${r.queries.length} queries) - ${ageStr}`; + } + if (r.type === "fetch" && r.urls) { + return `[${r.id.slice(0, 6)}] ${r.urls.length} URLs fetched - ${ageStr}`; + } + return `[${r.id.slice(0, 6)}] ${r.type} - ${ageStr}`; + }); + + const choice = await ctx.ui.select("Stored Search Results", options); + if (!choice) return; + + const match = choice.match(/^\[([a-z0-9]+)\]/); + if (!match) return; + + const selected = results.find((r) => r.id.startsWith(match[1])); + if (!selected) return; + + const actions = ["View details", "Delete"]; + const action = await ctx.ui.select(`Result ${selected.id.slice(0, 6)}`, actions); + + if (action === "Delete") { + (0, _storage.deleteResult)(selected.id); + ctx.ui.notify(`Deleted ${selected.id.slice(0, 6)}`, "info"); + } else if (action === "View details") { + let info = `ID: ${selected.id}\nType: ${selected.type}\nAge: ${Math.floor((Date.now() - selected.timestamp) / 60000)}m\n\n`; + if (selected.type === "search" && selected.queries) { + info += "Queries:\n"; + const queries = selected.queries.slice(0, 10); + for (const q of queries) { + info += `- "${q.query}" (${q.results.length} results)\n`; + } + if (selected.queries.length > 10) { + info += `... and ${selected.queries.length - 10} more\n`; + } + } + if (selected.type === "fetch" && selected.urls) { + info += "URLs:\n"; + const urls = selected.urls.slice(0, 10); + for (const u of urls) { + const urlDisplay = u.url.length > 50 ? u.url.slice(0, 47) + "..." : u.url; + info += `- ${urlDisplay} (${u.error || `${u.content.length} chars`})\n`; + } + if (selected.urls.length > 10) { + info += `... and ${selected.urls.length - 10} more\n`; + } + } + ctx.ui.notify(info, "info"); + } + } + }); +} /* v9-9c4ad013a63a016f */ diff --git a/pip-tmp/jiti/pi-web-access-pdf-extract.26fa3575.mjs b/pip-tmp/jiti/pi-web-access-pdf-extract.26fa3575.mjs new file mode 100644 index 0000000000000000000000000000000000000000..ac76970498b57768831496535c82d5683c04dded --- /dev/null +++ b/pip-tmp/jiti/pi-web-access-pdf-extract.26fa3575.mjs @@ -0,0 +1,192 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.extractPDFToMarkdown = extractPDFToMarkdown;exports.isPDF = isPDF; + + + + + + +var _unpdf = await jitiImport("unpdf"); +var _promises = await jitiImport("node:fs/promises"); +var _nodePath = await jitiImport("node:path"); +var _nodeOs = await jitiImport("node:os"); /** + * PDF Content Extractor + * + * Extracts text from PDF files and saves to markdown. + * Uses unpdf (pdfjs-dist wrapper) for text extraction. + */ + + + + + + + + + +const DEFAULT_MAX_PAGES = 100; +const DEFAULT_OUTPUT_DIR = (0, _nodePath.join)((0, _nodeOs.homedir)(), "Downloads"); + +/** + * Extract text from a PDF buffer and save to markdown file + */ +async function extractPDFToMarkdown( +buffer, +url, +options = {}) +{ + const { + maxPages = DEFAULT_MAX_PAGES, + outputDir = DEFAULT_OUTPUT_DIR, + filename + } = options; + + const safeMaxPages = Number.isFinite(maxPages) ? + Math.max(1, Math.floor(maxPages)) : + DEFAULT_MAX_PAGES; + + const pdf = await (0, _unpdf.getDocumentProxy)(new Uint8Array(buffer)); + const metadata = await pdf.getMetadata(); + const metadataInfo = metadata.info && typeof metadata.info === "object" ? + metadata.info : + null; + + // Extract title from metadata or URL + const metaTitle = typeof metadataInfo?.Title === "string" ? metadataInfo.Title : undefined; + const metaAuthor = typeof metadataInfo?.Author === "string" ? metadataInfo.Author : undefined; + const urlTitle = extractTitleFromURL(url); + const title = metaTitle?.trim() || urlTitle; + + // Determine pages to extract + const pagesToExtract = Math.min(pdf.numPages, safeMaxPages); + const truncated = pdf.numPages > safeMaxPages; + + // Extract text page by page for better structure + const pages = []; + for (let i = 1; i <= pagesToExtract; i++) { + const page = await pdf.getPage(i); + const textContent = await page.getTextContent(); + const pageText = textContent.items. + map((item) => { + const textItem = item; + return textItem.str || ""; + }). + join(" "). + replace(/\s+/g, " "). + trim(); + + if (pageText) { + pages.push({ pageNum: i, text: pageText }); + } + } + + // Build markdown content + const lines = []; + + // Header with metadata + lines.push(`# ${title}`); + lines.push(""); + lines.push(`> Source: ${url}`); + lines.push(`> Pages: ${pdf.numPages}${truncated ? ` (extracted first ${pagesToExtract})` : ""}`); + if (metaAuthor) { + lines.push(`> Author: ${metaAuthor}`); + } + lines.push(""); + lines.push("---"); + lines.push(""); + + // Content with page markers + for (let i = 0; i < pages.length; i++) { + if (i > 0) { + lines.push(""); + lines.push(``); + lines.push(""); + } + lines.push(pages[i].text); + } + + if (truncated) { + lines.push(""); + lines.push("---"); + lines.push(""); + lines.push(`*[Truncated: Only first ${pagesToExtract} of ${pdf.numPages} pages extracted]*`); + } + + const content = lines.join("\n"); + + // Generate output filename + const outputFilename = filename || sanitizeFilename(title) + ".md"; + const outputPath = (0, _nodePath.join)(outputDir, outputFilename); + + // Ensure output directory exists + await (0, _promises.mkdir)(outputDir, { recursive: true }); + + // Write file + await (0, _promises.writeFile)(outputPath, content, "utf-8"); + + return { + title, + pages: pdf.numPages, + chars: content.length, + outputPath + }; +} + +/** + * Extract a reasonable title from URL + */ +function extractTitleFromURL(url) { + try { + const urlObj = new URL(url); + const pathname = urlObj.pathname; + + // Get filename without extension + let filename = (0, _nodePath.basename)(pathname, ".pdf"); + + // Handle arxiv URLs: /pdf/1706.03762 → "arxiv-1706.03762" + if (urlObj.hostname.includes("arxiv.org")) { + const match = pathname.match(/\/(?:pdf|abs)\/(\d+\.\d+)/); + if (match) { + filename = `arxiv-${match[1]}`; + } + } + + // Clean up filename + filename = filename. + replace(/[_-]+/g, " "). + replace(/\s+/g, " "). + trim(); + + return filename || "document"; + } catch { + return "document"; + } +} + +/** + * Sanitize string for use as filename + */ +function sanitizeFilename(name) { + return name. + toLowerCase(). + replace(/[^a-z0-9\s-]/g, ""). + replace(/\s+/g, "-"). + replace(/-+/g, "-"). + slice(0, 100). + replace(/^-|-$/g, "") || + "document"; +} + +/** + * Check if URL or content-type indicates a PDF + */ +function isPDF(url, contentType) { + if (contentType?.includes("application/pdf")) { + return true; + } + try { + const urlObj = new URL(url); + return urlObj.pathname.toLowerCase().endsWith(".pdf"); + } catch { + return false; + } +} /* v9-0fe2a52e2f39cf7d */ diff --git a/pip-tmp/jiti/pi-web-access-perplexity.17545eb1.mjs b/pip-tmp/jiti/pi-web-access-perplexity.17545eb1.mjs new file mode 100644 index 0000000000000000000000000000000000000000..5b83454386c397ea8b4fb8b75c13f49dd12f99c6 --- /dev/null +++ b/pip-tmp/jiti/pi-web-access-perplexity.17545eb1.mjs @@ -0,0 +1,195 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.isPerplexityAvailable = isPerplexityAvailable;exports.searchWithPerplexity = searchWithPerplexity;var _nodeFs = await jitiImport("node:fs"); +var _nodeOs = await jitiImport("node:os"); +var _nodePath = await jitiImport("node:path"); +var _activity = await jitiImport("./activity.js"); + + +const PERPLEXITY_API_URL = "https://api.perplexity.ai/chat/completions"; +const CONFIG_PATH = (0, _nodePath.join)((0, _nodeOs.homedir)(), ".pi", "web-search.json"); + +const RATE_LIMIT = { + maxRequests: 10, + windowMs: 60 * 1000 +}; + +const requestTimestamps = []; + + + + + + + + + + + + + + + + + + + + + + + + +let cachedConfig = null; + +function loadConfig() { + if (cachedConfig) return cachedConfig; + if (!(0, _nodeFs.existsSync)(CONFIG_PATH)) { + cachedConfig = {}; + return cachedConfig; + } + + const content = (0, _nodeFs.readFileSync)(CONFIG_PATH, "utf-8"); + try { + cachedConfig = JSON.parse(content); + return cachedConfig; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + throw new Error(`Failed to parse ${CONFIG_PATH}: ${message}`); + } +} + +function normalizeApiKey(value) { + if (typeof value !== "string") return null; + const normalized = value.trim(); + return normalized.length > 0 ? normalized : null; +} + +function getApiKey() { + const config = loadConfig(); + const key = normalizeApiKey(process.env.PERPLEXITY_API_KEY) ?? normalizeApiKey(config.perplexityApiKey); + if (!key) { + throw new Error( + "Perplexity API key not found. Either:\n" + + ` 1. Create ${CONFIG_PATH} with { "perplexityApiKey": "your-key" }\n` + + " 2. Set PERPLEXITY_API_KEY environment variable\n" + + "Get a key at https://perplexity.ai/settings/api" + ); + } + return key; +} + +function checkRateLimit() { + const now = Date.now(); + const windowStart = now - RATE_LIMIT.windowMs; + + while (requestTimestamps.length > 0 && requestTimestamps[0] < windowStart) { + requestTimestamps.shift(); + } + + if (requestTimestamps.length >= RATE_LIMIT.maxRequests) { + const waitMs = requestTimestamps[0] + RATE_LIMIT.windowMs - now; + throw new Error(`Rate limited. Try again in ${Math.ceil(waitMs / 1000)}s`); + } + + requestTimestamps.push(now); +} + +function validateDomainFilter(domains) { + return domains.filter((d) => { + const domain = d.startsWith("-") ? d.slice(1) : d; + return /^[a-zA-Z0-9][a-zA-Z0-9-_.]*\.[a-zA-Z]{2,}$/.test(domain); + }); +} + +function isPerplexityAvailable() { + const config = loadConfig(); + return !!(normalizeApiKey(process.env.PERPLEXITY_API_KEY) ?? normalizeApiKey(config.perplexityApiKey)); +} + +async function searchWithPerplexity(query, options = {}) { + checkRateLimit(); + + const activityId = _activity.activityMonitor.logStart({ type: "api", query }); + + _activity.activityMonitor.updateRateLimit({ + used: requestTimestamps.length, + max: RATE_LIMIT.maxRequests, + oldestTimestamp: requestTimestamps[0] ?? null, + windowMs: RATE_LIMIT.windowMs + }); + + const apiKey = getApiKey(); + const numResults = Math.min(options.numResults ?? 5, 20); + + const requestBody = { + model: "sonar", + messages: [{ role: "user", content: query }], + max_tokens: 1024, + return_related_questions: false + }; + + if (options.recencyFilter) { + requestBody.search_recency_filter = options.recencyFilter; + } + + if (options.domainFilter && options.domainFilter.length > 0) { + const validated = validateDomainFilter(options.domainFilter); + if (validated.length > 0) { + requestBody.search_domain_filter = validated; + } + } + + let response; + try { + response = await fetch(PERPLEXITY_API_URL, { + method: "POST", + headers: { + Authorization: `Bearer ${apiKey}`, + "Content-Type": "application/json" + }, + body: JSON.stringify(requestBody), + signal: options.signal + }); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + if (message.toLowerCase().includes("abort")) { + _activity.activityMonitor.logComplete(activityId, 0); + } else { + _activity.activityMonitor.logError(activityId, message); + } + throw err; + } + + if (!response.ok) { + _activity.activityMonitor.logComplete(activityId, response.status); + const errorText = await response.text(); + throw new Error(`Perplexity API error ${response.status}: ${errorText}`); + } + + let data; + try { + data = await response.json(); + } catch (err) { + _activity.activityMonitor.logComplete(activityId, response.status); + const message = err instanceof Error ? err.message : String(err); + throw new Error(`Perplexity API returned invalid JSON: ${message}`); + } + + const answer = data.choices?.[0]?.message?.content || ""; + const citations = Array.isArray(data.citations) ? data.citations : []; + + const results = []; + for (let i = 0; i < Math.min(citations.length, numResults); i++) { + const citation = citations[i]; + if (typeof citation === "string") { + results.push({ title: `Source ${i + 1}`, url: citation, snippet: "" }); + } else if (citation && typeof citation === "object" && typeof citation.url === "string") { + results.push({ + title: citation.title || `Source ${i + 1}`, + url: citation.url, + snippet: "" + }); + } + } + + _activity.activityMonitor.logComplete(activityId, response.status); + return { answer, results }; +} /* v9-6a0424696d651933 */ diff --git a/pip-tmp/jiti/pi-web-access-rsc-extract.d6b6a62d.mjs b/pip-tmp/jiti/pi-web-access-rsc-extract.d6b6a62d.mjs new file mode 100644 index 0000000000000000000000000000000000000000..4764293d4cb1939df403d2f1433efb8eb1a0bf8a --- /dev/null +++ b/pip-tmp/jiti/pi-web-access-rsc-extract.d6b6a62d.mjs @@ -0,0 +1,338 @@ +"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.extractRSCContent = extractRSCContent; /** + * RSC Content Extractor + * + * Extracts readable content from Next.js React Server Components (RSC) flight payloads. + * RSC pages embed content as JSON in tags. + */ + + + + + + +function extractRSCContent(html) { + if (!html.includes("self.__next_f.push")) { + return null; + } + + // Parse all RSC chunks into a map + const chunkMap = new Map(); + const scriptRegex = /