openclacky 1.0.0 → 1.0.2
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/CHANGELOG.md +39 -0
- data/README.md +87 -53
- data/lib/clacky/agent/cost_tracker.rb +19 -2
- data/lib/clacky/agent/llm_caller.rb +218 -0
- data/lib/clacky/agent/message_compressor_helper.rb +32 -2
- data/lib/clacky/agent.rb +54 -22
- data/lib/clacky/client.rb +44 -5
- data/lib/clacky/default_parsers/pdf_parser.rb +58 -17
- data/lib/clacky/default_parsers/pdf_parser_ocr.py +103 -0
- data/lib/clacky/default_parsers/pdf_parser_plumber.py +62 -0
- data/lib/clacky/default_skills/deploy/SKILL.md +201 -77
- data/lib/clacky/default_skills/new/SKILL.md +3 -114
- data/lib/clacky/default_skills/onboard/SKILL.md +349 -133
- data/lib/clacky/default_skills/onboard/scripts/import_external_skills.rb +371 -0
- data/lib/clacky/default_skills/onboard/scripts/install_builtin_skills.rb +175 -0
- data/lib/clacky/default_skills/skill-add/scripts/install_from_zip.rb +59 -26
- data/lib/clacky/message_format/anthropic.rb +72 -8
- data/lib/clacky/message_format/bedrock.rb +6 -3
- data/lib/clacky/providers.rb +146 -3
- data/lib/clacky/server/channel/adapters/feishu/adapter.rb +14 -0
- data/lib/clacky/server/channel/adapters/feishu/bot.rb +10 -0
- data/lib/clacky/server/channel/adapters/feishu/message_parser.rb +1 -0
- data/lib/clacky/server/channel/channel_manager.rb +12 -4
- data/lib/clacky/server/channel/channel_ui_controller.rb +8 -2
- data/lib/clacky/server/http_server.rb +746 -13
- data/lib/clacky/server/session_registry.rb +55 -24
- data/lib/clacky/skill.rb +10 -9
- data/lib/clacky/skill_loader.rb +23 -11
- data/lib/clacky/tools/file_reader.rb +232 -127
- data/lib/clacky/tools/security.rb +42 -64
- data/lib/clacky/tools/terminal/persistent_session.rb +15 -4
- data/lib/clacky/tools/terminal/safe_rm.sh +106 -0
- data/lib/clacky/tools/terminal/session_manager.rb +8 -3
- data/lib/clacky/tools/terminal.rb +263 -16
- data/lib/clacky/ui2/layout_manager.rb +8 -1
- data/lib/clacky/ui2/output_buffer.rb +83 -23
- data/lib/clacky/ui2/ui_controller.rb +74 -7
- data/lib/clacky/utils/file_processor.rb +14 -40
- data/lib/clacky/utils/model_pricing.rb +215 -0
- data/lib/clacky/utils/parser_manager.rb +70 -6
- data/lib/clacky/utils/string_matcher.rb +23 -1
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +673 -9
- data/lib/clacky/web/app.js +40 -1608
- data/lib/clacky/web/i18n.js +209 -0
- data/lib/clacky/web/index.html +166 -2
- data/lib/clacky/web/onboard.js +77 -1
- data/lib/clacky/web/profile.js +442 -0
- data/lib/clacky/web/sessions.js +1034 -2
- data/lib/clacky/web/settings.js +127 -6
- data/lib/clacky/web/sidebar.js +39 -0
- data/lib/clacky/web/skills.js +460 -0
- data/lib/clacky/web/trash.js +343 -0
- data/lib/clacky/web/ws-dispatcher.js +255 -0
- data/lib/clacky.rb +5 -3
- metadata +16 -17
- data/lib/clacky/clacky_auth_client.rb +0 -152
- data/lib/clacky/clacky_cloud_config.rb +0 -123
- data/lib/clacky/cloud_project_client.rb +0 -169
- data/lib/clacky/default_skills/deploy/scripts/rails_deploy.rb +0 -1377
- data/lib/clacky/default_skills/deploy/tools/check_health.rb +0 -116
- data/lib/clacky/default_skills/deploy/tools/create_database_service.rb +0 -341
- data/lib/clacky/default_skills/deploy/tools/execute_deployment.rb +0 -99
- data/lib/clacky/default_skills/deploy/tools/fetch_runtime_logs.rb +0 -77
- data/lib/clacky/default_skills/deploy/tools/list_services.rb +0 -67
- data/lib/clacky/default_skills/deploy/tools/report_deploy_status.rb +0 -67
- data/lib/clacky/default_skills/deploy/tools/set_deploy_variables.rb +0 -189
- data/lib/clacky/default_skills/new/scripts/cloud_project_init.sh +0 -74
- data/lib/clacky/deploy_api_client.rb +0 -484
|
@@ -22,16 +22,23 @@ module Clacky
|
|
|
22
22
|
# 1. Block hard-dangerous commands: sudo, pkill clacky, eval, exec,
|
|
23
23
|
# `...`, $(...), | sh, | bash,
|
|
24
24
|
# redirect to /etc /usr /bin.
|
|
25
|
-
# 2. Rewrite `
|
|
26
|
-
# 3. Rewrite `curl ... | bash` → save script to a file for manual
|
|
25
|
+
# 2. Rewrite `curl ... | bash` → save script to a file for manual
|
|
27
26
|
# review instead of exec.
|
|
28
|
-
#
|
|
27
|
+
# 3. Protect credential/secret files: .env, .ssh/, .aws/ — block
|
|
29
28
|
# writes to these only. Other
|
|
30
29
|
# "project" files (Gemfile,
|
|
31
30
|
# README.md, package.json, …)
|
|
32
31
|
# are NOT protected — editing
|
|
33
32
|
# them is a normal dev task.
|
|
34
33
|
#
|
|
34
|
+
# Note on `rm`:
|
|
35
|
+
# `rm` is NOT rewritten here — it's intercepted at runtime by a shell
|
|
36
|
+
# function installed in each PTY session (see Terminal::SAFE_RM_BASH
|
|
37
|
+
# and Terminal#install_marker). This lets the shell's own parser
|
|
38
|
+
# handle heredocs / multi-line / globs / variables correctly. A
|
|
39
|
+
# static Ruby-side rewrite cannot — it would mis-parse heredoc
|
|
40
|
+
# bodies and destroy legitimate commands.
|
|
41
|
+
#
|
|
35
42
|
# Notes:
|
|
36
43
|
# - `cp`, `mv`, `mkdir`, `touch`, `echo` are allowed to touch ANY path
|
|
37
44
|
# (including outside the project root). The source of a `cp` is
|
|
@@ -96,7 +103,6 @@ module Clacky
|
|
|
96
103
|
@project_root = File.expand_path(project_root)
|
|
97
104
|
|
|
98
105
|
trash_directory = Clacky::TrashDirectory.new(@project_root)
|
|
99
|
-
@trash_dir = trash_directory.trash_dir
|
|
100
106
|
@backup_dir = trash_directory.backup_dir
|
|
101
107
|
|
|
102
108
|
@project_hash = trash_directory.generate_project_hash(@project_root)
|
|
@@ -114,12 +120,16 @@ module Clacky
|
|
|
114
120
|
@safe_check_command = Clacky::Utils::Encoding.safe_check(command)
|
|
115
121
|
|
|
116
122
|
case @safe_check_command
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
# Block attempts to terminate the clacky server process.
|
|
124
|
+
# IMPORTANT: each verb is anchored with \b so substrings like
|
|
125
|
+
# "Skill" (contains "kill") or "Bill Killalina" don't trigger
|
|
126
|
+
# false positives. We also require `clacky` to appear as a whole
|
|
127
|
+
# word AND within a reasonable distance (same logical command,
|
|
128
|
+
# not hundreds of chars later in an unrelated echo string).
|
|
129
|
+
when /\bpkill\b[^\n;|&]{0,80}\bclacky\b|\bkillall\b[^\n;|&]{0,80}\bclacky\b|\bkill\s+(?:-\S+\s+)*[^\n;|&]{0,40}\bclacky\b/i
|
|
130
|
+
raise SecurityError, "Killing the clacky server process is not allowed. To restart, use: #{restart_hint}"
|
|
131
|
+
when /\bclacky\s+server\b/
|
|
132
|
+
raise SecurityError, "Managing the clacky server from within a session is not allowed. To restart, use: #{restart_hint}"
|
|
123
133
|
when /^chmod\s+x/
|
|
124
134
|
replace_chmod_command(command)
|
|
125
135
|
when /^curl.*\|\s*(sh|bash)/
|
|
@@ -136,27 +146,6 @@ module Clacky
|
|
|
136
146
|
end
|
|
137
147
|
end
|
|
138
148
|
|
|
139
|
-
def replace_rm_command(command)
|
|
140
|
-
files = parse_rm_files(command)
|
|
141
|
-
raise SecurityError, "No files specified for deletion" if files.empty?
|
|
142
|
-
|
|
143
|
-
commands = files.map do |file|
|
|
144
|
-
validate_file_path(file)
|
|
145
|
-
|
|
146
|
-
timestamp = Time.now.strftime("%Y%m%d_%H%M%S_%N")
|
|
147
|
-
safe_name = "#{File.basename(file)}_deleted_#{timestamp}"
|
|
148
|
-
trash_path = File.join(@trash_dir, safe_name)
|
|
149
|
-
|
|
150
|
-
create_delete_metadata(file, trash_path) if File.exist?(file)
|
|
151
|
-
|
|
152
|
-
"mv #{Shellwords.escape(file)} #{Shellwords.escape(trash_path)}"
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
result = commands.join(' && ')
|
|
156
|
-
log_replacement("rm", result, "Files moved to trash instead of permanent deletion")
|
|
157
|
-
result
|
|
158
|
-
end
|
|
159
|
-
|
|
160
149
|
def replace_chmod_command(command)
|
|
161
150
|
begin
|
|
162
151
|
parts = Shellwords.split(command)
|
|
@@ -194,6 +183,22 @@ module Clacky
|
|
|
194
183
|
command
|
|
195
184
|
end
|
|
196
185
|
|
|
186
|
+
# Build a copy-pasteable "how to restart clacky server" hint.
|
|
187
|
+
# When running inside a clacky server worker, `CLACKY_MASTER_PID` is
|
|
188
|
+
# injected by ServerMaster (see server_master.rb). We keep the
|
|
189
|
+
# variable name in the hint (so the AI / user learns the standard
|
|
190
|
+
# convention) AND append the resolved PID in parentheses so it's
|
|
191
|
+
# immediately actionable. When the variable isn't set (e.g. one-shot
|
|
192
|
+
# CLI invocation), we just show the variable name.
|
|
193
|
+
def restart_hint
|
|
194
|
+
pid = ENV["CLACKY_MASTER_PID"].to_s
|
|
195
|
+
if pid =~ /\A\d+\z/
|
|
196
|
+
"kill -USR1 $CLACKY_MASTER_PID (current master PID: #{pid})"
|
|
197
|
+
else
|
|
198
|
+
"kill -USR1 $CLACKY_MASTER_PID"
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
197
202
|
# Relaxed validator for mv / cp / mkdir / touch / echo.
|
|
198
203
|
#
|
|
199
204
|
# Historical behavior was to forbid any path outside @project_root,
|
|
@@ -258,16 +263,6 @@ module Clacky
|
|
|
258
263
|
command
|
|
259
264
|
end
|
|
260
265
|
|
|
261
|
-
def parse_rm_files(command)
|
|
262
|
-
begin
|
|
263
|
-
parts = Shellwords.split(command)
|
|
264
|
-
rescue ArgumentError
|
|
265
|
-
parts = command.split(/\s+/)
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
parts.drop(1).reject { |part| part.start_with?('-') }
|
|
269
|
-
end
|
|
270
|
-
|
|
271
266
|
# Block writes that would clobber credentials / secrets.
|
|
272
267
|
# These are the only paths truly dangerous to write to by accident:
|
|
273
268
|
# - ~/.ssh/* (SSH private keys)
|
|
@@ -298,30 +293,12 @@ module Clacky
|
|
|
298
293
|
end
|
|
299
294
|
end
|
|
300
295
|
|
|
301
|
-
#
|
|
302
|
-
#
|
|
296
|
+
# Alias retained for readability — chmod handler validates that
|
|
297
|
+
# the target is not a credential/secret file.
|
|
303
298
|
def validate_file_path(path)
|
|
304
299
|
validate_secret_write(path)
|
|
305
300
|
end
|
|
306
301
|
|
|
307
|
-
def create_delete_metadata(original_path, trash_path)
|
|
308
|
-
metadata = {
|
|
309
|
-
original_path: File.expand_path(original_path),
|
|
310
|
-
project_root: @project_root,
|
|
311
|
-
trash_directory: File.dirname(trash_path),
|
|
312
|
-
deleted_at: Time.now.iso8601,
|
|
313
|
-
deleted_by: 'AI_Terminal',
|
|
314
|
-
file_size: File.size(original_path),
|
|
315
|
-
file_type: File.extname(original_path),
|
|
316
|
-
file_mode: File.stat(original_path).mode.to_s(8)
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
metadata_file = "#{trash_path}.metadata.json"
|
|
320
|
-
File.write(metadata_file, JSON.pretty_generate(metadata))
|
|
321
|
-
rescue StandardError => e
|
|
322
|
-
log_warning("Failed to create metadata for #{original_path}: #{e.message}")
|
|
323
|
-
end
|
|
324
|
-
|
|
325
302
|
def log_replacement(original, replacement, reason)
|
|
326
303
|
write_log(
|
|
327
304
|
action: 'command_replacement',
|
|
@@ -342,12 +319,13 @@ module Clacky
|
|
|
342
319
|
# Logging must never break main functionality.
|
|
343
320
|
end
|
|
344
321
|
|
|
345
|
-
private :
|
|
322
|
+
private :replace_chmod_command,
|
|
346
323
|
:replace_curl_pipe_command, :block_sudo_command,
|
|
347
324
|
:allow_dev_null_redirect, :validate_and_allow,
|
|
348
|
-
:validate_general_command,
|
|
325
|
+
:validate_general_command,
|
|
349
326
|
:validate_file_path, :validate_secret_write,
|
|
350
|
-
:
|
|
327
|
+
:restart_hint,
|
|
328
|
+
:log_replacement,
|
|
351
329
|
:log_warning, :write_log
|
|
352
330
|
end
|
|
353
331
|
end
|
|
@@ -125,15 +125,16 @@ module Clacky
|
|
|
125
125
|
end
|
|
126
126
|
|
|
127
127
|
# Put a session back into the persistent slot after a successful
|
|
128
|
-
# command.
|
|
129
|
-
#
|
|
128
|
+
# command. Returns true if stored (caller keeps the session),
|
|
129
|
+
# false if the slot was already filled or the session is unhealthy
|
|
130
|
+
# (caller MUST clean up the session — fds and process — itself).
|
|
130
131
|
def release(session)
|
|
131
132
|
@mutex.synchronize do
|
|
132
133
|
if @session.nil? && session_healthy?(session)
|
|
133
134
|
@session = session
|
|
135
|
+
true
|
|
134
136
|
else
|
|
135
|
-
|
|
136
|
-
# Let the caller's cleanup_session path handle teardown.
|
|
137
|
+
false
|
|
137
138
|
end
|
|
138
139
|
end
|
|
139
140
|
end
|
|
@@ -156,6 +157,7 @@ module Clacky
|
|
|
156
157
|
rescue StandardError
|
|
157
158
|
# ignore
|
|
158
159
|
end
|
|
160
|
+
close_fds(sess)
|
|
159
161
|
end
|
|
160
162
|
end
|
|
161
163
|
|
|
@@ -168,11 +170,20 @@ module Clacky
|
|
|
168
170
|
rescue StandardError
|
|
169
171
|
# ignore
|
|
170
172
|
end
|
|
173
|
+
close_fds(sess)
|
|
171
174
|
SessionManager.forget(sess.id)
|
|
172
175
|
end
|
|
173
176
|
|
|
174
177
|
private :drop_locked
|
|
175
178
|
|
|
179
|
+
# Close all open file descriptors on a session struct. Safe to call
|
|
180
|
+
# multiple times (all closes are rescue-wrapped).
|
|
181
|
+
private def close_fds(session)
|
|
182
|
+
session.log_io&.close rescue nil
|
|
183
|
+
session.writer&.close rescue nil
|
|
184
|
+
session.reader&.close rescue nil
|
|
185
|
+
end
|
|
186
|
+
|
|
176
187
|
def session_healthy?(session)
|
|
177
188
|
return false unless session
|
|
178
189
|
return false if %w[exited killed].include?(session.status.to_s)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Safe rm shell function — sourced by Clacky::Tools::Terminal at the top
|
|
2
|
+
# of every interactive PTY session. See terminal.rb (SAFE_RM_PATH /
|
|
3
|
+
# install_marker) for rationale.
|
|
4
|
+
#
|
|
5
|
+
# Defines a `rm` function that moves files to $CLACKY_TRASH_DIR instead
|
|
6
|
+
# of deleting them, so deletions can be recovered via `trash_manager`.
|
|
7
|
+
# The metadata sidecar schema matches
|
|
8
|
+
# Clacky::Tools::Security::Replacer#create_delete_metadata so
|
|
9
|
+
# `trash_manager list/restore` keeps working unchanged.
|
|
10
|
+
#
|
|
11
|
+
# Covers: direct `rm ...` calls in the interactive shell, including
|
|
12
|
+
# multi-line commands, heredocs (heredoc bodies no longer trigger
|
|
13
|
+
# the rewriter), and shell glob expansion.
|
|
14
|
+
# Does NOT cover: `command rm`, `/bin/rm` (absolute path), `xargs rm`,
|
|
15
|
+
# `find -exec rm`, and child scripts — these bypass shell functions
|
|
16
|
+
# by design. This is the same coverage the old static Ruby rewriter
|
|
17
|
+
# had; it could not see inside those either.
|
|
18
|
+
|
|
19
|
+
rm() {
|
|
20
|
+
# Parse args: respect `--`, collect flag-like and path-like args.
|
|
21
|
+
local __dd=0
|
|
22
|
+
local -a __paths=() __flags=()
|
|
23
|
+
local __a
|
|
24
|
+
for __a in "$@"; do
|
|
25
|
+
if [ "$__dd" = "1" ]; then
|
|
26
|
+
__paths+=("$__a")
|
|
27
|
+
elif [ "$__a" = "--" ]; then
|
|
28
|
+
__dd=1
|
|
29
|
+
elif [ "${__a:0:1}" = "-" ] && [ -n "${__a:1}" ]; then
|
|
30
|
+
__flags+=("$__a")
|
|
31
|
+
else
|
|
32
|
+
__paths+=("$__a")
|
|
33
|
+
fi
|
|
34
|
+
done
|
|
35
|
+
|
|
36
|
+
# Trash dir is provisioned by the Ruby side via env.
|
|
37
|
+
local __trash="${CLACKY_TRASH_DIR:-}"
|
|
38
|
+
if [ -z "$__trash" ]; then
|
|
39
|
+
echo "[clacky-rm] CLACKY_TRASH_DIR not set; refusing to rm" >&2
|
|
40
|
+
return 1
|
|
41
|
+
fi
|
|
42
|
+
mkdir -p "$__trash" 2>/dev/null || true
|
|
43
|
+
|
|
44
|
+
# Safety: refuse catastrophic targets (pre-expansion by the shell).
|
|
45
|
+
local __p __norm
|
|
46
|
+
for __p in ${__paths[@]+"${__paths[@]}"}; do
|
|
47
|
+
__norm="${__p%/}"
|
|
48
|
+
[ -z "$__norm" ] && __norm="/"
|
|
49
|
+
case "$__norm" in
|
|
50
|
+
/|/root|/etc|/usr|/bin|/sbin|/var)
|
|
51
|
+
echo "[clacky-rm] refused dangerous target: $__p" >&2
|
|
52
|
+
return 1
|
|
53
|
+
;;
|
|
54
|
+
esac
|
|
55
|
+
if [ "$__norm" = "$HOME" ] || [ "$__p" = "~" ]; then
|
|
56
|
+
echo "[clacky-rm] refused dangerous target: $__p" >&2
|
|
57
|
+
return 1
|
|
58
|
+
fi
|
|
59
|
+
done
|
|
60
|
+
|
|
61
|
+
# `-f` semantics: suppress "no such file" errors.
|
|
62
|
+
local __has_f=0 __f
|
|
63
|
+
for __f in ${__flags[@]+"${__flags[@]}"}; do
|
|
64
|
+
case "$__f" in *f*) __has_f=1 ;; esac
|
|
65
|
+
done
|
|
66
|
+
|
|
67
|
+
local __rc=0 __base __ts __dest __abs __size __mode __ext __now
|
|
68
|
+
for __p in ${__paths[@]+"${__paths[@]}"}; do
|
|
69
|
+
if [ ! -e "$__p" ] && [ ! -L "$__p" ]; then
|
|
70
|
+
if [ "$__has_f" = "0" ]; then
|
|
71
|
+
echo "rm: $__p: No such file or directory" >&2
|
|
72
|
+
__rc=1
|
|
73
|
+
fi
|
|
74
|
+
continue
|
|
75
|
+
fi
|
|
76
|
+
__base="$(basename -- "$__p")"
|
|
77
|
+
__ts="$(date +%Y%m%d_%H%M%S_%N 2>/dev/null || date +%Y%m%d_%H%M%S)"
|
|
78
|
+
__dest="$__trash/${__base}_deleted_${__ts}"
|
|
79
|
+
# Resolve absolute path for metadata BEFORE mv (path won't exist after).
|
|
80
|
+
if [ -d "$__p" ]; then
|
|
81
|
+
__abs="$(cd "$__p" 2>/dev/null && pwd)" || __abs="$__p"
|
|
82
|
+
else
|
|
83
|
+
__abs="$(cd "$(dirname -- "$__p")" 2>/dev/null && pwd)/$(basename -- "$__p")" || __abs="$__p"
|
|
84
|
+
fi
|
|
85
|
+
# Size / mode best-effort; ignore for dirs or on failure.
|
|
86
|
+
__size="$(stat -f%z "$__p" 2>/dev/null || stat -c%s "$__p" 2>/dev/null || echo 0)"
|
|
87
|
+
__mode="$(stat -f%Lp "$__p" 2>/dev/null || stat -c%a "$__p" 2>/dev/null || echo 644)"
|
|
88
|
+
case "$__base" in
|
|
89
|
+
*.*) __ext=".${__base##*.}" ;;
|
|
90
|
+
*) __ext="" ;;
|
|
91
|
+
esac
|
|
92
|
+
__now="$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date +%s)"
|
|
93
|
+
if command mv -- "$__p" "$__dest" 2>/dev/null; then
|
|
94
|
+
# Metadata sidecar — schema matches
|
|
95
|
+
# Clacky::Tools::Security::Replacer#create_delete_metadata so
|
|
96
|
+
# `trash_manager list/restore` continue to work.
|
|
97
|
+
printf '{"original_path":"%s","trash_directory":"%s","deleted_at":"%s","deleted_by":"clacky_rm_shell","file_size":%s,"file_type":"%s","file_mode":"%s"}\n' \
|
|
98
|
+
"$__abs" "$__trash" "$__now" "${__size:-0}" "$__ext" "${__mode:-644}" \
|
|
99
|
+
> "$__dest.metadata.json" 2>/dev/null || true
|
|
100
|
+
else
|
|
101
|
+
echo "rm: failed to move $__p to trash" >&2
|
|
102
|
+
__rc=1
|
|
103
|
+
fi
|
|
104
|
+
done
|
|
105
|
+
return $__rc
|
|
106
|
+
}
|
|
@@ -175,12 +175,17 @@ module Clacky
|
|
|
175
175
|
end
|
|
176
176
|
end
|
|
177
177
|
|
|
178
|
-
# Kill every live session. Called from at_exit.
|
|
178
|
+
# Kill every live session and close any open fds. Called from at_exit.
|
|
179
179
|
def kill_all!
|
|
180
180
|
(@sessions.values rescue []).each do |s|
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
begin
|
|
182
|
+
Process.kill("KILL", s.pid) unless %w[exited killed].include?(s.status.to_s)
|
|
183
|
+
rescue StandardError
|
|
184
|
+
# ignore
|
|
185
|
+
end
|
|
183
186
|
s.log_io&.close rescue nil
|
|
187
|
+
s.writer&.close rescue nil
|
|
188
|
+
s.reader&.close rescue nil
|
|
184
189
|
end
|
|
185
190
|
end
|
|
186
191
|
|