@letta-ai/letta-code 0.13.8 → 0.13.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/letta.js CHANGED
@@ -256,7 +256,7 @@ var init_values = __esm(() => {
256
256
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
257
257
 
258
258
  // node_modules/@letta-ai/letta-client/version.mjs
259
- var VERSION = "1.7.5";
259
+ var VERSION = "1.7.6";
260
260
 
261
261
  // node_modules/@letta-ai/letta-client/internal/detect-platform.mjs
262
262
  function getDetectedPlatform() {
@@ -3108,7 +3108,7 @@ var package_default;
3108
3108
  var init_package = __esm(() => {
3109
3109
  package_default = {
3110
3110
  name: "@letta-ai/letta-code",
3111
- version: "0.13.8",
3111
+ version: "0.13.9",
3112
3112
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
3113
3113
  type: "module",
3114
3114
  bin: {
@@ -3138,7 +3138,7 @@ var init_package = __esm(() => {
3138
3138
  access: "public"
3139
3139
  },
3140
3140
  dependencies: {
3141
- "@letta-ai/letta-client": "1.7.5",
3141
+ "@letta-ai/letta-client": "^1.7.6",
3142
3142
  glob: "^13.0.0",
3143
3143
  "ink-link": "^5.0.0",
3144
3144
  open: "^10.2.0",
@@ -5591,7 +5591,451 @@ var init_general_purpose = () => {};
5591
5591
  // src/agent/subagents/builtin/memory.md
5592
5592
  var memory_default = `---
5593
5593
  name: memory
5594
- description: Reflect on and reorganize agent memory blocks - decide what to write, edit, delete, rename, split, or merge learned context
5594
+ description: Restructure memory blocks into focused, scannable, hierarchically-named blocks (use \`/\` naming)
5595
+ tools: Read, Edit, Write, Glob, Grep, Bash, conversation_search
5596
+ model: opus
5597
+ memoryBlocks: none
5598
+ mode: stateless
5599
+ permissionMode: bypassPermissions
5600
+ ---
5601
+
5602
+ You are a memory subagent launched via the Task tool to create a better structure of the memories store in files.. You run autonomously and return a **single final report** when done. You **cannot ask questions** mid-execution.
5603
+
5604
+ ## Goal
5605
+
5606
+ Your goal is to **explode** a few large memory blocks into a **deeply hierarchical structure of 15–25 small, focused files**.
5607
+
5608
+ You propose a new organization scheme that best captures the underlying memories, then implement it aggressively—creating new files, deleting old files, and renaming files until the directory is optimally structured.
5609
+
5610
+ ### Target Output
5611
+
5612
+ | Metric | Target |
5613
+ |--------|--------|
5614
+ | **Total files** | 15–25 (aim for ~20) |
5615
+ | **Max lines per file** | ~40 lines (split if larger) |
5616
+ | **Hierarchy depth** | 2–3 levels using \`/\` naming (e.g., \`project/tooling/bun\`) |
5617
+ | **Nesting requirement** | Every new block MUST be nested under a parent using \`/\` |
5618
+
5619
+ **Anti-patterns to avoid:**
5620
+ - ❌ Ending with only 3–5 large files
5621
+ - ❌ Flat naming (all blocks at top level)
5622
+ - ❌ Mega-blocks with 10+ sections
5623
+ - ❌ Single-level hierarchy (only \`project.md\`, \`human.md\`)
5624
+
5625
+ ## Scope and constraints (non-negotiable)
5626
+
5627
+ **The parent agent handles backup and creates memory files.** You only work inside \`.letta/backups/working/\`.
5628
+
5629
+ - ✅ Reorganize all the files in \`.letta/backups/working/\` so that they are hierarchical and well managed.
5630
+ - ✅ Rename/split/merge blocks when it improves structure
5631
+ - ✅ Delete blocks **only after** their content is fully consolidated elsewhere
5632
+ - ✅ Produce a detailed report with decisions and before/after examples
5633
+ - ❌ Do not run backup or restore scripts
5634
+ - ❌ Do not invent new facts; reorganize and clarify existing information only
5635
+
5636
+ ## Guiding principles (use these to decide what to do)
5637
+
5638
+ 1. **Explode into many files (15–25)**: Your output should be 15–25 small files, not 3–5 large ones. Split aggressively.
5639
+ 2. **Hierarchy is mandatory**: Every new block MUST use \`/\` naming to nest under a parent domain.
5640
+ - ✅ Good: \`human/prefs/communication\`, \`project/tooling/bun\`, \`project/gotchas/testing\`
5641
+ - ❌ Bad: \`communication_prefs.md\`, \`bun_notes.md\` (flat names)
5642
+ 3. **Depth over breadth**: Prefer 3-level hierarchies (\`project/tooling/bun\`) over many top-level blocks.
5643
+ 4. **Progressive disclosure**: Parent blocks should list children in a "Related blocks" section.
5644
+ 5. **One concept per file**: If a block has 2+ distinct topics, it should be 2+ files.
5645
+ 6. **40-line max**: If a file exceeds ~40 lines, split it further.
5646
+ 7. **Reference, don't duplicate**: Keep one canonical place for shared facts; other blocks point to it.
5647
+ 8. **Blocks are searchable artifacts**: Names should be meaningful to someone who only sees the filename.
5648
+ 9. **Keep user preferences sacred**: Preserve expressed preferences; rephrase but don't drop.
5649
+ 10. **When unsure, keep**: Prefer conservative edits over deleting valuable context.
5650
+
5651
+ ### Example Target Structure (what success looks like)
5652
+
5653
+ Starting from 3 files (\`project.md\`, \`human.md\`, \`persona.md\`), you should end with something like:
5654
+
5655
+ \`\`\`
5656
+ .letta/backups/working/
5657
+ ├── human.md # Index: points to children
5658
+ ├── human/
5659
+ │ ├── background.md # Who they are
5660
+ │ ├── prefs.md # Index for preferences
5661
+ │ ├── prefs/
5662
+ │ │ ├── communication.md # How they like to communicate
5663
+ │ │ ├── coding_style.md # Code formatting preferences
5664
+ │ │ └── review_style.md # PR/code review preferences
5665
+ │ └── context.md # Current project context
5666
+ ├── project.md # Index: points to children
5667
+ ├── project/
5668
+ │ ├── overview.md # What the project is
5669
+ │ ├── architecture.md # System design
5670
+ │ ├── tooling.md # Index for tooling
5671
+ │ ├── tooling/
5672
+ │ │ ├── bun.md # Bun-specific notes
5673
+ │ │ ├── testing.md # Test framework details
5674
+ │ │ └── linting.md # Linter configuration
5675
+ │ ├── conventions.md # Code conventions
5676
+ │ └── gotchas.md # Footguns and warnings
5677
+ ├── persona.md # Index: points to children
5678
+ └── persona/
5679
+ ├── role.md # Agent's role definition
5680
+ ├── behavior.md # How to behave
5681
+ └── constraints.md # What not to do
5682
+ \`\`\`
5683
+
5684
+ This example has **~20 files** with **3 levels of hierarchy**. Your output should look similar.
5685
+
5686
+ ## Actions available
5687
+
5688
+ - **KEEP + CLEAN**: Remove cruft, add structure, resolve contradictions.
5689
+ - **RENAME**: Change block name to match contents and improve searchability.
5690
+ - **SPLIT (DECOMPOSE)**: Extract distinct concepts into focused blocks (**prefer nested names**).
5691
+ - **MERGE**: Consolidate overlapping blocks into one canonical block, remove duplicates, then delete originals.
5692
+ - **DETACH**: Mark as detached when it’s not needed by default but should remain discoverable.
5693
+
5694
+ ## Operating procedure
5695
+
5696
+ ### Step 1: Read
5697
+
5698
+ The parent agent has already backed up memory files to \`.letta/backups/working/\`. Your job is to read and edit these files.
5699
+
5700
+ First, list what files are available:
5701
+
5702
+ \`\`\`bash
5703
+ ls .letta/backups/working/
5704
+ \`\`\`
5705
+
5706
+ Then read **all** relevant memory block files (examples):
5707
+
5708
+ \`\`\`
5709
+ Read({ file_path: ".letta/backups/working/project.md" })
5710
+ Read({ file_path: ".letta/backups/working/persona.md" })
5711
+ Read({ file_path: ".letta/backups/working/human.md" })
5712
+ \`\`\`
5713
+
5714
+ Before you edit anything, you MUST first **propose a new organization**:
5715
+ - Draft the **target hierarchy** (the \`/\`-named block set you want to end up with).
5716
+ - **Target 15–25 files total** — if your proposed structure has fewer than 15 files, split more aggressively.
5717
+ - **Use 2–3 levels of \`/\` nesting** — e.g., \`project/tooling/bun.md\`, not just \`project/tooling.md\`.
5718
+ - Be **aggressive about splitting**: if a block contains 2+ concepts, it should become 2+ files.
5719
+ - Keep each file to ~40 lines max; if larger, split further.
5720
+ - Include your proposed hierarchy as a "Proposed structure" section at the start of your final report, then execute it.
5721
+
5722
+ **Checkpoint before proceeding:** Count your proposed files. If < 15, go back and split more.
5723
+
5724
+ ### Step 2: Identify system-managed blocks (skip)
5725
+
5726
+ Do **not** edit:
5727
+ - \`skills.md\` (auto-generated; will be overwritten)
5728
+ - \`loaded_skills.md\` (system-managed)
5729
+ - \`manifest.json\` (metadata)
5730
+
5731
+ Focus on user-managed blocks like:
5732
+ - \`persona.md\` (agent behavioral adaptations/preferences)
5733
+ - \`human.md\` (user identity/context/preferences)
5734
+ - \`project.md\` (project/codebase-specific conventions, workflows, gotchas)
5735
+ - any other non-system blocks present
5736
+
5737
+ ### Step 3: Defragment block-by-block
5738
+
5739
+ For each editable block, decide one primary action (keep/clean, split, merge, rename, detach, delete), then execute it.
5740
+
5741
+ #### Naming convention (MANDATORY)
5742
+
5743
+ **All new files MUST use \`/\` nested naming.** This is non-negotiable.
5744
+
5745
+ | Depth | Example | When to use |
5746
+ |-------|---------|-------------|
5747
+ | Level 1 | \`project.md\` | Only for index files that point to children |
5748
+ | Level 2 | \`project/tooling.md\` | Main topic areas |
5749
+ | Level 3 | \`project/tooling/bun.md\` | Specific details |
5750
+
5751
+ ✅ **Good examples:**
5752
+ - \`human/prefs/communication.md\`
5753
+ - \`project/tooling/testing.md\`
5754
+ - \`persona/behavior/tone.md\`
5755
+
5756
+ ❌ **Bad examples (never do this):**
5757
+ - \`communication_prefs.md\` (flat, not nested)
5758
+ - \`bun.md\` (orphan file, no parent)
5759
+ - \`project_testing.md\` (underscore instead of \`/\`)
5760
+
5761
+ Rules:
5762
+ - Keep only 3 top-level index files: \`persona.md\`, \`human.md\`, \`project.md\`
5763
+ - **Every other file MUST be nested** under one of these using \`/\`
5764
+ - Go 2–3 levels deep: \`project/tooling/bun.md\` is better than \`project/bun.md\`
5765
+ - Parent files should contain a **Related blocks** section listing children
5766
+
5767
+ #### How to split (decompose) — BE AGGRESSIVE
5768
+
5769
+ **Split early and often.** Your goal is 15–25 files, so split more than feels necessary.
5770
+
5771
+ Split when:
5772
+ - A block has **40+ lines** (lower threshold than typical)
5773
+ - A block has **2+ distinct concepts** (not 3+, be aggressive)
5774
+ - A section could stand alone as its own file
5775
+ - You can name the extracted content with a clear \`/\` path
5776
+
5777
+ Process:
5778
+ 1. Extract each concept into a focused block with nested naming (e.g., \`project/tooling/bun.md\`)
5779
+ 2. Convert the original file to an index that points to children via **Related blocks**
5780
+ 3. Remove duplicates during extraction (canonicalize facts into the best home)
5781
+ 4. Repeat recursively until each file is <40 lines with one concept
5782
+
5783
+ **If in doubt, split.** Too many small files is better than too few large ones.
5784
+
5785
+ #### How to merge
5786
+
5787
+ Merge when multiple blocks overlap or are too small (<20 lines) and belong together.
5788
+ - Create the consolidated block (prefer a name that fits the hierarchy).
5789
+ - Remove duplicates.
5790
+ - **Delete** the originals after consolidation (the restore flow will prompt the user).
5791
+
5792
+ #### How to clean (within a block)
5793
+
5794
+ Prefer:
5795
+ - short headers (\`##\`, \`###\`)
5796
+ - small lists
5797
+ - tables for structured facts
5798
+ - “Procedure” sections for workflows
5799
+
5800
+ Actively fix:
5801
+ - redundancy
5802
+ - contradictions (rewrite into conditional guidance)
5803
+ - stale warnings (verify before keeping)
5804
+ - overly emotional urgency (tone down unless it’s a genuine footgun)
5805
+
5806
+ ### Step 4: Produce a decision-focused final report
5807
+
5808
+ Your output is a single markdown report that mirrors the reference example style: principles-driven, decision-centric, and scannable.
5809
+
5810
+ #### Required report sections
5811
+
5812
+ ##### 1) Summary
5813
+ - What changed in 2–3 sentences
5814
+ - **Total file count** (must be 15–25; if not, explain why)
5815
+ - Counts: edited / renamed / created / deleted
5816
+ - A short description of the **hierarchy created** (what parent domains exist and what children were created)
5817
+ - **Maximum hierarchy depth achieved** (should be 2–3 levels)
5818
+ - Note that the parent agent will confirm any creations/deletions during restore
5819
+
5820
+ ##### 2) Structural changes
5821
+ Include tables for:
5822
+ - **Renames**: old → new, reason (call out hierarchy improvements explicitly)
5823
+ - **Splits**: original → new blocks, whether original deleted, reason (show nested names)
5824
+ - **Merges**: merged blocks → result, which deleted, reason
5825
+ - **New blocks**: block name, size (chars), reason
5826
+
5827
+ ##### 3) Block-by-block decisions
5828
+ For each block you touched:
5829
+ - **Original state**: short characterization (what it contained / issues)
5830
+ - **Decision**: KEEP+CLEAN / SPLIT / MERGE / RENAME / DETACH / DELETE
5831
+ - **Reasoning**: 3–6 bullets grounded in the guiding principles (especially hierarchy)
5832
+ - **Action items performed**: what edits/renames/splits you actually executed
5833
+
5834
+ ##### 4) Content changes
5835
+ For each edited file:
5836
+ - Before chars, after chars, delta and %
5837
+ - What redundancy/contradictions/staleness you fixed
5838
+
5839
+ ##### 5) Before/after examples
5840
+ Show 2–4 high-signal examples (short excerpts) demonstrating:
5841
+ - redundancy removal,
5842
+ - contradiction resolution,
5843
+ - and/or a workflow rewritten into a procedure.
5844
+
5845
+ ## Final Checklist (verify before submitting)
5846
+
5847
+ Before you submit your report, confirm:
5848
+
5849
+ - [ ] **File count is 15–25** — Count your files. If < 15, split more.
5850
+ - [ ] **All new files use \`/\` naming** — No flat files like \`my_notes.md\`
5851
+ - [ ] **Hierarchy is 2–3 levels deep** — e.g., \`project/tooling/bun.md\`
5852
+ - [ ] **No file exceeds ~40 lines** — Split larger files
5853
+ - [ ] **Each file has one concept** — If 2+ topics, split into 2+ files
5854
+ - [ ] **Parent files have "Related blocks" sections** — Index files point to children
5855
+
5856
+ **If you have fewer than 15 files, you haven't split enough. Go back and split more.**
5857
+
5858
+ ## Reminder
5859
+
5860
+ Your goal is not to maximize deletion; it is to **explode monolithic memory into a deeply hierarchical structure of 15–25 small, focused files**. The primary tool for discoverability is **hierarchical \`/\` naming**.
5861
+ ---
5862
+ name: memory
5863
+ description: Defragment and reorganize agent memory blocks (edit/rename/split/merge/delete) into focused, scannable, hierarchically-named blocks
5864
+ tools: Read, Edit, Write, Glob, Grep, Bash, conversation_search
5865
+ model: opus
5866
+ memoryBlocks: none
5867
+ mode: stateless
5868
+ permissionMode: bypassPermissions
5869
+ ---
5870
+
5871
+ You are a memory defragmentation subagent launched via the Task tool to clean up and reorganize memory block files. You run autonomously and return a **single final report** when done. You **cannot ask questions** mid-execution.
5872
+
5873
+ ## Mission
5874
+
5875
+ **Explode** messy memory into a **deeply hierarchical structure of 15–25 small, focused files** that are easy to:
5876
+ - maintain,
5877
+ - search,
5878
+ - and selectively load later.
5879
+
5880
+ ### Target Output
5881
+
5882
+ | Metric | Target |
5883
+ |--------|--------|
5884
+ | **Total files** | 15–25 (aim for ~20) |
5885
+ | **Max lines per file** | ~40 lines |
5886
+ | **Hierarchy depth** | 2–3 levels using \`/\` naming |
5887
+ | **Nesting requirement** | Every new block MUST be nested under a parent |
5888
+
5889
+ You accomplish this by aggressively splitting blocks, using \`/\` naming for hierarchy, and removing redundancy.
5890
+
5891
+ ## Scope and constraints (non-negotiable)
5892
+
5893
+ **The parent agent handles backup and restore.** You only work inside \`.letta/backups/working/\`.
5894
+
5895
+ - ✅ Read and edit memory block files in \`.letta/backups/working/\`
5896
+ - ✅ Rename/split/merge blocks when it improves structure
5897
+ - ✅ Delete blocks **only after** their content is fully consolidated elsewhere
5898
+ - ✅ Produce a detailed report with decisions and before/after examples
5899
+ - ❌ Do not run backup or restore scripts
5900
+ - ❌ Do not invent new facts; reorganize and clarify existing information only
5901
+
5902
+ ## Guiding principles (use these to decide what to do)
5903
+
5904
+ 1. **Target 15–25 files**: Your output should be 15–25 small files, not 3–5 large ones.
5905
+ 2. **Hierarchy is mandatory**: Every new block MUST use \`/\` naming (e.g., \`project/tooling/bun.md\`).
5906
+ 3. **Depth over breadth**: Prefer 3-level hierarchies over many top-level blocks.
5907
+ 4. **One concept per file**: If a block has 2+ topics, split into 2+ files.
5908
+ 5. **40-line max**: If a file exceeds ~40 lines, split it further.
5909
+ 6. **Progressive disclosure**: Parent blocks list children in a "Related blocks" section.
5910
+ 7. **Reference, don't duplicate**: Keep one canonical place for shared facts.
5911
+ 8. **When unsure, split**: Too many small files is better than too few large ones.
5912
+
5913
+ ## Actions available
5914
+
5915
+ - **SPLIT (DECOMPOSE)**: The primary action. Extract concepts into focused, nested blocks.
5916
+ - **KEEP + CLEAN**: Remove cruft, add structure, resolve contradictions.
5917
+ - **RENAME**: Change block name to match contents and fit the hierarchy.
5918
+ - **MERGE**: Consolidate overlapping blocks, then delete originals.
5919
+ - **DELETE**: Only if redundant/empty AND content is preserved elsewhere.
5920
+
5921
+ ## Operating procedure
5922
+
5923
+ ### Step 1: Inventory
5924
+
5925
+ The parent agent has already backed up memory files to \`.letta/backups/working/\`. Your job is to read and edit these files.
5926
+
5927
+ First, list what files are available:
5928
+
5929
+ \`\`\`bash
5930
+ ls .letta/backups/working/
5931
+ \`\`\`
5932
+
5933
+ Then read relevant memory block files (examples):
5934
+
5935
+ \`\`\`
5936
+ Read({ file_path: ".letta/backups/working/project.md" })
5937
+ Read({ file_path: ".letta/backups/working/persona.md" })
5938
+ Read({ file_path: ".letta/backups/working/human.md" })
5939
+ \`\`\`
5940
+
5941
+ ### Step 2: Identify system-managed blocks (skip)
5942
+
5943
+ Do **not** edit:
5944
+ - \`skills.md\` (auto-generated; will be overwritten)
5945
+ - \`loaded_skills.md\` (system-managed)
5946
+ - \`manifest.json\` (metadata)
5947
+
5948
+ Focus on user-managed blocks like:
5949
+ - \`persona.md\` (agent behavioral adaptations/preferences)
5950
+ - \`human.md\` (user identity/context/preferences)
5951
+ - \`project.md\` (project/codebase-specific conventions, workflows, gotchas)
5952
+ - any other non-system blocks present
5953
+
5954
+ ### Step 3: Defragment block-by-block
5955
+
5956
+ For each editable block, decide one primary action (keep/clean, split, merge, rename, detach, delete), then execute it.
5957
+
5958
+ #### Naming convention (match the reference example)
5959
+
5960
+ Use **nested naming** with \`/\` to create a hierarchy (like folders). Examples:
5961
+ - \`human/personal_info\`, \`human/prefs\`
5962
+ - \`project/architecture\`, \`project/dev_workflow\`, \`project/gotchas\`
5963
+
5964
+ Rules of thumb:
5965
+ - Keep top-level blocks for the most universal concepts (\`persona\`, \`human\`, \`project\`).
5966
+ - Use nested names for shards created during defrag.
5967
+ - Prefer names that would make sense to another agent who only sees the name.
5968
+
5969
+ #### How to split (decompose)
5970
+
5971
+ Split when a block is long (~100+ lines) or contains 3+ distinct concepts.
5972
+ - Extract each concept into a focused block.
5973
+ - In the “parent” block, add a small **Related blocks** section pointing to children.
5974
+ - Remove duplicates during extraction (canonicalize facts into the best home).
5975
+
5976
+ #### How to merge
5977
+
5978
+ Merge when multiple blocks overlap or are too small (<20 lines) and belong together.
5979
+ - Create the consolidated block.
5980
+ - Remove duplicates.
5981
+ - **Delete** the originals after consolidation (the restore flow will prompt the user).
5982
+
5983
+ #### How to clean (within a block)
5984
+
5985
+ Prefer:
5986
+ - short headers (\`##\`, \`###\`)
5987
+ - small lists
5988
+ - tables for structured facts
5989
+ - “Procedure” sections for workflows
5990
+
5991
+ Actively fix:
5992
+ - redundancy
5993
+ - contradictions (rewrite into conditional guidance)
5994
+ - stale warnings (verify before keeping)
5995
+ - overly emotional urgency (tone down unless it’s a genuine footgun)
5996
+
5997
+ ### Step 4: Produce a decision-focused final report
5998
+
5999
+ Your output is a single markdown report that mirrors the reference example style: principles-driven, decision-centric, and scannable.
6000
+
6001
+ #### Required report sections
6002
+
6003
+ ##### 1) Summary
6004
+ - What changed in 2–3 sentences
6005
+ - Counts: edited / renamed / created / deleted
6006
+ - Note that the parent agent will confirm any creations/deletions during restore
6007
+
6008
+ ##### 2) Structural changes
6009
+ Include tables for:
6010
+ - **Renames**: old → new, reason
6011
+ - **Splits**: original → new blocks, whether original deleted, reason
6012
+ - **Merges**: merged blocks → result, which deleted, reason
6013
+ - **New blocks**: block name, size (chars), reason
6014
+
6015
+ ##### 3) Block-by-block decisions
6016
+ For each block you touched:
6017
+ - **Original state**: short characterization (what it contained / issues)
6018
+ - **Decision**: KEEP+CLEAN / SPLIT / MERGE / RENAME / DETACH / DELETE
6019
+ - **Reasoning**: 3–6 bullets grounded in the guiding principles
6020
+ - **Action items performed**: what edits/renames/splits you actually executed
6021
+
6022
+ ##### 4) Content changes
6023
+ For each edited file:
6024
+ - Before chars, after chars, delta and %
6025
+ - What redundancy/contradictions/staleness you fixed
6026
+
6027
+ ##### 5) Before/after examples
6028
+ Show 2–4 high-signal examples (short excerpts) demonstrating:
6029
+ - redundancy removal,
6030
+ - contradiction resolution,
6031
+ - and/or a workflow rewritten into a procedure.
6032
+
6033
+ ## Reminder
6034
+
6035
+ Your goal is to **explode monolithic memory into 15–25 small, hierarchically-nested files**. If you have fewer than 15 files, you haven't split enough.
6036
+ ---
6037
+ name: memory
6038
+ description: Explode memory into 15-25 hierarchically-nested files using \`/\` naming
5595
6039
  tools: Read, Edit, Write, Glob, Grep, Bash, conversation_search
5596
6040
  model: opus
5597
6041
  memoryBlocks: none
@@ -5603,12 +6047,23 @@ You are a memory management subagent launched via the Task tool to clean up and
5603
6047
 
5604
6048
  ## Your Purpose
5605
6049
 
5606
- You edit memory block files to make them clean, well-organized, and scannable by:
5607
- 1. **Removing redundancy** - Delete duplicate information
5608
- 2. **Adding structure** - Use markdown headers, bullet points, sections
5609
- 3. **Resolving contradictions** - Fix conflicting statements
5610
- 4. **Improving scannability** - Make content easy to read at a glance
5611
- 5. **Restructuring blocks** - Rename, decompose, or merge blocks as needed
6050
+ **Explode** a few large memory blocks into a **deeply hierarchical structure of 15–25 small, focused files**.
6051
+
6052
+ ### Target Output
6053
+
6054
+ | Metric | Target |
6055
+ |--------|--------|
6056
+ | **Total files** | 15–25 (aim for ~20) |
6057
+ | **Max lines per file** | ~40 lines |
6058
+ | **Hierarchy depth** | 2–3 levels using \`/\` naming |
6059
+ | **Nesting requirement** | Every new block MUST use \`/\` naming |
6060
+
6061
+ You achieve this by:
6062
+ 1. **Aggressively splitting** - Every block with 2+ concepts becomes 2+ files
6063
+ 2. **Using \`/\` hierarchy** - All new files are nested (e.g., \`project/tooling/bun.md\`)
6064
+ 3. **Keeping files small** - Max ~40 lines per file; split if larger
6065
+ 4. **Removing redundancy** - Delete duplicate information during splits
6066
+ 5. **Adding structure** - Use markdown headers, bullet points, sections
5612
6067
 
5613
6068
  ## Important: Your Role is File Editing ONLY
5614
6069
 
@@ -5651,6 +6106,56 @@ Read({ file_path: ".letta/backups/working/human.md" })
5651
6106
  - \`loaded_skills.md\` - System-managed
5652
6107
  - \`manifest.json\` - Metadata file
5653
6108
 
6109
+
6110
+ ### Propose Optimal Hierarchical Organizational Structure
6111
+
6112
+ Before you edit, propose a **clear hierarchy** for each memory block so information has an obvious “home” and you avoid duplicating facts across sections.
6113
+
6114
+ **Recommended hierarchy (within a single memory block):**
6115
+ - Use \`##\` for **major categories** (stable top-level buckets)
6116
+ - Use \`###\` for **subcategories** (group related details)
6117
+ - Use \`####\` for **high-churn details** or tightly-scoped lists (things you expect to update often)
6118
+
6119
+ **Recommended hierarchy (across multiple memory blocks):**
6120
+ - Keep blocks **topic-scoped**, not “everything.md” scoped.
6121
+ - Put the *most stable*, highest-signal info in fewer, well-named blocks.
6122
+ - Put volatile or frequently changing info into smaller, more focused blocks.
6123
+
6124
+ **Naming conventions (blocks and headings):**
6125
+ - Prefer **noun phrases** and **consistent casing** (e.g., “Coding Preferences”, “Project Context”).
6126
+ - Avoid vague names (“Misc”, “Notes”, “Stuff”) unless it’s truly temporary.
6127
+ - Prefer **one topic per heading**; avoid headings that imply overlap (“General”, “Other”).
6128
+
6129
+ **Example structure (good):**
6130
+ - \`project.md\`
6131
+ - \`## Overview\`
6132
+ - \`## Repo Conventions\`
6133
+ - \`### Tooling\`
6134
+ - \`### Code Style\`
6135
+ - \`### Testing\`
6136
+ - \`## Architecture\`
6137
+ - \`### Key Components\`
6138
+ - \`### Data Flow\`
6139
+ - \`human.md\`
6140
+ - \`## Background\`
6141
+ - \`## Preferences\`
6142
+ - \`### Communication\`
6143
+ - \`### Coding Style\`
6144
+ - \`### Review Style\`
6145
+ - \`persona.md\`
6146
+ - \`## Role\`
6147
+ - \`## Behavior\`
6148
+ - \`## Constraints\`
6149
+
6150
+
6151
+
6152
+ **When to split vs. keep together:**
6153
+ - Split when a section becomes a “grab bag” (3+ unrelated bullets) or exceeds ~1–2 screens of scrolling.
6154
+ - Keep together when items share a single decision context (e.g., all “Code Style” rules used during editing).
6155
+
6156
+ **Output format expectation:**
6157
+ - End this step with a short proposed outline per file (just headings), then implement it during the edits in Step 2.
6158
+
5654
6159
  ### Step 2: Edit Files to Clean Them Up
5655
6160
 
5656
6161
  Edit each file using the Edit tool:
@@ -5662,29 +6167,17 @@ Edit({
5662
6167
  new_string: "..."
5663
6168
  })
5664
6169
  \`\`\`
6170
+ ## Output Format
5665
6171
 
5666
- **What to fix:**
5667
- - **Redundancy**: Remove duplicate information (version mentioned 3x, preferences repeated)
5668
- - **Structure**: Add markdown headers (##, ###), bullet points, sections
5669
- - **Clarity**: Resolve contradictions ("be detailed" vs "be concise")
5670
- - **Scannability**: Make content easy to read at a glance
5671
-
5672
- **Good memory structure:**
5673
- - Use markdown headers (##, ###) for sections
5674
- - Use bullet points for lists
5675
- - Keep related information together
5676
- - Make it scannable
5677
-
5678
- ### Step 2b: Structural Changes (Rename, Decompose, Merge)
6172
+ ### Implement The Organizational Structure
5679
6173
 
5680
- Beyond editing content, you can restructure memory blocks when needed:
6174
+ Once you've proposed the hierarchy, execute it using file operations. Keep iterating until the directory matches your proposed structure exactly.
5681
6175
 
5682
6176
  #### Renaming Blocks
5683
6177
 
5684
- When a block's name doesn't reflect its content, rename it:
6178
+ When a block's name doesn't reflect its content:
5685
6179
 
5686
6180
  \`\`\`bash
5687
- # Rename a memory block file
5688
6181
  mv .letta/backups/working/old_name.md .letta/backups/working/new_name.md
5689
6182
  \`\`\`
5690
6183
 
@@ -5693,140 +6186,101 @@ mv .letta/backups/working/old_name.md .letta/backups/working/new_name.md
5693
6186
  - Block name doesn't match content (e.g., \`project.md\` contains user info → \`user_context.md\`)
5694
6187
  - Name uses poor conventions (e.g., \`NOTES.md\` → \`notes.md\`)
5695
6188
 
6189
+ #### Creating New Blocks
6190
+
6191
+ Create new \`.md\` files when content needs a new home:
6192
+
6193
+ \`\`\`
6194
+ Write({
6195
+ file_path: ".letta/backups/working/new_block.md",
6196
+ content: "## New Block\\n\\nContent here..."
6197
+ })
6198
+ \`\`\`
6199
+
6200
+ **When to create:**
6201
+ - Splitting a large block into focused smaller blocks
6202
+ - Content doesn't fit any existing block
6203
+ - A new category emerges from reorganization
6204
+
5696
6205
  #### Decomposing Blocks (Split)
5697
6206
 
5698
- When a single block contains too many unrelated topics, split it into focused blocks:
6207
+ When a block covers too many topics, split it:
5699
6208
 
5700
6209
  \`\`\`bash
5701
- # 1. Read the original block
6210
+ # 1. Read the original
5702
6211
  Read({ file_path: ".letta/backups/working/everything.md" })
5703
6212
 
5704
- # 2. Create new focused blocks
6213
+ # 2. Create focused blocks
5705
6214
  Write({ file_path: ".letta/backups/working/coding_preferences.md", content: "..." })
5706
6215
  Write({ file_path: ".letta/backups/working/user_info.md", content: "..." })
5707
6216
 
5708
- # 3. Delete the original bloated block
6217
+ # 3. Delete the original
5709
6218
  rm .letta/backups/working/everything.md
5710
6219
  \`\`\`
5711
6220
 
5712
- **When to decompose:**
5713
- - Block exceeds ~100 lines with multiple unrelated sections
5714
- - Block contains 3+ distinct topic areas (e.g., user info + coding prefs + project details)
5715
- - Block name can't capture all its content accurately
5716
- - Finding specific info requires scanning the whole block
5717
-
5718
- **Decomposition guidelines:**
5719
- - Each new block should have ONE clear purpose
5720
- - Use descriptive names: \`coding_style.md\`, \`user_preferences.md\`, \`project_context.md\`
5721
- - Preserve all information - just reorganize it
5722
- - Keep related information together in the same block
6221
+ **When to split (be aggressive):**
6222
+ - Block exceeds ~60 lines or has 2+ distinct topics
6223
+ - Block name can't capture all its content
6224
+ - Finding info requires scanning the whole block
5723
6225
 
5724
- #### Creating New Blocks
6226
+ #### Merging Blocks
5725
6227
 
5726
- You can create entirely new memory blocks by writing new \`.md\` files:
6228
+ When multiple blocks overlap, consolidate them:
5727
6229
 
5728
6230
  \`\`\`bash
5729
- Write({
5730
- file_path: ".letta/backups/working/new_block.md",
5731
- content: "## New Block\\n\\nContent here..."
5732
- })
5733
- \`\`\`
6231
+ # 1. Read blocks to merge
6232
+ Read({ file_path: ".letta/backups/working/user_info.md" })
6233
+ Read({ file_path: ".letta/backups/working/user_prefs.md" })
5734
6234
 
5735
- **When to create new blocks:**
5736
- - Splitting a large block (>150 lines) into focused smaller blocks
5737
- - Organizing content into a new category that doesn't fit existing blocks
5738
- - The parent agent will prompt the user for confirmation before creating
6235
+ # 2. Create unified block
6236
+ Write({ file_path: ".letta/backups/working/user.md", content: "..." })
5739
6237
 
5740
- #### Merging and Deleting Blocks
6238
+ # 3. Delete old blocks
6239
+ rm .letta/backups/working/user_info.md .letta/backups/working/user_prefs.md
6240
+ \`\`\`
5741
6241
 
5742
- When multiple blocks contain related/overlapping content, consolidate them and DELETE the old blocks:
6242
+ **When to merge:**
6243
+ - Multiple blocks cover the same topic
6244
+ - Small blocks (<20 lines) logically belong together
6245
+ - Overlapping/duplicate content exists
5743
6246
 
5744
- \`\`\`bash
5745
- # 1. Read all blocks to merge
5746
- Read({ file_path: ".letta/backups/working/user_info.md" })
5747
- Read({ file_path: ".letta/backups/working/user_prefs.md" })
6247
+ #### Editing Content Within Blocks
5748
6248
 
5749
- # 2. Create unified block with combined content
5750
- Write({ file_path: ".letta/backups/working/user.md", content: "..." })
6249
+ Use the Edit tool for in-place changes:
5751
6250
 
5752
- # 3. DELETE the old blocks using Bash
5753
- Bash({ command: "rm .letta/backups/working/user_info.md .letta/backups/working/user_prefs.md" })
6251
+ \`\`\`
6252
+ Edit({
6253
+ file_path: ".letta/backups/working/project.md",
6254
+ old_string: "...",
6255
+ new_string: "..."
6256
+ })
5754
6257
  \`\`\`
5755
6258
 
5756
- **IMPORTANT: When to delete blocks:**
5757
- - After consolidating content from multiple blocks into one
5758
- - When a block becomes nearly empty after moving content elsewhere
5759
- - When a block is redundant or no longer serves a purpose
5760
- - The parent agent will prompt the user for confirmation before deleting
6259
+ **What to fix:**
6260
+ - **Redundancy**: Remove duplicate information
6261
+ - **Structure**: Add markdown headers, bullet points
6262
+ - **Clarity**: Resolve contradictions
6263
+ - **Scannability**: Make content easy to read at a glance
5761
6264
 
5762
- **When to merge:**
5763
- - Multiple blocks cover the same topic area
5764
- - Information is fragmented across blocks, causing redundancy
5765
- - Small blocks (<20 lines) that logically belong together
5766
- - Blocks with overlapping/duplicate content
5767
-
5768
- **Merge guidelines:**
5769
- - Remove duplicates when combining
5770
- - Organize merged content with clear sections
5771
- - Choose the most descriptive name for the merged block
5772
- - Don't create blocks larger than ~150 lines
5773
- - **DELETE the old block files** after consolidating their content
5774
-
5775
- ### Step 3: Report Results
5776
-
5777
- Provide a comprehensive report showing what you changed and why.
5778
-
5779
- ## What to Write to Memory
5780
-
5781
- **DO write to memory:**
5782
- - Patterns that repeat across multiple sessions
5783
- - User corrections or clarifications (especially if repeated)
5784
- - Project conventions discovered through research or experience
5785
- - Important context that will be needed in future sessions
5786
- - Preferences expressed by the user about behavior or communication
5787
- - "Aha!" moments or insights about the codebase
5788
- - Footguns or gotchas discovered the hard way
5789
-
5790
- **DON'T write to memory:**
5791
- - Transient task details that won't matter tomorrow
5792
- - Information easily found in files (unless it's a critical pattern)
5793
- - Overly specific details that will quickly become stale
5794
- - Things that should go in TODO lists or plan files instead
5795
-
5796
- **Key principle**: Memory is for **persistent, important context** that makes the agent more effective over time. Not a dumping ground for everything.
5797
-
5798
- ## How to Decide What to Write
5799
-
5800
- Ask yourself:
5801
- 1. **Will future-me need this?** If the agent encounters a similar situation in a week, would this memory help?
5802
- 2. **Is this a pattern or one-off?** One-off details fade in importance; patterns persist.
5803
- 3. **Can I find this easily later?** If it's in a README that's always read, maybe it doesn't need to be in memory.
5804
- 4. **Did the user correct me?** User corrections are strong signals of what to remember.
5805
- 5. **Would I want to know this on day one?** Insights that would have saved time are worth storing.
5806
-
5807
- ## How to Reorganize Memory
5808
-
5809
- **Signs memory needs reorganization:**
5810
- - Blocks are long and hard to scan (>100 lines)
5811
- - Related content is scattered across blocks
5812
- - No clear structure (just walls of text)
5813
- - Redundant information in multiple places
5814
- - Outdated information mixed with current
5815
-
5816
- **Reorganization strategies:**
5817
- - **Add structure**: Use section headers, bullet points, categories
5818
- - **Rename blocks**: Give blocks names that accurately reflect their content
5819
- - **Decompose large blocks**: Break monolithic blocks (>100 lines, 3+ topics) into focused ones
5820
- - **Merge fragmented blocks**: Consolidate small/overlapping blocks into unified ones
5821
- - **Archive stale content**: Remove information that's no longer relevant
5822
- - **Improve scannability**: Use consistent formatting, clear hierarchies
6265
+ #### Iteration Checklist
5823
6266
 
5824
- ## Output Format
6267
+ Keep editing until:
6268
+ - [ ] **Total file count is 15–25** — Count your files; if < 15, split more
6269
+ - [ ] **All files use \`/\` naming** — No flat files like \`my_notes.md\`
6270
+ - [ ] **Hierarchy is 2–3 levels deep** — e.g., \`project/tooling/bun.md\`
6271
+ - [ ] **No file exceeds ~40 lines** — Split larger files
6272
+ - [ ] **Each file has one concept** — If 2+ topics, split into 2+ files
6273
+ - [ ] Content has been migrated (no data loss)
6274
+ - [ ] No duplicate information across blocks
6275
+
6276
+ **If you have fewer than 15 files, you haven't split enough. Go back and split more.**
5825
6277
 
5826
6278
  Return a structured report with these sections:
5827
6279
 
5828
6280
  ### 1. Summary
5829
6281
  - Brief overview of what you edited (2-3 sentences)
6282
+ - **Total file count** (must be 15–25)
6283
+ - **Maximum hierarchy depth achieved** (should be 2–3 levels)
5830
6284
  - Number of files modified, renamed, created, or deleted
5831
6285
  - The parent agent will prompt the user to confirm any creations or deletions
5832
6286
 
@@ -5839,15 +6293,16 @@ Report any renames, decompositions, or merges:
5839
6293
  |----------|----------|--------|
5840
6294
  | stuff.md | coding_preferences.md | Name now reflects content |
5841
6295
 
5842
- **Decompositions (splitting large blocks):**
6296
+ **Decompositions (using \`/\` hierarchy):**
5843
6297
  | Original Block | New Blocks | Deleted | Reason |
5844
6298
  |----------------|------------|---------|--------|
5845
- | everything.md | user.md, coding.md, project.md | ✅ everything.md | Block contained 3 unrelated topics |
6299
+ | project.md | project/overview.md, project/tooling/bun.md, project/tooling/testing.md, project/conventions.md, project/gotchas.md | ✅ content moved | Exploded into 5 nested files |
5846
6300
 
5847
- **New Blocks (created from scratch):**
6301
+ **New Blocks (all using \`/\` naming):**
5848
6302
  | Block Name | Size | Reason |
5849
6303
  |------------|------|--------|
5850
- | security_practices.md | 156 chars | New category for security-related conventions discovered |
6304
+ | project/security/auth.md | 156 chars | Nested under project/security |
6305
+ | human/prefs/communication.md | 98 chars | Split from human.md |
5851
6306
 
5852
6307
  **Merges:**
5853
6308
  | Merged Blocks | Result | Deleted | Reason |
@@ -5936,13 +6391,13 @@ Why: Resolved contradictions by explaining when to use each approach.
5936
6391
 
5937
6392
  ## Critical Reminders
5938
6393
 
5939
- 1. **You only edit files** - The parent agent handles backup and restore
5940
- 2. **Be conservative with deletions** - When in doubt, keep information
5941
- 3. **Preserve user preferences** - If the user expressed a preference, that's sacred
5942
- 4. **Don't invent information** - Only reorganize existing content
5943
- 5. **Test your changes mentally** - Imagine the parent agent reading this tomorrow
6394
+ 1. **Create new files** Reorganize large blocks into 15–25 small, nested files
6395
+ 2. **Remove old files** After moving content to new nested files, delete the originals
6396
+ 3. **Use \`/\` naming for ALL new files** Every new file must be nested (e.g., \`project/tooling/bun.md\`)
6397
+ 4. **Preserve user preferences** Keep expressed preferences, just reorganize them into the right files
6398
+ 5. **Don't invent information** Only reorganize existing content into better structure
5944
6399
 
5945
- Remember: Your goal is to make memory clean, scannable, and well-organized. You're improving the parent agent's long-term capabilities by maintaining quality memory.
6400
+ Remember: Your goal is to **completely reorganize** memory into a deeply hierarchical structure of 15–25 small files. You're not tidying up — you're exploding monolithic blocks into a proper file tree.
5946
6401
  `;
5947
6402
  var init_memory = () => {};
5948
6403
 
@@ -8156,7 +8611,17 @@ function buildShellLaunchers(command) {
8156
8611
  var SEP = "\x00";
8157
8612
 
8158
8613
  // src/hooks/types.ts
8159
- var init_types = () => {};
8614
+ function isToolEvent(event) {
8615
+ return TOOL_EVENTS.has(event);
8616
+ }
8617
+ var TOOL_EVENTS;
8618
+ var init_types = __esm(() => {
8619
+ TOOL_EVENTS = new Set([
8620
+ "PreToolUse",
8621
+ "PostToolUse",
8622
+ "PermissionRequest"
8623
+ ]);
8624
+ });
8160
8625
 
8161
8626
  // src/hooks/executor.ts
8162
8627
  import { spawn } from "node:child_process";
@@ -8222,11 +8687,25 @@ function executeWithLauncher(launcher, inputJson, workingDirectory, input, timeo
8222
8687
  if (!resolved) {
8223
8688
  resolved = true;
8224
8689
  const exitLabel = result.exitCode === 0 /* ALLOW */ ? "\x1B[32m✓ allowed\x1B[0m" : result.exitCode === 2 /* BLOCK */ ? "\x1B[31m✗ blocked\x1B[0m" : "\x1B[33m⚠ error\x1B[0m";
8225
- console.log(`\x1B[90m[hook] ${exitLabel} (${result.durationMs}ms)${result.stdout ? ` stdout: ${result.stdout.slice(0, 100)}` : ""}${result.stderr ? ` stderr: ${result.stderr.slice(0, 100)}` : ""}\x1B[0m`);
8690
+ console.log(`\x1B[90m[hook] ${command}\x1B[0m`);
8691
+ console.log(`\x1B[90m ⎿ ${exitLabel} (${result.durationMs}ms)\x1B[0m`);
8692
+ if (result.stdout) {
8693
+ console.log(`\x1B[90m ⎿ (stdout)\x1B[0m`);
8694
+ const indented = result.stdout.split(`
8695
+ `).map((line) => ` ${line}`).join(`
8696
+ `);
8697
+ console.log(`\x1B[90m${indented}\x1B[0m`);
8698
+ }
8699
+ if (result.stderr) {
8700
+ console.log(`\x1B[90m ⎿ (stderr)\x1B[0m`);
8701
+ const indented = result.stderr.split(`
8702
+ `).map((line) => ` ${line}`).join(`
8703
+ `);
8704
+ console.log(`\x1B[90m${indented}\x1B[0m`);
8705
+ }
8226
8706
  resolve2(result);
8227
8707
  }
8228
8708
  };
8229
- console.log(`\x1B[90m[hook] Running: ${command}\x1B[0m`);
8230
8709
  let child;
8231
8710
  try {
8232
8711
  child = trySpawnWithLauncher(launcher, workingDirectory, input);
@@ -8404,14 +8883,27 @@ function mergeHooksConfigs(global2, project, projectLocal = {}) {
8404
8883
  ...Object.keys(projectLocal)
8405
8884
  ]);
8406
8885
  for (const event of allEvents) {
8407
- const globalMatchers = global2[event] || [];
8408
- const projectMatchers = project[event] || [];
8409
- const projectLocalMatchers = projectLocal[event] || [];
8410
- merged[event] = [
8411
- ...projectLocalMatchers,
8412
- ...projectMatchers,
8413
- ...globalMatchers
8414
- ];
8886
+ if (isToolEvent(event)) {
8887
+ const toolEvent = event;
8888
+ const globalMatchers = global2[toolEvent] || [];
8889
+ const projectMatchers = project[toolEvent] || [];
8890
+ const projectLocalMatchers = projectLocal[toolEvent] || [];
8891
+ merged[toolEvent] = [
8892
+ ...projectLocalMatchers,
8893
+ ...projectMatchers,
8894
+ ...globalMatchers
8895
+ ];
8896
+ } else {
8897
+ const simpleEvent = event;
8898
+ const globalMatchers = global2[simpleEvent] || [];
8899
+ const projectMatchers = project[simpleEvent] || [];
8900
+ const projectLocalMatchers = projectLocal[simpleEvent] || [];
8901
+ merged[simpleEvent] = [
8902
+ ...projectLocalMatchers,
8903
+ ...projectMatchers,
8904
+ ...globalMatchers
8905
+ ];
8906
+ }
8415
8907
  }
8416
8908
  return merged;
8417
8909
  }
@@ -8434,23 +8926,36 @@ function matchesTool(pattern, toolName) {
8434
8926
  return pattern === toolName;
8435
8927
  }
8436
8928
  function getMatchingHooks(config, event, toolName) {
8437
- const matchers = config[event];
8438
- if (!matchers || matchers.length === 0) {
8439
- return [];
8440
- }
8441
- const hooks = [];
8442
- for (const matcher of matchers) {
8443
- if (!toolName || matchesTool(matcher.matcher, toolName)) {
8929
+ if (isToolEvent(event)) {
8930
+ const matchers = config[event];
8931
+ if (!matchers || matchers.length === 0) {
8932
+ return [];
8933
+ }
8934
+ const hooks = [];
8935
+ for (const matcher of matchers) {
8936
+ if (!toolName || matchesTool(matcher.matcher, toolName)) {
8937
+ hooks.push(...matcher.hooks);
8938
+ }
8939
+ }
8940
+ return hooks;
8941
+ } else {
8942
+ const matchers = config[event];
8943
+ if (!matchers || matchers.length === 0) {
8944
+ return [];
8945
+ }
8946
+ const hooks = [];
8947
+ for (const matcher of matchers) {
8444
8948
  hooks.push(...matcher.hooks);
8445
8949
  }
8950
+ return hooks;
8446
8951
  }
8447
- return hooks;
8448
8952
  }
8449
8953
  async function getHooksForEvent(event, toolName, workingDirectory = process.cwd()) {
8450
8954
  const config = await loadHooks(workingDirectory);
8451
8955
  return getMatchingHooks(config, event, toolName);
8452
8956
  }
8453
8957
  var init_loader = __esm(async () => {
8958
+ init_types();
8454
8959
  await init_settings_manager();
8455
8960
  });
8456
8961
 
@@ -61124,7 +61629,7 @@ function sortChronological2(messages) {
61124
61629
  async function getResumeData2(client, agent, conversationId) {
61125
61630
  try {
61126
61631
  let inContextMessageIds;
61127
- let messages;
61632
+ let messages = [];
61128
61633
  const useConversationsApi = conversationId && conversationId !== "default";
61129
61634
  if (process.env.DEBUG) {
61130
61635
  console.log(`[DEBUG] getResumeData: conversationId=${conversationId}, useConversationsApi=${useConversationsApi}, agentId=${agent.id}`);
@@ -61135,12 +61640,16 @@ async function getResumeData2(client, agent, conversationId) {
61135
61640
  if (!inContextMessageIds || inContextMessageIds.length === 0) {
61136
61641
  debugWarn("check-approval", "No in-context messages - no pending approvals");
61137
61642
  if (isBackfillEnabled2()) {
61138
- const backfill = await client.conversations.messages.list(conversationId, { limit: MESSAGE_HISTORY_LIMIT2, order: "desc" });
61139
- return {
61140
- pendingApproval: null,
61141
- pendingApprovals: [],
61142
- messageHistory: sortChronological2(backfill.getPaginatedItems())
61143
- };
61643
+ try {
61644
+ const backfill = await client.conversations.messages.list(conversationId, { limit: MESSAGE_HISTORY_LIMIT2, order: "desc" });
61645
+ return {
61646
+ pendingApproval: null,
61647
+ pendingApprovals: [],
61648
+ messageHistory: sortChronological2(backfill.getPaginatedItems())
61649
+ };
61650
+ } catch (backfillError) {
61651
+ debugWarn("check-approval", `Failed to load message history: ${backfillError instanceof Error ? backfillError.message : String(backfillError)}`);
61652
+ }
61144
61653
  }
61145
61654
  return {
61146
61655
  pendingApproval: null,
@@ -61153,11 +61662,17 @@ async function getResumeData2(client, agent, conversationId) {
61153
61662
  throw new Error("Expected at least one in-context message");
61154
61663
  }
61155
61664
  const retrievedMessages = await client.messages.retrieve(lastInContextId);
61156
- const backfillPage = isBackfillEnabled2() ? await client.conversations.messages.list(conversationId, {
61157
- limit: MESSAGE_HISTORY_LIMIT2,
61158
- order: "desc"
61159
- }) : null;
61160
- messages = backfillPage ? sortChronological2(backfillPage.getPaginatedItems()) : [];
61665
+ if (isBackfillEnabled2()) {
61666
+ try {
61667
+ const backfillPage = await client.conversations.messages.list(conversationId, {
61668
+ limit: MESSAGE_HISTORY_LIMIT2,
61669
+ order: "desc"
61670
+ });
61671
+ messages = sortChronological2(backfillPage.getPaginatedItems());
61672
+ } catch (backfillError) {
61673
+ debugWarn("check-approval", `Failed to load message history: ${backfillError instanceof Error ? backfillError.message : String(backfillError)}`);
61674
+ }
61675
+ }
61161
61676
  const messageToCheck = retrievedMessages.find((msg) => msg.message_type === "approval_request_message") ?? retrievedMessages[0];
61162
61677
  if (messageToCheck) {
61163
61678
  debugWarn("check-approval", `Found last in-context message: ${messageToCheck.id} (type: ${messageToCheck.message_type})` + (retrievedMessages.length > 1 ? ` - had ${retrievedMessages.length} variants` : ""));
@@ -61192,14 +61707,20 @@ async function getResumeData2(client, agent, conversationId) {
61192
61707
  throw new Error("Expected at least one in-context message");
61193
61708
  }
61194
61709
  const retrievedMessages = await client.messages.retrieve(lastInContextId);
61195
- const messagesPage = isBackfillEnabled2() ? await client.agents.messages.list(agent.id, {
61196
- limit: MESSAGE_HISTORY_LIMIT2,
61197
- order: "desc",
61198
- conversation_id: "default"
61199
- }) : null;
61200
- messages = messagesPage ? sortChronological2(messagesPage.items) : [];
61201
- if (process.env.DEBUG && messagesPage) {
61202
- console.log(`[DEBUG] agents.messages.list(conversation_id=default) returned ${messagesPage.items.length} messages`);
61710
+ if (isBackfillEnabled2()) {
61711
+ try {
61712
+ const messagesPage = await client.agents.messages.list(agent.id, {
61713
+ limit: MESSAGE_HISTORY_LIMIT2,
61714
+ order: "desc",
61715
+ conversation_id: "default"
61716
+ });
61717
+ messages = sortChronological2(messagesPage.items);
61718
+ if (process.env.DEBUG) {
61719
+ console.log(`[DEBUG] agents.messages.list(conversation_id=default) returned ${messagesPage.items.length} messages`);
61720
+ }
61721
+ } catch (backfillError) {
61722
+ debugWarn("check-approval", `Failed to load message history: ${backfillError instanceof Error ? backfillError.message : String(backfillError)}`);
61723
+ }
61203
61724
  }
61204
61725
  const messageToCheck = retrievedMessages.find((msg) => msg.message_type === "approval_request_message") ?? retrievedMessages[0];
61205
61726
  if (messageToCheck) {
@@ -71705,24 +72226,61 @@ async function addHookMatcher(event, matcher, location, workingDirectory = proce
71705
72226
  hooks[event] = [];
71706
72227
  }
71707
72228
  const eventMatchers = hooks[event];
71708
- if (eventMatchers) {
71709
- eventMatchers.push(matcher);
71710
- }
72229
+ eventMatchers.push(matcher);
71711
72230
  await saveHooksToLocation(hooks, location, workingDirectory);
71712
72231
  }
71713
- async function removeHookMatcher(event, matcherIndex, location, workingDirectory = process.cwd()) {
72232
+ async function addSimpleHookMatcher(event, matcher, location, workingDirectory = process.cwd()) {
71714
72233
  const hooks = loadHooksFromLocation(location, workingDirectory);
71715
- const eventMatchers = hooks[event];
71716
- if (!eventMatchers || matcherIndex < 0 || matcherIndex >= eventMatchers.length) {
71717
- throw new Error(`Invalid matcher index ${matcherIndex} for event ${event}`);
72234
+ if (!hooks[event]) {
72235
+ hooks[event] = [];
71718
72236
  }
71719
- eventMatchers.splice(matcherIndex, 1);
71720
- if (eventMatchers.length === 0) {
71721
- delete hooks[event];
72237
+ const eventMatchers = hooks[event];
72238
+ eventMatchers.push(matcher);
72239
+ await saveHooksToLocation(hooks, location, workingDirectory);
72240
+ }
72241
+ async function removeHook(event, index, location, workingDirectory = process.cwd()) {
72242
+ const hooks = loadHooksFromLocation(location, workingDirectory);
72243
+ if (isToolEvent(event)) {
72244
+ const eventMatchers = hooks[event];
72245
+ if (!eventMatchers || index < 0 || index >= eventMatchers.length) {
72246
+ throw new Error(`Invalid matcher index ${index} for event ${event}`);
72247
+ }
72248
+ eventMatchers.splice(index, 1);
72249
+ if (eventMatchers.length === 0) {
72250
+ delete hooks[event];
72251
+ }
72252
+ } else {
72253
+ const eventMatchers = hooks[event];
72254
+ if (!eventMatchers || index < 0 || index >= eventMatchers.length) {
72255
+ throw new Error(`Invalid matcher index ${index} for event ${event}`);
72256
+ }
72257
+ eventMatchers.splice(index, 1);
72258
+ if (eventMatchers.length === 0) {
72259
+ delete hooks[event];
72260
+ }
71722
72261
  }
71723
72262
  await saveHooksToLocation(hooks, location, workingDirectory);
71724
72263
  }
71725
- function loadHooksWithSource(event, workingDirectory = process.cwd()) {
72264
+ function loadMatchersWithSource(event, workingDirectory = process.cwd()) {
72265
+ const result = [];
72266
+ const locations = ["project-local", "project", "user"];
72267
+ for (const location of locations) {
72268
+ const hooks = loadHooksFromLocation(location, workingDirectory);
72269
+ const matchers = hooks[event] || [];
72270
+ for (let i = 0;i < matchers.length; i++) {
72271
+ const matcher = matchers[i];
72272
+ if (matcher) {
72273
+ result.push({
72274
+ ...matcher,
72275
+ source: location,
72276
+ sourceIndex: i
72277
+ });
72278
+ }
72279
+ }
72280
+ }
72281
+ return result;
72282
+ }
72283
+ function loadSimpleMatchersWithSource(event, workingDirectory = process.cwd()) {
71726
72284
  const result = [];
71727
72285
  const locations = ["project-local", "project", "user"];
71728
72286
  for (const location of locations) {
@@ -71747,9 +72305,16 @@ function countTotalHooks(workingDirectory = process.cwd()) {
71747
72305
  for (const location of locations) {
71748
72306
  const hooks = loadHooksFromLocation(location, workingDirectory);
71749
72307
  for (const event of Object.keys(hooks)) {
71750
- const matchers = hooks[event] || [];
71751
- for (const matcher of matchers) {
71752
- count += matcher.hooks.length;
72308
+ if (isToolEvent(event)) {
72309
+ const matchers = hooks[event] || [];
72310
+ for (const matcher of matchers) {
72311
+ count += matcher.hooks.length;
72312
+ }
72313
+ } else {
72314
+ const matchers = hooks[event] || [];
72315
+ for (const matcher of matchers) {
72316
+ count += matcher.hooks.length;
72317
+ }
71753
72318
  }
71754
72319
  }
71755
72320
  }
@@ -71760,14 +72325,22 @@ function countHooksForEvent(event, workingDirectory = process.cwd()) {
71760
72325
  const locations = ["project-local", "project", "user"];
71761
72326
  for (const location of locations) {
71762
72327
  const hooks = loadHooksFromLocation(location, workingDirectory);
71763
- const matchers = hooks[event] || [];
71764
- for (const matcher of matchers) {
71765
- count += matcher.hooks.length;
72328
+ if (isToolEvent(event)) {
72329
+ const matchers = hooks[event] || [];
72330
+ for (const matcher of matchers) {
72331
+ count += matcher.hooks.length;
72332
+ }
72333
+ } else {
72334
+ const matchers = hooks[event] || [];
72335
+ for (const matcher of matchers) {
72336
+ count += matcher.hooks.length;
72337
+ }
71766
72338
  }
71767
72339
  }
71768
72340
  return count;
71769
72341
  }
71770
72342
  var init_writer = __esm(async () => {
72343
+ init_types();
71771
72344
  await init_settings_manager();
71772
72345
  });
71773
72346
 
@@ -71795,6 +72368,7 @@ function boxBottom(width) {
71795
72368
  }
71796
72369
  var import_react57, jsx_dev_runtime34, BOX_TOP_LEFT = "╭", BOX_TOP_RIGHT = "╮", BOX_BOTTOM_LEFT = "╰", BOX_BOTTOM_RIGHT = "╯", BOX_HORIZONTAL = "─", BOX_VERTICAL = "│", HOOK_EVENTS, TOOL_NAMES3, SAVE_LOCATIONS, HooksManager;
71797
72370
  var init_HooksManager = __esm(async () => {
72371
+ init_types();
71798
72372
  init_useTerminalWidth();
71799
72373
  init_colors();
71800
72374
  await __promiseAll([
@@ -71855,13 +72429,14 @@ var init_HooksManager = __esm(async () => {
71855
72429
  const [screen, setScreen] = import_react57.useState("events");
71856
72430
  const [selectedIndex, setSelectedIndex] = import_react57.useState(0);
71857
72431
  const [selectedEvent, setSelectedEvent] = import_react57.useState(null);
71858
- const [matchers, setMatchers] = import_react57.useState([]);
72432
+ const [hooks, setHooks] = import_react57.useState([]);
71859
72433
  const [totalHooks, setTotalHooks] = import_react57.useState(0);
71860
72434
  const [newMatcher, setNewMatcher] = import_react57.useState("");
71861
72435
  const [newCommand, setNewCommand] = import_react57.useState("");
71862
72436
  const [selectedLocation, setSelectedLocation] = import_react57.useState(0);
71863
- const [deleteMatcherIndex, setDeleteMatcherIndex] = import_react57.useState(-1);
72437
+ const [deleteHookIndex, setDeleteHookIndex] = import_react57.useState(-1);
71864
72438
  const [deleteConfirmIndex, setDeleteConfirmIndex] = import_react57.useState(1);
72439
+ const isCurrentToolEvent = selectedEvent ? isToolEvent(selectedEvent) : false;
71865
72440
  const refreshCounts = import_react57.useCallback(() => {
71866
72441
  setTotalHooks(countTotalHooks());
71867
72442
  }, []);
@@ -71870,9 +72445,12 @@ var init_HooksManager = __esm(async () => {
71870
72445
  refreshCounts();
71871
72446
  }
71872
72447
  }, [screen, refreshCounts]);
71873
- const loadMatchers = import_react57.useCallback((event) => {
71874
- const loaded = loadHooksWithSource(event);
71875
- setMatchers(loaded);
72448
+ const loadHooks2 = import_react57.useCallback((event) => {
72449
+ if (isToolEvent(event)) {
72450
+ setHooks(loadMatchersWithSource(event));
72451
+ } else {
72452
+ setHooks(loadSimpleMatchersWithSource(event));
72453
+ }
71876
72454
  }, []);
71877
72455
  const handleAddHook = import_react57.useCallback(async () => {
71878
72456
  if (!selectedEvent || !newCommand.trim())
@@ -71880,45 +72458,46 @@ var init_HooksManager = __esm(async () => {
71880
72458
  const location = SAVE_LOCATIONS[selectedLocation]?.location;
71881
72459
  if (!location)
71882
72460
  return;
71883
- const matcher = {
71884
- matcher: newMatcher.trim() || "*",
71885
- hooks: [{ type: "command", command: newCommand.trim() }]
71886
- };
71887
- await addHookMatcher(selectedEvent, matcher, location);
71888
- loadMatchers(selectedEvent);
72461
+ if (isToolEvent(selectedEvent)) {
72462
+ const matcher = {
72463
+ matcher: newMatcher.trim() || "*",
72464
+ hooks: [{ type: "command", command: newCommand.trim() }]
72465
+ };
72466
+ await addHookMatcher(selectedEvent, matcher, location);
72467
+ } else {
72468
+ const matcher = {
72469
+ hooks: [{ type: "command", command: newCommand.trim() }]
72470
+ };
72471
+ await addSimpleHookMatcher(selectedEvent, matcher, location);
72472
+ }
72473
+ loadHooks2(selectedEvent);
71889
72474
  refreshCounts();
71890
72475
  setNewMatcher("");
71891
72476
  setNewCommand("");
71892
72477
  setSelectedLocation(0);
71893
- setScreen("matchers");
72478
+ setScreen("hooks-list");
71894
72479
  setSelectedIndex(0);
71895
72480
  }, [
71896
72481
  selectedEvent,
71897
72482
  newMatcher,
71898
72483
  newCommand,
71899
72484
  selectedLocation,
71900
- loadMatchers,
72485
+ loadHooks2,
71901
72486
  refreshCounts
71902
72487
  ]);
71903
72488
  const handleDeleteHook = import_react57.useCallback(async () => {
71904
- if (deleteMatcherIndex < 0 || !selectedEvent)
72489
+ if (deleteHookIndex < 0 || !selectedEvent)
71905
72490
  return;
71906
- const matcher = matchers[deleteMatcherIndex];
71907
- if (!matcher)
72491
+ const hook = hooks[deleteHookIndex];
72492
+ if (!hook)
71908
72493
  return;
71909
- await removeHookMatcher(selectedEvent, matcher.sourceIndex, matcher.source);
71910
- loadMatchers(selectedEvent);
72494
+ await removeHook(selectedEvent, hook.sourceIndex, hook.source);
72495
+ loadHooks2(selectedEvent);
71911
72496
  refreshCounts();
71912
- setDeleteMatcherIndex(-1);
71913
- setScreen("matchers");
72497
+ setDeleteHookIndex(-1);
72498
+ setScreen("hooks-list");
71914
72499
  setSelectedIndex(0);
71915
- }, [
71916
- deleteMatcherIndex,
71917
- selectedEvent,
71918
- matchers,
71919
- loadMatchers,
71920
- refreshCounts
71921
- ]);
72500
+ }, [deleteHookIndex, selectedEvent, hooks, loadHooks2, refreshCounts]);
71922
72501
  use_input_default((input, key) => {
71923
72502
  if (key.ctrl && input === "c") {
71924
72503
  onClose();
@@ -71933,26 +72512,31 @@ var init_HooksManager = __esm(async () => {
71933
72512
  const selected = HOOK_EVENTS[selectedIndex];
71934
72513
  if (selected) {
71935
72514
  setSelectedEvent(selected.event);
71936
- loadMatchers(selected.event);
71937
- setScreen("matchers");
72515
+ loadHooks2(selected.event);
72516
+ setScreen("hooks-list");
71938
72517
  setSelectedIndex(0);
71939
72518
  }
71940
72519
  } else if (key.escape) {
71941
72520
  onClose();
71942
72521
  }
71943
- } else if (screen === "matchers") {
71944
- const itemCount = matchers.length + 1;
72522
+ } else if (screen === "hooks-list") {
72523
+ const itemCount = hooks.length + 1;
71945
72524
  if (key.upArrow) {
71946
72525
  setSelectedIndex((prev) => Math.max(0, prev - 1));
71947
72526
  } else if (key.downArrow) {
71948
72527
  setSelectedIndex((prev) => Math.min(itemCount - 1, prev + 1));
71949
72528
  } else if (key.return) {
71950
72529
  if (selectedIndex === 0) {
71951
- setScreen("add-matcher");
71952
- setNewMatcher("");
72530
+ if (isCurrentToolEvent) {
72531
+ setScreen("add-matcher");
72532
+ setNewMatcher("");
72533
+ } else {
72534
+ setScreen("add-command");
72535
+ setNewCommand("");
72536
+ }
71953
72537
  } else {}
71954
72538
  } else if ((input === "d" || input === "D") && selectedIndex > 0) {
71955
- setDeleteMatcherIndex(selectedIndex - 1);
72539
+ setDeleteHookIndex(selectedIndex - 1);
71956
72540
  setDeleteConfirmIndex(1);
71957
72541
  setScreen("delete-confirm");
71958
72542
  } else if (key.escape) {
@@ -71965,7 +72549,7 @@ var init_HooksManager = __esm(async () => {
71965
72549
  setScreen("add-command");
71966
72550
  setNewCommand("");
71967
72551
  } else if (key.escape) {
71968
- setScreen("matchers");
72552
+ setScreen("hooks-list");
71969
72553
  setSelectedIndex(0);
71970
72554
  setNewMatcher("");
71971
72555
  }
@@ -71974,7 +72558,12 @@ var init_HooksManager = __esm(async () => {
71974
72558
  setScreen("save-location");
71975
72559
  setSelectedLocation(0);
71976
72560
  } else if (key.escape) {
71977
- setScreen("add-matcher");
72561
+ if (isCurrentToolEvent) {
72562
+ setScreen("add-matcher");
72563
+ } else {
72564
+ setScreen("hooks-list");
72565
+ setSelectedIndex(0);
72566
+ }
71978
72567
  }
71979
72568
  } else if (screen === "save-location") {
71980
72569
  if (key.upArrow) {
@@ -71993,10 +72582,10 @@ var init_HooksManager = __esm(async () => {
71993
72582
  if (deleteConfirmIndex === 0) {
71994
72583
  handleDeleteHook();
71995
72584
  } else {
71996
- setScreen("matchers");
72585
+ setScreen("hooks-list");
71997
72586
  }
71998
72587
  } else if (key.escape) {
71999
- setScreen("matchers");
72588
+ setScreen("hooks-list");
72000
72589
  }
72001
72590
  }
72002
72591
  });
@@ -72058,7 +72647,9 @@ var init_HooksManager = __esm(async () => {
72058
72647
  ]
72059
72648
  }, undefined, true, undefined, this);
72060
72649
  }
72061
- if (screen === "matchers" && selectedEvent) {
72650
+ if (screen === "hooks-list" && selectedEvent) {
72651
+ const title = isCurrentToolEvent ? ` ${selectedEvent} - Tool Matchers ` : ` ${selectedEvent} - Hooks `;
72652
+ const addLabel = isCurrentToolEvent ? "+ Add new matcher..." : "+ Add new hook...";
72062
72653
  return /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Box_default, {
72063
72654
  flexDirection: "column",
72064
72655
  paddingX: 1,
@@ -72067,27 +72658,46 @@ var init_HooksManager = __esm(async () => {
72067
72658
  children: boxTop(boxWidth)
72068
72659
  }, undefined, false, undefined, this),
72069
72660
  /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72070
- children: boxLine(` ${selectedEvent} - Tool Matchers `, boxWidth)
72661
+ children: boxLine(title, boxWidth)
72071
72662
  }, undefined, false, undefined, this),
72072
72663
  /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72073
72664
  children: boxBottom(boxWidth)
72074
72665
  }, undefined, false, undefined, this),
72075
- /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72076
- dimColor: true,
72077
- children: "Input to command is JSON of tool call arguments."
72078
- }, undefined, false, undefined, this),
72079
- /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72080
- dimColor: true,
72081
- children: "Exit code 0 - stdout/stderr not shown"
72082
- }, undefined, false, undefined, this),
72083
- /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72084
- dimColor: true,
72085
- children: "Exit code 2 - show stderr to model and block tool call"
72086
- }, undefined, false, undefined, this),
72087
- /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72088
- dimColor: true,
72089
- children: "Other exit codes - show stderr to user only but continue"
72090
- }, undefined, false, undefined, this),
72666
+ isCurrentToolEvent ? /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(jsx_dev_runtime34.Fragment, {
72667
+ children: [
72668
+ /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72669
+ dimColor: true,
72670
+ children: "Input to command is JSON of tool call arguments."
72671
+ }, undefined, false, undefined, this),
72672
+ /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72673
+ dimColor: true,
72674
+ children: "Exit code 0 - stdout/stderr not shown"
72675
+ }, undefined, false, undefined, this),
72676
+ /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72677
+ dimColor: true,
72678
+ children: "Exit code 2 - show stderr to model and block tool call"
72679
+ }, undefined, false, undefined, this),
72680
+ /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72681
+ dimColor: true,
72682
+ children: "Other exit codes - show stderr to user only but continue"
72683
+ }, undefined, false, undefined, this)
72684
+ ]
72685
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(jsx_dev_runtime34.Fragment, {
72686
+ children: [
72687
+ /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72688
+ dimColor: true,
72689
+ children: "Exit code 0 - success, continue"
72690
+ }, undefined, false, undefined, this),
72691
+ /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72692
+ dimColor: true,
72693
+ children: "Exit code 2 - show stderr to model and block"
72694
+ }, undefined, false, undefined, this),
72695
+ /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72696
+ dimColor: true,
72697
+ children: "Other exit codes - show stderr to user only"
72698
+ }, undefined, false, undefined, this)
72699
+ ]
72700
+ }, undefined, true, undefined, this),
72091
72701
  /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72092
72702
  children: " "
72093
72703
  }, undefined, false, undefined, this),
@@ -72103,16 +72713,17 @@ var init_HooksManager = __esm(async () => {
72103
72713
  }, undefined, true, undefined, this),
72104
72714
  /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72105
72715
  color: "green",
72106
- children: "+ Add new matcher..."
72716
+ children: addLabel
72107
72717
  }, undefined, false, undefined, this)
72108
72718
  ]
72109
72719
  }, undefined, true, undefined, this),
72110
- matchers.map((matcher, index) => {
72720
+ hooks.map((hook, index) => {
72111
72721
  const isSelected = index + 1 === selectedIndex;
72112
72722
  const prefix = isSelected ? "❯" : " ";
72113
- const sourceLabel = `[${getSourceLabel(matcher.source)}]`;
72114
- const matcherPattern = matcher.matcher || "*";
72115
- const command = matcher.hooks[0]?.command || "";
72723
+ const sourceLabel = `[${getSourceLabel(hook.source)}]`;
72724
+ const isToolMatcher = "matcher" in hook;
72725
+ const matcherPattern = isToolMatcher ? hook.matcher || "*" : null;
72726
+ const command = "hooks" in hook ? hook.hooks[0]?.command || "" : "";
72116
72727
  const truncatedCommand = command.length > 30 ? `${command.slice(0, 27)}...` : command;
72117
72728
  return /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72118
72729
  children: [
@@ -72130,7 +72741,7 @@ var init_HooksManager = __esm(async () => {
72130
72741
  color: "cyan",
72131
72742
  children: sourceLabel
72132
72743
  }, undefined, false, undefined, this),
72133
- /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72744
+ matcherPattern !== null && /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72134
72745
  children: [
72135
72746
  " ",
72136
72747
  matcherPattern.padEnd(12),
@@ -72142,7 +72753,7 @@ var init_HooksManager = __esm(async () => {
72142
72753
  children: truncatedCommand
72143
72754
  }, undefined, false, undefined, this)
72144
72755
  ]
72145
- }, `${matcher.source}-${index}`, true, undefined, this);
72756
+ }, `${hook.source}-${index}`, true, undefined, this);
72146
72757
  }),
72147
72758
  /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72148
72759
  children: " "
@@ -72248,6 +72859,7 @@ var init_HooksManager = __esm(async () => {
72248
72859
  }, undefined, true, undefined, this);
72249
72860
  }
72250
72861
  if (screen === "add-command" && selectedEvent) {
72862
+ const title = isCurrentToolEvent ? ` Add new matcher for ${selectedEvent} ` : ` Add new hook for ${selectedEvent} `;
72251
72863
  return /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Box_default, {
72252
72864
  flexDirection: "column",
72253
72865
  paddingX: 1,
@@ -72256,18 +72868,18 @@ var init_HooksManager = __esm(async () => {
72256
72868
  children: boxTop(boxWidth)
72257
72869
  }, undefined, false, undefined, this),
72258
72870
  /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72259
- children: boxLine(` Add new matcher for ${selectedEvent} `, boxWidth)
72871
+ children: boxLine(title, boxWidth)
72260
72872
  }, undefined, false, undefined, this),
72261
72873
  /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72262
72874
  children: boxBottom(boxWidth)
72263
72875
  }, undefined, false, undefined, this),
72264
- /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72876
+ isCurrentToolEvent && /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72265
72877
  children: [
72266
72878
  "Matcher: ",
72267
72879
  newMatcher || "*"
72268
72880
  ]
72269
72881
  }, undefined, true, undefined, this),
72270
- /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72882
+ isCurrentToolEvent && /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72271
72883
  children: " "
72272
72884
  }, undefined, false, undefined, this),
72273
72885
  /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
@@ -72327,7 +72939,7 @@ var init_HooksManager = __esm(async () => {
72327
72939
  selectedEvent
72328
72940
  ]
72329
72941
  }, undefined, true, undefined, this),
72330
- /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72942
+ isCurrentToolEvent && /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72331
72943
  children: [
72332
72944
  "Matcher: ",
72333
72945
  newMatcher || "*"
@@ -72383,8 +72995,11 @@ var init_HooksManager = __esm(async () => {
72383
72995
  ]
72384
72996
  }, undefined, true, undefined, this);
72385
72997
  }
72386
- if (screen === "delete-confirm" && deleteMatcherIndex >= 0) {
72387
- const matcher = matchers[deleteMatcherIndex];
72998
+ if (screen === "delete-confirm" && deleteHookIndex >= 0) {
72999
+ const hook = hooks[deleteHookIndex];
73000
+ const isToolMatcher = hook && "matcher" in hook;
73001
+ const matcherPattern = isToolMatcher ? hook.matcher || "*" : null;
73002
+ const command = hook && "hooks" in hook ? hook.hooks[0]?.command : "";
72388
73003
  return /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Box_default, {
72389
73004
  flexDirection: "column",
72390
73005
  paddingX: 1,
@@ -72401,22 +73016,22 @@ var init_HooksManager = __esm(async () => {
72401
73016
  /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72402
73017
  children: " "
72403
73018
  }, undefined, false, undefined, this),
72404
- /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
73019
+ matcherPattern !== null && /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72405
73020
  children: [
72406
73021
  "Matcher: ",
72407
- matcher?.matcher || "*"
73022
+ matcherPattern
72408
73023
  ]
72409
73024
  }, undefined, true, undefined, this),
72410
73025
  /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72411
73026
  children: [
72412
73027
  "Command: ",
72413
- matcher?.hooks[0]?.command
73028
+ command
72414
73029
  ]
72415
73030
  }, undefined, true, undefined, this),
72416
73031
  /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
72417
73032
  children: [
72418
73033
  "Source: ",
72419
- matcher ? getSourceLabel(matcher.source) : ""
73034
+ hook ? getSourceLabel(hook.source) : ""
72420
73035
  ]
72421
73036
  }, undefined, true, undefined, this),
72422
73037
  /* @__PURE__ */ jsx_dev_runtime34.jsxDEV(Text, {
@@ -84542,6 +85157,13 @@ function App2({
84542
85157
  runSessionEndHooks(durationMs, undefined, undefined, agentIdRef.current ?? undefined, conversationIdRef.current ?? undefined).catch(() => {});
84543
85158
  };
84544
85159
  }, []);
85160
+ import_react86.useEffect(() => {
85161
+ return () => {
85162
+ if (queueAppendTimeoutRef.current) {
85163
+ clearTimeout(queueAppendTimeoutRef.current);
85164
+ }
85165
+ };
85166
+ }, []);
84545
85167
  const [showExitStats, setShowExitStats] = import_react86.useState(false);
84546
85168
  const hasSentSessionContextRef = import_react86.useRef(false);
84547
85169
  const turnCountRef = import_react86.useRef(0);
@@ -84554,6 +85176,10 @@ function App2({
84554
85176
  const llmApiErrorRetriesRef = import_react86.useRef(0);
84555
85177
  const conversationBusyRetriesRef = import_react86.useRef(0);
84556
85178
  const [messageQueue, setMessageQueue] = import_react86.useState([]);
85179
+ const messageQueueRef = import_react86.useRef([]);
85180
+ import_react86.useEffect(() => {
85181
+ messageQueueRef.current = messageQueue;
85182
+ }, [messageQueue]);
84557
85183
  const waitingForQueueCancelRef = import_react86.useRef(false);
84558
85184
  const queueSnapshotRef = import_react86.useRef([]);
84559
85185
  const [restoreQueueOnCancel, setRestoreQueueOnCancel] = import_react86.useState(false);
@@ -84561,12 +85187,24 @@ function App2({
84561
85187
  import_react86.useEffect(() => {
84562
85188
  restoreQueueOnCancelRef.current = restoreQueueOnCancel;
84563
85189
  }, [restoreQueueOnCancel]);
85190
+ const queueAppendTimeoutRef = import_react86.useRef(null);
84564
85191
  const [dequeueEpoch, setDequeueEpoch] = import_react86.useState(0);
84565
85192
  const lastDequeuedMessageRef = import_react86.useRef(null);
84566
85193
  const [restoredInput, setRestoredInput] = import_react86.useState(null);
84567
85194
  const isAgentBusy = import_react86.useCallback(() => {
84568
85195
  return streamingRef.current || isExecutingTool || commandRunningRef.current || abortControllerRef.current !== null;
84569
85196
  }, [isExecutingTool]);
85197
+ const consumeQueuedMessages = import_react86.useCallback(() => {
85198
+ if (messageQueueRef.current.length === 0)
85199
+ return null;
85200
+ if (queueAppendTimeoutRef.current) {
85201
+ clearTimeout(queueAppendTimeoutRef.current);
85202
+ queueAppendTimeoutRef.current = null;
85203
+ }
85204
+ const messages = [...messageQueueRef.current];
85205
+ setMessageQueue([]);
85206
+ return messages;
85207
+ }, []);
84570
85208
  const withCommandLock = import_react86.useCallback(async (asyncFn) => {
84571
85209
  setActiveOverlay(null);
84572
85210
  setCommandRunning(true);
@@ -85225,7 +85863,35 @@ ${newState.originalPrompt}`
85225
85863
  llmApiErrorRetriesRef.current = 0;
85226
85864
  conversationBusyRetriesRef.current = 0;
85227
85865
  lastDequeuedMessageRef.current = null;
85228
- runStopHooks(stopReasonToHandle, buffersRef.current.order.length, Array.from(buffersRef.current.byId.values()).filter((item) => item.kind === "tool_call").length).catch(() => {});
85866
+ const stopHookResult = await runStopHooks(stopReasonToHandle, buffersRef.current.order.length, Array.from(buffersRef.current.byId.values()).filter((item) => item.kind === "tool_call").length);
85867
+ if (stopHookResult.blocked) {
85868
+ const stderrOutput = stopHookResult.results.map((r) => r.stderr).filter(Boolean).join(`
85869
+ `);
85870
+ const feedback = stderrOutput || "Stop hook blocked";
85871
+ const hookMessage = `<stop-hook>
85872
+ ${feedback}
85873
+ </stop-hook>`;
85874
+ const statusId = uid4("status");
85875
+ buffersRef.current.byId.set(statusId, {
85876
+ kind: "status",
85877
+ id: statusId,
85878
+ lines: [
85879
+ "Stop hook encountered blocking error, continuing loop with stderr feedback."
85880
+ ]
85881
+ });
85882
+ buffersRef.current.order.push(statusId);
85883
+ refreshDerived();
85884
+ setTimeout(() => {
85885
+ processConversation([
85886
+ {
85887
+ type: "message",
85888
+ role: "user",
85889
+ content: hookMessage
85890
+ }
85891
+ ], { allowReentry: true });
85892
+ }, 0);
85893
+ return;
85894
+ }
85229
85895
  if (needsEagerApprovalCheck) {
85230
85896
  setNeedsEagerApprovalCheck(false);
85231
85897
  }
@@ -85481,6 +86147,31 @@ ${newState.originalPrompt}`
85481
86147
  refreshDerived();
85482
86148
  return;
85483
86149
  }
86150
+ const queuedMessagesToAppend = consumeQueuedMessages();
86151
+ if (queuedMessagesToAppend?.length) {
86152
+ for (const msg of queuedMessagesToAppend) {
86153
+ const userId = uid4("user");
86154
+ buffersRef.current.byId.set(userId, {
86155
+ kind: "user",
86156
+ id: userId,
86157
+ text: msg
86158
+ });
86159
+ buffersRef.current.order.push(userId);
86160
+ }
86161
+ setThinkingMessage(getRandomThinkingVerb());
86162
+ refreshDerived();
86163
+ toolResultsInFlightRef.current = true;
86164
+ await processConversation([
86165
+ { type: "approval", approvals: allResults },
86166
+ ...queuedMessagesToAppend.map((msg) => ({
86167
+ type: "message",
86168
+ role: "user",
86169
+ content: msg
86170
+ }))
86171
+ ], { allowReentry: true });
86172
+ toolResultsInFlightRef.current = false;
86173
+ return;
86174
+ }
85484
86175
  if (waitingForQueueCancelRef.current) {
85485
86176
  if (allResults.length > 0) {
85486
86177
  queueApprovalResults(allResults, autoAllowedMetadata);
@@ -85829,7 +86520,8 @@ ${newState.originalPrompt}`
85829
86520
  currentModelId,
85830
86521
  updateStreamingOutput,
85831
86522
  needsEagerApprovalCheck,
85832
- queueApprovalResults
86523
+ queueApprovalResults,
86524
+ consumeQueuedMessages
85833
86525
  ]);
85834
86526
  const handleExit = import_react86.useCallback(async () => {
85835
86527
  saveLastAgentBeforeExit();
@@ -86475,29 +87167,34 @@ ${expanded.command}` : expanded.command;
86475
87167
  setMessageQueue((prev) => {
86476
87168
  const newQueue = [...prev, msg];
86477
87169
  const isSlashCommand = msg.startsWith("/");
86478
- if (!isSlashCommand && streamingRef.current && !waitingForQueueCancelRef.current) {
86479
- waitingForQueueCancelRef.current = true;
86480
- queueSnapshotRef.current = [...newQueue];
86481
- debugLog("queue", `Initiating queue-cancel: queueing "${msg.slice(0, 50)}${msg.length > 50 ? "..." : ""}", sending cancel to server`);
86482
- if (toolAbortControllerRef.current) {
86483
- toolAbortControllerRef.current.abort();
86484
- }
86485
- getClient2().then((client) => {
86486
- if (conversationIdRef.current === "default") {
86487
- return client.agents.messages.cancel(agentIdRef.current);
86488
- }
86489
- return client.conversations.cancel(conversationIdRef.current);
86490
- }).then(() => {}).catch(() => {
86491
- waitingForQueueCancelRef.current = false;
86492
- });
86493
- setTimeout(() => {
86494
- if (waitingForQueueCancelRef.current && abortControllerRef.current) {
86495
- debugLog("queue", "Timeout fallback: aborting stream after 3s (server cancel was slow/failed)");
86496
- abortControllerRef.current.abort();
86497
- waitingForQueueCancelRef.current = false;
86498
- queueSnapshotRef.current = [];
87170
+ if (!isSlashCommand && streamingRef.current && !waitingForQueueCancelRef.current && !queueAppendTimeoutRef.current) {
87171
+ queueAppendTimeoutRef.current = setTimeout(() => {
87172
+ if (messageQueueRef.current.length === 0) {
87173
+ queueAppendTimeoutRef.current = null;
87174
+ return;
87175
+ }
87176
+ queueAppendTimeoutRef.current = null;
87177
+ waitingForQueueCancelRef.current = true;
87178
+ queueSnapshotRef.current = [...messageQueueRef.current];
87179
+ if (toolAbortControllerRef.current) {
87180
+ toolAbortControllerRef.current.abort();
86499
87181
  }
86500
- }, 3000);
87182
+ getClient2().then((client) => {
87183
+ if (conversationIdRef.current === "default") {
87184
+ return client.agents.messages.cancel(agentIdRef.current);
87185
+ }
87186
+ return client.conversations.cancel(conversationIdRef.current);
87187
+ }).catch(() => {
87188
+ waitingForQueueCancelRef.current = false;
87189
+ });
87190
+ setTimeout(() => {
87191
+ if (waitingForQueueCancelRef.current && abortControllerRef.current) {
87192
+ abortControllerRef.current.abort();
87193
+ waitingForQueueCancelRef.current = false;
87194
+ queueSnapshotRef.current = [];
87195
+ }
87196
+ }, 3000);
87197
+ }, 15000);
86501
87198
  }
86502
87199
  return newQueue;
86503
87200
  });
@@ -88416,13 +89113,29 @@ ${SYSTEM_REMINDER_CLOSE}
88416
89113
  waitingForQueueCancelRef.current = false;
88417
89114
  queueSnapshotRef.current = [];
88418
89115
  } else {
88419
- toolResultsInFlightRef.current = true;
88420
- await processConversation([
88421
- {
88422
- type: "approval",
88423
- approvals: allResults
89116
+ const queuedMessagesToAppend = consumeQueuedMessages();
89117
+ const input = [
89118
+ { type: "approval", approvals: allResults }
89119
+ ];
89120
+ if (queuedMessagesToAppend?.length) {
89121
+ for (const msg of queuedMessagesToAppend) {
89122
+ const userId = uid4("user");
89123
+ buffersRef.current.byId.set(userId, {
89124
+ kind: "user",
89125
+ id: userId,
89126
+ text: msg
89127
+ });
89128
+ buffersRef.current.order.push(userId);
89129
+ input.push({
89130
+ type: "message",
89131
+ role: "user",
89132
+ content: msg
89133
+ });
88424
89134
  }
88425
- ]);
89135
+ refreshDerived();
89136
+ }
89137
+ toolResultsInFlightRef.current = true;
89138
+ await processConversation(input);
88426
89139
  toolResultsInFlightRef.current = false;
88427
89140
  queueApprovalResults(null);
88428
89141
  }
@@ -88443,7 +89156,8 @@ ${SYSTEM_REMINDER_CLOSE}
88443
89156
  appendError,
88444
89157
  setStreaming,
88445
89158
  updateStreamingOutput,
88446
- queueApprovalResults
89159
+ queueApprovalResults,
89160
+ consumeQueuedMessages
88447
89161
  ]);
88448
89162
  const handleApproveCurrent = import_react86.useCallback(async (diffs) => {
88449
89163
  if (isExecutingTool)
@@ -91408,7 +92122,7 @@ function sortChronological(messages) {
91408
92122
  async function getResumeData(client, agent, conversationId) {
91409
92123
  try {
91410
92124
  let inContextMessageIds;
91411
- let messages;
92125
+ let messages = [];
91412
92126
  const useConversationsApi = conversationId && conversationId !== "default";
91413
92127
  if (process.env.DEBUG) {
91414
92128
  console.log(`[DEBUG] getResumeData: conversationId=${conversationId}, useConversationsApi=${useConversationsApi}, agentId=${agent.id}`);
@@ -91419,12 +92133,16 @@ async function getResumeData(client, agent, conversationId) {
91419
92133
  if (!inContextMessageIds || inContextMessageIds.length === 0) {
91420
92134
  debugWarn("check-approval", "No in-context messages - no pending approvals");
91421
92135
  if (isBackfillEnabled()) {
91422
- const backfill = await client.conversations.messages.list(conversationId, { limit: MESSAGE_HISTORY_LIMIT, order: "desc" });
91423
- return {
91424
- pendingApproval: null,
91425
- pendingApprovals: [],
91426
- messageHistory: sortChronological(backfill.getPaginatedItems())
91427
- };
92136
+ try {
92137
+ const backfill = await client.conversations.messages.list(conversationId, { limit: MESSAGE_HISTORY_LIMIT, order: "desc" });
92138
+ return {
92139
+ pendingApproval: null,
92140
+ pendingApprovals: [],
92141
+ messageHistory: sortChronological(backfill.getPaginatedItems())
92142
+ };
92143
+ } catch (backfillError) {
92144
+ debugWarn("check-approval", `Failed to load message history: ${backfillError instanceof Error ? backfillError.message : String(backfillError)}`);
92145
+ }
91428
92146
  }
91429
92147
  return {
91430
92148
  pendingApproval: null,
@@ -91437,11 +92155,17 @@ async function getResumeData(client, agent, conversationId) {
91437
92155
  throw new Error("Expected at least one in-context message");
91438
92156
  }
91439
92157
  const retrievedMessages = await client.messages.retrieve(lastInContextId);
91440
- const backfillPage = isBackfillEnabled() ? await client.conversations.messages.list(conversationId, {
91441
- limit: MESSAGE_HISTORY_LIMIT,
91442
- order: "desc"
91443
- }) : null;
91444
- messages = backfillPage ? sortChronological(backfillPage.getPaginatedItems()) : [];
92158
+ if (isBackfillEnabled()) {
92159
+ try {
92160
+ const backfillPage = await client.conversations.messages.list(conversationId, {
92161
+ limit: MESSAGE_HISTORY_LIMIT,
92162
+ order: "desc"
92163
+ });
92164
+ messages = sortChronological(backfillPage.getPaginatedItems());
92165
+ } catch (backfillError) {
92166
+ debugWarn("check-approval", `Failed to load message history: ${backfillError instanceof Error ? backfillError.message : String(backfillError)}`);
92167
+ }
92168
+ }
91445
92169
  const messageToCheck = retrievedMessages.find((msg) => msg.message_type === "approval_request_message") ?? retrievedMessages[0];
91446
92170
  if (messageToCheck) {
91447
92171
  debugWarn("check-approval", `Found last in-context message: ${messageToCheck.id} (type: ${messageToCheck.message_type})` + (retrievedMessages.length > 1 ? ` - had ${retrievedMessages.length} variants` : ""));
@@ -91476,14 +92200,20 @@ async function getResumeData(client, agent, conversationId) {
91476
92200
  throw new Error("Expected at least one in-context message");
91477
92201
  }
91478
92202
  const retrievedMessages = await client.messages.retrieve(lastInContextId);
91479
- const messagesPage = isBackfillEnabled() ? await client.agents.messages.list(agent.id, {
91480
- limit: MESSAGE_HISTORY_LIMIT,
91481
- order: "desc",
91482
- conversation_id: "default"
91483
- }) : null;
91484
- messages = messagesPage ? sortChronological(messagesPage.items) : [];
91485
- if (process.env.DEBUG && messagesPage) {
91486
- console.log(`[DEBUG] agents.messages.list(conversation_id=default) returned ${messagesPage.items.length} messages`);
92203
+ if (isBackfillEnabled()) {
92204
+ try {
92205
+ const messagesPage = await client.agents.messages.list(agent.id, {
92206
+ limit: MESSAGE_HISTORY_LIMIT,
92207
+ order: "desc",
92208
+ conversation_id: "default"
92209
+ });
92210
+ messages = sortChronological(messagesPage.items);
92211
+ if (process.env.DEBUG) {
92212
+ console.log(`[DEBUG] agents.messages.list(conversation_id=default) returned ${messagesPage.items.length} messages`);
92213
+ }
92214
+ } catch (backfillError) {
92215
+ debugWarn("check-approval", `Failed to load message history: ${backfillError instanceof Error ? backfillError.message : String(backfillError)}`);
92216
+ }
91487
92217
  }
91488
92218
  const messageToCheck = retrievedMessages.find((msg) => msg.message_type === "approval_request_message") ?? retrievedMessages[0];
91489
92219
  if (messageToCheck) {
@@ -94989,4 +95719,4 @@ Error during initialization: ${message}`);
94989
95719
  }
94990
95720
  main();
94991
95721
 
94992
- //# debugId=0D26B6A9B64965F064756E2164756E21
95722
+ //# debugId=4B93041AB98FEDD564756E2164756E21