deel-local-cli 1.20.12 → 2.0.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.ko.md +96 -77
- package/README.md +119 -80
- package/bin/deel.js +398 -32
- package/package.json +2 -1
- package/src/acp/jsonrpc.js +87 -7
- package/src/acp/map.js +62 -13
- package/src/acp/serve.js +338 -30
- package/src/agent/agents.js +110 -12
- package/src/agent/askcheck.js +50 -6
- package/src/agent/asks.js +59 -5
- package/src/agent/budget.js +9 -2
- package/src/agent/card.js +8 -2
- package/src/agent/commit.js +168 -16
- package/src/agent/compact.js +93 -21
- package/src/agent/{/354/213/240/353/242/260/353/217/204.js → confidence.js} +1 -1
- package/src/agent/effort.js +85 -15
- package/src/agent/evidence.js +63 -7
- package/src/agent/evolve.js +132 -24
- package/src/agent/grade.js +12 -3
- package/src/agent/loop.js +408 -81
- package/src/agent/memory.js +176 -29
- package/src/agent/mention.js +22 -5
- package/src/agent/models.js +31 -7
- package/src/agent/modes.js +25 -10
- package/src/agent/outschema.js +381 -56
- package/src/agent/pins.js +33 -2
- package/src/agent/project.js +73 -8
- package/src/agent/recall.js +40 -9
- package/src/agent/{/354/204/261/355/225/234/352/270/260/353/241/235.js → recordshape.js} +19 -8
- package/src/agent/review.js +143 -13
- package/src/agent/route.js +506 -32
- package/src/agent/salvage.js +2 -1
- package/src/agent/session.js +457 -31
- package/src/agent/store.js +232 -22
- package/src/agent/threads.js +52 -3
- package/src/backend/adapter.js +508 -85
- package/src/backend/azure.js +12 -1
- package/src/backend/cachemark.js +31 -5
- package/src/backend/clientcert.js +23 -1
- package/src/backend/ctxsize.js +56 -9
- package/src/backend/detect.js +252 -19
- package/src/backend/http.js +383 -42
- package/src/backend/learn.js +101 -11
- package/src/backend/mcp.js +333 -24
- package/src/backend/price.js +61 -13
- package/src/backend/probe.js +188 -36
- package/src/backend/proxy.js +108 -14
- package/src/backend/quota.js +226 -30
- package/src/backend/retry.js +46 -3
- package/src/backend/scan.js +47 -10
- package/src/backend/scanui.js +57 -7
- package/src/backend/toolfit.js +204 -22
- package/src/backend/vision.js +14 -1
- package/src/backend/wire.js +58 -10
- package/src/cmdnames.js +16 -0
- package/src/commands/common.js +70 -0
- package/src/commands/extend.js +300 -0
- package/src/commands/model.js +852 -0
- package/src/commands/view.js +328 -0
- package/src/commands/work.js +823 -0
- package/src/commands.js +193 -2082
- package/src/completion.js +45 -3
- package/src/config.js +452 -39
- package/src/configexplain.js +125 -14
- package/src/doctor.js +164 -26
- package/src/i18n/en.js +22 -0
- package/src/i18n/index.js +31 -1
- package/src/i18n/ja.js +25 -1
- package/src/i18n/ko.js +25 -2
- package/src/i18n/zh.js +25 -1
- package/src/lsp/client.js +111 -9
- package/src/lsp/servers.js +86 -22
- package/src/oneshot.js +325 -65
- package/src/pack/sbom.js +28 -5
- package/src/pack/selfpack.js +54 -5
- package/src/pack/sheet.en.js +24 -3
- package/src/pack/tar.js +45 -8
- package/src/pack/zip.js +103 -8
- package/src/plugins/manage.js +165 -14
- package/src/preview/serve.js +239 -23
- package/src/providers/bedrock.js +30 -5
- package/src/providers/gemini.js +8 -0
- package/src/providers/index.js +17 -1
- package/src/repl.js +254 -61
- package/src/report.js +7 -2
- package/src/reset.js +151 -25
- package/src/safety/audit.js +108 -8
- package/src/safety/authcmd.js +75 -5
- package/src/safety/guard.js +507 -15
- package/src/safety/hooks.js +172 -20
- package/src/safety/keystore.js +178 -18
- package/src/safety/network.js +209 -6
- package/src/safety/policy.js +377 -36
- package/src/safety/runmode.js +9 -2
- package/src/safety/secrets.js +243 -17
- package/src/safety/shellenv.js +64 -3
- package/src/safety/trust.js +298 -32
- package/src/safety/undo.js +409 -17
- package/src/setup.js +175 -17
- package/src/skills/discover.js +122 -19
- package/src/stats.js +57 -16
- package/src/tools/{/355/231/225/354/235/270/353/262/225.js → checkmethods.js} +26 -1
- package/src/tools/clipboard.js +53 -20
- package/src/tools/convert.js +118 -37
- package/src/tools/desc.en.js +48 -5
- package/src/tools/doc2md.js +11 -1
- package/src/tools/docs.js +94 -18
- package/src/tools/edit-match.js +212 -13
- package/src/tools/encoding.js +525 -28
- package/src/tools/excel-com.js +63 -15
- package/src/tools/excel.js +12 -7
- package/src/tools/fastgrep.js +444 -36
- package/src/tools/fig.js +40 -9
- package/src/tools/fsutil.js +235 -21
- package/src/tools/hwpxwrite.js +80 -19
- package/src/tools/ignore.js +55 -7
- package/src/tools/index.js +1029 -145
- package/src/tools/jobs.js +325 -50
- package/src/tools/kiwi.js +50 -5
- package/src/tools/lsp.js +164 -32
- package/src/tools/outline.js +52 -12
- package/src/tools/pdf.js +366 -56
- package/src/tools/shell.js +12 -4
- package/src/tools/spawn.js +203 -18
- package/src/tools/todo.js +73 -4
- package/src/tools/verify.js +312 -43
- package/src/tools/webfetch.js +184 -25
- package/src/tools/xlsx.js +232 -31
- package/src/ui/ansi.js +209 -15
- package/src/ui/banner.js +2 -1
- package/src/ui/complete.js +46 -4
- package/src/ui/diff.js +4 -2
- package/src/ui/export.js +91 -17
- package/src/ui/inputbox.js +58 -18
- package/src/ui/intro.js +9 -2
- package/src/ui/level.js +32 -7
- package/src/ui/md.js +101 -15
- package/src/ui/motion.js +5 -2
- package/src/ui/notify.js +7 -1
- package/src/ui/office.js +37 -5
- package/src/ui/pastechip.js +3 -2
- package/src/ui/pick.js +85 -12
- package/src/ui/prompt.js +176 -13
- package/src/ui/screen.js +2 -1
- package/src/ui/spinner.js +6 -1
- package/src/ui/status.js +80 -12
- package/src/ui/wrap.js +23 -37
- /package/src/agent/{/353/213/250/352/263/204.js" → phase.js} +0 -0
- /package/src/skills/builtin/{/352/271/212/354/235/264/354/236/210/352/262/214-/353/247/214/353/223/244/352/270/260/SKILL.md" → build-deep/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/260/250/352/267/274/354/260/250/352/267/274-/353/224/224/353/262/204/352/271/205/SKILL.md" → debug-step-by-step/SKILL.md} +0 -0
- /package/src/skills/builtin/{/353/201/235/352/271/214/354/247/200-/355/225/230/352/270/260/SKILL.md" → finish-all/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/212/244/354/212/244/353/241/234-/352/262/200/355/206/240/SKILL.md" → self-review/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/275/224/353/223/234-/354/244/204/354/235/264/352/270/260/SKILL.md" → simplify-code/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/260/224/353/237/254/353/263/264/352/270/260/SKILL.md" → spike/SKILL.md} +0 -0
- /package/src/skills/builtin/{/352/262/200/354/202/254-/353/250/274/354/240/200/SKILL.md" → test-first/SKILL.md} +0 -0
package/src/tools/jobs.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* 그건 '내가 안 띄운 서버가 포트를 물고 있는' 상태고, 원인을 못 찾는다.
|
|
24
24
|
* · 출력은 상한이 있다. watch 하나가 몇 시간 돌면 몇 GB 가 된다.
|
|
25
25
|
*/
|
|
26
|
-
import { spawn
|
|
26
|
+
import { spawn } from 'node:child_process';
|
|
27
27
|
import { decode as decodeBytes, consoleCodepage } from './encoding.js';
|
|
28
28
|
import { 셸명령 } from './shell.js';
|
|
29
29
|
import { 셸환경 } from '../safety/shellenv.js';
|
|
@@ -131,15 +131,39 @@ export function 띄우기옵션(cwd, { 남길것 = [] } = {}) {
|
|
|
131
131
|
*
|
|
132
132
|
* 갈리는 자리는 **signal 과 code** 다. 아래 검사(test/jobs.test.js 의
|
|
133
133
|
* '★ taskkill 이 잘린 것과 이미 죽은 것을 가른다')가 이 값을 못박아 둔다.
|
|
134
|
+
*
|
|
135
|
+
* ── 상한에 걸려도 taskkill 을 **죽이지 않는다** (2.0.0 6회차) ─────────────────
|
|
136
|
+
*
|
|
137
|
+
* 위처럼 execFileSync 의 timeout 으로 부르면, 상한에 걸리는 순간 Node 가 taskkill 을 죽인다.
|
|
138
|
+
* 한가할 때는 그 순간 taskkill 이 아직 아무것도 안 죽인 채라 괜찮았다. 그런데 컴퓨터가
|
|
139
|
+
* 바쁘면 taskkill 이 **나무를 반쯤 죽인 채** 죽는다 — Git Bash 나무(`bin/bash.exe →
|
|
140
|
+
* usr/bin/bash.exe → node`)에서 뿌리와 가운데 셸만 죽고 손자가 부모 없이 남았다. 그 뒤로는
|
|
141
|
+
* 손자를 가리킬 길이 없어(남은무리끊기 머리말) 두 번째 부름도 「프로세스 없음」 으로 끝났다.
|
|
142
|
+
* 코어를 다 쓰는 판에서 끝내기 4번 중 1번 · 모두끝내기 4번 중 1번이 dev 서버를 남겼고,
|
|
143
|
+
* 검사를 돌린 하루 동안 jobs 검사의 아이 74개가 그렇게 살아 있었다.
|
|
144
|
+
*
|
|
145
|
+
* 그래서 떼어서(detached) 띄우고 **상한까지만 동기로 지켜본다.** 그 안에 끝나면 다 훑은 것,
|
|
146
|
+
* 못 끝나면 「잘렸다」 로 돌려주되 taskkill 은 제 할 일을 끝까지 한다 — deel 이 끝나는
|
|
147
|
+
* 길이어도 떼어 띄운 것은 살아남는다. 돌려주는 뜻(잘렸으면 뿌리를 살려 두고 다시 온다)은 같다.
|
|
148
|
+
* 잘렸나() 는 execFileSync 오류를 가르는 판별식으로 그대로 둔다 (검사가 값을 못박아 둔다).
|
|
134
149
|
*/
|
|
135
150
|
export function 나무끊기(pid, 상한 = 1500) {
|
|
136
151
|
if (process.platform !== 'win32' || !pid) return true;
|
|
152
|
+
let 끊개;
|
|
137
153
|
try {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
|
|
154
|
+
끊개 = spawn('taskkill', ['/pid', String(pid), '/t', '/f'], { windowsHide: true, stdio: 'ignore', detached: true });
|
|
155
|
+
} catch {
|
|
156
|
+
return true; // taskkill 을 못 띄운 판 — 예전에도 「잘림」 이 아닌 실패는 다 참이었다
|
|
157
|
+
}
|
|
158
|
+
끊개.once('error', () => { /* 못 띄웠다 — 위와 같다 */ });
|
|
159
|
+
끊개.unref();
|
|
160
|
+
if (!끊개.pid) return true;
|
|
161
|
+
const 끝 = Date.now() + 상한;
|
|
162
|
+
for (;;) {
|
|
163
|
+
// 신호 0 은 「살아 있나」 만 묻는다. 끝났으면 던진다 — 없는 pid 로 끝난 것(이미 죽음)도 여기로 온다.
|
|
164
|
+
try { process.kill(끊개.pid, 0); } catch { return true; }
|
|
165
|
+
if (Date.now() >= 끝) return false;
|
|
166
|
+
동기로쉬기(15);
|
|
143
167
|
}
|
|
144
168
|
}
|
|
145
169
|
|
|
@@ -148,30 +172,24 @@ export function 잘렸나(err) {
|
|
|
148
172
|
return err?.code === 'ETIMEDOUT' || err?.signal != null || err?.killed === true;
|
|
149
173
|
}
|
|
150
174
|
|
|
151
|
-
/**
|
|
152
|
-
* 유닉스에서 pid 가 이끄는 **무리째** 죽인다.
|
|
153
|
-
*
|
|
154
|
-
* 음수 pid 는 '그 무리 전부' 라는 뜻이다 — 띄울 때 detached 로 무리를 만들어
|
|
155
|
-
* 둔 것과 짝이다. 이걸 안 하고 kid.kill() 만 하면 sh 나 npm 만 죽고 그 아래
|
|
156
|
-
* vite 가 포트를 문 채로 남는다. 무엇이 물고 있는지 못 찾는, 제일 나쁜 자리다.
|
|
157
|
-
*
|
|
158
|
-
* 먼저 곱게 말하고(TERM), 안 들으면 끊는다(KILL). 곧장 KILL 로 가면 dev 서버가
|
|
159
|
-
* 임시 파일이나 소켓을 치울 틈이 없다.
|
|
160
|
-
*
|
|
161
|
-
* **띄울 때 무리를 안 만들었으면 여기서 할 수 있는 것이 없다.** 나중에 무리를
|
|
162
|
-
* 만들어 줄 방법은 없다 — 그래서 이 판단이 띄우는 자리와 짝을 이뤄야 한다.
|
|
163
|
-
* (Bash 도구가 한동안 이 짝을 안 맞춰서, 윈도우만 고쳐지고 유닉스는 그대로였다.)
|
|
164
|
-
*/
|
|
165
175
|
/**
|
|
166
176
|
* 그 무리가 아직 살아 있나. 죽었으면 false.
|
|
167
177
|
*
|
|
168
|
-
* 무리로 못 물어보면 **그 하나에게** 물어본다 —
|
|
178
|
+
* 무리로 못 물어보면 **그 하나에게** 물어본다 — 아래 신호주기() 와 같은 까닭이다.
|
|
169
179
|
* 무리가 없는 판에서 「무리가 없다 = 다 죽었다」 로 읽으면, 살아 있는 자식을
|
|
170
180
|
* 죽은 것으로 세고 그냥 지나간다.
|
|
171
181
|
*/
|
|
172
|
-
function 무리살아있나(pid) {
|
|
173
|
-
|
|
174
|
-
|
|
182
|
+
export function 무리살아있나(pid) {
|
|
183
|
+
/*
|
|
184
|
+
* EPERM 은 「없다」 가 아니라 **「있는데 못 건드린다」** 이다.
|
|
185
|
+
*
|
|
186
|
+
* 여기가 아무 탈이나 다 「죽었다」 로 읽어서, 무리끊기 의 지켜보기 고리가
|
|
187
|
+
* 첫 바퀴에 빠져나가고 마지막 SIGKILL 까지 통째로 건너뛰었다 — 그 자리
|
|
188
|
+
* 머리말이 「권한이 없으면 살아 있는데도 「없다」 가 나오고, 그러면 안 죽이고
|
|
189
|
+
* 지나간다」 라며 걷어냈다고 적은 바로 그 길이다. 고침이 반만 붙어 있었다.
|
|
190
|
+
*/
|
|
191
|
+
try { process.kill(-pid, 0); return true; } catch (탈) { if (탈?.code === 'EPERM') return true; }
|
|
192
|
+
try { process.kill(pid, 0); return true; } catch (탈) { return 탈?.code === 'EPERM'; }
|
|
175
193
|
}
|
|
176
194
|
|
|
177
195
|
/**
|
|
@@ -214,6 +232,21 @@ function 신호주기(pid, 신호) {
|
|
|
214
232
|
return 닿았나;
|
|
215
233
|
}
|
|
216
234
|
|
|
235
|
+
/**
|
|
236
|
+
* 유닉스에서 pid 가 이끄는 **무리째** 죽인다.
|
|
237
|
+
*
|
|
238
|
+
* 음수 pid 는 '그 무리 전부' 라는 뜻이다 — 띄울 때 detached 로 무리를 만들어
|
|
239
|
+
* 둔 것과 짝이다. 이걸 안 하고 kid.kill() 만 하면 sh 나 npm 만 죽고 그 아래
|
|
240
|
+
* vite 가 포트를 문 채로 남는다. 무엇이 물고 있는지 못 찾는, 제일 나쁜 자리다.
|
|
241
|
+
*
|
|
242
|
+
* 먼저 곱게 말하고(TERM), 안 들으면 끊는다(KILL). 곧장 KILL 로 가면 dev 서버가
|
|
243
|
+
* 임시 파일이나 소켓을 치울 틈이 없다. 얼마나 기다릴지(`늦게`)는 부르는 쪽이
|
|
244
|
+
* 정한다 — 끝나는 길에서는 짧게, 사람이 「끝내」 라고 시킨 자리에서는 길게.
|
|
245
|
+
*
|
|
246
|
+
* **띄울 때 무리를 안 만들었으면 여기서 할 수 있는 것이 없다.** 나중에 무리를
|
|
247
|
+
* 만들어 줄 방법은 없다 — 그래서 이 판단이 띄우는 자리와 짝을 이뤄야 한다.
|
|
248
|
+
* (Bash 도구가 한동안 이 짝을 안 맞춰서, 윈도우만 고쳐지고 유닉스는 그대로였다.)
|
|
249
|
+
*/
|
|
217
250
|
export function 무리끊기(pid, { 곧장 = false, 늦게 = 800 } = {}) {
|
|
218
251
|
if (process.platform === 'win32' || !pid) return;
|
|
219
252
|
if (곧장) {
|
|
@@ -241,7 +274,7 @@ export function 무리끊기(pid, { 곧장 = false, 늦게 = 800 } = {}) {
|
|
|
241
274
|
* 거의 안 기다린다. 안 죽는 놈에게만 `늦게` 만큼 쓴다.
|
|
242
275
|
*/
|
|
243
276
|
const 조각 = 20;
|
|
244
|
-
for (let 쓴시간 = 0; 쓴시간 <
|
|
277
|
+
for (let 쓴시간 = 0; 쓴시간 < Math.min(늦게, 못재고기다릴최대); 쓴시간 += 조각) {
|
|
245
278
|
if (!무리살아있나(pid)) return;
|
|
246
279
|
동기로쉬기(조각);
|
|
247
280
|
}
|
|
@@ -256,6 +289,45 @@ export function 무리끊기(pid, { 곧장 = false, 늦게 = 800 } = {}) {
|
|
|
256
289
|
신호주기(pid, 'SIGKILL');
|
|
257
290
|
}
|
|
258
291
|
|
|
292
|
+
/*
|
|
293
|
+
* ── 그 지켜보기는 유닉스에서 **한 번도 안 끝났다** (2.0.0 16회차) ──────────
|
|
294
|
+
*
|
|
295
|
+
* 위 고리의 머리말은 「얌전히 끝나는 무리는 첫 20ms 에 사라지므로 보통은 거의
|
|
296
|
+
* 안 기다린다」 고 적어 뒀다. 윈도우에서만 참인 말이었다.
|
|
297
|
+
*
|
|
298
|
+
* 우리가 띄운 자식은 **죽어도 우리가 거두기(reap) 전까지 좀비로 남는다.**
|
|
299
|
+
* 거두는 일은 이벤트 루프에서만 일어나는데, 위 고리는 `Atomics.wait` 로 그
|
|
300
|
+
* 루프를 세워 놓고 묻는다. `무리살아있나` 는 좀비를 「산다」 로 답하므로
|
|
301
|
+
* (`kill(pid, 0)` 은 좀비에게도 성공한다) 답은 영영 안 바뀌고, 고리는 **늘
|
|
302
|
+
* `늦게` 를 다 쓴다.**
|
|
303
|
+
*
|
|
304
|
+
* 값이 얼마였나. 사람이 「끝내」 를 한 번 칠 때마다 6초였다 — 리눅스 CI 에서
|
|
305
|
+
* test/jobs.test.js 의 「오래된 것에는 안 머문다」(400ms 아래여야 한다)가
|
|
306
|
+
* **6037ms** 로 빨개진 것이 이것이고, 화면에는 아무 말도 안 난다.
|
|
307
|
+
*
|
|
308
|
+
* 그래서 둘로 가른다.
|
|
309
|
+
*
|
|
310
|
+
* · 기다릴 수 있는 자리(끝내기 — async) → 무리끊기기다려(). 진짜로 쉬어
|
|
311
|
+
* 주니 루프가 돌아 자식이 거둬지고, 같은 물음이 그때부터 바르게 답한다.
|
|
312
|
+
* · 못 기다리는 자리(프로그램이 끝나는 길) → 위 동기 고리. 다만 **못 재는
|
|
313
|
+
* 기다림은 최선이 아니라 모두가 내는 값**이라 짧게 못 박는다. SIGKILL 은
|
|
314
|
+
* 그대로 나가므로, 신호를 무시하는 손자를 살려 보내는 일은 없다.
|
|
315
|
+
*/
|
|
316
|
+
const 못재고기다릴최대 = 400;
|
|
317
|
+
|
|
318
|
+
/** 무리끊기 의 **기다릴 수 있는** 판. 까닭은 바로 위 머리말에. */
|
|
319
|
+
export async function 무리끊기기다려(pid, { 곧장 = false, 늦게 = 800 } = {}) {
|
|
320
|
+
if (process.platform === 'win32' || !pid) return;
|
|
321
|
+
if (곧장) { 신호주기(pid, 'SIGKILL'); return; }
|
|
322
|
+
신호주기(pid, 'SIGTERM');
|
|
323
|
+
const 조각 = 20;
|
|
324
|
+
for (let 쓴시간 = 0; 쓴시간 < 늦게; 쓴시간 += 조각) {
|
|
325
|
+
if (!무리살아있나(pid)) return;
|
|
326
|
+
await new Promise((r) => setTimeout(r, 조각));
|
|
327
|
+
}
|
|
328
|
+
신호주기(pid, 'SIGKILL'); // 위와 같은 까닭으로 묻지 않고 보낸다
|
|
329
|
+
}
|
|
330
|
+
|
|
259
331
|
/**
|
|
260
332
|
* 프로세스 나무를 통째로 끝낸다. 자식만 죽이면 손자가 남아 포트를 계속 문다.
|
|
261
333
|
*
|
|
@@ -272,7 +344,7 @@ export function 무리끊기(pid, { 곧장 = false, 늦게 = 800 } = {}) {
|
|
|
272
344
|
* 그래서 동기로 부른다. 여기서 멈추는 몇십 ms 로 '안 죽은 서버' 를 없앤다.
|
|
273
345
|
* test/jobs.test.js 의 '프로세스가 진짜 멈춘다' 가 이 자리를 지킨다.
|
|
274
346
|
*/
|
|
275
|
-
function 나무죽이기(kid, { 파이프끊기: 끊을까 = true, 곧장 = false, 상한 = 1500, 뿌리살리기 = true } = {}) {
|
|
347
|
+
export function 나무죽이기(kid, { 파이프끊기: 끊을까 = true, 곧장 = false, 상한 = 1500, 뿌리살리기 = true } = {}) {
|
|
276
348
|
if (!kid) return true;
|
|
277
349
|
let 나무끊었나 = true;
|
|
278
350
|
if (process.platform === 'win32' && kid.pid) {
|
|
@@ -291,7 +363,17 @@ function 나무죽이기(kid, { 파이프끊기: 끊을까 = true, 곧장 = fals
|
|
|
291
363
|
*/
|
|
292
364
|
if (!나무끊었나 && 뿌리살리기) return false;
|
|
293
365
|
} else if (kid.pid) {
|
|
294
|
-
|
|
366
|
+
/*
|
|
367
|
+
* **상한은 여기도 넘긴다.** 윈도우 갈래(바로 위)는 부르는 쪽이 정한 상한을
|
|
368
|
+
* 그대로 쓰는데, 유닉스 갈래만 그 값을 버리고 무리끊기의 기본 800ms 를 썼다.
|
|
369
|
+
* 재 보니 상한 5000 을 줘도 100 을 줘도 걸린 시간이 똑같았다 — 위 머리말이
|
|
370
|
+
* 「상한은 부르는 쪽이 정한다」 고 적어 놓고 한쪽에서만 지키고 있었다.
|
|
371
|
+
*
|
|
372
|
+
* 값이 다르다. 끝나는 길(process 'exit')에서는 일감마다 몇 초씩 붙들면 deel 이
|
|
373
|
+
* 멈춘 것처럼 보이고, 사람이 「끝내」 라고 시킨 자리에서는 안 죽는 서버 하나가
|
|
374
|
+
* 훨씬 비싸다. 그 판단을 부르는 쪽이 이미 하고 있었다.
|
|
375
|
+
*/
|
|
376
|
+
무리끊기(kid.pid, { 곧장, 늦게: 상한 });
|
|
295
377
|
}
|
|
296
378
|
try { kid.kill(); } catch { /* 이미 죽음 */ }
|
|
297
379
|
if (끊을까) 파이프끊기(kid);
|
|
@@ -299,6 +381,23 @@ function 나무죽이기(kid, { 파이프끊기: 끊을까 = true, 곧장 = fals
|
|
|
299
381
|
return 나무끊었나;
|
|
300
382
|
}
|
|
301
383
|
|
|
384
|
+
/**
|
|
385
|
+
* 나무죽이기 의 **기다릴 수 있는** 판 (무리끊기기다려 머리말).
|
|
386
|
+
*
|
|
387
|
+
* 윈도우 갈래는 taskkill 을 이미 동기로 기다리므로 그대로 넘긴다. 유닉스에서만
|
|
388
|
+
* 지켜보기를 진짜 쉬면서 한다 — 그래야 죽은 자식이 거둬지고 「아직 사나」 가
|
|
389
|
+
* 바르게 답한다.
|
|
390
|
+
*/
|
|
391
|
+
export async function 나무죽이기기다려(kid, 몫 = {}) {
|
|
392
|
+
const { 파이프끊기: 끊을까 = true, 곧장 = false, 상한 = 1500 } = 몫;
|
|
393
|
+
if (!kid || process.platform === 'win32' || !kid.pid) return 나무죽이기(kid, 몫);
|
|
394
|
+
await 무리끊기기다려(kid.pid, { 곧장, 늦게: 상한 });
|
|
395
|
+
try { kid.kill(); } catch { /* 이미 죽음 */ }
|
|
396
|
+
if (끊을까) 파이프끊기(kid);
|
|
397
|
+
try { kid.unref(); } catch { /* 없으면 그만 */ }
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
|
|
302
401
|
/**
|
|
303
402
|
* 파이프를 놓는다.
|
|
304
403
|
*
|
|
@@ -315,11 +414,45 @@ function 파이프끊기(kid) {
|
|
|
315
414
|
try { kid?.stderr?.destroy(); } catch { /* 이미 닫힘 */ }
|
|
316
415
|
}
|
|
317
416
|
|
|
417
|
+
/*
|
|
418
|
+
* ── 갓 띄운 셸은 나무를 **나중에** 세운다 (2.0.0 6회차) ─────────────────────────
|
|
419
|
+
*
|
|
420
|
+
* 윈도우의 Git Bash 는 `bin/bash.exe`(발사대) → `usr/bin/bash.exe` → 명령 으로 한 겹씩 띄운다.
|
|
421
|
+
* 띄우고 몇 ms 안에 taskkill /t 를 보내면 그 순간 있는 나무(발사대뿐)만 죽고, 곧이어 뜬 손자가
|
|
422
|
+
* 부모 없이 남는다. 뿌리가 죽은 뒤로는 그 손자를 가리킬 길이 없다(남은무리끊기 머리말).
|
|
423
|
+
* 재 보니 띄우고 0ms 에 끊으면 4번 중 1~2번, 30ms 에 1번 남았고 150ms 부터 0 이었다(옛 코드도 같음).
|
|
424
|
+
* 바쁜 컴퓨터에서는 그 창이 넓어져 150~600ms 에 끊은 판도 남았다. 검사 판마다 이 파일의 아이가
|
|
425
|
+
* 남던 것이 이것이다.
|
|
426
|
+
*
|
|
427
|
+
* 그래서 끄는 쪽에서 갓 띄운 것에 1초를 채워 준다. 모델이 띄운 것은 띄우기 가 이미 1.5초를
|
|
428
|
+
* 기다렸고 왕복이 수 초라 거의 안 걸린다 — 곧장 끄는 판(사람이 띄우자마자 나감 · 검사)만 기다린다.
|
|
429
|
+
*/
|
|
430
|
+
const 나무설틈 = 1000;
|
|
431
|
+
|
|
318
432
|
/** 아직 살아 있나. 종료코드도 시그널도 없으면 안 죽은 것이다. */
|
|
319
433
|
function 아직사나(kid) {
|
|
320
434
|
return !!kid && kid.exitCode == null && kid.signalCode == null;
|
|
321
435
|
}
|
|
322
436
|
|
|
437
|
+
/**
|
|
438
|
+
* 뿌리(셸)는 이미 끝났는데 그 아래 남은 것이 파이프를 물고 있을 때, 남은 것을 끊는다.
|
|
439
|
+
* Bash(tools/index.js)와 끝내기 가 같이 쓴다 (사냥5 M2).
|
|
440
|
+
*
|
|
441
|
+
* 유닉스는 띄울 때 만든 무리가 우두머리가 죽은 뒤에도 남아 있어 `-pid` 로 무리째 닿는다.
|
|
442
|
+
*
|
|
443
|
+
* 윈도우는 **못 한다.** taskkill /t 는 살아 있는 뿌리부터 나무를 훑는데, 뿌리가 끝나면
|
|
444
|
+
* 가리킬 자리가 없다. 부모 번호(ParentProcessId)로 거꾸로 찾아도 소용없었다 — Git Bash
|
|
445
|
+
* 는 `bin/bash.exe → usr/bin/bash.exe → 손자` 로 한 겹이 더 있고, 가운데 것이 이미 끝나
|
|
446
|
+
* 사슬이 끊겨 있다(재 봄). 그래서 false 를 돌려주고, 부르는 쪽이 「못 껐다」 고 말한다.
|
|
447
|
+
*
|
|
448
|
+
* @returns {boolean} 끊으러 갈 길이 있었나
|
|
449
|
+
*/
|
|
450
|
+
export function 남은무리끊기(kid) {
|
|
451
|
+
if (process.platform === 'win32' || !kid?.pid) return false;
|
|
452
|
+
무리끊기(kid.pid);
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
|
|
323
456
|
/**
|
|
324
457
|
* 죽인 뒤, 남은 말이 다 나오기를 잠깐 기다린다.
|
|
325
458
|
*
|
|
@@ -456,7 +589,17 @@ class 일감 {
|
|
|
456
589
|
const r = decodeBytes(다, { fallback: 콘솔 });
|
|
457
590
|
// 처음 한 번만 못 박는다. 아직 아무것도 안 쌓였을 때 정하면
|
|
458
591
|
// 표본이 너무 적어 엉뚱하게 잡히므로, 실제로 글이 나온 뒤에 정한다.
|
|
459
|
-
|
|
592
|
+
/*
|
|
593
|
+
* ── ASCII 뿐인 동안은 못 박지 않는다 (사냥5 L1) ───────────────────────
|
|
594
|
+
*
|
|
595
|
+
* 「글이 나온 뒤」 를 글자가 있느냐로만 봤다. 그래서 dev 서버가 흔히 그러듯 영어 배너가
|
|
596
|
+
* 먼저 나오면 그 순간 utf-8 로 못 박혔고, 나중에 한글 윈도우 콘솔이 CP949 로 한글을
|
|
597
|
+
* 뱉자 **처음부터 다시 읽어도** 영영 깨졌다 — 통째로 새로 풀면 멀쩡한 바이트인데.
|
|
598
|
+
*
|
|
599
|
+
* ASCII(0x7F 이하)만 있으면 어느 인코딩으로 풀어도 같은 글자·같은 글자 수라, 안 못
|
|
600
|
+
* 박아도 읽은 자리(읽은글자)가 어긋나지 않는다. 판정에 쓸 바이트가 처음 나왔을 때 박는다.
|
|
601
|
+
*/
|
|
602
|
+
if (r.text && 다.some((b) => b > 127)) this.인코딩 = r.encoding;
|
|
460
603
|
this.푼것 = { 판: this.판, 글: r.text };
|
|
461
604
|
return r.text;
|
|
462
605
|
} catch { return 다.toString('utf8'); }
|
|
@@ -553,6 +696,9 @@ export async function 띄우기(명령, { cwd, 설명 = null, 기다림 = 1500,
|
|
|
553
696
|
j.띄우기실패 = true;
|
|
554
697
|
});
|
|
555
698
|
kid.on('close', (code, sig) => {
|
|
699
|
+
// 파이프까지 다 닫혔다 — 셸만이 아니라 출력을 문 것이 하나도 안 남았다는 뜻이다.
|
|
700
|
+
// 끝내기 가 「진짜 끝났나」 를 이것으로 본다 (사냥5 M2 · 끝내기 머리말).
|
|
701
|
+
j.닫힘 = true;
|
|
556
702
|
if (j.상태 === '죽임') return;
|
|
557
703
|
j.상태 = '끝남';
|
|
558
704
|
/*
|
|
@@ -608,6 +754,8 @@ export function 목록() {
|
|
|
608
754
|
시그널: j.시그널,
|
|
609
755
|
초: j.산햇수(),
|
|
610
756
|
안읽은글자: Math.max(0, j.전체글().length - j.읽은글자),
|
|
757
|
+
// 셸은 끝났는데 파이프가 안 닫혔다 — 셸이 뒤로 띄운 것이 아직 돈다 (사냥5 M2).
|
|
758
|
+
셸끝남: j.상태 === '도는중' && !!j.kid && !j.닫힘 && !아직사나(j.kid),
|
|
611
759
|
}));
|
|
612
760
|
}
|
|
613
761
|
|
|
@@ -631,7 +779,21 @@ export function 읽기(번호, opts) {
|
|
|
631
779
|
export async function 끝내기(번호) {
|
|
632
780
|
const j = 하나(번호);
|
|
633
781
|
if (!j) return null;
|
|
634
|
-
|
|
782
|
+
// 갓 띄운 것이면 셸이 나무를 다 세울 틈을 준다 (아래 나무설틈). 그 사이 끝났으면 바로 아래 「이미 끝난 것」 으로 간다.
|
|
783
|
+
const 갓 = 나무설틈 - (Date.now() - j.띄운때);
|
|
784
|
+
if (j.상태 === '도는중' && 갓 > 0) await new Promise((r) => setTimeout(r, 갓));
|
|
785
|
+
/*
|
|
786
|
+
* 이미 끝난 것도 **안 읽은 출력은 건네고** 지운다 (사냥5 L2).
|
|
787
|
+
*
|
|
788
|
+
* 여기는 곧장 지웠다. 뜨고 조금 돌다 죽은 일감에 stop 을 주면, 죽기 직전 남긴 말 —
|
|
789
|
+
* 이 머리말이 「제일 중요한 몇 줄」 이라 부르는 그것 — 을 한 번도 안 보여 주고 버렸다.
|
|
790
|
+
* 지우고 나면 다시 읽을 길도 없다.
|
|
791
|
+
*/
|
|
792
|
+
if (j.상태 !== '도는중') {
|
|
793
|
+
const 남은 = j.읽기({});
|
|
794
|
+
일감들.delete(j.번호);
|
|
795
|
+
return { 이미: true, 명령: j.명령, 남은: 남은.글, 종료코드: j.종료코드, 시그널: j.시그널 };
|
|
796
|
+
}
|
|
635
797
|
j.상태 = '죽임';
|
|
636
798
|
/*
|
|
637
799
|
* 사람이 시킨 자리라 넉넉히 기다린다.
|
|
@@ -641,8 +803,10 @@ export async function 끝내기(번호) {
|
|
|
641
803
|
* 안 죽은 서버 하나가 훨씬 비싸다.
|
|
642
804
|
*/
|
|
643
805
|
const 넉넉히 = 6000;
|
|
644
|
-
|
|
645
|
-
|
|
806
|
+
// 여기는 async 라 **진짜로 기다릴 수 있다** (무리끊기기다려 머리말). 동기로
|
|
807
|
+
// 기다리면 유닉스에서는 죽은 자식이 거둬지지 않아 이 6초를 매번 다 쓴다.
|
|
808
|
+
await 나무죽이기기다려(j.kid, { 파이프끊기: false, 상한: 넉넉히 });
|
|
809
|
+
if (!j.닫힘) await 죽는말기다리기(j.kid, 죽는말기다림);
|
|
646
810
|
/*
|
|
647
811
|
* 아직 살아 있으면 여기서 끊는다.
|
|
648
812
|
*
|
|
@@ -656,8 +820,21 @@ export async function 끝내기(번호) {
|
|
|
656
820
|
* 윈도우에서는 아무 뜻이 없었다는 말이다. 여기는 마지막 자리라 뿌리라도
|
|
657
821
|
* 죽여야 한다. (모두끝내기 는 이미 그렇게 하고 있었다.)
|
|
658
822
|
*/
|
|
659
|
-
|
|
660
|
-
|
|
823
|
+
/*
|
|
824
|
+
* ── 셸은 죽었는데 **파이프가 안 닫힌** 판도 안 끝난 것이다 (사냥5 M2) ──────────
|
|
825
|
+
*
|
|
826
|
+
* 여기는 `아직사나(j.kid)` — 셸의 exitCode — 만 봤다. `node gc.cjs & echo done` 처럼
|
|
827
|
+
* 셸이 뒤로 띄운 손자가 출력을 물고 있으면, 셸은 이미 끝나 있어 taskkill /t 가 나무를
|
|
828
|
+
* 못 찾고(「그런 프로세스 없음」 = 이미 죽음으로 친다), 아래 죽었나 도 참이 되어
|
|
829
|
+
* 「끝냈습니다」 가 나갔다. 손자는 그대로 돌았다.
|
|
830
|
+
*
|
|
831
|
+
* 그래서 「끝났나」 를 'close'(닫힘)로 본다. 셸이 살아 있으면 예전처럼 곧장 끊고, 셸이
|
|
832
|
+
* 이미 없으면 남은 것을 끊으러 간다 — 유닉스는 무리가 우두머리 없이도 남아 있어 무리째
|
|
833
|
+
* 끊을 수 있다. 윈도우는 뿌리가 사라지면 그 아래를 가리킬 길이 없다(남은무리끊기 머리말).
|
|
834
|
+
*/
|
|
835
|
+
if (!j.닫힘) {
|
|
836
|
+
if (아직사나(j.kid)) 나무죽이기(j.kid, { 파이프끊기: false, 곧장: true, 상한: 넉넉히, 뿌리살리기: false });
|
|
837
|
+
else 남은무리끊기(j.kid);
|
|
661
838
|
/*
|
|
662
839
|
* 죽인 **직후에 살았나 물으면 안 된다.**
|
|
663
840
|
*
|
|
@@ -669,7 +846,11 @@ export async function 끝내기(번호) {
|
|
|
669
846
|
* 전체에서만 빨개졌다 — 컴퓨터가 바쁘면 관찰이 늦기 때문이다.
|
|
670
847
|
* 그러니 한 번 더 기다렸다가 묻는다.
|
|
671
848
|
*/
|
|
672
|
-
await 죽는말기다리기(j.kid, 죽는말기다림);
|
|
849
|
+
if (!j.닫힘) await 죽는말기다리기(j.kid, 죽는말기다림);
|
|
850
|
+
// 셸은 끝났는데 아직 안 닫혔으면 바쁜 컴퓨터가 파이프를 비울 틈을 한 번 더 준다.
|
|
851
|
+
// 1.5초로는 모자랐다 — 검사 여덟 개를 한꺼번에 돌린 판에서 멀쩡히 죽은 자식을 「안 끝났습니다」
|
|
852
|
+
// 로 적었다. 이 기다림은 진짜로 손자가 파이프를 문 판에서만 끝까지 쓰인다.
|
|
853
|
+
if (!j.닫힘 && !아직사나(j.kid)) await 죽는말기다리기(j.kid, 3000);
|
|
673
854
|
}
|
|
674
855
|
/*
|
|
675
856
|
* ── 안 죽은 놈의 파이프는 **끊지 않는다** ──────────────────────────────
|
|
@@ -680,7 +861,9 @@ export async function 끝내기(번호) {
|
|
|
680
861
|
* 사람은 화면을 보며 기다리고, 자식은 다음 쓰기에서 EPIPE 를 맞는다.
|
|
681
862
|
* 남겨 두기로 한 까닭이 「다시 볼 수 있게」 인데 정작 볼 길을 끊던 셈이다.
|
|
682
863
|
*/
|
|
683
|
-
|
|
864
|
+
// 셸의 종료만이 아니라 파이프가 닫혀야 끝난 것이다 (위 「파이프가 안 닫힌 판」).
|
|
865
|
+
// 흉내 자식(검사)처럼 close 를 못 내는 것도 있어, 셸이 살아 있으면 여전히 안 끝난 것으로 본다.
|
|
866
|
+
const 죽었나 = j.닫힘 === true || (!아직사나(j.kid) && !j.kid?.stdout?.readable && !j.kid?.stderr?.readable);
|
|
684
867
|
if (죽었나) 파이프끊기(j.kid);
|
|
685
868
|
const 남은 = j.읽기({});
|
|
686
869
|
/*
|
|
@@ -695,7 +878,8 @@ export async function 끝내기(번호) {
|
|
|
695
878
|
*/
|
|
696
879
|
if (!죽었나) {
|
|
697
880
|
j.상태 = '도는중'; // 지우지 않는다 — 다시 시킬 수 있어야 한다
|
|
698
|
-
|
|
881
|
+
// 셸은 끝났는데 남은 것이 문 판이면 그렇다고 따로 말한다 — 할 수 있는 일이 다르다.
|
|
882
|
+
return { 이미: false, 안죽음: true, 뒤에남음: !아직사나(j.kid), 명령: j.명령, 초: j.산햇수(), 남은: 남은.글 };
|
|
699
883
|
}
|
|
700
884
|
일감들.delete(j.번호);
|
|
701
885
|
return { 이미: false, 명령: j.명령, 초: j.산햇수(), 남은: 남은.글 };
|
|
@@ -709,6 +893,9 @@ export async function 끝내기(번호) {
|
|
|
709
893
|
*/
|
|
710
894
|
export function 모두끝내기() {
|
|
711
895
|
const n = [...일감들.values()].filter((j) => j.상태 === '도는중').length;
|
|
896
|
+
// 갓 띄운 것이 있으면 제일 어린 것이 나무설틈 만큼 살 때까지 한 번만 기다린다 — 끝나는 길이라 동기로.
|
|
897
|
+
const 제일갓 = Math.max(0, ...[...일감들.values()].filter((j) => j.상태 === '도는중').map((j) => 나무설틈 - (Date.now() - j.띄운때)));
|
|
898
|
+
if (제일갓 > 0) 동기로쉬기(제일갓);
|
|
712
899
|
for (const j of 일감들.values()) {
|
|
713
900
|
if (j.상태 !== '도는중') continue;
|
|
714
901
|
j.상태 = '죽임';
|
|
@@ -763,24 +950,73 @@ const 인자이름 = {
|
|
|
763
950
|
};
|
|
764
951
|
const 아는이름 = new Set(Object.values(인자이름).flat());
|
|
765
952
|
|
|
953
|
+
/*
|
|
954
|
+
* ── 참·거짓을 **글자로** 보내는 모델 ───────────────────────────────────
|
|
955
|
+
*
|
|
956
|
+
* 규격에는 boolean 이라고 적어 뒀지만, 여기 오는 것은 모델이 지어낸 JSON 이다.
|
|
957
|
+
* 작은 로컬 모델은 `"stop": "false"` · `"no"` · `"0"` 처럼 글자로 보낸다.
|
|
958
|
+
* `!!'false'` 는 **참**이다 — 끄지 말라고 적어 보낸 부름이 도는 서버를 껐다.
|
|
959
|
+
* 위 머리말이 없애겠다고 적어 둔 「시킨 적 없는 일을 조용히 하는」 고장이,
|
|
960
|
+
* 이름(`action`)이 아니라 **값** 쪽에 그대로 남아 있던 자리다.
|
|
961
|
+
*
|
|
962
|
+
* 그래서 아는 꼴은 뜻대로 읽고, 모르는 꼴(`"아마도"`)은 **거짓으로 두고 말한다.**
|
|
963
|
+
* 모르는 값으로 끄는 쪽이 훨씬 나쁘다 — 끈 것은 못 되돌리고, 안 끈 것은
|
|
964
|
+
* 다음 걸음에서 고쳐 부르면 된다.
|
|
965
|
+
*/
|
|
966
|
+
const 참인말 = new Set(['true', '1', 'yes', 'y', 'on', '참', '예', '네']);
|
|
967
|
+
const 거짓인말 = new Set(['false', '0', 'no', 'n', 'off', 'none', 'null', '거짓', '아니오', '아니요', '']);
|
|
968
|
+
|
|
969
|
+
/** @returns {{값: boolean, 모름?: string}} */
|
|
970
|
+
function 참거짓(v) {
|
|
971
|
+
if (v == null) return { 값: false };
|
|
972
|
+
if (typeof v === 'boolean') return { 값: v };
|
|
973
|
+
// 숫자는 0 만 거짓이다. NaN 은 「무엇을 보냈는지」 를 적어 주는 쪽으로 보낸다.
|
|
974
|
+
if (typeof v === 'number') return Number.isFinite(v) ? { 값: v !== 0 } : { 값: false, 모름: String(v) };
|
|
975
|
+
const s = String(v).trim().toLowerCase();
|
|
976
|
+
if (참인말.has(s)) return { 값: true };
|
|
977
|
+
if (거짓인말.has(s)) return { 값: false };
|
|
978
|
+
return { 값: false, 모름: String(v) };
|
|
979
|
+
}
|
|
980
|
+
|
|
766
981
|
/**
|
|
767
982
|
* 준 인자에서 아는 것만 골라낸다.
|
|
768
983
|
*
|
|
769
|
-
*
|
|
984
|
+
* `못읽은값` 은 `[받은 칸 이름, 받은 값]` 들이다 — 참·거짓 자리에 참도 거짓도
|
|
985
|
+
* 아닌 것이 왔을 때. 부르는 쪽이 **그 칸 이름 그대로** 적어 줘야 모델이 무엇을
|
|
986
|
+
* 고쳐 부를지 안다(`kill` 로 보냈는데 `stop` 을 받았다고 적으면 못 고친다).
|
|
987
|
+
*
|
|
988
|
+
* @returns {{날번호: any, 번호: number|null, 끝내기: boolean, 처음부터: boolean,
|
|
989
|
+
* 못읽은값: [string, string][], 준것: string[], 모르는것: string[]}}
|
|
770
990
|
*/
|
|
771
991
|
export function 일감인자(args) {
|
|
772
992
|
const 준것 = Object.keys(args ?? {});
|
|
773
993
|
const 하나씩 = (이름) => {
|
|
774
|
-
for (const n of 인자이름[이름]) if (args?.[n] != null) return args[n];
|
|
775
|
-
return null;
|
|
994
|
+
for (const n of 인자이름[이름]) if (args?.[n] != null) return { 칸: n, 값: args[n] };
|
|
995
|
+
return { 칸: null, 값: null };
|
|
996
|
+
};
|
|
997
|
+
const 날번호 = 하나씩('번호').값;
|
|
998
|
+
const 못읽은값 = [];
|
|
999
|
+
const 참거짓칸 = (이름) => {
|
|
1000
|
+
const { 칸, 값 } = 하나씩(이름);
|
|
1001
|
+
const r = 참거짓(값);
|
|
1002
|
+
if (r.모름 !== undefined) 못읽은값.push([칸 ?? 이름, r.모름]);
|
|
1003
|
+
return r.값;
|
|
776
1004
|
};
|
|
777
|
-
const 날번호 = 하나씩('번호');
|
|
778
1005
|
return {
|
|
779
1006
|
// 날것을 같이 내놓는다 — 숫자가 아닐 때 **무엇을 받았는지** 적어야 한다.
|
|
780
1007
|
날번호,
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
1008
|
+
/*
|
|
1009
|
+
* 숫자도 숫자 글자도 아닌 것은 **숫자로 안 읽는다.** 날 `Number()` 였을 때
|
|
1010
|
+
* 자바스크립트가 `true` 를 1 로, `[]` 를 0 으로 조용히 바꿔서 `Jobs({job:true,
|
|
1011
|
+
* stop:true})` 가 1번 일감을 끊었다 — 모델이 가리킨 적도 없는 서버를. 위 참거짓()
|
|
1012
|
+
* 이 stop·처음부터 에 이미 세워 둔 문턱이고, 번호 칸만 그 밖에 있었다. NaN 으로
|
|
1013
|
+
* 두면 아래 「번호는 숫자여야 합니다 (받은 것: …)」 갈래가 받아 준다.
|
|
1014
|
+
*/
|
|
1015
|
+
번호: 날번호 == null ? null
|
|
1016
|
+
: (typeof 날번호 === 'number' || typeof 날번호 === 'string' ? Number(날번호) : NaN),
|
|
1017
|
+
끝내기: 참거짓칸('끝내기'),
|
|
1018
|
+
처음부터: 참거짓칸('처음부터'),
|
|
1019
|
+
못읽은값,
|
|
784
1020
|
준것,
|
|
785
1021
|
모르는것: 준것.filter((k) => !아는이름.has(k)),
|
|
786
1022
|
};
|
|
@@ -862,6 +1098,17 @@ export const JOBS_TOOL = {
|
|
|
862
1098
|
? `\n(못 알아들어 버린 인자: ${받은것.모르는것.join(', ')} — Jobs 는 job · stop · from_start 만 받습니다.`
|
|
863
1099
|
+ ' 끝내려던 것이면 stop: true 로 다시 불러라.)'
|
|
864
1100
|
: '';
|
|
1101
|
+
/*
|
|
1102
|
+
* 이름은 아는데 **값**이 참도 거짓도 아닌 판. 같은 자세로 말해 준다.
|
|
1103
|
+
*
|
|
1104
|
+
* 끄지는 않는다(일감인자 머리말) — 모르는 값으로 끈 것은 못 되돌리고,
|
|
1105
|
+
* 안 끈 것은 다음 걸음에서 고쳐 부르면 된다. 대신 받은 칸 이름과 받은
|
|
1106
|
+
* 값을 그대로 적어, 뜻이 거기 있었으면 고쳐 부르게 한다.
|
|
1107
|
+
*/
|
|
1108
|
+
const 못읽은말 = 받은것.못읽은값.length
|
|
1109
|
+
? `\n(참·거짓으로 못 읽은 값: ${받은것.못읽은값.map(([칸, 값]) => `${칸}=${JSON.stringify(값)}`).join(', ')}`
|
|
1110
|
+
+ ' — true·false 로 보내세요. 끝내려던 것이면 stop: true 로 다시 불러라.)'
|
|
1111
|
+
: '';
|
|
865
1112
|
const 번호 = 받은것.번호;
|
|
866
1113
|
// 숫자가 아닌 것을 받으면 NaN 이 되고, 그대로 두면 `NaN번 일감이 없습니다`
|
|
867
1114
|
// 라는 말이 모델에게 나간다. 무엇이 잘못됐는지 안 알려 주는 오류다.
|
|
@@ -894,9 +1141,20 @@ export const JOBS_TOOL = {
|
|
|
894
1141
|
};
|
|
895
1142
|
}
|
|
896
1143
|
const ls = 목록();
|
|
897
|
-
|
|
1144
|
+
/*
|
|
1145
|
+
* 목록이 비어도 **하던 말은 한다.**
|
|
1146
|
+
*
|
|
1147
|
+
* 여기가 그 자리에서 곧장 돌아서느라 버린말·못읽은말 이 통째로 빠졌다.
|
|
1148
|
+
* 위 두 머리말이 「몇 개만 못 알아들은 판도 말해 준다」 고 적어 두었는데,
|
|
1149
|
+
* 하필 **일감이 하나도 없을 때**만 그 약속이 안 지켜졌다. 일감이 없다는
|
|
1150
|
+
* 것은 대개 「끄려던 것이 이미 없다」 거나 「아직 안 띄웠다」 인데, 그때
|
|
1151
|
+
* 오타를 안 알려 주면 모델은 제 인자가 맞는 줄 알고 그대로 다시 부른다.
|
|
1152
|
+
*/
|
|
1153
|
+
if (!ls.length) return { content: '뒤에서 도는 명령이 없습니다.' + 버린말 + 못읽은말, summary: 세말('count', 0) };
|
|
898
1154
|
const 줄들 = ls.map((j) => {
|
|
899
|
-
const 상 = j.상태 === '도는중'
|
|
1155
|
+
const 상 = j.상태 === '도는중'
|
|
1156
|
+
? `도는중 ${j.초}초${j.셸끝남 ? ' (셸은 끝남 — 셸이 뒤로 띄운 것이 아직 출력을 물고 돕니다)' : ''}`
|
|
1157
|
+
: j.시그널 ? `${j.시그널} 로 죽음` : `끝남 (종료코드 ${j.종료코드})`;
|
|
900
1158
|
const 새것 = j.안읽은글자 ? ` · 안 읽은 출력 ${j.안읽은글자}자` : '';
|
|
901
1159
|
return ` ${j.번호}. ${j.명령} — ${상}${새것}`;
|
|
902
1160
|
});
|
|
@@ -908,13 +1166,20 @@ export const JOBS_TOOL = {
|
|
|
908
1166
|
* 그러면 포트가 물려서 안 뜨고, 왜 안 되는지도 모른다.
|
|
909
1167
|
*/
|
|
910
1168
|
if (치운것) 줄들.push(` (오래된 것 ${치운것}개는 자리를 위해 지웠습니다)`);
|
|
911
|
-
return { content: 줄들.join('\n') +
|
|
1169
|
+
return { content: 줄들.join('\n') + 버린말 + 못읽은말, summary: 세말('count', ls.length), 일감수: ls.length };
|
|
912
1170
|
}
|
|
913
1171
|
|
|
914
1172
|
if (받은것.끝내기) {
|
|
915
1173
|
const r = await 끝내기(번호);
|
|
916
1174
|
if (!r) return { error: `${번호}번 일감이 없습니다. 번호 없이 Jobs 를 불러 목록을 보세요.` };
|
|
917
|
-
if (r.이미)
|
|
1175
|
+
if (r.이미) {
|
|
1176
|
+
// 안 읽은 마지막 출력을 같이 준다 — 지우고 나면 다시 읽을 길이 없다 (끝내기 머리말 · 사냥5 L2).
|
|
1177
|
+
return {
|
|
1178
|
+
content: `${번호}번은 이미 끝나 있었습니다 (${끝난말(r)}): ${r.명령}`
|
|
1179
|
+
+ (r.남은 ? `\n\n안 읽은 마지막 출력:\n${뒤만(r.남은, 2000).글}` : '') + 버린말 + 못읽은말,
|
|
1180
|
+
summary: 말('sum.jobEndedAlready'),
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
918
1183
|
/*
|
|
919
1184
|
* ★ 안 죽었으면 「끝냈습니다」 라고 하지 않는다.
|
|
920
1185
|
*
|
|
@@ -923,16 +1188,26 @@ export const JOBS_TOOL = {
|
|
|
923
1188
|
* 남겨 뒀으니 한 번 더 시킬 수 있다고 알려 준다.
|
|
924
1189
|
*/
|
|
925
1190
|
if (r.안죽음) {
|
|
1191
|
+
/*
|
|
1192
|
+
* 셸은 끝났는데 셸이 뒤로 띄운 것이 남은 판은 따로 말한다 (사냥5 M2). 한 번 더 시켜도
|
|
1193
|
+
* 윈도우에서는 같은 결과라, 「다시 시켜 보세요」 만 적으면 헛걸음을 시킨다.
|
|
1194
|
+
*/
|
|
1195
|
+
const 뒤에남은말 = !r.뒤에남음 ? '\n죽이라고 시켰는데 아직 살아 있습니다. 목록에 그대로 두었으니 한 번 더 시켜 보세요.'
|
|
1196
|
+
: process.platform === 'win32'
|
|
1197
|
+
? '\n셸은 끝났지만 셸이 뒤로 띄운 프로세스가 아직 출력을 물고 돕니다. 윈도우에서는 셸이 먼저 끝나면'
|
|
1198
|
+
+ ' 그 아래를 찾아 끌 길이 없습니다 — 작업 관리자에서 찾아 끄세요. 출력은 계속 읽을 수 있게 목록에 남겨 두었습니다.'
|
|
1199
|
+
: '\n셸은 끝났지만 셸이 뒤로 띄운 프로세스가 아직 출력을 물고 돕니다. 무리째 끊으라고 했는데 아직 살아 있습니다.'
|
|
1200
|
+
+ ' 목록에 그대로 두었으니 한 번 더 시켜 보세요.';
|
|
926
1201
|
return {
|
|
927
1202
|
error: `${번호}번이 안 끝났습니다 (${r.초}초 돌았습니다): ${r.명령}`
|
|
928
|
-
+
|
|
1203
|
+
+ 뒤에남은말
|
|
929
1204
|
+ (r.남은 ? `\n\n마지막 출력:\n${뒤만(r.남은, 2000).글}` : ''),
|
|
930
1205
|
summary: 말('sum.jobNotStopped'),
|
|
931
1206
|
};
|
|
932
1207
|
}
|
|
933
1208
|
return {
|
|
934
1209
|
content: `${번호}번을 끝냈습니다 (${r.초}초 돌았습니다): ${r.명령}`
|
|
935
|
-
+ (r.남은 ? `\n\n마지막 출력:\n${뒤만(r.남은, 2000).글}` : '') +
|
|
1210
|
+
+ (r.남은 ? `\n\n마지막 출력:\n${뒤만(r.남은, 2000).글}` : '') + 버린말 + 못읽은말,
|
|
936
1211
|
summary: 이어(말('sum.jobStopped'), 말('unit.sec', { n: r.초 })),
|
|
937
1212
|
};
|
|
938
1213
|
}
|
|
@@ -948,7 +1223,7 @@ export const JOBS_TOOL = {
|
|
|
948
1223
|
? (r.앞잘림 ? '(앞부분은 너무 길어 잘렸습니다 — 뒤쪽만 남았습니다)\n' : '') + 잘라낸.글
|
|
949
1224
|
: '(지난번 읽은 뒤로 새 출력이 없습니다)';
|
|
950
1225
|
return {
|
|
951
|
-
content: `${머리}\n\n${몸}${버린말}`,
|
|
1226
|
+
content: `${머리}\n\n${몸}${버린말}${못읽은말}`,
|
|
952
1227
|
summary: (r.상태 === '도는중' ? 이어(말('sum.jobRunning'), 말('unit.sec', { n: r.초 })) : 끝난말(r))
|
|
953
1228
|
+ (잘라낸.줄임 ? ' · 일부만' : ''),
|
|
954
1229
|
// 끝난 일감이 종료코드 0 이 아니면 실패로 물들인다 — Bash 와 같은 규칙이다.
|