consolle 0.3.5 → 0.3.6
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.
- checksums.yaml +4 -4
- data/.gitignore +3 -1
- data/.version +1 -1
- data/Gemfile.lock +1 -1
- data/README.md +43 -9
- data/lib/consolle/adapters/rails_console.rb +1 -1
- data/lib/consolle/cli.rb +39 -28
- data/lib/consolle/errors.rb +10 -2
- data/lib/consolle/server/console_supervisor.rb +58 -18
- data/lib/consolle/server/request_broker.rb +11 -4
- data/rule.ko.md +2 -2
- data/rule.md +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9b3d8a284ca63f38a9e02ca394141f50cd4c0d02a63bae7f78d384a027d0c400
|
|
4
|
+
data.tar.gz: 7ddfdc210a765e4fc8c6598e5b2d38ffe78f2997a4811c55f6bf461862ccba0a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ce17c1df36c8284f051bfd658e63252846547e0886cbe4330005470ce4997f24334a470196de0310f24cccf7f305b4c358985752d301cc76a9514d15c296feb5
|
|
7
|
+
data.tar.gz: 93ff0d888e2436be4cc2952508e6b8dba29afdd79303434e0126871022c23f67de70cccbfc0c8e7e33c0d853c2f90aaf8c2be61f6d3e0559ee8694b195b85807
|
data/.gitignore
CHANGED
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
1
|
+
0.3.6
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Consolle is a library that manages Rails console through PTY (Pseudo-Terminal).
|
|
|
8
8
|
- **Socket Server Architecture**: Stable client-server communication through Unix socket
|
|
9
9
|
- **Automatic Restart (Watchdog)**: Automatic recovery on process failure
|
|
10
10
|
- **Environment-specific Execution**: Supports Rails environments (development, test, production)
|
|
11
|
-
- **Timeout Handling**: Automatic termination of
|
|
11
|
+
- **Timeout Handling**: Automatic termination of long-running code with robust prompt recovery
|
|
12
12
|
- **Log Management**: Automatic management of execution history and session logs
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
@@ -50,17 +50,46 @@ cone restart
|
|
|
50
50
|
### Advanced Usage
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
|
-
# Start with specific environment
|
|
54
|
-
cone start
|
|
53
|
+
# Start with specific environment (use RAILS_ENV)
|
|
54
|
+
RAILS_ENV=test cone start
|
|
55
55
|
|
|
56
|
-
# Restart with environment change
|
|
57
|
-
cone restart
|
|
56
|
+
# Restart with environment change (use RAILS_ENV)
|
|
57
|
+
RAILS_ENV=production cone restart
|
|
58
58
|
|
|
59
59
|
# Force full server restart
|
|
60
60
|
cone restart --force
|
|
61
61
|
|
|
62
|
-
# Set timeout
|
|
63
|
-
cone exec "
|
|
62
|
+
# Set timeout (default: 60s; precedence: CONSOLLE_TIMEOUT > --timeout > defaults)
|
|
63
|
+
CONSOLLE_TIMEOUT=90 cone exec "long_running_task" # env wins
|
|
64
|
+
cone exec "long_running_task" --timeout 120 # falls back when env not set
|
|
65
|
+
|
|
66
|
+
# Pre-exec Ctrl-C (prompt separation)
|
|
67
|
+
By default (development/production), cone sends Ctrl-C before each `exec` and waits for the IRB prompt (up to 3 seconds) to ensure a clean state and avoid hanging on partial input. If the prompt does not return within 3 seconds, the console subprocess is force-restarted and the request fails with `SERVER_UNHEALTHY`, so the caller can retry.
|
|
68
|
+
|
|
69
|
+
Timeout precedence
|
|
70
|
+
- `CONSOLLE_TIMEOUT` (if set and > 0) overrides all other sources on both client and server.
|
|
71
|
+
- Otherwise, CLI `--timeout` is used.
|
|
72
|
+
- Otherwise, default of 60s applies.
|
|
73
|
+
|
|
74
|
+
- Per‑call control (CLI):
|
|
75
|
+
- Enable: `cone exec --pre-sigint 'code'`
|
|
76
|
+
- Disable: `cone exec --no-pre-sigint 'code'`
|
|
77
|
+
- Global (server‑wide) control via environment when starting the server:
|
|
78
|
+
- Disable: `CONSOLLE_DISABLE_PRE_SIGINT=1 cone start`
|
|
79
|
+
- Note: this env var is read by the server process at start time, not by `cone exec`.
|
|
80
|
+
|
|
81
|
+
# Timeout and interrupt behavior during execution
|
|
82
|
+
- On execution timeout, cone sends Ctrl‑C to interrupt and attempts prompt recovery. For local consoles, it also sends an OS‑level `SIGINT` as a fallback.
|
|
83
|
+
- After recovery, subsequent `cone exec` requests continue normally.
|
|
84
|
+
|
|
85
|
+
# Error codes
|
|
86
|
+
- `EXECUTION_TIMEOUT`: The executed code exceeded its timeout.
|
|
87
|
+
- `SERVER_UNHEALTHY`: Pre‑exec prompt did not return within 3 seconds; the console subprocess was restarted and the request failed.
|
|
88
|
+
|
|
89
|
+
# Examples
|
|
90
|
+
- Force an execution timeout quickly and verify recovery:
|
|
91
|
+
- `cone exec 'sleep 999' --timeout 2` → fails with `EXECUTION_TIMEOUT`
|
|
92
|
+
- `cone exec 'puts :after_timeout; :ok'` → should succeed (`:ok`)
|
|
64
93
|
|
|
65
94
|
# Verbose log output
|
|
66
95
|
cone -v exec "User.all"
|
|
@@ -120,7 +149,7 @@ Consolle consists of the following structure:
|
|
|
120
149
|
- Manages Rails console process through PTY
|
|
121
150
|
- Automatic restart (Watchdog) feature
|
|
122
151
|
- Environment variable settings and IRB automation configuration
|
|
123
|
-
- Timeout handling and Ctrl-C support
|
|
152
|
+
- Timeout handling and Ctrl-C support (pre-exec safety check + post-timeout recovery)
|
|
124
153
|
|
|
125
154
|
#### RequestBroker
|
|
126
155
|
- Ensures request order through serial queue
|
|
@@ -145,6 +174,11 @@ env = {
|
|
|
145
174
|
}
|
|
146
175
|
```
|
|
147
176
|
|
|
177
|
+
Additional environment variables
|
|
178
|
+
|
|
179
|
+
- `CONSOLLE_TIMEOUT`: Highest‑priority timeout (seconds). Overrides CLI `--timeout` and defaults on both client and server.
|
|
180
|
+
- `CONSOLLE_DISABLE_PRE_SIGINT=1`: Disable the pre‑exec Ctrl‑C prompt check at the server level (read when starting the server).
|
|
181
|
+
|
|
148
182
|
## File Locations
|
|
149
183
|
|
|
150
184
|
- **Socket file**: `{Rails.root}/tmp/cone/cone.socket`
|
|
@@ -187,4 +221,4 @@ ls ~/.cone/sessions/
|
|
|
187
221
|
|
|
188
222
|
- **Current version**: 0.1.0
|
|
189
223
|
- **Ruby version**: 3.0 or higher
|
|
190
|
-
- **Rails version**: 7.0 or higher
|
|
224
|
+
- **Rails version**: 7.0 or higher
|
data/lib/consolle/cli.rb
CHANGED
|
@@ -50,7 +50,7 @@ module Consolle
|
|
|
50
50
|
shell.say
|
|
51
51
|
shell.say 'EXAMPLES:', :yellow
|
|
52
52
|
shell.say " cone exec 'User.count' # Execute code in default session"
|
|
53
|
-
shell.say " cone start
|
|
53
|
+
shell.say " RAILS_ENV=production cone start # Start console in production"
|
|
54
54
|
shell.say " cone exec -t api 'Rails.env' # Execute code in 'api' session"
|
|
55
55
|
shell.say ' cone exec -f script.rb # Execute code from file'
|
|
56
56
|
shell.say
|
|
@@ -121,8 +121,8 @@ module Consolle
|
|
|
121
121
|
The console runs as a daemon and can be accessed through the exec command.
|
|
122
122
|
|
|
123
123
|
You can specify a custom session name with --target to run multiple consoles:
|
|
124
|
-
cone start --target api
|
|
125
|
-
cone start --target worker
|
|
124
|
+
RAILS_ENV=production cone start --target api
|
|
125
|
+
RAILS_ENV=development cone start --target worker
|
|
126
126
|
|
|
127
127
|
Custom console commands are supported for special environments:
|
|
128
128
|
cone start --command "kamal app exec -i 'bin/rails console'"
|
|
@@ -131,7 +131,7 @@ module Consolle
|
|
|
131
131
|
For SSH-based commands that require authentication (e.g., 1Password SSH agent):
|
|
132
132
|
cone start --command "kamal console" --wait-timeout 60
|
|
133
133
|
LONGDESC
|
|
134
|
-
|
|
134
|
+
# Rails environment is now controlled via RAILS_ENV, not a CLI option
|
|
135
135
|
method_option :command, type: :string, aliases: '-c', desc: 'Custom console command', default: 'bin/rails console'
|
|
136
136
|
method_option :wait_timeout, type: :numeric, aliases: '-w', desc: 'Timeout for console startup (seconds)', default: Consolle::DEFAULT_WAIT_TIMEOUT
|
|
137
137
|
def start
|
|
@@ -160,7 +160,7 @@ module Consolle
|
|
|
160
160
|
clear_session_info
|
|
161
161
|
end
|
|
162
162
|
|
|
163
|
-
adapter = create_rails_adapter(
|
|
163
|
+
adapter = create_rails_adapter(current_rails_env, options[:target], options[:command], options[:wait_timeout])
|
|
164
164
|
|
|
165
165
|
puts 'Starting Rails console...'
|
|
166
166
|
|
|
@@ -175,7 +175,7 @@ module Consolle
|
|
|
175
175
|
|
|
176
176
|
# Log session start
|
|
177
177
|
log_session_event(adapter.process_pid, 'session_start', {
|
|
178
|
-
rails_env:
|
|
178
|
+
rails_env: current_rails_env,
|
|
179
179
|
socket_path: adapter.socket_path
|
|
180
180
|
})
|
|
181
181
|
rescue StandardError => e
|
|
@@ -197,7 +197,7 @@ module Consolle
|
|
|
197
197
|
end
|
|
198
198
|
|
|
199
199
|
# Check if server is actually responsive
|
|
200
|
-
adapter = create_rails_adapter(
|
|
200
|
+
adapter = create_rails_adapter(current_rails_env, options[:target])
|
|
201
201
|
server_status = begin
|
|
202
202
|
adapter.get_status
|
|
203
203
|
rescue StandardError
|
|
@@ -255,7 +255,7 @@ module Consolle
|
|
|
255
255
|
# Check if process is alive
|
|
256
256
|
if info['process_pid'] && process_alive?(info['process_pid'])
|
|
257
257
|
# Try to get server status
|
|
258
|
-
|
|
258
|
+
adapter = create_rails_adapter(current_rails_env, name)
|
|
259
259
|
server_status = begin
|
|
260
260
|
adapter.get_status
|
|
261
261
|
rescue StandardError
|
|
@@ -390,13 +390,12 @@ module Consolle
|
|
|
390
390
|
cone restart -t api
|
|
391
391
|
cone restart --target worker --force
|
|
392
392
|
LONGDESC
|
|
393
|
-
method_option :rails_env, type: :string, aliases: '-e', desc: 'Rails environment', default: 'development'
|
|
394
393
|
method_option :force, type: :boolean, aliases: '-f', desc: 'Force restart the entire server'
|
|
395
394
|
def restart
|
|
396
395
|
ensure_rails_project!
|
|
397
396
|
validate_session_name!(options[:target])
|
|
398
397
|
|
|
399
|
-
adapter = create_rails_adapter(
|
|
398
|
+
adapter = create_rails_adapter(current_rails_env, options[:target])
|
|
400
399
|
|
|
401
400
|
if adapter.running?
|
|
402
401
|
# Check if environment needs to be changed
|
|
@@ -406,25 +405,20 @@ module Consolle
|
|
|
406
405
|
nil
|
|
407
406
|
end
|
|
408
407
|
current_env = current_status&.dig('rails_env') || 'development'
|
|
409
|
-
|
|
408
|
+
desired_env = current_rails_env
|
|
409
|
+
needs_full_restart = options[:force] || (current_env != desired_env)
|
|
410
410
|
|
|
411
411
|
if needs_full_restart
|
|
412
|
-
if current_env !=
|
|
413
|
-
puts "Environment change detected (#{current_env} -> #{
|
|
412
|
+
if current_env != desired_env
|
|
413
|
+
puts "Environment change detected (#{current_env} -> #{desired_env})"
|
|
414
414
|
puts 'Performing full server restart...'
|
|
415
415
|
else
|
|
416
416
|
puts 'Force restarting Rails console server...'
|
|
417
417
|
end
|
|
418
418
|
|
|
419
|
-
# Save current rails_env for start command
|
|
420
|
-
old_env = @rails_env
|
|
421
|
-
@rails_env = options[:rails_env]
|
|
422
|
-
|
|
423
419
|
stop
|
|
424
420
|
sleep 1
|
|
425
|
-
invoke(:start
|
|
426
|
-
|
|
427
|
-
@rails_env = old_env
|
|
421
|
+
invoke(:start)
|
|
428
422
|
else
|
|
429
423
|
puts 'Restarting Rails console subprocess...'
|
|
430
424
|
|
|
@@ -459,7 +453,7 @@ module Consolle
|
|
|
459
453
|
end
|
|
460
454
|
else
|
|
461
455
|
puts 'Rails console is not running. Starting it...'
|
|
462
|
-
invoke(:start
|
|
456
|
+
invoke(:start)
|
|
463
457
|
end
|
|
464
458
|
end
|
|
465
459
|
|
|
@@ -484,7 +478,8 @@ module Consolle
|
|
|
484
478
|
#{' '}
|
|
485
479
|
The console must be started first with 'cone start'.
|
|
486
480
|
LONGDESC
|
|
487
|
-
method_option :timeout, type: :numeric, desc: 'Timeout in seconds', default:
|
|
481
|
+
method_option :timeout, type: :numeric, desc: 'Timeout in seconds', default: 60
|
|
482
|
+
method_option :pre_sigint, type: :boolean, desc: 'Send Ctrl-C before executing code (experimental)'
|
|
488
483
|
method_option :file, type: :string, aliases: '-f', desc: 'Read Ruby code from FILE'
|
|
489
484
|
method_option :raw, type: :boolean, desc: 'Do not apply escape fixes for Claude Code (keep \\! as is)'
|
|
490
485
|
def exec(*code_parts)
|
|
@@ -548,7 +543,13 @@ module Consolle
|
|
|
548
543
|
puts "Executing: #{code}" if options[:verbose]
|
|
549
544
|
|
|
550
545
|
# Send code to socket
|
|
551
|
-
|
|
546
|
+
send_opts = { timeout: options[:timeout] }
|
|
547
|
+
send_opts[:pre_sigint] = options[:pre_sigint] unless options[:pre_sigint].nil?
|
|
548
|
+
result = send_code_to_socket(
|
|
549
|
+
session_info[:socket_path],
|
|
550
|
+
code,
|
|
551
|
+
**send_opts
|
|
552
|
+
)
|
|
552
553
|
|
|
553
554
|
# Log the request and response
|
|
554
555
|
log_session_activity(session_info[:process_pid], code, result)
|
|
@@ -583,6 +584,10 @@ module Consolle
|
|
|
583
584
|
|
|
584
585
|
private
|
|
585
586
|
|
|
587
|
+
def current_rails_env
|
|
588
|
+
ENV['RAILS_ENV'] || 'development'
|
|
589
|
+
end
|
|
590
|
+
|
|
586
591
|
def ensure_rails_project!
|
|
587
592
|
return if File.exist?('config/environment.rb') || File.exist?('config/application.rb')
|
|
588
593
|
|
|
@@ -621,21 +626,27 @@ module Consolle
|
|
|
621
626
|
File.join(Dir.pwd, 'tmp', 'cone', "#{target}.log")
|
|
622
627
|
end
|
|
623
628
|
|
|
624
|
-
def send_code_to_socket(socket_path, code, timeout:
|
|
629
|
+
def send_code_to_socket(socket_path, code, timeout: 60, pre_sigint: nil)
|
|
625
630
|
request_id = SecureRandom.uuid
|
|
626
631
|
# Ensure code is UTF-8 encoded
|
|
627
632
|
code = code.force_encoding('UTF-8') if code.respond_to?(:force_encoding)
|
|
628
633
|
|
|
634
|
+
# CONSOLLE_TIMEOUT takes highest priority on client side if present and > 0
|
|
635
|
+
env_timeout = ENV['CONSOLLE_TIMEOUT']&.to_i
|
|
636
|
+
effective_timeout = (env_timeout && env_timeout > 0) ? env_timeout : timeout
|
|
637
|
+
|
|
629
638
|
request = {
|
|
630
639
|
'action' => 'eval',
|
|
631
640
|
'code' => code,
|
|
632
|
-
'timeout' =>
|
|
641
|
+
'timeout' => effective_timeout,
|
|
633
642
|
'request_id' => request_id
|
|
634
643
|
}
|
|
644
|
+
# Include pre_sigint flag only when explicitly provided (true/false)
|
|
645
|
+
request['pre_sigint'] = pre_sigint unless pre_sigint.nil?
|
|
635
646
|
|
|
636
647
|
STDERR.puts "[DEBUG] Creating socket connection to: #{socket_path}" if ENV['DEBUG']
|
|
637
648
|
|
|
638
|
-
Timeout.timeout(
|
|
649
|
+
Timeout.timeout(effective_timeout + 5) do
|
|
639
650
|
socket = UNIXSocket.new(socket_path)
|
|
640
651
|
STDERR.puts "[DEBUG] Socket connected" if ENV['DEBUG']
|
|
641
652
|
|
|
@@ -680,8 +691,8 @@ module Consolle
|
|
|
680
691
|
JSON.parse(json_line) if json_line
|
|
681
692
|
end
|
|
682
693
|
rescue Timeout::Error
|
|
683
|
-
STDERR.puts "[DEBUG] Timeout occurred after #{
|
|
684
|
-
{ 'success' => false, 'error' => 'Timeout', 'message' => "Request timed out after #{
|
|
694
|
+
STDERR.puts "[DEBUG] Timeout occurred after #{effective_timeout} seconds" if ENV['DEBUG']
|
|
695
|
+
{ 'success' => false, 'error' => 'Timeout', 'message' => "Request timed out after #{effective_timeout} seconds" }
|
|
685
696
|
rescue StandardError => e
|
|
686
697
|
STDERR.puts "[DEBUG] Error: #{e.class}: #{e.message}" if ENV['DEBUG']
|
|
687
698
|
{ 'success' => false, 'error' => e.class.name, 'message' => e.message }
|
data/lib/consolle/errors.rb
CHANGED
|
@@ -32,6 +32,13 @@ module Consolle
|
|
|
32
32
|
# Execution errors
|
|
33
33
|
class ExecutionError < Error; end
|
|
34
34
|
|
|
35
|
+
# Server/console health issues
|
|
36
|
+
class ServerUnhealthy < Error
|
|
37
|
+
def initialize(message = 'Console server is unhealthy')
|
|
38
|
+
super(message)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
35
42
|
# Syntax error in executed code
|
|
36
43
|
class SyntaxError < ExecutionError
|
|
37
44
|
def initialize(message)
|
|
@@ -72,7 +79,8 @@ module Consolle
|
|
|
72
79
|
'RuntimeError' => 'RUNTIME_ERROR',
|
|
73
80
|
'::RuntimeError' => 'RUNTIME_ERROR',
|
|
74
81
|
'StandardError' => 'STANDARD_ERROR',
|
|
75
|
-
'Exception' => 'EXCEPTION'
|
|
82
|
+
'Exception' => 'EXCEPTION',
|
|
83
|
+
'Consolle::Errors::ServerUnhealthy' => 'SERVER_UNHEALTHY'
|
|
76
84
|
}.freeze
|
|
77
85
|
|
|
78
86
|
def self.to_code(exception)
|
|
@@ -115,4 +123,4 @@ module Consolle
|
|
|
115
123
|
end
|
|
116
124
|
end
|
|
117
125
|
end
|
|
118
|
-
end
|
|
126
|
+
end
|
|
@@ -47,10 +47,14 @@ module Consolle
|
|
|
47
47
|
start_watchdog
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
def eval(code, timeout: nil)
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
timeout
|
|
50
|
+
def eval(code, timeout: nil, pre_sigint: nil)
|
|
51
|
+
# CONSOLLE_TIMEOUT has highest priority if present and > 0
|
|
52
|
+
env_timeout = ENV['CONSOLLE_TIMEOUT']&.to_i
|
|
53
|
+
timeout = if env_timeout && env_timeout > 0
|
|
54
|
+
env_timeout
|
|
55
|
+
else
|
|
56
|
+
timeout || 60
|
|
57
|
+
end
|
|
54
58
|
@mutex.synchronize do
|
|
55
59
|
raise 'Console is not running' unless running?
|
|
56
60
|
|
|
@@ -60,22 +64,40 @@ module Consolle
|
|
|
60
64
|
# Check if this is a remote console
|
|
61
65
|
is_remote = @command.include?('ssh') || @command.include?('kamal') || @command.include?('docker')
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
# Decide whether to send pre-exec Ctrl-C
|
|
68
|
+
# Default: enabled for all consoles to avoid getting stuck mid-input.
|
|
69
|
+
# Opt-out via param or ENV `CONSOLLE_DISABLE_PRE_SIGINT=1`.
|
|
70
|
+
disable_pre_sigint = ENV['CONSOLLE_DISABLE_PRE_SIGINT'] == '1'
|
|
71
|
+
default_pre_sigint = (@rails_env != 'test')
|
|
72
|
+
do_pre_sigint = if pre_sigint.nil?
|
|
73
|
+
default_pre_sigint && !disable_pre_sigint
|
|
74
|
+
else
|
|
75
|
+
pre_sigint
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
if do_pre_sigint
|
|
79
|
+
# Send Ctrl-C to ensure clean state before execution, then wait up to 3s for prompt.
|
|
80
|
+
# If prompt doesn't come back, consider server unhealthy, force-restart the subprocess,
|
|
81
|
+
# and return an error so the caller can retry after recovery.
|
|
65
82
|
@writer.write(CTRL_C)
|
|
66
83
|
@writer.flush
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
# Wait for prompt after Ctrl-C
|
|
84
|
+
# Nudge IRB and force prompt emission with a trivial probe
|
|
85
|
+
@writer.puts "puts '__consolle_probe__'"
|
|
86
|
+
@writer.flush
|
|
72
87
|
begin
|
|
73
|
-
wait_for_prompt(timeout:
|
|
88
|
+
wait_for_prompt(timeout: 3, consume_all: true)
|
|
74
89
|
rescue Timeout::Error
|
|
75
|
-
|
|
90
|
+
logger.error '[ConsoleSupervisor] No prompt after pre-exec Ctrl-C (3s). Forcing console restart.'
|
|
91
|
+
# Forcefully stop subprocess so watchdog can restart
|
|
92
|
+
@process_mutex.synchronize do
|
|
93
|
+
stop_console
|
|
94
|
+
end
|
|
95
|
+
# Return an unhealthy error to the caller
|
|
96
|
+
err = Consolle::Errors::ServerUnhealthy.new('No prompt after pre-exec interrupt (3s); console restarted')
|
|
97
|
+
return build_error_response(err, execution_time: 0)
|
|
76
98
|
end
|
|
77
99
|
else
|
|
78
|
-
# For local consoles,
|
|
100
|
+
# For local consoles without pre-sigint, clear buffer only
|
|
79
101
|
clear_buffer
|
|
80
102
|
end
|
|
81
103
|
|
|
@@ -164,10 +186,28 @@ module Consolle
|
|
|
164
186
|
if Time.now > deadline
|
|
165
187
|
logger.debug "[ConsoleSupervisor] Timeout reached after #{Time.now - start_time}s, output so far: #{output.bytesize} bytes"
|
|
166
188
|
logger.debug "[ConsoleSupervisor] Output content: #{output.inspect}" if ENV['DEBUG']
|
|
167
|
-
# Timeout -
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
189
|
+
# Timeout - try to interrupt current execution and recover prompt
|
|
190
|
+
3.times do |i|
|
|
191
|
+
@writer.write(CTRL_C)
|
|
192
|
+
@writer.flush
|
|
193
|
+
logger.debug "[ConsoleSupervisor] Sent Ctrl-C (attempt #{i + 1})"
|
|
194
|
+
begin
|
|
195
|
+
wait_for_prompt(timeout: 1.0, consume_all: true)
|
|
196
|
+
logger.debug '[ConsoleSupervisor] Prompt recovered after Ctrl-C'
|
|
197
|
+
break
|
|
198
|
+
rescue Timeout::Error
|
|
199
|
+
# As a fallback for local consoles, send OS-level SIGINT to the subprocess
|
|
200
|
+
unless is_remote
|
|
201
|
+
begin
|
|
202
|
+
Process.kill('INT', @pid)
|
|
203
|
+
logger.warn '[ConsoleSupervisor] Sent OS-level SIGINT to subprocess'
|
|
204
|
+
rescue StandardError => e
|
|
205
|
+
logger.warn "[ConsoleSupervisor] Failed to send OS-level SIGINT: #{e.message}"
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
# Final cleanup
|
|
171
211
|
clear_buffer
|
|
172
212
|
execution_time = Time.now - start_time
|
|
173
213
|
return build_timeout_response(timeout)
|
|
@@ -62,8 +62,10 @@ module Consolle
|
|
|
62
62
|
|
|
63
63
|
# Wait for response (with timeout)
|
|
64
64
|
begin
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
env_timeout = ENV['CONSOLLE_TIMEOUT']&.to_i
|
|
66
|
+
future_timeout = (env_timeout && env_timeout > 0) ? env_timeout : (request['timeout'] || 30)
|
|
67
|
+
logger.debug "[RequestBroker] Waiting for response: #{request_id}, timeout: #{future_timeout}" if ENV['DEBUG']
|
|
68
|
+
response = future.get(timeout: future_timeout)
|
|
67
69
|
logger.debug "[RequestBroker] Got response: #{request_id}" if ENV['DEBUG']
|
|
68
70
|
response
|
|
69
71
|
rescue Timeout::Error
|
|
@@ -157,7 +159,8 @@ module Consolle
|
|
|
157
159
|
|
|
158
160
|
def process_eval_request(request)
|
|
159
161
|
code = request['code']
|
|
160
|
-
|
|
162
|
+
env_timeout = ENV['CONSOLLE_TIMEOUT']&.to_i
|
|
163
|
+
timeout = (env_timeout && env_timeout > 0) ? env_timeout : (request['timeout'] || 30)
|
|
161
164
|
|
|
162
165
|
unless code
|
|
163
166
|
return {
|
|
@@ -168,7 +171,11 @@ module Consolle
|
|
|
168
171
|
end
|
|
169
172
|
|
|
170
173
|
# Execute through supervisor
|
|
171
|
-
|
|
174
|
+
if request.key?('pre_sigint')
|
|
175
|
+
result = @supervisor.eval(code, timeout: timeout, pre_sigint: request['pre_sigint'])
|
|
176
|
+
else
|
|
177
|
+
result = @supervisor.eval(code, timeout: timeout)
|
|
178
|
+
end
|
|
172
179
|
|
|
173
180
|
# Format response
|
|
174
181
|
if result[:success]
|
data/rule.ko.md
CHANGED
|
@@ -24,7 +24,7 @@ Cone은 디버깅, 데이터 탐색, 그리고 개발 보조 도구로 사용됩
|
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
$ cone start # 서버 시작
|
|
27
|
-
$ cone start
|
|
27
|
+
$ RAILS_ENV=test cone start # test 환경에서 console 시작
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
중지와 재시작 명령어도 제공합니다.
|
|
@@ -114,4 +114,4 @@ users.first
|
|
|
114
114
|
$ cone exec -f complex_task.rb
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
-
모든 방법은 세션 상태를 유지하므로 변수와 객체가 실행 간에 지속됩니다.
|
|
117
|
+
모든 방법은 세션 상태를 유지하므로 변수와 객체가 실행 간에 지속됩니다.
|
data/rule.md
CHANGED
|
@@ -24,7 +24,7 @@ You can start cone with the `start` command and specify the execution environmen
|
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
$ cone start # Start server
|
|
27
|
-
$ cone start
|
|
27
|
+
$ RAILS_ENV=test cone start # Start console in test environment
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
It also provides stop and restart commands.
|
|
@@ -114,4 +114,4 @@ For complex multi-line code, save it in a file:
|
|
|
114
114
|
$ cone exec -f complex_task.rb
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
-
All methods maintain the session state, so variables and objects persist between executions.
|
|
117
|
+
All methods maintain the session state, so variables and objects persist between executions.
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: consolle
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- nacyot
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2025-
|
|
10
|
+
date: 2025-09-01 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: logger
|