deel-local-cli 1.15.1 → 1.17.1
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 +118 -17
- package/README.md +119 -16
- package/bin/deel.js +430 -6
- package/package.json +1 -1
- package/src/acp/serve.js +39 -1
- package/src/agent/agents.js +203 -0
- package/src/agent/asks.js +58 -2
- package/src/agent/budget.js +33 -0
- package/src/agent/effort.js +69 -0
- package/src/agent/loop.js +219 -19
- package/src/agent/models.js +4 -0
- package/src/agent/modes.js +179 -17
- package/src/agent/outschema.js +351 -0
- package/src/agent/route.js +119 -8
- package/src/agent/session.js +13 -2
- package/src/backend/adapter.js +94 -7
- package/src/backend/clientcert.js +164 -0
- package/src/backend/http.js +232 -26
- package/src/backend/mcp.js +194 -30
- package/src/backend/wire.js +10 -0
- package/src/cmdnames.js +3 -0
- package/src/commands.js +158 -15
- package/src/completion.js +35 -4
- package/src/config.js +86 -9
- package/src/configexplain.js +177 -0
- package/src/doctor.js +195 -0
- package/src/i18n/en.js +50 -0
- package/src/i18n/ja.js +50 -0
- package/src/i18n/ko.js +50 -0
- package/src/i18n/zh.js +50 -0
- package/src/lsp/client.js +2 -2
- package/src/oneshot.js +181 -4
- package/src/pack/sbom.js +77 -3
- package/src/pack/selfpack.js +1 -1
- package/src/pack/sheet.en.js +1 -1
- package/src/repl.js +74 -3
- package/src/safety/hooks.js +389 -0
- package/src/safety/policy.js +74 -0
- package/src/safety/shellenv.js +116 -0
- package/src/safety/trust.js +230 -0
- package/src/setup.js +9 -0
- package/src/stats.js +162 -0
- package/src/tools/doc2md.js +266 -0
- package/src/tools/docs.js +26 -6
- package/src/tools/fig.js +393 -0
- package/src/tools/hwpxwrite.js +268 -0
- package/src/tools/index.js +176 -16
- package/src/tools/jobs.js +11 -6
- package/src/tools/kiwi.js +293 -0
- package/src/tools/spawn.js +19 -1
- package/src/tools/task.js +8 -2
package/src/agent/loop.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// 에이전트 루프. 모델 → 도구 → 결과 → 모델 을 답이 나올 때까지 돈다.
|
|
2
2
|
// 화면에 그릴 것은 이벤트로 흘려보낸다 — 화면 코드와 섞지 않는다.
|
|
3
|
-
import { chat, chatStream, assistantMessage, toolMessage, 말없이끝남, 보낸토큰, 부른것들, 도구결과인가 } from '../backend/adapter.js';
|
|
3
|
+
import { chat, chatStream, assistantMessage, toolMessage, 말없이끝남, 흐름멎음, 보낸토큰, 부른것들, 도구결과인가 } from '../backend/adapter.js';
|
|
4
4
|
import { 그림메시지 } from '../backend/vision.js';
|
|
5
5
|
import { 어떻게할까 } from '../safety/policy.js';
|
|
6
|
+
import { 자리돌리기, 막힘말 } from '../safety/hooks.js';
|
|
6
7
|
import { toolSchemas, runTool, TOOLS, 파일현황 } from '../tools/index.js';
|
|
7
8
|
import { isMutating } from '../safety/guard.js';
|
|
8
|
-
import { effortFor, tokensFor, fullCap, wasCut, shiftLevel, 자동강도, 인사인가 as 인사말인가 } from './effort.js';
|
|
9
|
+
import { effortFor, tokensFor, fullCap, wasCut, shiftLevel, 자동강도, 천장고르기, 인사인가 as 인사말인가 } from './effort.js';
|
|
9
10
|
import { 배울전선, 카드고치기, 카드저장꼴, 전선붙이기 } from '../backend/wire.js';
|
|
10
11
|
import { 살린쓰기 } from './salvage.js';
|
|
11
12
|
import { 배울것, 길이문제인가 } from '../backend/learn.js';
|
|
@@ -13,6 +14,7 @@ import { compact, shouldCompact, shouldFold, foldToolResults, foldImages, 못박
|
|
|
13
14
|
import { 걸음수, 하위걸음수, 요약길이 } from './budget.js';
|
|
14
15
|
import { Session } from './session.js';
|
|
15
16
|
import { 최대깊이, 하위모드, 하위요약 } from '../tools/task.js';
|
|
17
|
+
import { 찾기 as 에이전트찾기, 할일합치기, 도구줄이기 } from './agents.js';
|
|
16
18
|
import { 프로필찾기, 쓸수있나, 연결만들기, 알릴말, 목록보기 } from './models.js';
|
|
17
19
|
import { allowTemporarily, isOffline } from '../safety/network.js';
|
|
18
20
|
import { 가리기, 훑기, 가렸다는말, 봤다는말, 가릴까 } from '../safety/secrets.js';
|
|
@@ -124,7 +126,20 @@ export function 묶기(calls) {
|
|
|
124
126
|
return out;
|
|
125
127
|
}
|
|
126
128
|
|
|
127
|
-
|
|
129
|
+
/*
|
|
130
|
+
* `고쳐쓰기` — 방금 낸 답을 **모양에 맞춰 다시 적으라고** 시키는 턴.
|
|
131
|
+
*
|
|
132
|
+
* 새 일이 아니라 같은 일의 뒷정리라, 사람 말로 시작한 턴과 세 가지가 다르다.
|
|
133
|
+
* 되돌리기 턴을 새로 열지 않고(같은 턴이다), 되밀기를 안 걸고, 기록에도
|
|
134
|
+
* 사람이 새로 시킨 말이 아니라고 적는다.
|
|
135
|
+
*
|
|
136
|
+
* 되밀기를 꼭 꺼야 하는 까닭:
|
|
137
|
+
* 되밀기는 「시킨 일을 안 하고 끝냈나」 를 본다. 그런데 여기서 시킨 말은
|
|
138
|
+
* 스키마 설명과 안 맞은 자리 목록이라, 그 안의 낱말들이 「안 한 일」 로
|
|
139
|
+
* 잡힌다. 그러면 한 번 더 시키려고 부른 자리에서 모델을 **두 번** 부르고,
|
|
140
|
+
* 그 왕복은 사람이 낸다. 실제로 그랬다 — 되묻기 한 번에 부름이 셋이었다.
|
|
141
|
+
*/
|
|
142
|
+
export async function* run(session, ctx, userText, { signal = null, 깊이 = 0, 그림들 = null, 끼어들기 = null, 고쳐쓰기 = false } = {}) {
|
|
128
143
|
/*
|
|
129
144
|
* 되돌리기 턴은 **부모만** 연다.
|
|
130
145
|
*
|
|
@@ -136,12 +151,37 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
136
151
|
* 표시되고, 되감을 때 시킨 말까지 같이 걷힌다. 시킨 말만 남으면 모델은
|
|
137
152
|
* 되돌린 일을 또 하려 든다.
|
|
138
153
|
*/
|
|
139
|
-
if (!깊이) session.턴시작(ctx.history.nextTurn());
|
|
154
|
+
if (!깊이 && !고쳐쓰기) session.턴시작(ctx.history.nextTurn());
|
|
155
|
+
|
|
156
|
+
/*
|
|
157
|
+
* ── 사람 말이 나가기 전에 (safety/hooks.js) ─────────────────────────
|
|
158
|
+
*
|
|
159
|
+
* **밀어 넣기 전에** 돈다. 넣고 나서 막으면 막힌 말이 대화에 그대로 남고,
|
|
160
|
+
* 다음 요청에 실려 나간다 — 막은 것이 아니라 늦춘 것뿐이다. 사내 DLP 훅을
|
|
161
|
+
* 여기 거는 사람이 원하는 것은 정확히 그 반대다.
|
|
162
|
+
*
|
|
163
|
+
* 하위 작업(깊이>0)에서는 안 돈다. 하위가 받은 할일은 사람이 친 말이
|
|
164
|
+
* 아니라 부모 모델이 쓴 글이고, 그 글의 재료가 된 사람 말은 이미 여기를
|
|
165
|
+
* 한 번 지나왔다. 다시 돌리면 같은 말을 두 번 재는 셈이다.
|
|
166
|
+
*/
|
|
167
|
+
if (!깊이 && ctx.훅들?.length) {
|
|
168
|
+
const 훅 = await 자리돌리기(ctx.훅들, '말전', {
|
|
169
|
+
넣을것: { 말: String(userText ?? '') }, signal, audit: ctx.audit,
|
|
170
|
+
});
|
|
171
|
+
if (훅.막힘) {
|
|
172
|
+
ctx.audit.blocked('말전 훅이 막음', 훅.막힘.훅.명령);
|
|
173
|
+
yield { type: 'hook_block', 자리: '말전', 말: 막힘말(훅.막힘), 훅: 훅.막힘.훅 };
|
|
174
|
+
yield { type: 'done', steps: 0, text: 막힘말(훅.막힘), files: [], 빠진: [], 훅막힘: true };
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
for (const 말 of 훅.말들) yield { type: 'hook_note', 자리: '말전', 말 };
|
|
178
|
+
}
|
|
179
|
+
|
|
140
180
|
// @ 로 그림을 지목했으면 그 말과 함께 실어 보낸다 (backend/vision.js).
|
|
141
181
|
session.push(그림들?.length
|
|
142
182
|
? 그림메시지(session.conn?.kind, { 글: userText, 그림들 })
|
|
143
183
|
: { role: 'user', content: userText });
|
|
144
|
-
ctx.audit.turn(깊이 ? `[하위작업 ${깊이}겹] ${userText}` : userText);
|
|
184
|
+
ctx.audit.turn(고쳐쓰기 ? `[다시 쓰기] ${userText}` : 깊이 ? `[하위작업 ${깊이}겹] ${userText}` : userText);
|
|
145
185
|
|
|
146
186
|
/*
|
|
147
187
|
* 중단 신호를 도구도 볼 수 있게 여기 걸어 둔다.
|
|
@@ -365,6 +405,8 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
365
405
|
work: session.effectiveWork(), // 작업 모드가 쓰는 것만 (modes.js)
|
|
366
406
|
// 밖에서 붙인 도구(MCP). 붙은 것이 없으면 아무것도 안 는다.
|
|
367
407
|
mcp: ctx.mcp ?? null,
|
|
408
|
+
// 이름 붙인 하위 작업 (agent/agents.js). 이름과 한 줄 설명만 실린다.
|
|
409
|
+
에이전트들: 깊이 + 1 >= 최대깊이 ? null : (ctx.에이전트들 ?? null),
|
|
368
410
|
// 이 자리에 언어 서버가 있을 때만 Def·Refs 를 보여 준다 (tools/lsp.js).
|
|
369
411
|
// 없는 자리에서 목록에 세워 두면 모델이 부르고, 실패를 받고, 또 부른다.
|
|
370
412
|
lsp: session.lsp === true,
|
|
@@ -407,7 +449,15 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
407
449
|
* 한 번 정하면 이 턴 안에서는 안 바꾼다. while 바깥에서 정하는 것이 그
|
|
408
450
|
* 뜻이다 — 걸음마다 흔들리면 그것이 곧 캐시가 매번 새로 엮인다는 말이다.
|
|
409
451
|
*/
|
|
410
|
-
|
|
452
|
+
/*
|
|
453
|
+
* 모드가 정한 강도는 **가벼운 말에만** 낮춘다 (effort.js 의 천장고르기).
|
|
454
|
+
*
|
|
455
|
+
* 묻기 모드는 low 다. "이 함수 뭐야?" 에는 맞는 값이다. 그런데 무엇이
|
|
456
|
+
* 묻기로 가는지는 낱말이 정하고(route.js), "…설명해줘" 한 마디면 간다.
|
|
457
|
+
* 그래서 파일 열 개를 읽고 경합 조건을 짚어야 하는 일까지 low 로 돌았다 —
|
|
458
|
+
* 생각 블록이 0자로 나가고, 답이 그만큼 얕았다.
|
|
459
|
+
*/
|
|
460
|
+
const 천장 = session.thinkSet ? session.think : 천장고르기(모드.think, session.think, userText);
|
|
411
461
|
const think = 자동강도(userText, 천장, {
|
|
412
462
|
대화크기: session.messages.length,
|
|
413
463
|
켜짐: session.autoThink !== false,
|
|
@@ -488,7 +538,7 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
488
538
|
|
|
489
539
|
/** 이번 답을 되밀까. 밀 이유를 돌려주고, 아니면 null. */
|
|
490
540
|
const 밀어줄까 = (글) => {
|
|
491
|
-
if (민적 || 깊이) return null;
|
|
541
|
+
if (민적 || 깊이 || 고쳐쓰기) return null;
|
|
492
542
|
if (인사인가) return null; // 시킨 것이 없으면 안 한 것도 없다
|
|
493
543
|
if (손댄파일.size) return null; // 뭐라도 바꿨으면 일은 한 것이다
|
|
494
544
|
if (!모드.tools.includes('Write')) return null; // 안 바꾸는 모드는 그게 맞다
|
|
@@ -517,7 +567,7 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
517
567
|
let 빠뜨림민적 = false;
|
|
518
568
|
const 빠뜨린것 = (글) => {
|
|
519
569
|
// 하위 작업은 사람이 시킨 말이 아니다. 거기 할일은 부모가 이미 쪼개 준 것이다.
|
|
520
|
-
if (깊이) return [];
|
|
570
|
+
if (깊이 || 고쳐쓰기) return [];
|
|
521
571
|
return 빠진것({
|
|
522
572
|
요청: userText,
|
|
523
573
|
자국: [
|
|
@@ -802,14 +852,18 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
802
852
|
cap: 마지막상한,
|
|
803
853
|
정한값: conn.maxTokens ?? conn.maxOut ?? null,
|
|
804
854
|
};
|
|
805
|
-
} else if (msg?.stopped === 말없이끝남) {
|
|
855
|
+
} else if (msg?.stopped === 말없이끝남 || msg?.stopped === 흐름멎음) {
|
|
806
856
|
/*
|
|
807
857
|
* 상한 때문에 잘린 것과는 다른 사고다. 서버가 끝난 까닭을 한 번도 안
|
|
808
858
|
* 주고 흘려보내기를 멈췄다 — 중계 프록시가 몸통을 자르고 연결을 곱게
|
|
809
859
|
* 닫으면 이 모양이 된다. 상한을 올려 다시 부르는 것은 답이 아니라서
|
|
810
860
|
* capped 와 섞으면 엉뚱한 곳(/out)을 고치게 만든다.
|
|
861
|
+
*
|
|
862
|
+
* 흐름이 멎어서 **우리가** 끊은 것은 한 겹 더 말해 준다. 그때는 몇 초를
|
|
863
|
+
* 기다렸는지가 곧 손댈 자리다 — 게이트웨이가 답을 통째로 모았다가 주는
|
|
864
|
+
* 자리면 잠잠 상한을 올리면 되고, 그걸 모르면 사람은 연결을 의심한다.
|
|
811
865
|
*/
|
|
812
|
-
yield { type: 'cutoff' };
|
|
866
|
+
yield { type: 'cutoff', 멎은초: msg?.stopped === 흐름멎음 ? (msg.멎은초 ?? null) : null };
|
|
813
867
|
}
|
|
814
868
|
|
|
815
869
|
/*
|
|
@@ -1116,6 +1170,23 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
1116
1170
|
yield { type: 'nudge', why: '요청누락', 빠진, text: msg.content };
|
|
1117
1171
|
continue;
|
|
1118
1172
|
}
|
|
1173
|
+
/*
|
|
1174
|
+
* ── 턴이 끝나는 자리 (safety/hooks.js) ────────────────────────────
|
|
1175
|
+
*
|
|
1176
|
+
* 여기도 못 막는다 — 일은 이미 다 끝났다. 여기 훅을 거는 사람이 하려는
|
|
1177
|
+
* 것은 대개 뒷정리다: 검사 한 번 돌리기, 사내 알림 보내기, 바뀐 파일
|
|
1178
|
+
* 목록 적어 두기.
|
|
1179
|
+
*
|
|
1180
|
+
* 하위 작업에서는 안 돈다. 하위가 끝난 것은 사람이 시킨 일이 끝난 것이
|
|
1181
|
+
* 아니라 그 한 덩이가 끝난 것뿐인데, 여기서 돌리면 한 번 시킨 일에
|
|
1182
|
+
* 뒷정리가 세 번 돈다.
|
|
1183
|
+
*/
|
|
1184
|
+
if (!깊이 && ctx.훅들?.length) {
|
|
1185
|
+
const 훅 = await 자리돌리기(ctx.훅들, '턴끝', {
|
|
1186
|
+
넣을것: { 걸음: steps, 파일: [...손댄파일] }, signal, audit: ctx.audit,
|
|
1187
|
+
});
|
|
1188
|
+
for (const 말 of 훅.말들) yield { type: 'hook_note', 자리: '턴끝', 말 };
|
|
1189
|
+
}
|
|
1119
1190
|
// 밀고도 그대로면 조용히 넘어가지 않는다. 사람이 알아야 다음을 정한다.
|
|
1120
1191
|
yield { type: 'done', steps, text: msg.content, files: 마무리(), 빠진 };
|
|
1121
1192
|
return;
|
|
@@ -1275,6 +1346,37 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
1275
1346
|
continue;
|
|
1276
1347
|
}
|
|
1277
1348
|
|
|
1349
|
+
/*
|
|
1350
|
+
* ── 사람이 적어 둔 훅 (safety/hooks.js) ──────────────────────────
|
|
1351
|
+
*
|
|
1352
|
+
* 규칙 **다음**, 승인 **앞**이다. 자리가 여기여야 하는 이유가 둘이다.
|
|
1353
|
+
*
|
|
1354
|
+
* · 규칙으로 이미 막힌 것에 훅을 돌리면 남의 프로그램을 헛돌린다.
|
|
1355
|
+
* · 승인 앞에 두어야 사내 규칙이 사람을 안 귀찮게 하고 막는다.
|
|
1356
|
+
* 뒤에 두면 「y 를 치고 나서 막혔습니다」 가 되는데, 그건 사람에게
|
|
1357
|
+
* 제 손으로 허락한 것을 빼앗기는 것처럼 보인다.
|
|
1358
|
+
*
|
|
1359
|
+
* 승인을 훅이 대신할 수는 없다. 훅이 통과시켜도 strict 는 여전히
|
|
1360
|
+
* 묻는다 — 그래야 `허락 = 규칙 그리고 훅 그리고 사람` 이 된다.
|
|
1361
|
+
*/
|
|
1362
|
+
if (ctx.훅들?.length) {
|
|
1363
|
+
const 훅 = await 자리돌리기(ctx.훅들, '도구전', {
|
|
1364
|
+
도구: call.name, 넣을것: { 인자: call.args ?? {} }, signal, audit: ctx.audit,
|
|
1365
|
+
});
|
|
1366
|
+
if (훅.막힘) {
|
|
1367
|
+
const 까닭 = 막힘말(훅.막힘);
|
|
1368
|
+
ctx.audit?.blocked?.('도구전 훅이 막음', `${call.name} · ${훅.막힘.훅.명령}`);
|
|
1369
|
+
거절(call, 까닭);
|
|
1370
|
+
if (막힘셈(call, '훅 막음')) 멈출까 = '훅이 막은 것을 계속 다시 부르고 있습니다';
|
|
1371
|
+
yield {
|
|
1372
|
+
type: 'tool', name: call.name, args: call.args, showLabel: true,
|
|
1373
|
+
result: { error: `훅이 막았습니다 — ${훅.막힘.훅.이름 ?? 훅.막힘.훅.출처}` },
|
|
1374
|
+
};
|
|
1375
|
+
continue;
|
|
1376
|
+
}
|
|
1377
|
+
for (const 말 of 훅.말들) yield { type: 'hook_note', 자리: '도구전', 도구: call.name, 말 };
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1278
1380
|
// 모드에 따라 물어본다. 기본(auto)은 안 묻고 되돌리기로 대응한다.
|
|
1279
1381
|
const needsOk = 판정.답 === 'allow' ? false : session.mode === 'strict'
|
|
1280
1382
|
? ['Write', 'Edit', 'Bash'].includes(call.name)
|
|
@@ -1311,7 +1413,33 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
1311
1413
|
if (실행할것.length === 1 && 실행할것[0].name === 'Task') {
|
|
1312
1414
|
const call = 실행할것[0];
|
|
1313
1415
|
const 목적 = String(call.args?.purpose ?? call.args?.목적 ?? '').trim() || '이름 없는 작업';
|
|
1314
|
-
|
|
1416
|
+
let 할일 = String(call.args?.task ?? call.args?.할일 ?? '').trim();
|
|
1417
|
+
|
|
1418
|
+
/*
|
|
1419
|
+
* ── 이름 붙인 하위 작업 (agent/agents.js) ────────────────────────
|
|
1420
|
+
*
|
|
1421
|
+
* 이름을 골랐으면 그 정의가 모드·모델·도구·지침·걸음 수를 대신 정한다.
|
|
1422
|
+
* 팀에서 늘 같은 일을 시키는데 매번 다르게 적히는 것을 막는 자리다.
|
|
1423
|
+
*
|
|
1424
|
+
* 못 찾으면 **조용히 그냥 돌지 않는다.** 시킨 쪽은 「리뷰어가 본다」 고
|
|
1425
|
+
* 알고 있는데 실제로는 평범한 하위가 도는 셈이 되고, 그 어긋남은 결과를
|
|
1426
|
+
* 읽어도 안 보인다. 모델 이름을 못 찾았을 때와 같은 자세다.
|
|
1427
|
+
*/
|
|
1428
|
+
let 정의 = null;
|
|
1429
|
+
const 부른에이전트 = String(call.args?.agent ?? call.args?.에이전트 ?? '').trim();
|
|
1430
|
+
if (부른에이전트) {
|
|
1431
|
+
정의 = 에이전트찾기(ctx.에이전트들, 부른에이전트);
|
|
1432
|
+
if (!정의) {
|
|
1433
|
+
const 있는것 = (ctx.에이전트들 ?? []).map((a) => a.이름).join(' · ') || '(정의해 둔 것이 없습니다)';
|
|
1434
|
+
거절(call, `"${부른에이전트}" 라는 하위 작업 정의가 없습니다. 쓸 수 있는 이름: ${있는것}`);
|
|
1435
|
+
if (막힘셈(call, '없는 에이전트')) 멈출까 = '없는 하위 작업 이름을 계속 부르고 있습니다';
|
|
1436
|
+
yield {
|
|
1437
|
+
type: 'tool', name: 'Task', args: call.args, showLabel: true,
|
|
1438
|
+
result: { error: `"${부른에이전트}" 는 없는 하위 작업 이름입니다` },
|
|
1439
|
+
};
|
|
1440
|
+
continue;
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1315
1443
|
|
|
1316
1444
|
// 할 일이 비면 하위는 아무것도 모른 채로 시작한다. 하위는 이 대화를
|
|
1317
1445
|
// 못 보므로, 여기서 통과시키면 걸음만 태우고 빈손으로 돌아온다.
|
|
@@ -1324,7 +1452,20 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
1324
1452
|
continue;
|
|
1325
1453
|
}
|
|
1326
1454
|
|
|
1327
|
-
|
|
1455
|
+
/*
|
|
1456
|
+
* 정의의 지침은 **빈 할일 검사를 지난 뒤에** 붙인다.
|
|
1457
|
+
*
|
|
1458
|
+
* 먼저 붙이면 지침이 있는 에이전트는 할일을 비워도 그 검사를
|
|
1459
|
+
* 빠져나간다. 그러면 하위는 「어떻게」 만 받고 「무엇을」 은 모른 채로
|
|
1460
|
+
* 시작해서, 걸음만 태우고 빈손으로 돌아온다.
|
|
1461
|
+
*/
|
|
1462
|
+
if (정의) 할일 = 할일합치기(정의, 할일);
|
|
1463
|
+
|
|
1464
|
+
/*
|
|
1465
|
+
* 모드도 모델도 정의가 먼저다 — 그러라고 이름을 붙인 것이다.
|
|
1466
|
+
* 다만 부모보다 셀 수는 없다: 하위모드() 를 그대로 지난다(task.js).
|
|
1467
|
+
*/
|
|
1468
|
+
const 자식모드 = 하위모드(정의?.모드 ?? call.args?.mode ?? call.args?.모드, 모드.id);
|
|
1328
1469
|
|
|
1329
1470
|
/*
|
|
1330
1471
|
* ── 다른 모델에게 떼어 주기 ─────────────────────────────────────
|
|
@@ -1340,7 +1481,7 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
1340
1481
|
let 자식conn = conn;
|
|
1341
1482
|
let 자리닫기 = null;
|
|
1342
1483
|
let 모델알림 = null;
|
|
1343
|
-
const 부른모델 = String(call.args?.model ?? call.args?.모델 ?? '').trim();
|
|
1484
|
+
const 부른모델 = String(정의?.모델 ?? call.args?.model ?? call.args?.모델 ?? '').trim();
|
|
1344
1485
|
if (부른모델) {
|
|
1345
1486
|
const 찾음 = 프로필찾기(부른모델);
|
|
1346
1487
|
if (!찾음.ok) {
|
|
@@ -1379,19 +1520,27 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
1379
1520
|
think: session.think,
|
|
1380
1521
|
effort: session.effort,
|
|
1381
1522
|
web: session.web,
|
|
1382
|
-
// 부모보다 적게 준다 (budget.js). 사람이
|
|
1383
|
-
maxSteps: session.stepsSet
|
|
1523
|
+
// 부모보다 적게 준다 (budget.js). 정의에 적힌 것 > 사람이 정한 값의 절반 > 모드.
|
|
1524
|
+
maxSteps: 정의?.걸음 ?? (session.stepsSet
|
|
1384
1525
|
? Math.max(4, Math.floor(session.maxSteps / 2))
|
|
1385
1526
|
// 걸음 수는 **하위가 쓸 창**으로 잰다. 부모 창으로 재면, 작은 모델에게
|
|
1386
1527
|
// 떼어 준 일이 제 창보다 큰 걸음 수를 받아 중간에 창이 찬 채로 돈다.
|
|
1387
|
-
: 하위걸음수(자식모드, 자식conn.ctx),
|
|
1528
|
+
: 하위걸음수(자식모드, 자식conn.ctx)),
|
|
1388
1529
|
});
|
|
1389
1530
|
// 스킬·명령·기억은 부모가 켤 때 한 번 찾아 든 것이다. 하위도 같은 것을 본다.
|
|
1390
1531
|
자식.skills = session.skills;
|
|
1391
1532
|
자식.commands = session.commands;
|
|
1392
1533
|
자식.plugins = session.plugins;
|
|
1393
1534
|
자식.memory = session.memory;
|
|
1394
|
-
|
|
1535
|
+
/*
|
|
1536
|
+
* 정의가 적은 도구로 **줄인다.** 늘리지 않는다 (agent/agents.js).
|
|
1537
|
+
*
|
|
1538
|
+
* 자식도구 는 이미 모드가 거른 목록이다. 여기서 교집합을 취하므로,
|
|
1539
|
+
* 정의에 `Write` 를 적어 넣는 것으로 설계 모드의 약속을 깨고 나갈 길은
|
|
1540
|
+
* 안 생긴다. 모드가 안 주는 것을 적어 두면 그것만 무시된다.
|
|
1541
|
+
*/
|
|
1542
|
+
const 줄인것 = 도구줄이기(정의, 자식도구);
|
|
1543
|
+
자식.도구제한 = 줄인것.도구;
|
|
1395
1544
|
|
|
1396
1545
|
/*
|
|
1397
1546
|
* 여닫는 줄에는 깊이를 안 붙인다.
|
|
@@ -1409,8 +1558,21 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
1409
1558
|
* 있어서 하위가 딴 데로 나가는 것을 모른다. 그러니 이 줄과 감사기록이
|
|
1410
1559
|
* 그 사실을 남기는 유일한 자리다.
|
|
1411
1560
|
*/
|
|
1561
|
+
/*
|
|
1562
|
+
* 정의에 적혔는데 모드가 안 주는 도구는 **말해 준다.**
|
|
1563
|
+
*
|
|
1564
|
+
* 조용히 빼면 사람은 제가 적은 도구가 도는 줄 알고, 하위가 그걸 안 했을
|
|
1565
|
+
* 때 정의 파일이 아니라 모델을 의심한다. 오타 하나가 며칠이 되는 자리다.
|
|
1566
|
+
*/
|
|
1567
|
+
if (줄인것.못준것.length) {
|
|
1568
|
+
yield {
|
|
1569
|
+
type: 'hook_note', 자리: '에이전트',
|
|
1570
|
+
말: `${정의.이름} 정의의 ${줄인것.못준것.join(' · ')} 은 ${자식모드} 모드가 안 주는 도구라 뺀습니다`,
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1412
1573
|
yield {
|
|
1413
1574
|
type: 'task_start', 목적, 모드: 자식모드, steps: 자식.maxSteps,
|
|
1575
|
+
에이전트: 정의?.이름 ?? null,
|
|
1414
1576
|
모델: 모델알림?.말 ?? null, 밖으로: 모델알림?.밖으로 ?? false,
|
|
1415
1577
|
};
|
|
1416
1578
|
ctx.audit.tool('Task', { 목적, 모드: 자식모드, 모델: 모델알림?.말 ?? null },
|
|
@@ -1474,13 +1636,13 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
1474
1636
|
session.usage.못잰것 = (session.usage.못잰것 ?? 0) + (자식.usage.못잰것 ?? 0);
|
|
1475
1637
|
|
|
1476
1638
|
const 글 = 하위요약({
|
|
1477
|
-
목적, 모드: 자식모드, 끝, 모델: 모델알림?.말 ?? null,
|
|
1639
|
+
목적, 모드: 자식모드, 끝, 모델: 모델알림?.말 ?? null, 에이전트: 정의?.이름 ?? null,
|
|
1478
1640
|
글자수: 요약길이(conn.ctx),
|
|
1479
1641
|
보인이름: (경로) => ctx.scope?.show?.(경로) ?? 경로,
|
|
1480
1642
|
});
|
|
1481
1643
|
session.push(toolMessage(conn.kind, { callId: call.id, name: 'Task', content: 글 }));
|
|
1482
1644
|
ctx.audit.tool('Task', { 목적 }, { summary: 글.slice(0, 300) });
|
|
1483
|
-
yield { type: 'task_done', 목적, 모드: 자식모드, 끝, 모델: 모델알림?.말 ?? null };
|
|
1645
|
+
yield { type: 'task_done', 목적, 모드: 자식모드, 끝, 모델: 모델알림?.말 ?? null, 에이전트: 정의?.이름 ?? null };
|
|
1484
1646
|
|
|
1485
1647
|
// 사용자가 중단했으면 부모도 여기서 멈춘다. 하위만 끊고 이어가면
|
|
1486
1648
|
// 무엇이 중단된 것인지 알 수 없는 화면이 된다.
|
|
@@ -1561,6 +1723,36 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
1561
1723
|
*/
|
|
1562
1724
|
for (const f of result.바뀐것들 ?? []) 손댄파일.add(f);
|
|
1563
1725
|
|
|
1726
|
+
/*
|
|
1727
|
+
* ── 도구가 끝난 뒤 (safety/hooks.js) ─────────────────────────────
|
|
1728
|
+
*
|
|
1729
|
+
* 여기서는 **못 막는다.** 이미 파일이 바뀌었고 명령이 돌았다. 막을 수
|
|
1730
|
+
* 없는 것을 막는 척하면 그게 제일 나쁜 화면이다. 할 수 있는 것은
|
|
1731
|
+
* 모델에게 말해 주는 것뿐이고, 그것만 한다.
|
|
1732
|
+
*
|
|
1733
|
+
* 제일 흔한 쓰임이 포맷터와 검사기다. `Edit` 뒤에 사내 포맷터를 돌리고
|
|
1734
|
+
* 그 결과를 모델에게 돌려주면, 모델이 다음 걸음에서 스스로 맞춘다.
|
|
1735
|
+
*
|
|
1736
|
+
* 훅이 뱉은 글은 **명령 출력**이라 열쇠가 섞여 나오기 쉽다. 아래 비밀
|
|
1737
|
+
* 가리기는 도구 이름으로 갈래를 정하는데(가릴까), 훅은 어느 도구 뒤에도
|
|
1738
|
+
* 붙을 수 있어서 그 갈래를 못 믿는다. 그래서 여기서 한 번 가린다.
|
|
1739
|
+
*/
|
|
1740
|
+
let 훅덧말 = '';
|
|
1741
|
+
if (ctx.훅들?.length) {
|
|
1742
|
+
const 훅 = await 자리돌리기(ctx.훅들, '도구후', {
|
|
1743
|
+
도구: call.name,
|
|
1744
|
+
넣을것: { 인자: call.args ?? {}, 실패: !!result.error, 바뀐것: result.changed ?? null },
|
|
1745
|
+
signal,
|
|
1746
|
+
audit: ctx.audit,
|
|
1747
|
+
});
|
|
1748
|
+
if (훅.말들.length) {
|
|
1749
|
+
const 가린 = 가리기(훅.말들.join('\n'), { 열쇠들: [conn.key, ...환경속열쇠들()].filter(Boolean) });
|
|
1750
|
+
const 글 = 가린.글 + (가린.가린것.length ? 가렸다는말(가린.가린것) : '');
|
|
1751
|
+
훅덧말 = `\n\n[도구후 훅]\n${글}`;
|
|
1752
|
+
for (const 말 of 훅.말들) yield { type: 'hook_note', 자리: '도구후', 도구: call.name, 말 };
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1564
1756
|
// 앞에서 똑같이 부른 적이 있고 결과도 같으면, 결과를 다시 싣지 않는다.
|
|
1565
1757
|
let 실을것 = 실을글(result);
|
|
1566
1758
|
const 날것 = 실을것; // 되풀이는 도구가 진짜 돌려준 것으로 센다(아래 참고)
|
|
@@ -1678,6 +1870,14 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
|
|
|
1678
1870
|
* 무엇이 열쇠인지는 config.js 한 곳만 안다 (환경속열쇠들).
|
|
1679
1871
|
*/
|
|
1680
1872
|
const 아는열쇠들 = [conn.key, ...환경속열쇠들()].filter(Boolean);
|
|
1873
|
+
/*
|
|
1874
|
+
* 훅이 한 말은 **되풀이 판정이 끝난 뒤에** 붙인다.
|
|
1875
|
+
*
|
|
1876
|
+
* 앞에 붙이면 같은 도구를 같은 인자로 두 번 불렀을 때, 훅이 시각이나
|
|
1877
|
+
* 줄 번호를 한 글자만 달리 뱉어도 「결과가 달라졌다」 가 된다. 그러면
|
|
1878
|
+
* 되풀이 그물이 통째로 풀려서, 헛도는 것을 안 잡는다.
|
|
1879
|
+
*/
|
|
1880
|
+
if (훅덧말) 실을것 += 훅덧말;
|
|
1681
1881
|
let 비밀 = [];
|
|
1682
1882
|
// 바깥으로 나가면 파일에서 읽어 온 글까지 가린다 (safety/secrets.js 의 가릴까).
|
|
1683
1883
|
const 이번엔가릴까 = 가릴까(call.name, { 바깥: 바깥으로나감 });
|
package/src/agent/models.js
CHANGED
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
*/
|
|
35
35
|
import { load, activeProfile, resolveKey } from '../config.js';
|
|
36
36
|
import { isLocalHost, isOffline } from '../safety/network.js';
|
|
37
|
+
import { 잠잠기본 } from '../backend/http.js';
|
|
38
|
+
import { 인증서설정 } from '../backend/clientcert.js';
|
|
37
39
|
|
|
38
40
|
// 컨텍스트를 못 알아냈을 때 쓰는 값. repl.js 와 같은 값을 봐야 한다.
|
|
39
41
|
export const CTX_DEFAULT = 32768;
|
|
@@ -56,6 +58,8 @@ export function 연결만들기(prof, { ctx = null, maxTokens = null } = {}) {
|
|
|
56
58
|
ctx: ctx ?? prof.ctx ?? CTX_DEFAULT,
|
|
57
59
|
maxTokens: maxTokens ?? prof.maxTokens ?? null,
|
|
58
60
|
streaming: prof.streaming ?? false,
|
|
61
|
+
잠잠: prof.잠잠 ?? prof.streamIdleMs ?? 잠잠기본,
|
|
62
|
+
인증서: 인증서설정(prof),
|
|
59
63
|
tools: prof.tools ?? false,
|
|
60
64
|
json: prof.json ?? false,
|
|
61
65
|
think: prof.think ?? false,
|