agentlas 1.0.55 → 1.0.58
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.
|
@@ -157,6 +157,166 @@ function listGraphs(ctx) {
|
|
|
157
157
|
return 0;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
/**
|
|
161
|
+
* 옛 실행이 부수효과를 남겨 막힌 자동화를, 사람이 **한 번** 판단해 풀어 준다.
|
|
162
|
+
*
|
|
163
|
+
* 실측 2026-08-20: 터미널 코어에는 재조정 모듈이 아예 실리지 않아,
|
|
164
|
+
* `automation_partial_reconciliation_required` 를 **낼 수는 있는데 풀 수단이 없었다**.
|
|
165
|
+
* CLI 만 쓰는 사람은 그 자동화를 영원히 못 돌린다. 내는 오류가 있으면 푸는 길도 있어야 한다.
|
|
166
|
+
*
|
|
167
|
+
* 판단은 노드마다 둘 중 하나다 — "이미 일어났다(--done)" / "확실히 안 일어났다(--not-done)".
|
|
168
|
+
* 아무 것도 주지 않으면 무엇을 정해야 하는지 보여 주기만 한다. 실행 중에 묻지 않는다.
|
|
169
|
+
*/
|
|
170
|
+
/**
|
|
171
|
+
* 엔진을 손에 넣는다 — 캐시에 없으면 **받는다**.
|
|
172
|
+
*
|
|
173
|
+
* ★엔진이 필요한 명령은 전부 이 길을 지나야 한다. 실측 2026-08-20: `graph run` 은 이걸
|
|
174
|
+
* 고쳤는데, 바로 뒤에 만든 `graph reconcile` 이 동기 로더만 불러 같은 병을 반복했다 —
|
|
175
|
+
* 새로 설치한 사람에게는 "이 엔진에는 재조정 기능이 없습니다" 로 보인다(사실은 엔진이
|
|
176
|
+
* 아직 없는 것이다). 한 곳으로 모아 다음 명령이 또 빠뜨리지 않게 한다.
|
|
177
|
+
*/
|
|
178
|
+
async function acquireCore(ctx) {
|
|
179
|
+
const desktopCore = require("../core/desktop-core.cjs");
|
|
180
|
+
let core = ctx.desktopCore || desktopCore.loadDesktopCore();
|
|
181
|
+
if (!core || core.error || typeof core.runGraph !== "function") {
|
|
182
|
+
try {
|
|
183
|
+
core = await desktopCore.loadDesktopCoreAsync({ onNotice: (message) => ctx.err(message) });
|
|
184
|
+
} catch {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return core && !core.error ? core : null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function reconcileGraph(ctx, needle, flags, en) {
|
|
192
|
+
const rows = graphRows(ctx, ctx.db());
|
|
193
|
+
const row = findGraph(rows, needle);
|
|
194
|
+
if (row?.ambiguous) { reportAmbiguous(ctx, needle, row.ambiguous, en); return 1; }
|
|
195
|
+
if (!row) {
|
|
196
|
+
ctx.err(en ? `No graph matches "${needle}".` : `"${needle}"와 맞는 그래프가 없습니다.`);
|
|
197
|
+
return 1;
|
|
198
|
+
}
|
|
199
|
+
const core = await acquireCore(ctx);
|
|
200
|
+
const reconciliation = core && core.graphReconciliation;
|
|
201
|
+
if (!reconciliation || typeof reconciliation.get !== "function") {
|
|
202
|
+
ctx.err(en
|
|
203
|
+
? "This engine build cannot reconcile. Update the CLI (`npm i -g agentlas@latest`) and try again."
|
|
204
|
+
: "이 엔진에는 재조정 기능이 없습니다. CLI를 업데이트한 뒤(`npm i -g agentlas@latest`) 다시 시도하세요.");
|
|
205
|
+
return 1;
|
|
206
|
+
}
|
|
207
|
+
// 그래프를 고친 뒤에는 옛 좌표가 그 그래프의 것이 아니라 재조정도 거절된다. 그 상태는
|
|
208
|
+
// 실행도 재조정도 안 되는 잠김이므로, 사람이 스스로 나갈 문을 둔다.
|
|
209
|
+
if (flags && flags['start-over']) {
|
|
210
|
+
if (typeof reconciliation.forgetStale !== 'function') {
|
|
211
|
+
ctx.err(en ? 'This engine build cannot do that. Update the CLI and try again.' : '이 엔진에는 그 기능이 없습니다. CLI를 업데이트한 뒤 다시 시도하세요.');
|
|
212
|
+
return 1;
|
|
213
|
+
}
|
|
214
|
+
const graph = parseGraph(row);
|
|
215
|
+
const automation = core.getAutomation ? core.getAutomation(row.id) : null;
|
|
216
|
+
const digest = core.graphExecutionDigest && automation && graph
|
|
217
|
+
? core.graphExecutionDigest(automation, graph)
|
|
218
|
+
: null;
|
|
219
|
+
if (!digest) {
|
|
220
|
+
ctx.err(en ? 'Could not compute the current graph identity.' : '지금 그래프의 신원을 계산하지 못했습니다.');
|
|
221
|
+
return 1;
|
|
222
|
+
}
|
|
223
|
+
const outcome = reconciliation.forgetStale(row.id, digest);
|
|
224
|
+
if (!outcome.forgot) {
|
|
225
|
+
ctx.err(en
|
|
226
|
+
? `Nothing to forget (${outcome.reason}). If the graph has not changed, decide each step instead.`
|
|
227
|
+
: `잊을 것이 없습니다(${outcome.reason}). 그래프가 그대로라면, 단계마다 정하는 쪽이 맞습니다.`);
|
|
228
|
+
return 1;
|
|
229
|
+
}
|
|
230
|
+
ctx.out(en
|
|
231
|
+
? 'The earlier run is forgotten. Note: whatever it already did can happen again on the next run.'
|
|
232
|
+
: '이전 실행을 잊었습니다. 그 실행이 이미 한 일은 다음 실행에서 다시 일어날 수 있습니다.');
|
|
233
|
+
return 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const view = reconciliation.get(row.id);
|
|
237
|
+
if (!view) {
|
|
238
|
+
ctx.out(en
|
|
239
|
+
? `"${row.name}" has nothing waiting to be reconciled.`
|
|
240
|
+
: `"${row.name}"에는 재조정을 기다리는 것이 없습니다.`);
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const done = new Set(asList(flags && (flags.done ?? flags["done"])));
|
|
245
|
+
const notDone = new Set(asList(flags && (flags["not-done"] ?? flags.notDone)));
|
|
246
|
+
const outputs = new Map();
|
|
247
|
+
for (const pair of asList(flags && flags.output)) {
|
|
248
|
+
const at = String(pair).indexOf("=");
|
|
249
|
+
if (at > 0) outputs.set(String(pair).slice(0, at).trim(), String(pair).slice(at + 1));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (done.size === 0 && notDone.size === 0) {
|
|
253
|
+
ctx.out(en
|
|
254
|
+
? `"${row.name}" stopped with steps whose effect cannot be told from the record.`
|
|
255
|
+
: `"${row.name}"은(는) 기록만으로는 일어났는지 알 수 없는 단계가 있어 멈춰 있습니다.`);
|
|
256
|
+
ctx.out("");
|
|
257
|
+
for (const node of view.nodes) {
|
|
258
|
+
const needs = node.outputRequired
|
|
259
|
+
? (en ? ` (needs --output ${node.nodeId}=<value> when done)` : ` (일어났다면 --output ${node.nodeId}=<값> 필요)`)
|
|
260
|
+
: "";
|
|
261
|
+
ctx.out(` ${node.nodeId} ${node.label}${needs}`);
|
|
262
|
+
}
|
|
263
|
+
ctx.out("");
|
|
264
|
+
ctx.out(ctx.ui.dim(en
|
|
265
|
+
? `Decide each one: agentlas graph reconcile "${row.name}" --done <node> --not-done <node>`
|
|
266
|
+
: `각각 정하세요: agentlas graph reconcile "${row.name}" --done <노드> --not-done <노드>`));
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const undecided = view.nodes.filter((n) => !done.has(n.nodeId) && !notDone.has(n.nodeId));
|
|
271
|
+
if (undecided.length > 0) {
|
|
272
|
+
ctx.err(en
|
|
273
|
+
? `Still undecided: ${undecided.map((n) => n.nodeId).join(", ")}. Every step needs one answer.`
|
|
274
|
+
: `아직 안 정한 단계가 있습니다: ${undecided.map((n) => n.nodeId).join(", ")}. 모든 단계에 답이 필요합니다.`);
|
|
275
|
+
return 1;
|
|
276
|
+
}
|
|
277
|
+
const missingOutput = view.nodes.filter((n) => done.has(n.nodeId) && n.outputRequired && !outputs.has(n.nodeId));
|
|
278
|
+
if (missingOutput.length > 0) {
|
|
279
|
+
ctx.err(en
|
|
280
|
+
? `These steps populate a value, so completing them needs it: ${missingOutput.map((n) => `--output ${n.nodeId}=<value>`).join(" ")}`
|
|
281
|
+
: `값을 만드는 단계라 "일어났다"로 두려면 값이 필요합니다: ${missingOutput.map((n) => `--output ${n.nodeId}=<값>`).join(" ")}`);
|
|
282
|
+
return 1;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
const result = reconciliation.apply({
|
|
287
|
+
automationId: view.automationId,
|
|
288
|
+
runId: view.runId,
|
|
289
|
+
occurrenceId: view.occurrenceId,
|
|
290
|
+
graphDigest: view.graphDigest,
|
|
291
|
+
checkpointDigest: view.checkpointDigest,
|
|
292
|
+
expectedUpdatedAt: view.updatedAt,
|
|
293
|
+
eventId: view.triggerEvent ? view.triggerEvent.id : null,
|
|
294
|
+
expectedEventUpdatedAt: view.triggerEvent ? view.triggerEvent.updatedAt : null,
|
|
295
|
+
decisions: view.nodes.map((n) => (done.has(n.nodeId)
|
|
296
|
+
? { nodeId: n.nodeId, resolution: "completed", ...(outputs.has(n.nodeId) ? { output: outputs.get(n.nodeId) } : {}) }
|
|
297
|
+
: { nodeId: n.nodeId, resolution: "retry" })),
|
|
298
|
+
});
|
|
299
|
+
ctx.out(en
|
|
300
|
+
? `Reconciled. ${result.completedNodeIds.length} step(s) marked as done, ${result.retryNodeIds.length} to redo.`
|
|
301
|
+
: `재조정했습니다. ${result.completedNodeIds.length}개는 일어난 것으로, ${result.retryNodeIds.length}개는 다시 하는 것으로 두었습니다.`);
|
|
302
|
+
ctx.out(ctx.ui.dim(en
|
|
303
|
+
? `Now run it: agentlas graph run "${row.name}"`
|
|
304
|
+
: `이제 실행하세요: agentlas graph run "${row.name}"`));
|
|
305
|
+
return 0;
|
|
306
|
+
} catch (error) {
|
|
307
|
+
ctx.err(en
|
|
308
|
+
? `Reconciliation was refused: ${String(error && error.message || error)}`
|
|
309
|
+
: `재조정이 거절됐습니다: ${String(error && error.message || error)}`);
|
|
310
|
+
return 1;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** 같은 깃발이 여러 번 올 수 있다 — 하나든 여럿이든 배열로 본다. */
|
|
315
|
+
function asList(value) {
|
|
316
|
+
if (value == null) return [];
|
|
317
|
+
return Array.isArray(value) ? value.map(String) : [String(value)];
|
|
318
|
+
}
|
|
319
|
+
|
|
160
320
|
function showGraph(ctx, needle) {
|
|
161
321
|
const db = ctx.db();
|
|
162
322
|
const rows = graphRows(ctx, db);
|
|
@@ -558,19 +718,10 @@ async function runGraph(ctx, needle, flags) {
|
|
|
558
718
|
*
|
|
559
719
|
* 받는 동안은 조용하지 않다(onNotice) — 12MB 를 말없이 끌어오지 않는다.
|
|
560
720
|
*/
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
} catch (fetchError) {
|
|
566
|
-
ctx.err(JSON.stringify({
|
|
567
|
-
ok: false,
|
|
568
|
-
error: `graph-execution engine could not be fetched: ${fetchError instanceof Error ? fetchError.message : String(fetchError)}`,
|
|
569
|
-
}, null, 2));
|
|
570
|
-
return 1;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
if (!core || core.error || typeof core.runGraph !== "function") {
|
|
721
|
+
// ★엔진을 얻는 법을 아는 곳은 acquireCore 하나뿐이다 — 둘이면 다음에 만드는 명령이
|
|
722
|
+
// 또 한쪽만 보고 캐시에서 끝난다(실측 2026-08-20: reconcile 이 정확히 그랬다).
|
|
723
|
+
let core = await acquireCore(ctx);
|
|
724
|
+
if (!core || typeof core.runGraph !== "function") {
|
|
574
725
|
const cause = core?.error instanceof Error ? core.error.message : "vendored Desktop Core is unavailable";
|
|
575
726
|
ctx.err(JSON.stringify({ ok: false, error: cause }, null, 2));
|
|
576
727
|
return 1;
|
|
@@ -1147,6 +1298,7 @@ async function run(ctx, args = []) {
|
|
|
1147
1298
|
for (let i = 0; i < args.length; i += 1) {
|
|
1148
1299
|
const arg = args[i];
|
|
1149
1300
|
if (arg === "-y" || arg === "--yes") { flags.yes = true; continue; }
|
|
1301
|
+
if (arg === "--start-over") { flags["start-over"] = true; continue; }
|
|
1150
1302
|
if (arg === "--input" || arg === "-i") { flags.input = String(args[i + 1] ?? "").trim(); i += 1; continue; }
|
|
1151
1303
|
if (arg.startsWith("--input=")) { flags.input = arg.slice("--input=".length).trim(); continue; }
|
|
1152
1304
|
// ★--name 도 값을 하나 받는 플래그다. 걷어내지 않으면 그 값이 파일 경로에 붙어
|
|
@@ -1154,6 +1306,23 @@ async function run(ctx, args = []) {
|
|
|
1154
1306
|
// 실제로는 한 번도 동작한 적이 없었다(실사용 실측 2026-08-06).
|
|
1155
1307
|
if (arg === "--name" || arg === "-n") { flags.name = String(args[i + 1] ?? "").trim(); i += 1; continue; }
|
|
1156
1308
|
if (arg.startsWith("--name=")) { flags.name = arg.slice("--name=".length).trim(); continue; }
|
|
1309
|
+
// 재조정 판단 — 같은 깃발이 여러 번 올 수 있으므로 모아 둔다. 값을 함께 걷어내지
|
|
1310
|
+
// 않으면 위 --name 사고와 똑같이 그래프 이름에 붙어 "맞는 그래프가 없다"가 된다.
|
|
1311
|
+
{
|
|
1312
|
+
const multi = { "--done": "done", "--not-done": "not-done", "--output": "output" };
|
|
1313
|
+
let matched = false;
|
|
1314
|
+
for (const [token, key] of Object.entries(multi)) {
|
|
1315
|
+
if (arg === token) {
|
|
1316
|
+
(flags[key] || (flags[key] = [])).push(String(args[i + 1] ?? "").trim());
|
|
1317
|
+
i += 1; matched = true; break;
|
|
1318
|
+
}
|
|
1319
|
+
if (arg.startsWith(`${token}=`)) {
|
|
1320
|
+
(flags[key] || (flags[key] = [])).push(arg.slice(token.length + 1).trim());
|
|
1321
|
+
matched = true; break;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
if (matched) continue;
|
|
1325
|
+
}
|
|
1157
1326
|
rest.push(arg);
|
|
1158
1327
|
}
|
|
1159
1328
|
const sub = (rest[0] || "list").toLowerCase();
|
|
@@ -1165,6 +1334,7 @@ async function run(ctx, args = []) {
|
|
|
1165
1334
|
ctx.out(en ? " list what is saved" : " list 저장된 것 목록");
|
|
1166
1335
|
ctx.out(en ? " show \"<name>\" steps, wiring, and problems" : " show \"<이름>\" 단계·배선·문제점");
|
|
1167
1336
|
ctx.out(en ? " run \"<name>\" [--input \"<value>\"] run locally with the included Desktop Core" : " run \"<이름>\" [--input \"<값>\"] 포함된 Desktop Core로 로컬 실행");
|
|
1337
|
+
ctx.out(en ? " reconcile \"<name>\" decide what already happened, when a run stopped unsure" : " reconcile \"<이름>\" 멈춘 실행에서 무엇이 이미 일어났는지 정하기");
|
|
1168
1338
|
ctx.out(en ? " export \"<name>\" [file] write a shareable package file" : " export \"<이름>\" [파일] 남에게 줄 수 있는 파일로 저장");
|
|
1169
1339
|
ctx.out(en ? " inspect <file> read a package file before installing" : " inspect <파일> 설치 전에 패키지 파일 확인");
|
|
1170
1340
|
ctx.out(en ? " install <file> [--name \"<new name>\"] install a package file" : " install <파일> [--name \"<새 이름>\"] 패키지 파일 설치");
|
|
@@ -1185,6 +1355,15 @@ async function run(ctx, args = []) {
|
|
|
1185
1355
|
}
|
|
1186
1356
|
return showGraph(ctx, target);
|
|
1187
1357
|
}
|
|
1358
|
+
if (sub === "reconcile") {
|
|
1359
|
+
if (!target) {
|
|
1360
|
+
ctx.err(en
|
|
1361
|
+
? "Usage: agentlas graph reconcile \"<name>\" [--done <node>] [--not-done <node>] [--output <node>=<value>]"
|
|
1362
|
+
: "사용법: agentlas graph reconcile \"<이름>\" [--done <노드>] [--not-done <노드>] [--output <노드>=<값>]");
|
|
1363
|
+
return 1;
|
|
1364
|
+
}
|
|
1365
|
+
return await reconcileGraph(ctx, target, flags, en);
|
|
1366
|
+
}
|
|
1188
1367
|
if (sub === "export") {
|
|
1189
1368
|
if (!target) {
|
|
1190
1369
|
ctx.err(en ? "Usage: agentlas graph export \"<name>\" [file]" : "사용법: agentlas graph export \"<이름>\" [파일]");
|
|
@@ -231,8 +231,33 @@ function loadDesktopCore(options = {}) {
|
|
|
231
231
|
initStore: req("store/db").initStore,
|
|
232
232
|
getDb: req("store/db").getDb,
|
|
233
233
|
runGraph: kernel.runGraph,
|
|
234
|
+
getAutomation: req("store/automations").getAutomation,
|
|
235
|
+
// shared/ 는 electron/ 밖이라 req 로 못 닿는다 — 직접 해석한다.
|
|
236
|
+
graphExecutionDigest: (() => {
|
|
237
|
+
try { return require(path.join(root, "shared", "graph-execution-digest.js")).graphExecutionDigest; }
|
|
238
|
+
catch { return null; }
|
|
239
|
+
})(),
|
|
234
240
|
graphFailureOf: kernel.graphFailureOf,
|
|
235
241
|
planGraphLoops: kernel.planGraphLoops,
|
|
242
|
+
/*
|
|
243
|
+
* ★내는 오류가 있으면 푸는 길도 함께 준다. 실측 2026-08-20: 커널은
|
|
244
|
+
* `automation_partial_reconciliation_required` 를 내는데, 그것을 푸는 모듈은
|
|
245
|
+
* 벤더에 실리지도 않았고 여기 표면에도 없었다 — CLI 만 쓰는 사람은 그 자동화를
|
|
246
|
+
* 영원히 못 돌린다. 옛 코어를 쓰는 동안에는 없을 수 있으므로 정직하게 null 로 둔다.
|
|
247
|
+
*/
|
|
248
|
+
graphReconciliation: (() => {
|
|
249
|
+
try {
|
|
250
|
+
const mod = req("store/graph-reconciliation");
|
|
251
|
+
if (typeof mod.getAutomationGraphReconciliation !== "function") return null;
|
|
252
|
+
return {
|
|
253
|
+
get: mod.getAutomationGraphReconciliation,
|
|
254
|
+
apply: mod.reconcileAutomationGraph,
|
|
255
|
+
forgetStale: mod.forgetStaleGraphCheckpoint,
|
|
256
|
+
};
|
|
257
|
+
} catch {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
})(),
|
|
236
261
|
};
|
|
237
262
|
return _cache;
|
|
238
263
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "
|
|
3
|
-
"url": "https://github.com/agentlas-ai/agentlas-terminal/releases/download/desktop-core-
|
|
4
|
-
"sha256": "
|
|
5
|
-
"sizeBytes":
|
|
6
|
-
"writtenAt": "2026-08-
|
|
2
|
+
"version": "10",
|
|
3
|
+
"url": "https://github.com/agentlas-ai/agentlas-terminal/releases/download/desktop-core-v10/desktop-core.tar.gz",
|
|
4
|
+
"sha256": "24276f065bdef65089f315b990f3573d88d5b47df650cb0deb023d979ecbcf95",
|
|
5
|
+
"sizeBytes": 12556818,
|
|
6
|
+
"writtenAt": "2026-08-19T21:31:43.680Z"
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentlas",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.58",
|
|
4
4
|
"description": "Agentlas project terminal — run project controllers and task-scoped agent teams from the terminal. Standalone: no desktop app process required.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agentlas": "bin/agentlas.cjs"
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"vendor:core": "node scripts/vendor-desktop-core.cjs",
|
|
14
14
|
"test:tool-access-notice-parity": "node test/tool-access-notice-parity.cjs",
|
|
15
15
|
"verify:engine-reachable": "node scripts/verify-engine-reachable.cjs",
|
|
16
|
-
"prepublishOnly": "npm run verify:engine-reachable"
|
|
16
|
+
"prepublishOnly": "npm run verify:engine-reachable && npm run verify:way-out",
|
|
17
|
+
"verify:way-out": "node scripts/verify-raised-errors-have-a-way-out.cjs"
|
|
17
18
|
},
|
|
18
19
|
"engines": {
|
|
19
20
|
"node": ">=20.19"
|