micode 0.5.2 → 0.7.0
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/README.md +64 -289
- package/dist/hooks/auto-clear-ledger.d.ts +1 -1
- package/dist/index.js +1157 -159
- package/dist/tools/background-task/manager.d.ts +1 -0
- package/dist/tools/background-task/tools.d.ts +0 -4
- package/dist/tools/background-task/types.d.ts +27 -1
- package/dist/tools/pty/buffer.d.ts +11 -0
- package/dist/tools/pty/index.d.ts +74 -0
- package/dist/tools/pty/manager.d.ts +14 -0
- package/dist/tools/pty/tools/kill.d.ts +12 -0
- package/dist/tools/pty/tools/list.d.ts +6 -0
- package/dist/tools/pty/tools/read.d.ts +18 -0
- package/dist/tools/pty/tools/spawn.d.ts +20 -0
- package/dist/tools/pty/tools/write.d.ts +12 -0
- package/dist/tools/pty/types.d.ts +54 -0
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ var __export = (target, all) => {
|
|
|
9
9
|
set: (newValue) => all[name] = () => newValue
|
|
10
10
|
});
|
|
11
11
|
};
|
|
12
|
+
var __require = import.meta.require;
|
|
12
13
|
|
|
13
14
|
// src/agents/brainstormer.ts
|
|
14
15
|
var brainstormerAgent = {
|
|
@@ -24,34 +25,50 @@ This is DESIGN ONLY. The planner agent handles detailed implementation plans.
|
|
|
24
25
|
<critical-rules>
|
|
25
26
|
<rule priority="HIGHEST">ONE QUESTION AT A TIME: Ask exactly ONE question, then STOP and wait for the user's response. NEVER ask multiple questions in a single message. This is the most important rule.</rule>
|
|
26
27
|
<rule>NO CODE: Never write code. Never provide code examples. Design only.</rule>
|
|
27
|
-
<rule>
|
|
28
|
-
<rule>TOOLS (grep, read, etc.): Do NOT use directly - use subagents instead.</rule>
|
|
28
|
+
<rule>BACKGROUND TASKS: Use background_task for parallel codebase analysis.</rule>
|
|
29
|
+
<rule>TOOLS (grep, read, etc.): Do NOT use directly - use background subagents instead.</rule>
|
|
29
30
|
</critical-rules>
|
|
30
31
|
|
|
32
|
+
<background-tools>
|
|
33
|
+
<tool name="background_task">Fire subagent tasks that run in parallel. Returns task_id immediately.</tool>
|
|
34
|
+
<tool name="background_list">List all background tasks and their current status. Use to poll for completion.</tool>
|
|
35
|
+
<tool name="background_output">Get results from a completed task. Only call after background_list shows task is done.</tool>
|
|
36
|
+
</background-tools>
|
|
37
|
+
|
|
31
38
|
<available-subagents>
|
|
32
|
-
<subagent name="codebase-locator" spawn="
|
|
33
|
-
Find files, modules, patterns.
|
|
34
|
-
|
|
39
|
+
<subagent name="codebase-locator" spawn="background_task">
|
|
40
|
+
Find files, modules, patterns. Fire multiple with different queries.
|
|
41
|
+
Example: background_task(agent="codebase-locator", prompt="Find authentication code", description="Find auth files")
|
|
42
|
+
</subagent>
|
|
43
|
+
<subagent name="codebase-analyzer" spawn="background_task">
|
|
44
|
+
Deep analysis of specific modules. Fire multiple for different areas.
|
|
45
|
+
Example: background_task(agent="codebase-analyzer", prompt="Analyze the auth module", description="Analyze auth")
|
|
35
46
|
</subagent>
|
|
36
|
-
<subagent name="
|
|
37
|
-
|
|
38
|
-
|
|
47
|
+
<subagent name="pattern-finder" spawn="background_task">
|
|
48
|
+
Find existing patterns in codebase. Fire for different pattern types.
|
|
49
|
+
Example: background_task(agent="pattern-finder", prompt="Find error handling patterns", description="Find error patterns")
|
|
39
50
|
</subagent>
|
|
40
|
-
<subagent name="
|
|
41
|
-
|
|
42
|
-
|
|
51
|
+
<subagent name="planner" spawn="Task" when="design approved">
|
|
52
|
+
Creates detailed implementation plan from validated design.
|
|
53
|
+
Example: Task(subagent_type="planner", prompt="Create implementation plan for [design path]", description="Create plan")
|
|
43
54
|
</subagent>
|
|
44
55
|
</available-subagents>
|
|
45
56
|
|
|
46
57
|
<process>
|
|
47
|
-
<phase name="understanding">
|
|
48
|
-
<action>
|
|
49
|
-
<
|
|
50
|
-
In a SINGLE message,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
</
|
|
58
|
+
<phase name="understanding" pattern="fire-poll-collect">
|
|
59
|
+
<action>Fire background tasks in PARALLEL to gather context:</action>
|
|
60
|
+
<fire-example>
|
|
61
|
+
In a SINGLE message, fire ALL background tasks:
|
|
62
|
+
background_task(agent="codebase-locator", prompt="Find files related to [topic]", description="Find [topic] files")
|
|
63
|
+
background_task(agent="codebase-analyzer", prompt="Analyze existing [related feature]", description="Analyze [feature]")
|
|
64
|
+
background_task(agent="pattern-finder", prompt="Find patterns for [similar functionality]", description="Find patterns")
|
|
65
|
+
</fire-example>
|
|
66
|
+
<poll>
|
|
67
|
+
background_list() // repeat until all show "completed" or "error"
|
|
68
|
+
</poll>
|
|
69
|
+
<collect>
|
|
70
|
+
background_output(task_id=...) for each completed task (skip errored tasks)
|
|
71
|
+
</collect>
|
|
55
72
|
<focus>purpose, constraints, success criteria</focus>
|
|
56
73
|
</phase>
|
|
57
74
|
|
|
@@ -81,16 +98,29 @@ This is DESIGN ONLY. The planner agent handles detailed implementation plans.
|
|
|
81
98
|
<action>Commit the design document to git</action>
|
|
82
99
|
<action>Ask: "Ready for the planner to create a detailed implementation plan?"</action>
|
|
83
100
|
</phase>
|
|
101
|
+
|
|
102
|
+
<phase name="handoff" trigger="user approves design">
|
|
103
|
+
<action>When user says yes/approved/ready, IMMEDIATELY spawn the planner:</action>
|
|
104
|
+
<spawn>
|
|
105
|
+
Task(
|
|
106
|
+
subagent_type="planner",
|
|
107
|
+
prompt="Create a detailed implementation plan based on the design at thoughts/shared/designs/YYYY-MM-DD-{topic}-design.md",
|
|
108
|
+
description="Create implementation plan"
|
|
109
|
+
)
|
|
110
|
+
</spawn>
|
|
111
|
+
<rule>Do NOT ask again - if user approved, spawn planner immediately</rule>
|
|
112
|
+
</phase>
|
|
84
113
|
</process>
|
|
85
114
|
|
|
86
115
|
<principles>
|
|
87
116
|
<principle name="design-only">NO CODE. Describe components, not implementations. Planner writes code.</principle>
|
|
88
|
-
<principle name="
|
|
89
|
-
<principle name="parallel-
|
|
117
|
+
<principle name="background-tasks">Use background_task for parallel research, poll with background_list, collect with background_output</principle>
|
|
118
|
+
<principle name="parallel-fire">Fire ALL background tasks in a SINGLE message for true parallelism</principle>
|
|
90
119
|
<principle name="one-question">Ask exactly ONE question per message. STOP after asking. Wait for user's answer before continuing. NEVER bundle multiple questions together.</principle>
|
|
91
120
|
<principle name="yagni">Remove unnecessary features from ALL designs</principle>
|
|
92
121
|
<principle name="explore-alternatives">ALWAYS propose 2-3 approaches before settling</principle>
|
|
93
122
|
<principle name="incremental-validation">Present in sections, validate each before proceeding</principle>
|
|
123
|
+
<principle name="auto-handoff">When user approves design, IMMEDIATELY spawn planner - don't ask again</principle>
|
|
94
124
|
</principles>
|
|
95
125
|
|
|
96
126
|
<never-do>
|
|
@@ -351,13 +381,24 @@ Every task is bite-sized (2-5 minutes), with exact paths and complete code.
|
|
|
351
381
|
|
|
352
382
|
<critical-rules>
|
|
353
383
|
<rule>FOLLOW THE DESIGN: The brainstormer's design is the spec. Do not explore alternatives.</rule>
|
|
354
|
-
<rule>
|
|
355
|
-
<rule>TOOLS (grep, read, etc.): Do NOT use directly - use subagents instead.</rule>
|
|
384
|
+
<rule>BACKGROUND TASKS: Use background_task for parallel research (fire-and-collect pattern).</rule>
|
|
385
|
+
<rule>TOOLS (grep, read, etc.): Do NOT use directly - use background subagents instead.</rule>
|
|
356
386
|
<rule>Every code example MUST be complete - never write "add validation here"</rule>
|
|
357
387
|
<rule>Every file path MUST be exact - never write "somewhere in src/"</rule>
|
|
358
388
|
<rule>Follow TDD: failing test \u2192 verify fail \u2192 implement \u2192 verify pass \u2192 commit</rule>
|
|
359
389
|
</critical-rules>
|
|
360
390
|
|
|
391
|
+
<background-tools>
|
|
392
|
+
<tool name="background_task">Fire subagent tasks that run in parallel. Returns task_id immediately.</tool>
|
|
393
|
+
<tool name="background_list">List all background tasks and their current status. Use to poll for completion.</tool>
|
|
394
|
+
<tool name="background_output">Get results from a completed task. Only call after background_list shows task is done.</tool>
|
|
395
|
+
</background-tools>
|
|
396
|
+
|
|
397
|
+
<fallback-rule>
|
|
398
|
+
If background_task fails or is unavailable, fall back to Task() for sequential execution.
|
|
399
|
+
Always prefer background_task for parallel research, but Task() works as a reliable fallback.
|
|
400
|
+
</fallback-rule>
|
|
401
|
+
|
|
361
402
|
<research-scope>
|
|
362
403
|
Brainstormer did conceptual research (architecture, patterns, approaches).
|
|
363
404
|
Your research is IMPLEMENTATION-LEVEL only:
|
|
@@ -375,18 +416,19 @@ All research must serve the design - never second-guess design decisions.
|
|
|
375
416
|
</library-research>
|
|
376
417
|
|
|
377
418
|
<available-subagents>
|
|
378
|
-
<subagent name="codebase-locator" spawn="
|
|
419
|
+
<subagent name="codebase-locator" spawn="background">
|
|
379
420
|
Find exact file paths needed for implementation.
|
|
380
421
|
Examples: "Find exact path to UserService", "Find test directory structure"
|
|
381
422
|
</subagent>
|
|
382
|
-
<subagent name="codebase-analyzer" spawn="
|
|
423
|
+
<subagent name="codebase-analyzer" spawn="background">
|
|
383
424
|
Get exact signatures and types for code examples.
|
|
384
425
|
Examples: "Get function signature for createUser", "Get type definition for UserConfig"
|
|
385
426
|
</subagent>
|
|
386
|
-
<subagent name="pattern-finder" spawn="
|
|
427
|
+
<subagent name="pattern-finder" spawn="background">
|
|
387
428
|
Find exact patterns to copy in code examples.
|
|
388
429
|
Examples: "Find exact test setup pattern", "Find exact error handling in similar endpoint"
|
|
389
430
|
</subagent>
|
|
431
|
+
<fallback>If background_task unavailable, use Task() with same subagent types.</fallback>
|
|
390
432
|
</available-subagents>
|
|
391
433
|
|
|
392
434
|
<inputs>
|
|
@@ -402,15 +444,21 @@ All research must serve the design - never second-guess design decisions.
|
|
|
402
444
|
<action>Note any constraints or decisions made by brainstormer</action>
|
|
403
445
|
</phase>
|
|
404
446
|
|
|
405
|
-
<phase name="implementation-research">
|
|
406
|
-
<action>
|
|
407
|
-
<
|
|
408
|
-
In a SINGLE message,
|
|
409
|
-
- codebase-locator
|
|
410
|
-
- codebase-
|
|
411
|
-
-
|
|
412
|
-
-
|
|
413
|
-
|
|
447
|
+
<phase name="implementation-research" pattern="fire-and-collect">
|
|
448
|
+
<action>Fire background tasks AND library research in parallel:</action>
|
|
449
|
+
<fire-phase description="Launch all research simultaneously">
|
|
450
|
+
In a SINGLE message, fire:
|
|
451
|
+
- background_task(agent="codebase-locator", prompt="Find exact path to [component]")
|
|
452
|
+
- background_task(agent="codebase-analyzer", prompt="Get signature for [function]")
|
|
453
|
+
- background_task(agent="pattern-finder", prompt="Find test setup pattern")
|
|
454
|
+
- context7_resolve-library-id + context7_query-docs for API docs
|
|
455
|
+
- btca_ask for library internals when needed
|
|
456
|
+
</fire-phase>
|
|
457
|
+
<collect-phase description="Poll until all complete, then collect">
|
|
458
|
+
- Poll with background_list until all tasks show completed or error
|
|
459
|
+
- Call background_output(task_id=...) for each completed task (skip errored)
|
|
460
|
+
- Combine all results for planning phase
|
|
461
|
+
</collect-phase>
|
|
414
462
|
<rule>Only research what's needed to implement the design</rule>
|
|
415
463
|
<rule>Never research alternatives to design decisions</rule>
|
|
416
464
|
</phase>
|
|
@@ -502,6 +550,29 @@ git commit -m "feat(scope): add specific feature"
|
|
|
502
550
|
</template>
|
|
503
551
|
</output-format>
|
|
504
552
|
|
|
553
|
+
<execution-example pattern="fire-and-collect">
|
|
554
|
+
<step name="fire">
|
|
555
|
+
// In a SINGLE message, fire all research tasks:
|
|
556
|
+
background_task(agent="codebase-locator", prompt="Find UserService path") // returns task_id_1
|
|
557
|
+
background_task(agent="codebase-analyzer", prompt="Get createUser signature") // returns task_id_2
|
|
558
|
+
background_task(agent="pattern-finder", prompt="Find test setup pattern") // returns task_id_3
|
|
559
|
+
context7_resolve-library-id(libraryName="express") // runs in parallel
|
|
560
|
+
btca_ask(tech="express", question="middleware chain order") // runs in parallel
|
|
561
|
+
</step>
|
|
562
|
+
<step name="collect">
|
|
563
|
+
// Poll until all background tasks complete:
|
|
564
|
+
background_list() // check status of all tasks
|
|
565
|
+
// When all show "completed":
|
|
566
|
+
background_output(task_id=task_id_1) // get result
|
|
567
|
+
background_output(task_id=task_id_2) // get result
|
|
568
|
+
background_output(task_id=task_id_3) // get result
|
|
569
|
+
// context7 and btca_ask results already available from fire step
|
|
570
|
+
</step>
|
|
571
|
+
<step name="plan">
|
|
572
|
+
// Use all collected results to write the implementation plan
|
|
573
|
+
</step>
|
|
574
|
+
</execution-example>
|
|
575
|
+
|
|
505
576
|
<principles>
|
|
506
577
|
<principle name="zero-context">Engineer knows nothing about our codebase</principle>
|
|
507
578
|
<principle name="complete-code">Every code block is copy-paste ready</principle>
|
|
@@ -553,9 +624,17 @@ Execute the plan. Write code. Verify.
|
|
|
553
624
|
<step>Verify preconditions match plan</step>
|
|
554
625
|
<step>Make the changes</step>
|
|
555
626
|
<step>Run verification (tests, lint, build)</step>
|
|
627
|
+
<step>If verification passes: commit with message from plan</step>
|
|
556
628
|
<step>Report results</step>
|
|
557
629
|
</process>
|
|
558
630
|
|
|
631
|
+
<terminal-tools>
|
|
632
|
+
<bash>Use for synchronous commands that complete (npm install, git, builds)</bash>
|
|
633
|
+
<pty>Use for background processes (dev servers, watch modes, REPLs)</pty>
|
|
634
|
+
<rule>If plan says "start dev server" or "run in background", use pty_spawn</rule>
|
|
635
|
+
<rule>If plan says "run command" or "install", use bash</rule>
|
|
636
|
+
</terminal-tools>
|
|
637
|
+
|
|
559
638
|
<before-each-change>
|
|
560
639
|
<check>Verify file exists where expected</check>
|
|
561
640
|
<check>Verify code structure matches plan assumptions</check>
|
|
@@ -566,8 +645,17 @@ Execute the plan. Write code. Verify.
|
|
|
566
645
|
<check>Run tests if available</check>
|
|
567
646
|
<check>Check for type errors</check>
|
|
568
647
|
<check>Verify no regressions</check>
|
|
648
|
+
<check>If all pass: git add and commit with plan's commit message</check>
|
|
569
649
|
</after-each-change>
|
|
570
650
|
|
|
651
|
+
<commit-rules>
|
|
652
|
+
<rule>Commit ONLY after verification passes</rule>
|
|
653
|
+
<rule>Use the commit message from the plan (e.g., "feat(scope): description")</rule>
|
|
654
|
+
<rule>Stage only the files mentioned in the task</rule>
|
|
655
|
+
<rule>If plan doesn't specify commit message, use: "feat(task): [task description]"</rule>
|
|
656
|
+
<rule>Do NOT push - just commit locally</rule>
|
|
657
|
+
</commit-rules>
|
|
658
|
+
|
|
571
659
|
<output-format>
|
|
572
660
|
<template>
|
|
573
661
|
## Task: [Description]
|
|
@@ -580,6 +668,8 @@ Execute the plan. Write code. Verify.
|
|
|
580
668
|
- [x] Types check
|
|
581
669
|
- [ ] Manual check needed: [what]
|
|
582
670
|
|
|
671
|
+
**Commit**: \`[commit hash]\` - [commit message]
|
|
672
|
+
|
|
583
673
|
**Issues**: None / [description]
|
|
584
674
|
</template>
|
|
585
675
|
</output-format>
|
|
@@ -671,6 +761,12 @@ Check correctness and style. Be specific. Run code, don't just read.
|
|
|
671
761
|
<step>Report with precise references</step>
|
|
672
762
|
</process>
|
|
673
763
|
|
|
764
|
+
<terminal-verification>
|
|
765
|
+
<rule>If implementation includes PTY usage, verify sessions are properly cleaned up</rule>
|
|
766
|
+
<rule>If tests require a running server, check that pty_spawn was used appropriately</rule>
|
|
767
|
+
<rule>Check that long-running processes use PTY, not blocking bash</rule>
|
|
768
|
+
</terminal-verification>
|
|
769
|
+
|
|
674
770
|
<output-format>
|
|
675
771
|
<template>
|
|
676
772
|
## Review: [Component]
|
|
@@ -708,16 +804,42 @@ var executorAgent = {
|
|
|
708
804
|
model: "anthropic/claude-opus-4-5",
|
|
709
805
|
temperature: 0.2,
|
|
710
806
|
prompt: `<purpose>
|
|
711
|
-
Execute plan tasks with maximum parallelism.
|
|
807
|
+
Execute plan tasks with maximum parallelism using fire-and-check pattern.
|
|
712
808
|
Each task gets its own implementer \u2192 reviewer cycle.
|
|
713
809
|
Detect and parallelize independent tasks.
|
|
714
810
|
</purpose>
|
|
715
811
|
|
|
716
|
-
<
|
|
812
|
+
<background-tools>
|
|
813
|
+
You have access to background task management tools:
|
|
814
|
+
- background_task: Fire a subagent to run in background, returns task_id immediately
|
|
815
|
+
- background_output: Check status or get results from a background task
|
|
816
|
+
- background_list: List all background tasks and their status
|
|
817
|
+
</background-tools>
|
|
818
|
+
|
|
819
|
+
<pty-tools description="For background bash processes">
|
|
820
|
+
PTY tools manage background terminal sessions (different from background_task which runs subagents):
|
|
821
|
+
- pty_spawn: Start a background process (dev server, watch mode, REPL)
|
|
822
|
+
- pty_write: Send input to a PTY (commands, Ctrl+C, etc.)
|
|
823
|
+
- pty_read: Read output from a PTY buffer
|
|
824
|
+
- pty_list: List all PTY sessions
|
|
825
|
+
- pty_kill: Terminate a PTY session
|
|
826
|
+
|
|
827
|
+
Use PTY when:
|
|
828
|
+
- Plan requires starting a dev server before running tests
|
|
829
|
+
- Plan requires a watch mode process running during implementation
|
|
830
|
+
- Plan requires interactive terminal input
|
|
831
|
+
|
|
832
|
+
Do NOT use PTY for:
|
|
833
|
+
- Quick commands (use bash)
|
|
834
|
+
- Subagent tasks (use background_task)
|
|
835
|
+
</pty-tools>
|
|
836
|
+
|
|
837
|
+
<workflow pattern="fire-and-check">
|
|
717
838
|
<step>Parse plan to extract individual tasks</step>
|
|
718
839
|
<step>Analyze task dependencies to build execution graph</step>
|
|
719
840
|
<step>Group tasks into parallel batches (independent tasks run together)</step>
|
|
720
|
-
<step>
|
|
841
|
+
<step>Fire ALL implementers in batch as background_task</step>
|
|
842
|
+
<step>Poll with background_list, start reviewer immediately when each implementer finishes</step>
|
|
721
843
|
<step>Wait for batch to complete before starting dependent batch</step>
|
|
722
844
|
<step>Aggregate results and report</step>
|
|
723
845
|
</workflow>
|
|
@@ -737,83 +859,115 @@ Tasks are DEPENDENT (must be sequential) when:
|
|
|
737
859
|
When uncertain, assume DEPENDENT (safer).
|
|
738
860
|
</dependency-analysis>
|
|
739
861
|
|
|
740
|
-
<execution-pattern>
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
Batch 3 (parallel):
|
|
756
|
-
- Spawn implementer for task 7 \u2192 reviewer
|
|
757
|
-
- Spawn implementer for task 8 \u2192 reviewer
|
|
758
|
-
- Spawn implementer for task 9 \u2192 reviewer
|
|
759
|
-
[Wait for all to complete]
|
|
862
|
+
<execution-pattern name="fire-and-check">
|
|
863
|
+
The fire-and-check pattern maximizes parallelism by:
|
|
864
|
+
1. Firing all implementers as background tasks simultaneously
|
|
865
|
+
2. Polling to detect completion as early as possible
|
|
866
|
+
3. Starting each reviewer immediately when its implementer finishes
|
|
867
|
+
4. Not waiting for all implementers before starting any reviewers
|
|
868
|
+
|
|
869
|
+
Example: 3 independent tasks
|
|
870
|
+
- Fire implementer 1, 2, 3 as background_task (all start immediately)
|
|
871
|
+
- Poll with background_list
|
|
872
|
+
- Task 2 finishes first \u2192 immediately start reviewer 2
|
|
873
|
+
- Task 1 finishes \u2192 immediately start reviewer 1
|
|
874
|
+
- Task 3 finishes \u2192 immediately start reviewer 3
|
|
875
|
+
- Reviewers run in parallel as they're spawned
|
|
760
876
|
</execution-pattern>
|
|
761
877
|
|
|
762
878
|
<available-subagents>
|
|
763
|
-
<subagent name="implementer"
|
|
879
|
+
<subagent name="implementer">
|
|
764
880
|
Executes ONE task from the plan.
|
|
765
881
|
Input: Single task with context (which files, what to do).
|
|
766
882
|
Output: Changes made and verification results for that task.
|
|
767
|
-
|
|
883
|
+
<invocation type="background">
|
|
884
|
+
background_task(description="Implement task 1", prompt="...", agent="implementer")
|
|
885
|
+
</invocation>
|
|
886
|
+
<invocation type="fallback">
|
|
887
|
+
Task(description="Implement task 1", prompt="...", subagent_type="implementer")
|
|
888
|
+
</invocation>
|
|
768
889
|
</subagent>
|
|
769
|
-
<subagent name="reviewer"
|
|
890
|
+
<subagent name="reviewer">
|
|
770
891
|
Reviews ONE task's implementation.
|
|
771
892
|
Input: Single task's changes against its requirements.
|
|
772
893
|
Output: APPROVED or CHANGES REQUESTED for that task.
|
|
773
|
-
|
|
894
|
+
<invocation type="background">
|
|
895
|
+
background_task(description="Review task 1", prompt="...", agent="reviewer")
|
|
896
|
+
</invocation>
|
|
897
|
+
<invocation type="fallback">
|
|
898
|
+
Task(description="Review task 1", prompt="...", subagent_type="reviewer")
|
|
899
|
+
</invocation>
|
|
774
900
|
</subagent>
|
|
775
901
|
</available-subagents>
|
|
776
902
|
|
|
777
|
-
<critical-instruction>
|
|
778
|
-
You MUST use the Task tool to spawn implementer and reviewer subagents.
|
|
779
|
-
Example: Task(description="Implement task 1", prompt="...", subagent_type="implementer")
|
|
780
|
-
Do NOT try to implement or review yourself - delegate to subagents.
|
|
781
|
-
</critical-instruction>
|
|
782
|
-
|
|
783
903
|
<per-task-cycle>
|
|
784
904
|
For each task:
|
|
785
|
-
1.
|
|
786
|
-
2.
|
|
787
|
-
3.
|
|
788
|
-
4. If reviewer requests changes:
|
|
905
|
+
1. Fire implementer as background_task
|
|
906
|
+
2. Poll until implementer completes
|
|
907
|
+
3. Start reviewer immediately when implementer finishes
|
|
908
|
+
4. If reviewer requests changes: fire new implementer for fixes
|
|
789
909
|
5. Max 3 cycles per task before marking as blocked
|
|
790
910
|
6. Report task status: DONE / BLOCKED
|
|
791
911
|
</per-task-cycle>
|
|
792
912
|
|
|
793
|
-
<
|
|
794
|
-
Within a batch
|
|
913
|
+
<fire-and-check-loop>
|
|
914
|
+
Within a batch:
|
|
915
|
+
1. Fire ALL implementers as background_task in ONE message
|
|
916
|
+
2. Enter polling loop:
|
|
917
|
+
a. Call background_list to check status of ALL tasks
|
|
918
|
+
b. For each newly completed task (status != "running"):
|
|
919
|
+
- Get result with background_output (task is already done)
|
|
920
|
+
- If implementer completed: start its reviewer as background_task
|
|
921
|
+
- If reviewer completed: check APPROVED or CHANGES REQUESTED
|
|
922
|
+
c. If changes needed and cycles < 3: fire new implementer
|
|
923
|
+
d. Sleep briefly, then repeat until all tasks done or blocked
|
|
924
|
+
3. Move to next batch
|
|
795
925
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
-
|
|
799
|
-
- Task(description="Task 3", prompt="Execute task 3: [details]", subagent_type="implementer")
|
|
926
|
+
IMPORTANT: Always poll with background_list first to check status,
|
|
927
|
+
then fetch results with background_output only for completed tasks.
|
|
928
|
+
</fire-and-check-loop>
|
|
800
929
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
- Task(description="
|
|
804
|
-
- Task(description="
|
|
805
|
-
|
|
930
|
+
<fallback-rule>
|
|
931
|
+
If background_task fails or is unavailable, fall back to Task() tool:
|
|
932
|
+
- Task(description="...", prompt="...", subagent_type="implementer")
|
|
933
|
+
- Task(description="...", prompt="...", subagent_type="reviewer")
|
|
934
|
+
The Task tool blocks until completion but still works correctly.
|
|
935
|
+
</fallback-rule>
|
|
806
936
|
|
|
807
937
|
<rules>
|
|
808
938
|
<rule>Parse ALL tasks from plan before starting execution</rule>
|
|
809
939
|
<rule>ALWAYS analyze dependencies before parallelizing</rule>
|
|
810
|
-
<rule>
|
|
940
|
+
<rule>Fire parallel tasks as background_task for true parallelism</rule>
|
|
941
|
+
<rule>Start reviewer immediately when its implementer finishes - don't wait for others</rule>
|
|
811
942
|
<rule>Wait for entire batch before starting next batch</rule>
|
|
812
943
|
<rule>Each task gets its own implement \u2192 review cycle</rule>
|
|
813
944
|
<rule>Max 3 review cycles per task</rule>
|
|
814
945
|
<rule>Continue with other tasks if one is blocked</rule>
|
|
815
946
|
</rules>
|
|
816
947
|
|
|
948
|
+
<execution-example pattern="fire-and-check">
|
|
949
|
+
# Batch with tasks 1, 2, 3 (independent)
|
|
950
|
+
|
|
951
|
+
## Step 1: Fire all implementers
|
|
952
|
+
background_task(description="Task 1", prompt="Execute task 1: [details]", agent="implementer") \u2192 task_id_1
|
|
953
|
+
background_task(description="Task 2", prompt="Execute task 2: [details]", agent="implementer") \u2192 task_id_2
|
|
954
|
+
background_task(description="Task 3", prompt="Execute task 3: [details]", agent="implementer") \u2192 task_id_3
|
|
955
|
+
|
|
956
|
+
## Step 2: Poll and react
|
|
957
|
+
background_list() \u2192 shows task_id_2 completed
|
|
958
|
+
background_output(task_id="task_id_2") \u2192 get result
|
|
959
|
+
background_task(description="Review 2", prompt="Review task 2 implementation", agent="reviewer") \u2192 review_id_2
|
|
960
|
+
|
|
961
|
+
background_list() \u2192 shows task_id_1, task_id_3 completed
|
|
962
|
+
background_output(task_id="task_id_1") \u2192 get result
|
|
963
|
+
background_output(task_id="task_id_3") \u2192 get result
|
|
964
|
+
background_task(description="Review 1", prompt="Review task 1 implementation", agent="reviewer") \u2192 review_id_1
|
|
965
|
+
background_task(description="Review 3", prompt="Review task 3 implementation", agent="reviewer") \u2192 review_id_3
|
|
966
|
+
|
|
967
|
+
## Step 3: Continue polling until all reviews complete
|
|
968
|
+
...
|
|
969
|
+
</execution-example>
|
|
970
|
+
|
|
817
971
|
<output-format>
|
|
818
972
|
<template>
|
|
819
973
|
## Execution Complete
|
|
@@ -848,11 +1002,13 @@ Then after all complete, in ONE message call Task tool for reviewers:
|
|
|
848
1002
|
</output-format>
|
|
849
1003
|
|
|
850
1004
|
<never-do>
|
|
1005
|
+
<forbidden>NEVER call background_output on running tasks - always poll with background_list first</forbidden>
|
|
851
1006
|
<forbidden>Never skip dependency analysis</forbidden>
|
|
852
1007
|
<forbidden>Never spawn dependent tasks in parallel</forbidden>
|
|
853
1008
|
<forbidden>Never skip reviewer for any task</forbidden>
|
|
854
1009
|
<forbidden>Never continue past 3 cycles for a single task</forbidden>
|
|
855
1010
|
<forbidden>Never report success if any task is blocked</forbidden>
|
|
1011
|
+
<forbidden>Never wait for all implementers before starting any reviewer</forbidden>
|
|
856
1012
|
</never-do>`
|
|
857
1013
|
};
|
|
858
1014
|
|
|
@@ -928,7 +1084,7 @@ Just do it - including obvious follow-up actions.
|
|
|
928
1084
|
</phase>
|
|
929
1085
|
|
|
930
1086
|
<phase name="ledger" trigger="context getting full or session ending">
|
|
931
|
-
<action>System auto-updates ledger at
|
|
1087
|
+
<action>System auto-updates ledger at 60% context usage</action>
|
|
932
1088
|
<output>thoughts/ledgers/CONTINUITY_{session-name}.md</output>
|
|
933
1089
|
</phase>
|
|
934
1090
|
</workflow>
|
|
@@ -956,6 +1112,23 @@ Just do it - including obvious follow-up actions.
|
|
|
956
1112
|
</when-to-use>
|
|
957
1113
|
</library-research>
|
|
958
1114
|
|
|
1115
|
+
<terminal-tools description="Choose the right terminal tool">
|
|
1116
|
+
<tool name="bash">Synchronous commands. Use for: npm install, git, builds, quick commands that complete.</tool>
|
|
1117
|
+
<tool name="pty_spawn">Background PTY sessions. Use for: dev servers, watch modes, REPLs, long-running processes.</tool>
|
|
1118
|
+
<when-to-use>
|
|
1119
|
+
<use tool="bash">Command completes quickly (npm install, git status, mkdir)</use>
|
|
1120
|
+
<use tool="pty_spawn">Process runs indefinitely (npm run dev, pytest --watch, python REPL)</use>
|
|
1121
|
+
<use tool="pty_spawn">Need to send interactive input (Ctrl+C, responding to prompts)</use>
|
|
1122
|
+
<use tool="pty_spawn">Want to check output later without blocking</use>
|
|
1123
|
+
</when-to-use>
|
|
1124
|
+
<pty-workflow>
|
|
1125
|
+
<step>pty_spawn to start the process</step>
|
|
1126
|
+
<step>pty_read to check output (use pattern to filter)</step>
|
|
1127
|
+
<step>pty_write to send input (\\n for Enter, \\x03 for Ctrl+C)</step>
|
|
1128
|
+
<step>pty_kill when done (cleanup=true to remove)</step>
|
|
1129
|
+
</pty-workflow>
|
|
1130
|
+
</terminal-tools>
|
|
1131
|
+
|
|
959
1132
|
<tracking>
|
|
960
1133
|
<rule>Use TodoWrite to track what you're doing</rule>
|
|
961
1134
|
<rule>Never discard tasks without explicit approval</rule>
|
|
@@ -986,7 +1159,7 @@ var PROMPT2 = `
|
|
|
986
1159
|
|
|
987
1160
|
<critical-rule>
|
|
988
1161
|
MAXIMIZE PARALLELISM. Speed is critical.
|
|
989
|
-
-
|
|
1162
|
+
- Fire ALL background tasks simultaneously
|
|
990
1163
|
- Run multiple tool calls in single message
|
|
991
1164
|
- Never wait for one thing when you can do many
|
|
992
1165
|
</critical-rule>
|
|
@@ -999,16 +1172,33 @@ var PROMPT2 = `
|
|
|
999
1172
|
</outputs>
|
|
1000
1173
|
</task>
|
|
1001
1174
|
|
|
1002
|
-
<
|
|
1003
|
-
<
|
|
1004
|
-
|
|
1005
|
-
|
|
1175
|
+
<background-tools>
|
|
1176
|
+
<tool name="background_task">
|
|
1177
|
+
Fire a subagent to run in background. Returns task_id immediately.
|
|
1178
|
+
Parameters: description, prompt, agent (subagent type)
|
|
1179
|
+
Example: background_task(description="Find entry points", prompt="Find all entry points", agent="codebase-locator")
|
|
1180
|
+
</tool>
|
|
1181
|
+
<tool name="background_list">
|
|
1182
|
+
List all background tasks and their status. Use to poll for completion.
|
|
1183
|
+
No parameters required.
|
|
1184
|
+
</tool>
|
|
1185
|
+
<tool name="background_output">
|
|
1186
|
+
Get results from a completed task. Only call after background_list shows task is done.
|
|
1187
|
+
Parameters: task_id
|
|
1188
|
+
Example: background_output(task_id="abc123")
|
|
1189
|
+
</tool>
|
|
1190
|
+
</background-tools>
|
|
1191
|
+
|
|
1192
|
+
<parallel-execution-strategy pattern="fire-and-collect">
|
|
1193
|
+
<phase name="1-fire" description="Fire ALL tasks simultaneously">
|
|
1194
|
+
<description>Launch ALL discovery agents + run tools in a SINGLE message</description>
|
|
1195
|
+
<fire-agents>
|
|
1006
1196
|
<agent name="codebase-locator">Find entry points, configs, main modules</agent>
|
|
1007
1197
|
<agent name="codebase-locator">Find test files and test patterns</agent>
|
|
1008
1198
|
<agent name="codebase-locator">Find linter, formatter, CI configs</agent>
|
|
1009
1199
|
<agent name="codebase-analyzer">Analyze directory structure</agent>
|
|
1010
1200
|
<agent name="pattern-finder">Find naming conventions across files</agent>
|
|
1011
|
-
</
|
|
1201
|
+
</fire-agents>
|
|
1012
1202
|
<parallel-tools>
|
|
1013
1203
|
<tool>Glob for package.json, pyproject.toml, go.mod, Cargo.toml, etc.</tool>
|
|
1014
1204
|
<tool>Glob for *.config.*, .eslintrc*, .prettierrc*, ruff.toml, etc.</tool>
|
|
@@ -1017,13 +1207,20 @@ var PROMPT2 = `
|
|
|
1017
1207
|
</parallel-tools>
|
|
1018
1208
|
</phase>
|
|
1019
1209
|
|
|
1020
|
-
<phase name="2-
|
|
1021
|
-
<description>
|
|
1022
|
-
<
|
|
1210
|
+
<phase name="2-collect" description="Poll and collect all results">
|
|
1211
|
+
<description>Poll background_list until all tasks complete, then collect with background_output</description>
|
|
1212
|
+
<action>Poll background_list until all tasks show "completed" or "error"</action>
|
|
1213
|
+
<action>Call background_output for each completed task (skip errored)</action>
|
|
1214
|
+
<action>Process tool results from phase 1</action>
|
|
1215
|
+
</phase>
|
|
1216
|
+
|
|
1217
|
+
<phase name="3-deep-analysis" description="Fire deep analysis tasks">
|
|
1218
|
+
<description>Based on discovery, fire more background tasks</description>
|
|
1219
|
+
<fire-agents>
|
|
1023
1220
|
<agent name="codebase-analyzer">Analyze core/domain logic</agent>
|
|
1024
1221
|
<agent name="codebase-analyzer">Analyze API/entry points</agent>
|
|
1025
1222
|
<agent name="codebase-analyzer">Analyze data layer</agent>
|
|
1026
|
-
</
|
|
1223
|
+
</fire-agents>
|
|
1027
1224
|
<parallel-tools>
|
|
1028
1225
|
<tool>Read 5 core source files simultaneously</tool>
|
|
1029
1226
|
<tool>Read 3 test files simultaneously</tool>
|
|
@@ -1031,8 +1228,9 @@ var PROMPT2 = `
|
|
|
1031
1228
|
</parallel-tools>
|
|
1032
1229
|
</phase>
|
|
1033
1230
|
|
|
1034
|
-
<phase name="
|
|
1035
|
-
<description>
|
|
1231
|
+
<phase name="4-collect-and-write" description="Collect and write output">
|
|
1232
|
+
<description>Collect deep analysis results, then write both files</description>
|
|
1233
|
+
<action>Collect all deep analysis results</action>
|
|
1036
1234
|
<action>Write ARCHITECTURE.md</action>
|
|
1037
1235
|
<action>Write CODE_STYLE.md</action>
|
|
1038
1236
|
</phase>
|
|
@@ -1042,23 +1240,37 @@ var PROMPT2 = `
|
|
|
1042
1240
|
<subagent name="codebase-locator" spawn="multiple">
|
|
1043
1241
|
Fast file/pattern finder. Spawn multiple with different queries.
|
|
1044
1242
|
Examples: "Find all entry points", "Find all config files", "Find test directories"
|
|
1045
|
-
|
|
1243
|
+
|
|
1244
|
+
Background: background_task(description="Find entry points", prompt="Find all entry points and main files", agent="codebase-locator")
|
|
1245
|
+
Fallback: Task(description="Find entry points", prompt="Find all entry points and main files", subagent_type="codebase-locator")
|
|
1046
1246
|
</subagent>
|
|
1047
1247
|
<subagent name="codebase-analyzer" spawn="multiple">
|
|
1048
1248
|
Deep module analyzer. Spawn multiple for different areas.
|
|
1049
1249
|
Examples: "Analyze src/core", "Analyze api layer", "Analyze database module"
|
|
1050
|
-
|
|
1250
|
+
|
|
1251
|
+
Background: background_task(description="Analyze core", prompt="Analyze the core module", agent="codebase-analyzer")
|
|
1252
|
+
Fallback: Task(description="Analyze core", prompt="Analyze the core module", subagent_type="codebase-analyzer")
|
|
1051
1253
|
</subagent>
|
|
1052
1254
|
<subagent name="pattern-finder" spawn="multiple">
|
|
1053
1255
|
Pattern extractor. Spawn for different pattern types.
|
|
1054
1256
|
Examples: "Find naming patterns", "Find error handling patterns", "Find async patterns"
|
|
1055
|
-
|
|
1257
|
+
|
|
1258
|
+
Background: background_task(description="Find patterns", prompt="Find naming conventions", agent="pattern-finder")
|
|
1259
|
+
Fallback: Task(description="Find patterns", prompt="Find naming conventions", subagent_type="pattern-finder")
|
|
1056
1260
|
</subagent>
|
|
1057
1261
|
</available-subagents>
|
|
1058
1262
|
|
|
1263
|
+
<fallback-rule>
|
|
1264
|
+
If background_task fails or is unavailable, fall back to Task() tool.
|
|
1265
|
+
The Task tool provides synchronous subagent execution.
|
|
1266
|
+
Example fallback: Task(description="Find entry points", prompt="Find all entry points", subagent_type="codebase-locator")
|
|
1267
|
+
</fallback-rule>
|
|
1268
|
+
|
|
1059
1269
|
<critical-instruction>
|
|
1060
|
-
|
|
1061
|
-
|
|
1270
|
+
Use background_task to fire subagents for TRUE parallelism.
|
|
1271
|
+
Fire ALL background_task calls in a SINGLE message.
|
|
1272
|
+
Then poll with background_list until all complete, and collect with background_output.
|
|
1273
|
+
This is the fire-and-collect pattern - fire everything, poll, then collect everything.
|
|
1062
1274
|
</critical-instruction>
|
|
1063
1275
|
|
|
1064
1276
|
<language-detection>
|
|
@@ -1124,10 +1336,10 @@ var PROMPT2 = `
|
|
|
1124
1336
|
|
|
1125
1337
|
<rules>
|
|
1126
1338
|
<category name="Speed">
|
|
1127
|
-
<rule>ALWAYS
|
|
1339
|
+
<rule>ALWAYS fire multiple background_task calls in a SINGLE message</rule>
|
|
1128
1340
|
<rule>ALWAYS run multiple tool calls in a SINGLE message</rule>
|
|
1129
1341
|
<rule>NEVER wait for one task when you can start others</rule>
|
|
1130
|
-
<rule>
|
|
1342
|
+
<rule>Use fire-and-collect: fire all, then collect all</rule>
|
|
1131
1343
|
</category>
|
|
1132
1344
|
|
|
1133
1345
|
<category name="Analysis">
|
|
@@ -1152,27 +1364,40 @@ var PROMPT2 = `
|
|
|
1152
1364
|
</category>
|
|
1153
1365
|
</rules>
|
|
1154
1366
|
|
|
1155
|
-
<execution-example>
|
|
1156
|
-
<step description="
|
|
1157
|
-
In a SINGLE message,
|
|
1158
|
-
-
|
|
1159
|
-
-
|
|
1160
|
-
-
|
|
1161
|
-
-
|
|
1162
|
-
-
|
|
1367
|
+
<execution-example pattern="fire-and-collect">
|
|
1368
|
+
<step description="FIRE: Launch all discovery tasks simultaneously">
|
|
1369
|
+
In a SINGLE message, fire ALL background_task calls AND run other tools:
|
|
1370
|
+
- background_task(description="Find entry points", prompt="Find all entry points and main files", agent="codebase-locator") -> task_id_1
|
|
1371
|
+
- background_task(description="Find configs", prompt="Find all config files (linters, formatters, build)", agent="codebase-locator") -> task_id_2
|
|
1372
|
+
- background_task(description="Find tests", prompt="Find test directories and test files", agent="codebase-locator") -> task_id_3
|
|
1373
|
+
- background_task(description="Analyze structure", prompt="Analyze the directory structure and organization", agent="codebase-analyzer") -> task_id_4
|
|
1374
|
+
- background_task(description="Find patterns", prompt="Find naming conventions used across the codebase", agent="pattern-finder") -> task_id_5
|
|
1163
1375
|
- Glob: package.json, pyproject.toml, go.mod, Cargo.toml, etc.
|
|
1164
1376
|
- Glob: README*, ARCHITECTURE*, docs/*
|
|
1165
1377
|
</step>
|
|
1166
1378
|
|
|
1167
|
-
<step description="
|
|
1168
|
-
|
|
1169
|
-
-
|
|
1379
|
+
<step description="COLLECT: Poll and gather all results">
|
|
1380
|
+
First poll until all tasks complete:
|
|
1381
|
+
- background_list() // repeat until all show "completed" or "error"
|
|
1382
|
+
Then collect results (skip errored tasks):
|
|
1383
|
+
- background_output(task_id=task_id_1)
|
|
1384
|
+
- background_output(task_id=task_id_2)
|
|
1385
|
+
- background_output(task_id=task_id_3)
|
|
1386
|
+
- background_output(task_id=task_id_4)
|
|
1387
|
+
- background_output(task_id=task_id_5)
|
|
1388
|
+
</step>
|
|
1389
|
+
|
|
1390
|
+
<step description="FIRE: Deep analysis based on discovery">
|
|
1391
|
+
Based on discovery, in a SINGLE message fire more tasks:
|
|
1392
|
+
- background_task for each major module: agent="codebase-analyzer"
|
|
1170
1393
|
- Read multiple source files simultaneously
|
|
1171
1394
|
- Read multiple test files simultaneously
|
|
1172
1395
|
</step>
|
|
1173
1396
|
|
|
1174
|
-
<step description="
|
|
1175
|
-
|
|
1397
|
+
<step description="COLLECT and WRITE">
|
|
1398
|
+
Collect deep analysis results, then write:
|
|
1399
|
+
- Write ARCHITECTURE.md
|
|
1400
|
+
- Write CODE_STYLE.md
|
|
1176
1401
|
</step>
|
|
1177
1402
|
</execution-example>
|
|
1178
1403
|
</agent>
|
|
@@ -15235,7 +15460,7 @@ function createFileOpsTrackerHook(_ctx) {
|
|
|
15235
15460
|
}
|
|
15236
15461
|
|
|
15237
15462
|
// src/hooks/auto-clear-ledger.ts
|
|
15238
|
-
var DEFAULT_THRESHOLD = 0.
|
|
15463
|
+
var DEFAULT_THRESHOLD = 0.6;
|
|
15239
15464
|
var MIN_TOKENS_FOR_CLEAR = 50000;
|
|
15240
15465
|
var CLEAR_COOLDOWN_MS = 60000;
|
|
15241
15466
|
function createAutoClearLedgerHook(ctx) {
|
|
@@ -15495,6 +15720,7 @@ function createArtifactAutoIndexHook(_ctx) {
|
|
|
15495
15720
|
|
|
15496
15721
|
// src/tools/background-task/manager.ts
|
|
15497
15722
|
var POLL_INTERVAL_MS = 2000;
|
|
15723
|
+
var TASK_TTL_MS = 60 * 60 * 1000;
|
|
15498
15724
|
function generateTaskId() {
|
|
15499
15725
|
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
15500
15726
|
let result = "bg_";
|
|
@@ -15556,6 +15782,7 @@ class BackgroundTaskManager {
|
|
|
15556
15782
|
},
|
|
15557
15783
|
query: { directory: this.ctx.directory }
|
|
15558
15784
|
}).catch((error45) => {
|
|
15785
|
+
console.error(`[background-task] Failed to prompt session ${sessionID}:`, error45);
|
|
15559
15786
|
task.status = "error";
|
|
15560
15787
|
task.error = error45 instanceof Error ? error45.message : String(error45);
|
|
15561
15788
|
task.completedAt = new Date;
|
|
@@ -15573,7 +15800,9 @@ class BackgroundTaskManager {
|
|
|
15573
15800
|
this.ctx.client.session.abort({
|
|
15574
15801
|
path: { id: task.sessionID },
|
|
15575
15802
|
query: { directory: this.ctx.directory }
|
|
15576
|
-
}).catch(() => {
|
|
15803
|
+
}).catch((error45) => {
|
|
15804
|
+
console.error(`[background-task] Failed to abort session ${task.sessionID}:`, error45);
|
|
15805
|
+
});
|
|
15577
15806
|
task.status = "cancelled";
|
|
15578
15807
|
task.completedAt = new Date;
|
|
15579
15808
|
this.markForNotification(task);
|
|
@@ -15615,20 +15844,18 @@ class BackgroundTaskManager {
|
|
|
15615
15844
|
path: { id: task.sessionID },
|
|
15616
15845
|
query: { directory: this.ctx.directory }
|
|
15617
15846
|
});
|
|
15618
|
-
const
|
|
15619
|
-
const
|
|
15620
|
-
|
|
15621
|
-
const info = msg.info;
|
|
15622
|
-
return info?.role === "assistant";
|
|
15623
|
-
});
|
|
15847
|
+
const messagesResp = resp;
|
|
15848
|
+
const messages = messagesResp.data || [];
|
|
15849
|
+
const lastAssistant = [...messages].reverse().find((m) => m.info?.role === "assistant");
|
|
15624
15850
|
if (lastAssistant) {
|
|
15625
|
-
const
|
|
15626
|
-
const textParts = parts?.filter((p) => p.type === "text") || [];
|
|
15851
|
+
const textParts = lastAssistant.parts?.filter((p) => p.type === "text") || [];
|
|
15627
15852
|
task.result = textParts.map((p) => p.text || "").join(`
|
|
15628
15853
|
`);
|
|
15629
15854
|
return task.result;
|
|
15630
15855
|
}
|
|
15631
|
-
} catch {
|
|
15856
|
+
} catch (error45) {
|
|
15857
|
+
console.error(`[background-task] Failed to fetch result for task ${taskId}:`, error45);
|
|
15858
|
+
}
|
|
15632
15859
|
return;
|
|
15633
15860
|
}
|
|
15634
15861
|
formatTaskStatus(task) {
|
|
@@ -15660,13 +15887,6 @@ class BackgroundTaskManager {
|
|
|
15660
15887
|
output += `
|
|
15661
15888
|
### Error
|
|
15662
15889
|
${task.error}
|
|
15663
|
-
`;
|
|
15664
|
-
}
|
|
15665
|
-
if (task.progress?.lastMessage) {
|
|
15666
|
-
const preview = task.progress.lastMessage.length > 200 ? `${task.progress.lastMessage.slice(0, 200)}...` : task.progress.lastMessage;
|
|
15667
|
-
output += `
|
|
15668
|
-
### Last Message Preview
|
|
15669
|
-
${preview}
|
|
15670
15890
|
`;
|
|
15671
15891
|
}
|
|
15672
15892
|
return output;
|
|
@@ -15684,7 +15904,19 @@ ${preview}
|
|
|
15684
15904
|
this.pollingInterval = undefined;
|
|
15685
15905
|
}
|
|
15686
15906
|
}
|
|
15907
|
+
cleanupOldTasks() {
|
|
15908
|
+
const now = Date.now();
|
|
15909
|
+
for (const [taskId, task] of this.tasks) {
|
|
15910
|
+
if (task.status === "running")
|
|
15911
|
+
continue;
|
|
15912
|
+
const completedAt = task.completedAt?.getTime() || 0;
|
|
15913
|
+
if (now - completedAt > TASK_TTL_MS) {
|
|
15914
|
+
this.tasks.delete(taskId);
|
|
15915
|
+
}
|
|
15916
|
+
}
|
|
15917
|
+
}
|
|
15687
15918
|
async pollRunningTasks() {
|
|
15919
|
+
this.cleanupOldTasks();
|
|
15688
15920
|
const runningTasks = this.getRunningTasks();
|
|
15689
15921
|
if (runningTasks.length === 0) {
|
|
15690
15922
|
this.stopPolling();
|
|
@@ -15710,9 +15942,12 @@ ${preview}
|
|
|
15710
15942
|
variant: "success",
|
|
15711
15943
|
duration: 5000
|
|
15712
15944
|
}
|
|
15713
|
-
}).catch(() => {
|
|
15945
|
+
}).catch((error45) => {
|
|
15946
|
+
console.error(`[background-task] Failed to show toast for task ${task.id}:`, error45);
|
|
15947
|
+
});
|
|
15714
15948
|
}
|
|
15715
|
-
} catch {
|
|
15949
|
+
} catch (error45) {
|
|
15950
|
+
console.error(`[background-task] Failed to poll task ${task.id}:`, error45);
|
|
15716
15951
|
if (task.status === "running") {
|
|
15717
15952
|
task.status = "error";
|
|
15718
15953
|
task.error = "Session lost";
|
|
@@ -15801,27 +16036,17 @@ Use \`background_output\` with task_id="${task.id}" to check progress or get res
|
|
|
15801
16036
|
}
|
|
15802
16037
|
});
|
|
15803
16038
|
const background_output = tool({
|
|
15804
|
-
description: `
|
|
15805
|
-
|
|
15806
|
-
Set block=true to wait for completion (with timeout).`,
|
|
16039
|
+
description: `Get status or results from a background task.
|
|
16040
|
+
Returns immediately with current status. Use background_list to poll for completion.`,
|
|
15807
16041
|
args: {
|
|
15808
|
-
task_id: tool.schema.string().describe("ID of the task to check (e.g., 'bg_abc12345')")
|
|
15809
|
-
block: tool.schema.boolean().optional().describe("Wait for task completion (default: false)"),
|
|
15810
|
-
timeout: tool.schema.number().optional().describe("Max seconds to wait if blocking (default: 60, max: 600)")
|
|
16042
|
+
task_id: tool.schema.string().describe("ID of the task to check (e.g., 'bg_abc12345')")
|
|
15811
16043
|
},
|
|
15812
16044
|
execute: async (args) => {
|
|
15813
|
-
const { task_id
|
|
16045
|
+
const { task_id } = args;
|
|
15814
16046
|
const task = manager.getTask(task_id);
|
|
15815
16047
|
if (!task) {
|
|
15816
16048
|
return `Task not found: ${task_id}`;
|
|
15817
16049
|
}
|
|
15818
|
-
if (block && task.status === "running") {
|
|
15819
|
-
const maxWait = Math.min(timeout || 60, 600) * 1000;
|
|
15820
|
-
const startTime = Date.now();
|
|
15821
|
-
while (task.status === "running" && Date.now() - startTime < maxWait) {
|
|
15822
|
-
await new Promise((resolve2) => setTimeout(resolve2, 1000));
|
|
15823
|
-
}
|
|
15824
|
-
}
|
|
15825
16050
|
let output = manager.formatTaskStatus(task);
|
|
15826
16051
|
if (task.status === "completed") {
|
|
15827
16052
|
const result = await manager.getTaskResult(task_id);
|
|
@@ -15887,20 +16112,789 @@ ${result}
|
|
|
15887
16112
|
background_list
|
|
15888
16113
|
};
|
|
15889
16114
|
}
|
|
16115
|
+
// node_modules/bun-pty/src/terminal.ts
|
|
16116
|
+
import { dlopen, FFIType, ptr } from "bun:ffi";
|
|
16117
|
+
import { Buffer } from "buffer";
|
|
16118
|
+
|
|
16119
|
+
// node_modules/bun-pty/src/interfaces.ts
|
|
16120
|
+
class EventEmitter {
|
|
16121
|
+
listeners = [];
|
|
16122
|
+
event = (listener) => {
|
|
16123
|
+
this.listeners.push(listener);
|
|
16124
|
+
return {
|
|
16125
|
+
dispose: () => {
|
|
16126
|
+
const i = this.listeners.indexOf(listener);
|
|
16127
|
+
if (i !== -1) {
|
|
16128
|
+
this.listeners.splice(i, 1);
|
|
16129
|
+
}
|
|
16130
|
+
}
|
|
16131
|
+
};
|
|
16132
|
+
};
|
|
16133
|
+
fire(data) {
|
|
16134
|
+
for (const listener of this.listeners) {
|
|
16135
|
+
listener(data);
|
|
16136
|
+
}
|
|
16137
|
+
}
|
|
16138
|
+
}
|
|
16139
|
+
|
|
16140
|
+
// node_modules/bun-pty/src/terminal.ts
|
|
16141
|
+
import { join as join4, dirname as dirname3, basename as basename2 } from "path";
|
|
16142
|
+
import { existsSync as existsSync2 } from "fs";
|
|
16143
|
+
var DEFAULT_COLS = 80;
|
|
16144
|
+
var DEFAULT_ROWS = 24;
|
|
16145
|
+
var DEFAULT_FILE = "sh";
|
|
16146
|
+
var DEFAULT_NAME = "xterm";
|
|
16147
|
+
function shQuote(s) {
|
|
16148
|
+
if (s.length === 0)
|
|
16149
|
+
return "''";
|
|
16150
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
16151
|
+
}
|
|
16152
|
+
function resolveLibPath() {
|
|
16153
|
+
const env = process.env.BUN_PTY_LIB;
|
|
16154
|
+
if (env && existsSync2(env))
|
|
16155
|
+
return env;
|
|
16156
|
+
try {
|
|
16157
|
+
const embeddedPath = __require(`../rust-pty/target/release/${process.platform === "win32" ? "rust_pty.dll" : process.platform === "darwin" ? process.arch === "arm64" ? "librust_pty_arm64.dylib" : "librust_pty.dylib" : process.arch === "arm64" ? "librust_pty_arm64.so" : "librust_pty.so"}`);
|
|
16158
|
+
if (embeddedPath)
|
|
16159
|
+
return embeddedPath;
|
|
16160
|
+
} catch {}
|
|
16161
|
+
const platform = process.platform;
|
|
16162
|
+
const arch = process.arch;
|
|
16163
|
+
const filenames = platform === "darwin" ? arch === "arm64" ? ["librust_pty_arm64.dylib", "librust_pty.dylib"] : ["librust_pty.dylib"] : platform === "win32" ? ["rust_pty.dll"] : arch === "arm64" ? ["librust_pty_arm64.so", "librust_pty.so"] : ["librust_pty.so"];
|
|
16164
|
+
const base = Bun.fileURLToPath(import.meta.url);
|
|
16165
|
+
const fileDir = dirname3(base);
|
|
16166
|
+
const dirName = basename2(fileDir);
|
|
16167
|
+
const here = dirName === "src" || dirName === "dist" ? dirname3(fileDir) : fileDir;
|
|
16168
|
+
const basePaths = [
|
|
16169
|
+
join4(here, "rust-pty", "target", "release"),
|
|
16170
|
+
join4(here, "..", "bun-pty", "rust-pty", "target", "release"),
|
|
16171
|
+
join4(process.cwd(), "node_modules", "bun-pty", "rust-pty", "target", "release")
|
|
16172
|
+
];
|
|
16173
|
+
const fallbackPaths = [];
|
|
16174
|
+
for (const basePath of basePaths) {
|
|
16175
|
+
for (const filename of filenames) {
|
|
16176
|
+
fallbackPaths.push(join4(basePath, filename));
|
|
16177
|
+
}
|
|
16178
|
+
}
|
|
16179
|
+
for (const path of fallbackPaths) {
|
|
16180
|
+
if (existsSync2(path))
|
|
16181
|
+
return path;
|
|
16182
|
+
}
|
|
16183
|
+
throw new Error(`librust_pty shared library not found.
|
|
16184
|
+
Checked:
|
|
16185
|
+
- BUN_PTY_LIB=${env ?? "<unset>"}
|
|
16186
|
+
- ${fallbackPaths.join(`
|
|
16187
|
+
- `)}
|
|
16188
|
+
|
|
16189
|
+
Set BUN_PTY_LIB or ensure one of these paths contains the file.`);
|
|
16190
|
+
}
|
|
16191
|
+
var libPath = resolveLibPath();
|
|
16192
|
+
var lib;
|
|
16193
|
+
try {
|
|
16194
|
+
lib = dlopen(libPath, {
|
|
16195
|
+
bun_pty_spawn: {
|
|
16196
|
+
args: [FFIType.cstring, FFIType.cstring, FFIType.cstring, FFIType.i32, FFIType.i32],
|
|
16197
|
+
returns: FFIType.i32
|
|
16198
|
+
},
|
|
16199
|
+
bun_pty_write: {
|
|
16200
|
+
args: [FFIType.i32, FFIType.pointer, FFIType.i32],
|
|
16201
|
+
returns: FFIType.i32
|
|
16202
|
+
},
|
|
16203
|
+
bun_pty_read: {
|
|
16204
|
+
args: [FFIType.i32, FFIType.pointer, FFIType.i32],
|
|
16205
|
+
returns: FFIType.i32
|
|
16206
|
+
},
|
|
16207
|
+
bun_pty_resize: {
|
|
16208
|
+
args: [FFIType.i32, FFIType.i32, FFIType.i32],
|
|
16209
|
+
returns: FFIType.i32
|
|
16210
|
+
},
|
|
16211
|
+
bun_pty_kill: { args: [FFIType.i32], returns: FFIType.i32 },
|
|
16212
|
+
bun_pty_get_pid: { args: [FFIType.i32], returns: FFIType.i32 },
|
|
16213
|
+
bun_pty_get_exit_code: { args: [FFIType.i32], returns: FFIType.i32 },
|
|
16214
|
+
bun_pty_close: { args: [FFIType.i32], returns: FFIType.void }
|
|
16215
|
+
});
|
|
16216
|
+
} catch (error45) {
|
|
16217
|
+
console.error("Failed to load lib", error45);
|
|
16218
|
+
}
|
|
16219
|
+
|
|
16220
|
+
class Terminal {
|
|
16221
|
+
handle = -1;
|
|
16222
|
+
_pid = -1;
|
|
16223
|
+
_cols = DEFAULT_COLS;
|
|
16224
|
+
_rows = DEFAULT_ROWS;
|
|
16225
|
+
_name = DEFAULT_NAME;
|
|
16226
|
+
_readLoop = false;
|
|
16227
|
+
_closing = false;
|
|
16228
|
+
_onData = new EventEmitter;
|
|
16229
|
+
_onExit = new EventEmitter;
|
|
16230
|
+
constructor(file2 = DEFAULT_FILE, args = [], opts = { name: DEFAULT_NAME }) {
|
|
16231
|
+
this._cols = opts.cols ?? DEFAULT_COLS;
|
|
16232
|
+
this._rows = opts.rows ?? DEFAULT_ROWS;
|
|
16233
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
16234
|
+
const cmdline = [shQuote(file2), ...args.map(shQuote)].join(" ");
|
|
16235
|
+
let envStr = "";
|
|
16236
|
+
if (opts.env) {
|
|
16237
|
+
const envPairs = Object.entries(opts.env).map(([k, v]) => `${k}=${v}`);
|
|
16238
|
+
envStr = envPairs.join("\x00") + "\x00";
|
|
16239
|
+
}
|
|
16240
|
+
this.handle = lib.symbols.bun_pty_spawn(Buffer.from(`${cmdline}\x00`, "utf8"), Buffer.from(`${cwd}\x00`, "utf8"), Buffer.from(`${envStr}\x00`, "utf8"), this._cols, this._rows);
|
|
16241
|
+
if (this.handle < 0)
|
|
16242
|
+
throw new Error("PTY spawn failed");
|
|
16243
|
+
this._pid = lib.symbols.bun_pty_get_pid(this.handle);
|
|
16244
|
+
this._startReadLoop();
|
|
16245
|
+
}
|
|
16246
|
+
get pid() {
|
|
16247
|
+
return this._pid;
|
|
16248
|
+
}
|
|
16249
|
+
get cols() {
|
|
16250
|
+
return this._cols;
|
|
16251
|
+
}
|
|
16252
|
+
get rows() {
|
|
16253
|
+
return this._rows;
|
|
16254
|
+
}
|
|
16255
|
+
get process() {
|
|
16256
|
+
return "shell";
|
|
16257
|
+
}
|
|
16258
|
+
get onData() {
|
|
16259
|
+
return this._onData.event;
|
|
16260
|
+
}
|
|
16261
|
+
get onExit() {
|
|
16262
|
+
return this._onExit.event;
|
|
16263
|
+
}
|
|
16264
|
+
write(data) {
|
|
16265
|
+
if (this._closing)
|
|
16266
|
+
return;
|
|
16267
|
+
const buf = Buffer.from(data, "utf8");
|
|
16268
|
+
lib.symbols.bun_pty_write(this.handle, ptr(buf), buf.length);
|
|
16269
|
+
}
|
|
16270
|
+
resize(cols, rows) {
|
|
16271
|
+
if (this._closing)
|
|
16272
|
+
return;
|
|
16273
|
+
this._cols = cols;
|
|
16274
|
+
this._rows = rows;
|
|
16275
|
+
lib.symbols.bun_pty_resize(this.handle, cols, rows);
|
|
16276
|
+
}
|
|
16277
|
+
kill(signal = "SIGTERM") {
|
|
16278
|
+
if (this._closing)
|
|
16279
|
+
return;
|
|
16280
|
+
this._closing = true;
|
|
16281
|
+
lib.symbols.bun_pty_kill(this.handle);
|
|
16282
|
+
lib.symbols.bun_pty_close(this.handle);
|
|
16283
|
+
this._onExit.fire({ exitCode: 0, signal });
|
|
16284
|
+
}
|
|
16285
|
+
async _startReadLoop() {
|
|
16286
|
+
if (this._readLoop)
|
|
16287
|
+
return;
|
|
16288
|
+
this._readLoop = true;
|
|
16289
|
+
const buf = Buffer.allocUnsafe(4096);
|
|
16290
|
+
while (this._readLoop && !this._closing) {
|
|
16291
|
+
const n = lib.symbols.bun_pty_read(this.handle, ptr(buf), buf.length);
|
|
16292
|
+
if (n > 0) {
|
|
16293
|
+
this._onData.fire(buf.subarray(0, n).toString("utf8"));
|
|
16294
|
+
} else if (n === -2) {
|
|
16295
|
+
const exitCode = lib.symbols.bun_pty_get_exit_code(this.handle);
|
|
16296
|
+
this._onExit.fire({ exitCode });
|
|
16297
|
+
break;
|
|
16298
|
+
} else if (n < 0) {
|
|
16299
|
+
break;
|
|
16300
|
+
} else {
|
|
16301
|
+
await new Promise((r) => setTimeout(r, 8));
|
|
16302
|
+
}
|
|
16303
|
+
}
|
|
16304
|
+
}
|
|
16305
|
+
}
|
|
16306
|
+
|
|
16307
|
+
// node_modules/bun-pty/src/index.ts
|
|
16308
|
+
function spawn3(file2, args, options) {
|
|
16309
|
+
return new Terminal(file2, args, options);
|
|
16310
|
+
}
|
|
16311
|
+
|
|
16312
|
+
// src/tools/pty/buffer.ts
|
|
16313
|
+
var parsed = parseInt(process.env.PTY_MAX_BUFFER_LINES || "50000", 10);
|
|
16314
|
+
var DEFAULT_MAX_LINES = isNaN(parsed) ? 50000 : parsed;
|
|
16315
|
+
|
|
16316
|
+
class RingBuffer {
|
|
16317
|
+
lines = [];
|
|
16318
|
+
maxLines;
|
|
16319
|
+
constructor(maxLines = DEFAULT_MAX_LINES) {
|
|
16320
|
+
this.maxLines = maxLines;
|
|
16321
|
+
}
|
|
16322
|
+
append(data) {
|
|
16323
|
+
const newLines = data.split(`
|
|
16324
|
+
`);
|
|
16325
|
+
for (const line of newLines) {
|
|
16326
|
+
this.lines.push(line);
|
|
16327
|
+
if (this.lines.length > this.maxLines) {
|
|
16328
|
+
this.lines.shift();
|
|
16329
|
+
}
|
|
16330
|
+
}
|
|
16331
|
+
}
|
|
16332
|
+
read(offset = 0, limit) {
|
|
16333
|
+
const start = Math.max(0, offset);
|
|
16334
|
+
const end = limit !== undefined ? start + limit : this.lines.length;
|
|
16335
|
+
return this.lines.slice(start, end);
|
|
16336
|
+
}
|
|
16337
|
+
search(pattern) {
|
|
16338
|
+
const matches = [];
|
|
16339
|
+
for (let i = 0;i < this.lines.length; i++) {
|
|
16340
|
+
const line = this.lines[i];
|
|
16341
|
+
if (line !== undefined && pattern.test(line)) {
|
|
16342
|
+
matches.push({ lineNumber: i + 1, text: line });
|
|
16343
|
+
}
|
|
16344
|
+
}
|
|
16345
|
+
return matches;
|
|
16346
|
+
}
|
|
16347
|
+
get length() {
|
|
16348
|
+
return this.lines.length;
|
|
16349
|
+
}
|
|
16350
|
+
clear() {
|
|
16351
|
+
this.lines = [];
|
|
16352
|
+
}
|
|
16353
|
+
}
|
|
16354
|
+
|
|
16355
|
+
// src/tools/pty/manager.ts
|
|
16356
|
+
function generateId() {
|
|
16357
|
+
const hex3 = Array.from(crypto.getRandomValues(new Uint8Array(4))).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
16358
|
+
return `pty_${hex3}`;
|
|
16359
|
+
}
|
|
16360
|
+
|
|
16361
|
+
class PTYManager {
|
|
16362
|
+
sessions = new Map;
|
|
16363
|
+
spawn(opts) {
|
|
16364
|
+
const id = generateId();
|
|
16365
|
+
const args = opts.args ?? [];
|
|
16366
|
+
const workdir = opts.workdir ?? process.cwd();
|
|
16367
|
+
const env = { ...process.env, ...opts.env };
|
|
16368
|
+
const title = opts.title ?? (`${opts.command} ${args.join(" ")}`.trim() || `Terminal ${id.slice(-4)}`);
|
|
16369
|
+
const ptyProcess = spawn3(opts.command, args, {
|
|
16370
|
+
name: "xterm-256color",
|
|
16371
|
+
cols: 120,
|
|
16372
|
+
rows: 40,
|
|
16373
|
+
cwd: workdir,
|
|
16374
|
+
env
|
|
16375
|
+
});
|
|
16376
|
+
const buffer = new RingBuffer;
|
|
16377
|
+
const session = {
|
|
16378
|
+
id,
|
|
16379
|
+
title,
|
|
16380
|
+
command: opts.command,
|
|
16381
|
+
args,
|
|
16382
|
+
workdir,
|
|
16383
|
+
env: opts.env,
|
|
16384
|
+
status: "running",
|
|
16385
|
+
pid: ptyProcess.pid,
|
|
16386
|
+
createdAt: new Date,
|
|
16387
|
+
parentSessionId: opts.parentSessionId,
|
|
16388
|
+
buffer,
|
|
16389
|
+
process: ptyProcess
|
|
16390
|
+
};
|
|
16391
|
+
this.sessions.set(id, session);
|
|
16392
|
+
ptyProcess.onData((data) => {
|
|
16393
|
+
buffer.append(data);
|
|
16394
|
+
});
|
|
16395
|
+
ptyProcess.onExit(({ exitCode }) => {
|
|
16396
|
+
if (session.status === "running") {
|
|
16397
|
+
session.status = "exited";
|
|
16398
|
+
session.exitCode = exitCode;
|
|
16399
|
+
}
|
|
16400
|
+
});
|
|
16401
|
+
return this.toInfo(session);
|
|
16402
|
+
}
|
|
16403
|
+
write(id, data) {
|
|
16404
|
+
const session = this.sessions.get(id);
|
|
16405
|
+
if (!session) {
|
|
16406
|
+
return false;
|
|
16407
|
+
}
|
|
16408
|
+
if (session.status !== "running") {
|
|
16409
|
+
return false;
|
|
16410
|
+
}
|
|
16411
|
+
session.process.write(data);
|
|
16412
|
+
return true;
|
|
16413
|
+
}
|
|
16414
|
+
read(id, offset = 0, limit) {
|
|
16415
|
+
const session = this.sessions.get(id);
|
|
16416
|
+
if (!session) {
|
|
16417
|
+
return null;
|
|
16418
|
+
}
|
|
16419
|
+
const lines = session.buffer.read(offset, limit);
|
|
16420
|
+
const totalLines = session.buffer.length;
|
|
16421
|
+
const hasMore = offset + lines.length < totalLines;
|
|
16422
|
+
return { lines, totalLines, offset, hasMore };
|
|
16423
|
+
}
|
|
16424
|
+
search(id, pattern, offset = 0, limit) {
|
|
16425
|
+
const session = this.sessions.get(id);
|
|
16426
|
+
if (!session) {
|
|
16427
|
+
return null;
|
|
16428
|
+
}
|
|
16429
|
+
const allMatches = session.buffer.search(pattern);
|
|
16430
|
+
const totalMatches = allMatches.length;
|
|
16431
|
+
const totalLines = session.buffer.length;
|
|
16432
|
+
const paginatedMatches = limit !== undefined ? allMatches.slice(offset, offset + limit) : allMatches.slice(offset);
|
|
16433
|
+
const hasMore = offset + paginatedMatches.length < totalMatches;
|
|
16434
|
+
return { matches: paginatedMatches, totalMatches, totalLines, offset, hasMore };
|
|
16435
|
+
}
|
|
16436
|
+
list() {
|
|
16437
|
+
return Array.from(this.sessions.values()).map((s) => this.toInfo(s));
|
|
16438
|
+
}
|
|
16439
|
+
get(id) {
|
|
16440
|
+
const session = this.sessions.get(id);
|
|
16441
|
+
return session ? this.toInfo(session) : null;
|
|
16442
|
+
}
|
|
16443
|
+
kill(id, cleanup = false) {
|
|
16444
|
+
const session = this.sessions.get(id);
|
|
16445
|
+
if (!session) {
|
|
16446
|
+
return false;
|
|
16447
|
+
}
|
|
16448
|
+
if (session.status === "running") {
|
|
16449
|
+
try {
|
|
16450
|
+
session.process.kill();
|
|
16451
|
+
} catch {}
|
|
16452
|
+
session.status = "killed";
|
|
16453
|
+
}
|
|
16454
|
+
if (cleanup) {
|
|
16455
|
+
session.buffer.clear();
|
|
16456
|
+
this.sessions.delete(id);
|
|
16457
|
+
}
|
|
16458
|
+
return true;
|
|
16459
|
+
}
|
|
16460
|
+
cleanupBySession(parentSessionId) {
|
|
16461
|
+
for (const [id, session] of this.sessions) {
|
|
16462
|
+
if (session.parentSessionId === parentSessionId) {
|
|
16463
|
+
this.kill(id, true);
|
|
16464
|
+
}
|
|
16465
|
+
}
|
|
16466
|
+
}
|
|
16467
|
+
cleanupAll() {
|
|
16468
|
+
for (const id of this.sessions.keys()) {
|
|
16469
|
+
this.kill(id, true);
|
|
16470
|
+
}
|
|
16471
|
+
}
|
|
16472
|
+
toInfo(session) {
|
|
16473
|
+
return {
|
|
16474
|
+
id: session.id,
|
|
16475
|
+
title: session.title,
|
|
16476
|
+
command: session.command,
|
|
16477
|
+
args: session.args,
|
|
16478
|
+
workdir: session.workdir,
|
|
16479
|
+
status: session.status,
|
|
16480
|
+
exitCode: session.exitCode,
|
|
16481
|
+
pid: session.pid,
|
|
16482
|
+
createdAt: session.createdAt,
|
|
16483
|
+
lineCount: session.buffer.length
|
|
16484
|
+
};
|
|
16485
|
+
}
|
|
16486
|
+
}
|
|
16487
|
+
// src/tools/pty/tools/spawn.ts
|
|
16488
|
+
var DESCRIPTION = `Spawns a new interactive PTY (pseudo-terminal) session that runs in the background.
|
|
16489
|
+
|
|
16490
|
+
Unlike the built-in bash tool which runs commands synchronously and waits for completion, PTY sessions persist and allow you to:
|
|
16491
|
+
- Run long-running processes (dev servers, watch modes, etc.)
|
|
16492
|
+
- Send interactive input (including Ctrl+C, arrow keys, etc.)
|
|
16493
|
+
- Read output at any time
|
|
16494
|
+
- Manage multiple concurrent terminal sessions
|
|
16495
|
+
|
|
16496
|
+
Usage:
|
|
16497
|
+
- The \`command\` parameter is required (e.g., "npm", "python", "bash")
|
|
16498
|
+
- Use \`args\` to pass arguments to the command (e.g., ["run", "dev"])
|
|
16499
|
+
- Use \`workdir\` to set the working directory (defaults to project root)
|
|
16500
|
+
- Use \`env\` to set additional environment variables
|
|
16501
|
+
- Use \`title\` to give the session a human-readable name
|
|
16502
|
+
- Use \`description\` for a clear, concise 5-10 word description (optional)
|
|
16503
|
+
|
|
16504
|
+
Returns the session info including:
|
|
16505
|
+
- \`id\`: Unique identifier (pty_XXXXXXXX) for use with other pty_* tools
|
|
16506
|
+
- \`pid\`: Process ID
|
|
16507
|
+
- \`status\`: Current status ("running")
|
|
16508
|
+
|
|
16509
|
+
After spawning, use:
|
|
16510
|
+
- \`pty_write\` to send input to the PTY
|
|
16511
|
+
- \`pty_read\` to read output from the PTY
|
|
16512
|
+
- \`pty_list\` to see all active PTY sessions
|
|
16513
|
+
- \`pty_kill\` to terminate the PTY
|
|
16514
|
+
|
|
16515
|
+
Examples:
|
|
16516
|
+
- Start a dev server: command="npm", args=["run", "dev"], title="Dev Server"
|
|
16517
|
+
- Start a Python REPL: command="python3", title="Python REPL"
|
|
16518
|
+
- Run tests in watch mode: command="npm", args=["test", "--", "--watch"]`;
|
|
16519
|
+
function createPtySpawnTool(manager) {
|
|
16520
|
+
return tool({
|
|
16521
|
+
description: DESCRIPTION,
|
|
16522
|
+
args: {
|
|
16523
|
+
command: tool.schema.string().describe("The command/executable to run"),
|
|
16524
|
+
args: tool.schema.array(tool.schema.string()).optional().describe("Arguments to pass to the command"),
|
|
16525
|
+
workdir: tool.schema.string().optional().describe("Working directory for the PTY session"),
|
|
16526
|
+
env: tool.schema.record(tool.schema.string(), tool.schema.string()).optional().describe("Additional environment variables"),
|
|
16527
|
+
title: tool.schema.string().optional().describe("Human-readable title for the session"),
|
|
16528
|
+
description: tool.schema.string().optional().describe("Clear, concise description of what this PTY session is for in 5-10 words")
|
|
16529
|
+
},
|
|
16530
|
+
execute: async (args, ctx) => {
|
|
16531
|
+
const info = manager.spawn({
|
|
16532
|
+
command: args.command,
|
|
16533
|
+
args: args.args,
|
|
16534
|
+
workdir: args.workdir,
|
|
16535
|
+
env: args.env,
|
|
16536
|
+
title: args.title,
|
|
16537
|
+
parentSessionId: ctx.sessionID
|
|
16538
|
+
});
|
|
16539
|
+
const output = [
|
|
16540
|
+
`<pty_spawned>`,
|
|
16541
|
+
`ID: ${info.id}`,
|
|
16542
|
+
`Title: ${info.title}`,
|
|
16543
|
+
`Command: ${info.command} ${info.args.join(" ")}`,
|
|
16544
|
+
`Workdir: ${info.workdir}`,
|
|
16545
|
+
`PID: ${info.pid}`,
|
|
16546
|
+
`Status: ${info.status}`,
|
|
16547
|
+
`</pty_spawned>`
|
|
16548
|
+
].join(`
|
|
16549
|
+
`);
|
|
16550
|
+
return output;
|
|
16551
|
+
}
|
|
16552
|
+
});
|
|
16553
|
+
}
|
|
16554
|
+
// src/tools/pty/tools/write.ts
|
|
16555
|
+
var DESCRIPTION2 = `Sends input data to an active PTY session.
|
|
16556
|
+
|
|
16557
|
+
Use this tool to:
|
|
16558
|
+
- Type commands or text into an interactive terminal
|
|
16559
|
+
- Send special key sequences (Ctrl+C, Enter, arrow keys, etc.)
|
|
16560
|
+
- Respond to prompts in interactive programs
|
|
16561
|
+
|
|
16562
|
+
Usage:
|
|
16563
|
+
- \`id\`: The PTY session ID (from pty_spawn or pty_list)
|
|
16564
|
+
- \`data\`: The input to send (text, commands, or escape sequences)
|
|
16565
|
+
|
|
16566
|
+
Common escape sequences:
|
|
16567
|
+
- Enter/newline: "\\n" or "\\r"
|
|
16568
|
+
- Ctrl+C (interrupt): "\\x03"
|
|
16569
|
+
- Ctrl+D (EOF): "\\x04"
|
|
16570
|
+
- Ctrl+Z (suspend): "\\x1a"
|
|
16571
|
+
- Tab: "\\t"
|
|
16572
|
+
- Arrow Up: "\\x1b[A"
|
|
16573
|
+
- Arrow Down: "\\x1b[B"
|
|
16574
|
+
- Arrow Right: "\\x1b[C"
|
|
16575
|
+
- Arrow Left: "\\x1b[D"
|
|
16576
|
+
|
|
16577
|
+
Returns success or error message.
|
|
16578
|
+
|
|
16579
|
+
Examples:
|
|
16580
|
+
- Send a command: data="ls -la\\n"
|
|
16581
|
+
- Interrupt a process: data="\\x03"
|
|
16582
|
+
- Answer a prompt: data="yes\\n"`;
|
|
16583
|
+
function parseEscapeSequences(input) {
|
|
16584
|
+
return input.replace(/\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|[nrt0\\])/g, (match, seq) => {
|
|
16585
|
+
if (seq.startsWith("x")) {
|
|
16586
|
+
return String.fromCharCode(parseInt(seq.slice(1), 16));
|
|
16587
|
+
}
|
|
16588
|
+
if (seq.startsWith("u")) {
|
|
16589
|
+
return String.fromCharCode(parseInt(seq.slice(1), 16));
|
|
16590
|
+
}
|
|
16591
|
+
switch (seq) {
|
|
16592
|
+
case "n":
|
|
16593
|
+
return `
|
|
16594
|
+
`;
|
|
16595
|
+
case "r":
|
|
16596
|
+
return "\r";
|
|
16597
|
+
case "t":
|
|
16598
|
+
return "\t";
|
|
16599
|
+
case "0":
|
|
16600
|
+
return "\x00";
|
|
16601
|
+
case "\\":
|
|
16602
|
+
return "\\";
|
|
16603
|
+
default:
|
|
16604
|
+
return match;
|
|
16605
|
+
}
|
|
16606
|
+
});
|
|
16607
|
+
}
|
|
16608
|
+
function createPtyWriteTool(manager) {
|
|
16609
|
+
return tool({
|
|
16610
|
+
description: DESCRIPTION2,
|
|
16611
|
+
args: {
|
|
16612
|
+
id: tool.schema.string().describe("The PTY session ID (e.g., pty_a1b2c3d4)"),
|
|
16613
|
+
data: tool.schema.string().describe("The input data to send to the PTY")
|
|
16614
|
+
},
|
|
16615
|
+
execute: async (args) => {
|
|
16616
|
+
const session = manager.get(args.id);
|
|
16617
|
+
if (!session) {
|
|
16618
|
+
throw new Error(`PTY session '${args.id}' not found. Use pty_list to see active sessions.`);
|
|
16619
|
+
}
|
|
16620
|
+
if (session.status !== "running") {
|
|
16621
|
+
throw new Error(`Cannot write to PTY '${args.id}' - session status is '${session.status}'.`);
|
|
16622
|
+
}
|
|
16623
|
+
const parsedData = parseEscapeSequences(args.data);
|
|
16624
|
+
const success2 = manager.write(args.id, parsedData);
|
|
16625
|
+
if (!success2) {
|
|
16626
|
+
throw new Error(`Failed to write to PTY '${args.id}'.`);
|
|
16627
|
+
}
|
|
16628
|
+
const preview = args.data.length > 50 ? `${args.data.slice(0, 50)}...` : args.data;
|
|
16629
|
+
const displayPreview = preview.replace(/\x03/g, "^C").replace(/\x04/g, "^D").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
|
|
16630
|
+
return `Sent ${parsedData.length} bytes to ${args.id}: "${displayPreview}"`;
|
|
16631
|
+
}
|
|
16632
|
+
});
|
|
16633
|
+
}
|
|
16634
|
+
// src/tools/pty/tools/read.ts
|
|
16635
|
+
var DESCRIPTION3 = `Reads output from a PTY session's buffer.
|
|
16636
|
+
|
|
16637
|
+
The PTY maintains a rolling buffer of output lines. Use offset and limit to paginate through the output, similar to reading a file.
|
|
16638
|
+
|
|
16639
|
+
Usage:
|
|
16640
|
+
- \`id\`: The PTY session ID (from pty_spawn or pty_list)
|
|
16641
|
+
- \`offset\`: Line number to start reading from (0-based, defaults to 0)
|
|
16642
|
+
- \`limit\`: Number of lines to read (defaults to 500)
|
|
16643
|
+
- \`pattern\`: Regex pattern to filter lines (optional)
|
|
16644
|
+
- \`ignoreCase\`: Case-insensitive pattern matching (default: false)
|
|
16645
|
+
|
|
16646
|
+
Returns:
|
|
16647
|
+
- Numbered lines of output (similar to cat -n format)
|
|
16648
|
+
- Total line count in the buffer
|
|
16649
|
+
- Indicator if more lines are available
|
|
16650
|
+
|
|
16651
|
+
The buffer stores up to PTY_MAX_BUFFER_LINES (default: 50000) lines. Older lines are discarded when the limit is reached.
|
|
16652
|
+
|
|
16653
|
+
Pattern Filtering:
|
|
16654
|
+
- When \`pattern\` is set, lines are FILTERED FIRST using the regex, then offset/limit apply to the MATCHES
|
|
16655
|
+
- Original line numbers are preserved so you can see where matches occurred in the buffer
|
|
16656
|
+
- Supports full regex syntax (e.g., "error", "ERROR|WARN", "failed.*connection", etc.)
|
|
16657
|
+
- If the pattern is invalid, an error message is returned explaining the issue
|
|
16658
|
+
- If no lines match the pattern, a clear message indicates zero matches
|
|
16659
|
+
|
|
16660
|
+
Tips:
|
|
16661
|
+
- To see the latest output, use a high offset or omit offset to read from the start
|
|
16662
|
+
- To tail recent output, calculate offset as (totalLines - N) where N is how many recent lines you want
|
|
16663
|
+
- Lines longer than 2000 characters are truncated
|
|
16664
|
+
- Empty output may mean the process hasn't produced output yet
|
|
16665
|
+
|
|
16666
|
+
Examples:
|
|
16667
|
+
- Read first 100 lines: offset=0, limit=100
|
|
16668
|
+
- Read lines 500-600: offset=500, limit=100
|
|
16669
|
+
- Read all available: omit both parameters
|
|
16670
|
+
- Find errors: pattern="error", ignoreCase=true
|
|
16671
|
+
- Find specific log levels: pattern="ERROR|WARN|FATAL"
|
|
16672
|
+
- First 10 matches only: pattern="error", limit=10`;
|
|
16673
|
+
var DEFAULT_LIMIT = 500;
|
|
16674
|
+
var MAX_LINE_LENGTH = 2000;
|
|
16675
|
+
function createPtyReadTool(manager) {
|
|
16676
|
+
return tool({
|
|
16677
|
+
description: DESCRIPTION3,
|
|
16678
|
+
args: {
|
|
16679
|
+
id: tool.schema.string().describe("The PTY session ID (e.g., pty_a1b2c3d4)"),
|
|
16680
|
+
offset: tool.schema.number().optional().describe("Line number to start reading from (0-based, defaults to 0)"),
|
|
16681
|
+
limit: tool.schema.number().optional().describe("Number of lines to read (defaults to 500)"),
|
|
16682
|
+
pattern: tool.schema.string().optional().describe("Regex pattern to filter lines"),
|
|
16683
|
+
ignoreCase: tool.schema.boolean().optional().describe("Case-insensitive pattern matching (default: false)")
|
|
16684
|
+
},
|
|
16685
|
+
execute: async (args) => {
|
|
16686
|
+
const session = manager.get(args.id);
|
|
16687
|
+
if (!session) {
|
|
16688
|
+
throw new Error(`PTY session '${args.id}' not found. Use pty_list to see active sessions.`);
|
|
16689
|
+
}
|
|
16690
|
+
const offset = Math.max(0, args.offset ?? 0);
|
|
16691
|
+
const limit = args.limit ?? DEFAULT_LIMIT;
|
|
16692
|
+
if (args.pattern) {
|
|
16693
|
+
let regex;
|
|
16694
|
+
try {
|
|
16695
|
+
regex = new RegExp(args.pattern, args.ignoreCase ? "i" : "");
|
|
16696
|
+
} catch (e) {
|
|
16697
|
+
const error45 = e instanceof Error ? e.message : String(e);
|
|
16698
|
+
throw new Error(`Invalid regex pattern '${args.pattern}': ${error45}`);
|
|
16699
|
+
}
|
|
16700
|
+
const result2 = manager.search(args.id, regex, offset, limit);
|
|
16701
|
+
if (!result2) {
|
|
16702
|
+
throw new Error(`PTY session '${args.id}' not found.`);
|
|
16703
|
+
}
|
|
16704
|
+
if (result2.matches.length === 0) {
|
|
16705
|
+
return [
|
|
16706
|
+
`<pty_output id="${args.id}" status="${session.status}" pattern="${args.pattern}">`,
|
|
16707
|
+
`No lines matched the pattern '${args.pattern}'.`,
|
|
16708
|
+
`Total lines in buffer: ${result2.totalLines}`,
|
|
16709
|
+
`</pty_output>`
|
|
16710
|
+
].join(`
|
|
16711
|
+
`);
|
|
16712
|
+
}
|
|
16713
|
+
const formattedLines2 = result2.matches.map((match) => {
|
|
16714
|
+
const lineNum = match.lineNumber.toString().padStart(5, "0");
|
|
16715
|
+
const truncatedLine = match.text.length > MAX_LINE_LENGTH ? `${match.text.slice(0, MAX_LINE_LENGTH)}...` : match.text;
|
|
16716
|
+
return `${lineNum}| ${truncatedLine}`;
|
|
16717
|
+
});
|
|
16718
|
+
const output2 = [
|
|
16719
|
+
`<pty_output id="${args.id}" status="${session.status}" pattern="${args.pattern}">`,
|
|
16720
|
+
...formattedLines2,
|
|
16721
|
+
""
|
|
16722
|
+
];
|
|
16723
|
+
if (result2.hasMore) {
|
|
16724
|
+
output2.push(`(${result2.matches.length} of ${result2.totalMatches} matches shown. Use offset=${offset + result2.matches.length} to see more.)`);
|
|
16725
|
+
} else {
|
|
16726
|
+
output2.push(`(${result2.totalMatches} match${result2.totalMatches === 1 ? "" : "es"} from ${result2.totalLines} total lines)`);
|
|
16727
|
+
}
|
|
16728
|
+
output2.push(`</pty_output>`);
|
|
16729
|
+
return output2.join(`
|
|
16730
|
+
`);
|
|
16731
|
+
}
|
|
16732
|
+
const result = manager.read(args.id, offset, limit);
|
|
16733
|
+
if (!result) {
|
|
16734
|
+
throw new Error(`PTY session '${args.id}' not found.`);
|
|
16735
|
+
}
|
|
16736
|
+
if (result.lines.length === 0) {
|
|
16737
|
+
return [
|
|
16738
|
+
`<pty_output id="${args.id}" status="${session.status}">`,
|
|
16739
|
+
`(No output available - buffer is empty)`,
|
|
16740
|
+
`Total lines: ${result.totalLines}`,
|
|
16741
|
+
`</pty_output>`
|
|
16742
|
+
].join(`
|
|
16743
|
+
`);
|
|
16744
|
+
}
|
|
16745
|
+
const formattedLines = result.lines.map((line, index) => {
|
|
16746
|
+
const lineNum = (result.offset + index + 1).toString().padStart(5, "0");
|
|
16747
|
+
const truncatedLine = line.length > MAX_LINE_LENGTH ? `${line.slice(0, MAX_LINE_LENGTH)}...` : line;
|
|
16748
|
+
return `${lineNum}| ${truncatedLine}`;
|
|
16749
|
+
});
|
|
16750
|
+
const output = [`<pty_output id="${args.id}" status="${session.status}">`, ...formattedLines];
|
|
16751
|
+
if (result.hasMore) {
|
|
16752
|
+
output.push("");
|
|
16753
|
+
output.push(`(Buffer has more lines. Use offset=${result.offset + result.lines.length} to read beyond line ${result.offset + result.lines.length})`);
|
|
16754
|
+
} else {
|
|
16755
|
+
output.push("");
|
|
16756
|
+
output.push(`(End of buffer - total ${result.totalLines} lines)`);
|
|
16757
|
+
}
|
|
16758
|
+
output.push(`</pty_output>`);
|
|
16759
|
+
return output.join(`
|
|
16760
|
+
`);
|
|
16761
|
+
}
|
|
16762
|
+
});
|
|
16763
|
+
}
|
|
16764
|
+
// src/tools/pty/tools/list.ts
|
|
16765
|
+
var DESCRIPTION4 = `Lists all PTY sessions (active and exited).
|
|
16766
|
+
|
|
16767
|
+
Use this tool to:
|
|
16768
|
+
- See all running and exited PTY sessions
|
|
16769
|
+
- Get session IDs for use with other pty_* tools
|
|
16770
|
+
- Check the status and output line count of each session
|
|
16771
|
+
- Monitor which processes are still running
|
|
16772
|
+
|
|
16773
|
+
Returns for each session:
|
|
16774
|
+
- \`id\`: Unique identifier for use with other tools
|
|
16775
|
+
- \`title\`: Human-readable name
|
|
16776
|
+
- \`command\`: The command that was executed
|
|
16777
|
+
- \`status\`: Current status (running, exited, killed)
|
|
16778
|
+
- \`exitCode\`: Exit code (if exited/killed)
|
|
16779
|
+
- \`pid\`: Process ID
|
|
16780
|
+
- \`lineCount\`: Number of lines in the output buffer
|
|
16781
|
+
- \`createdAt\`: When the session was created
|
|
16782
|
+
|
|
16783
|
+
Tips:
|
|
16784
|
+
- Use the session ID with pty_read, pty_write, or pty_kill
|
|
16785
|
+
- Sessions remain in the list after exit until explicitly cleaned up with pty_kill
|
|
16786
|
+
- This allows you to compare output from multiple sessions`;
|
|
16787
|
+
function createPtyListTool(manager) {
|
|
16788
|
+
return tool({
|
|
16789
|
+
description: DESCRIPTION4,
|
|
16790
|
+
args: {},
|
|
16791
|
+
execute: async () => {
|
|
16792
|
+
const sessions = manager.list();
|
|
16793
|
+
if (sessions.length === 0) {
|
|
16794
|
+
return `<pty_list>
|
|
16795
|
+
No active PTY sessions.
|
|
16796
|
+
</pty_list>`;
|
|
16797
|
+
}
|
|
16798
|
+
const lines = ["<pty_list>"];
|
|
16799
|
+
for (const session of sessions) {
|
|
16800
|
+
const exitInfo = session.exitCode !== undefined ? ` (exit: ${session.exitCode})` : "";
|
|
16801
|
+
lines.push(`[${session.id}] ${session.title}`);
|
|
16802
|
+
lines.push(` Command: ${session.command} ${session.args.join(" ")}`);
|
|
16803
|
+
lines.push(` Status: ${session.status}${exitInfo}`);
|
|
16804
|
+
lines.push(` PID: ${session.pid} | Lines: ${session.lineCount} | Workdir: ${session.workdir}`);
|
|
16805
|
+
lines.push(` Created: ${session.createdAt.toISOString()}`);
|
|
16806
|
+
lines.push("");
|
|
16807
|
+
}
|
|
16808
|
+
lines.push(`Total: ${sessions.length} session(s)`);
|
|
16809
|
+
lines.push("</pty_list>");
|
|
16810
|
+
return lines.join(`
|
|
16811
|
+
`);
|
|
16812
|
+
}
|
|
16813
|
+
});
|
|
16814
|
+
}
|
|
16815
|
+
// src/tools/pty/tools/kill.ts
|
|
16816
|
+
var DESCRIPTION5 = `Terminates a PTY session and optionally cleans up its buffer.
|
|
16817
|
+
|
|
16818
|
+
Use this tool to:
|
|
16819
|
+
- Stop a running process (sends SIGTERM)
|
|
16820
|
+
- Clean up an exited session to free memory
|
|
16821
|
+
- Remove a session from the list
|
|
16822
|
+
|
|
16823
|
+
Usage:
|
|
16824
|
+
- \`id\`: The PTY session ID (from pty_spawn or pty_list)
|
|
16825
|
+
- \`cleanup\`: If true, removes the session and frees the buffer (default: false)
|
|
16826
|
+
|
|
16827
|
+
Behavior:
|
|
16828
|
+
- If the session is running, it will be killed (status becomes "killed")
|
|
16829
|
+
- If cleanup=false (default), the session remains in the list with its output buffer intact
|
|
16830
|
+
- If cleanup=true, the session is removed entirely and the buffer is freed
|
|
16831
|
+
- Keeping sessions without cleanup allows you to compare logs between runs
|
|
16832
|
+
|
|
16833
|
+
Tips:
|
|
16834
|
+
- Use cleanup=false if you might want to read the output later
|
|
16835
|
+
- Use cleanup=true when you're done with the session entirely
|
|
16836
|
+
- To send Ctrl+C instead of killing, use pty_write with data="\\x03"
|
|
16837
|
+
|
|
16838
|
+
Examples:
|
|
16839
|
+
- Kill but keep logs: cleanup=false (or omit)
|
|
16840
|
+
- Kill and remove: cleanup=true`;
|
|
16841
|
+
function createPtyKillTool(manager) {
|
|
16842
|
+
return tool({
|
|
16843
|
+
description: DESCRIPTION5,
|
|
16844
|
+
args: {
|
|
16845
|
+
id: tool.schema.string().describe("The PTY session ID (e.g., pty_a1b2c3d4)"),
|
|
16846
|
+
cleanup: tool.schema.boolean().optional().describe("If true, removes the session and frees the buffer (default: false)")
|
|
16847
|
+
},
|
|
16848
|
+
execute: async (args) => {
|
|
16849
|
+
const session = manager.get(args.id);
|
|
16850
|
+
if (!session) {
|
|
16851
|
+
throw new Error(`PTY session '${args.id}' not found. Use pty_list to see active sessions.`);
|
|
16852
|
+
}
|
|
16853
|
+
const wasRunning = session.status === "running";
|
|
16854
|
+
const cleanup = args.cleanup ?? false;
|
|
16855
|
+
const success2 = manager.kill(args.id, cleanup);
|
|
16856
|
+
if (!success2) {
|
|
16857
|
+
throw new Error(`Failed to kill PTY session '${args.id}'.`);
|
|
16858
|
+
}
|
|
16859
|
+
const action = wasRunning ? "Killed" : "Cleaned up";
|
|
16860
|
+
const cleanupNote = cleanup ? " (session removed)" : " (session retained for log access)";
|
|
16861
|
+
return [
|
|
16862
|
+
`<pty_killed>`,
|
|
16863
|
+
`${action}: ${args.id}${cleanupNote}`,
|
|
16864
|
+
`Title: ${session.title}`,
|
|
16865
|
+
`Command: ${session.command} ${session.args.join(" ")}`,
|
|
16866
|
+
`Final line count: ${session.lineCount}`,
|
|
16867
|
+
`</pty_killed>`
|
|
16868
|
+
].join(`
|
|
16869
|
+
`);
|
|
16870
|
+
}
|
|
16871
|
+
});
|
|
16872
|
+
}
|
|
16873
|
+
// src/tools/pty/index.ts
|
|
16874
|
+
function createPtyTools(manager) {
|
|
16875
|
+
return {
|
|
16876
|
+
pty_spawn: createPtySpawnTool(manager),
|
|
16877
|
+
pty_write: createPtyWriteTool(manager),
|
|
16878
|
+
pty_read: createPtyReadTool(manager),
|
|
16879
|
+
pty_list: createPtyListTool(manager),
|
|
16880
|
+
pty_kill: createPtyKillTool(manager)
|
|
16881
|
+
};
|
|
16882
|
+
}
|
|
16883
|
+
|
|
15890
16884
|
// src/config-loader.ts
|
|
15891
16885
|
import { readFile as readFile3 } from "fs/promises";
|
|
15892
|
-
import { join as
|
|
16886
|
+
import { join as join5 } from "path";
|
|
15893
16887
|
import { homedir as homedir2 } from "os";
|
|
15894
16888
|
var SAFE_AGENT_PROPERTIES = ["model", "temperature", "maxTokens"];
|
|
15895
16889
|
async function loadMicodeConfig(configDir) {
|
|
15896
|
-
const baseDir = configDir ??
|
|
15897
|
-
const configPath =
|
|
16890
|
+
const baseDir = configDir ?? join5(homedir2(), ".config", "opencode");
|
|
16891
|
+
const configPath = join5(baseDir, "micode.json");
|
|
15898
16892
|
try {
|
|
15899
16893
|
const content = await readFile3(configPath, "utf-8");
|
|
15900
|
-
const
|
|
15901
|
-
if (
|
|
16894
|
+
const parsed2 = JSON.parse(content);
|
|
16895
|
+
if (parsed2.agents && typeof parsed2.agents === "object") {
|
|
15902
16896
|
const sanitizedAgents = {};
|
|
15903
|
-
for (const [agentName, agentConfig] of Object.entries(
|
|
16897
|
+
for (const [agentName, agentConfig] of Object.entries(parsed2.agents)) {
|
|
15904
16898
|
if (agentConfig && typeof agentConfig === "object") {
|
|
15905
16899
|
const sanitized = {};
|
|
15906
16900
|
const config2 = agentConfig;
|
|
@@ -15914,7 +16908,7 @@ async function loadMicodeConfig(configDir) {
|
|
|
15914
16908
|
}
|
|
15915
16909
|
return { agents: sanitizedAgents };
|
|
15916
16910
|
}
|
|
15917
|
-
return
|
|
16911
|
+
return parsed2;
|
|
15918
16912
|
} catch {
|
|
15919
16913
|
return null;
|
|
15920
16914
|
}
|
|
@@ -15992,6 +16986,8 @@ var OpenCodeConfigPlugin = async (ctx) => {
|
|
|
15992
16986
|
const fileOpsTrackerHook = createFileOpsTrackerHook(ctx);
|
|
15993
16987
|
const backgroundTaskManager = new BackgroundTaskManager(ctx);
|
|
15994
16988
|
const backgroundTaskTools = createBackgroundTaskTools(backgroundTaskManager);
|
|
16989
|
+
const ptyManager = new PTYManager;
|
|
16990
|
+
const ptyTools = createPtyTools(ptyManager);
|
|
15995
16991
|
return {
|
|
15996
16992
|
tool: {
|
|
15997
16993
|
ast_grep_search,
|
|
@@ -15999,7 +16995,8 @@ var OpenCodeConfigPlugin = async (ctx) => {
|
|
|
15999
16995
|
btca_ask,
|
|
16000
16996
|
look_at,
|
|
16001
16997
|
artifact_search,
|
|
16002
|
-
...backgroundTaskTools
|
|
16998
|
+
...backgroundTaskTools,
|
|
16999
|
+
...ptyTools
|
|
16003
17000
|
},
|
|
16004
17001
|
config: async (config2) => {
|
|
16005
17002
|
config2.permission = {
|
|
@@ -16073,6 +17070,7 @@ var OpenCodeConfigPlugin = async (ctx) => {
|
|
|
16073
17070
|
const props = event.properties;
|
|
16074
17071
|
if (props?.info?.id) {
|
|
16075
17072
|
thinkModeState.delete(props.info.id);
|
|
17073
|
+
ptyManager.cleanupBySession(props.info.id);
|
|
16076
17074
|
}
|
|
16077
17075
|
}
|
|
16078
17076
|
await autoCompactHook.event({ event });
|