@loops-adk/core 0.1.0 → 0.1.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.md +16 -1
- package/bin/loops.mjs +5 -5
- package/dist/api.d.ts +70 -1
- package/dist/api.js +2 -2
- package/dist/api.js.map +1 -1
- package/dist/{chunk-3BPU34DE.js → chunk-6BDWTFOS.js} +200 -14
- package/dist/chunk-6BDWTFOS.js.map +1 -0
- package/dist/index.js +111 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/author-loop/SKILL.md +12 -10
- package/dist/chunk-3BPU34DE.js.map +0 -1
|
@@ -2,10 +2,11 @@ import { redactSecrets } from './chunk-JFTXJ7I2.js';
|
|
|
2
2
|
import { isEngine } from './chunk-XC46B4FD.js';
|
|
3
3
|
import { isLimitError, waitMsFor } from './chunk-Y2SD7GBL.js';
|
|
4
4
|
import { LoopError } from './chunk-I3STY7U6.js';
|
|
5
|
-
import { readFileSync, mkdtempSync, existsSync, writeFileSync, appendFileSync, mkdirSync, rmSync } from 'fs';
|
|
5
|
+
import { readFileSync, mkdtempSync, existsSync, writeFileSync, appendFileSync, readdirSync, mkdirSync, rmSync } from 'fs';
|
|
6
6
|
import { execa } from 'execa';
|
|
7
|
-
import { tmpdir } from 'os';
|
|
7
|
+
import { tmpdir, homedir } from 'os';
|
|
8
8
|
import { join, dirname } from 'path';
|
|
9
|
+
import { randomBytes } from 'crypto';
|
|
9
10
|
|
|
10
11
|
// src/core/describe.ts
|
|
11
12
|
var META = /* @__PURE__ */ new WeakMap();
|
|
@@ -1975,6 +1976,174 @@ var NOISE = /* @__PURE__ */ new Set([
|
|
|
1975
1976
|
"engine:text",
|
|
1976
1977
|
"engine:thinking"
|
|
1977
1978
|
]);
|
|
1979
|
+
function runsHome() {
|
|
1980
|
+
const base = process.env.LOOPS_HOME ?? join(homedir(), ".loops");
|
|
1981
|
+
return join(base, "runs");
|
|
1982
|
+
}
|
|
1983
|
+
function slug(s) {
|
|
1984
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "run";
|
|
1985
|
+
}
|
|
1986
|
+
function newRunId(title) {
|
|
1987
|
+
return `${slug(title)}-${randomBytes(3).toString("hex")}`;
|
|
1988
|
+
}
|
|
1989
|
+
function startSupervisor(input) {
|
|
1990
|
+
const dir = join(runsHome(), input.runId);
|
|
1991
|
+
mkdirSync(dir, { recursive: true });
|
|
1992
|
+
const eventsPath = join(dir, "events.jsonl");
|
|
1993
|
+
const statusPath = join(dir, "status.json");
|
|
1994
|
+
try {
|
|
1995
|
+
writeFileSync(eventsPath, "");
|
|
1996
|
+
} catch {
|
|
1997
|
+
}
|
|
1998
|
+
const status = {
|
|
1999
|
+
runId: input.runId,
|
|
2000
|
+
pid: process.pid,
|
|
2001
|
+
cwd: input.cwd,
|
|
2002
|
+
title: input.title,
|
|
2003
|
+
startedAt: Date.now(),
|
|
2004
|
+
updatedAt: Date.now(),
|
|
2005
|
+
status: "running",
|
|
2006
|
+
shape: input.shape,
|
|
2007
|
+
live: {
|
|
2008
|
+
path: [],
|
|
2009
|
+
iteration: 0,
|
|
2010
|
+
usage: { inputTokens: 0, outputTokens: 0, calls: 0 }
|
|
2011
|
+
}
|
|
2012
|
+
};
|
|
2013
|
+
const writeStatus = () => {
|
|
2014
|
+
status.updatedAt = Date.now();
|
|
2015
|
+
try {
|
|
2016
|
+
writeFileSync(statusPath, JSON.stringify(status, null, 2));
|
|
2017
|
+
} catch {
|
|
2018
|
+
}
|
|
2019
|
+
};
|
|
2020
|
+
writeStatus();
|
|
2021
|
+
const sink = (event) => {
|
|
2022
|
+
if (!NOISE.has(event.kind)) {
|
|
2023
|
+
try {
|
|
2024
|
+
appendFileSync(eventsPath, `${JSON.stringify(event)}
|
|
2025
|
+
`);
|
|
2026
|
+
} catch {
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
switch (event.kind) {
|
|
2030
|
+
case "loop:iteration":
|
|
2031
|
+
status.live.path = event.path;
|
|
2032
|
+
status.live.iteration = event.iteration;
|
|
2033
|
+
break;
|
|
2034
|
+
case "loop:condition":
|
|
2035
|
+
status.live.lastGate = {
|
|
2036
|
+
which: event.which,
|
|
2037
|
+
met: event.result.met,
|
|
2038
|
+
confidence: event.result.confidence,
|
|
2039
|
+
reason: event.result.reason
|
|
2040
|
+
};
|
|
2041
|
+
break;
|
|
2042
|
+
case "dag:node":
|
|
2043
|
+
status.live.path = [...event.path, event.node];
|
|
2044
|
+
break;
|
|
2045
|
+
case "loop:end":
|
|
2046
|
+
case "dag:end":
|
|
2047
|
+
case "job:end":
|
|
2048
|
+
status.live.lastOutcome = {
|
|
2049
|
+
status: event.outcome.status,
|
|
2050
|
+
summary: event.outcome.summary
|
|
2051
|
+
};
|
|
2052
|
+
status.live.path = event.path;
|
|
2053
|
+
break;
|
|
2054
|
+
case "engine:usage":
|
|
2055
|
+
status.live.usage.inputTokens += event.usage.inputTokens;
|
|
2056
|
+
status.live.usage.outputTokens += event.usage.outputTokens;
|
|
2057
|
+
status.live.usage.calls += 1;
|
|
2058
|
+
break;
|
|
2059
|
+
}
|
|
2060
|
+
if (!NOISE.has(event.kind)) writeStatus();
|
|
2061
|
+
};
|
|
2062
|
+
const finish = (outcome) => {
|
|
2063
|
+
status.status = outcome.status;
|
|
2064
|
+
status.endedAt = Date.now();
|
|
2065
|
+
status.live.lastOutcome = {
|
|
2066
|
+
status: outcome.status,
|
|
2067
|
+
summary: outcome.summary
|
|
2068
|
+
};
|
|
2069
|
+
writeStatus();
|
|
2070
|
+
};
|
|
2071
|
+
return { runId: input.runId, dir, sink, finish };
|
|
2072
|
+
}
|
|
2073
|
+
function isAlive(pid) {
|
|
2074
|
+
try {
|
|
2075
|
+
process.kill(pid, 0);
|
|
2076
|
+
return true;
|
|
2077
|
+
} catch (e) {
|
|
2078
|
+
return e.code === "EPERM";
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
function readRunStatus(runId) {
|
|
2082
|
+
try {
|
|
2083
|
+
const raw = readFileSync(join(runsHome(), runId, "status.json"), "utf8");
|
|
2084
|
+
const s = JSON.parse(raw);
|
|
2085
|
+
s.alive = s.status === "running" ? isAlive(s.pid) : false;
|
|
2086
|
+
return s;
|
|
2087
|
+
} catch {
|
|
2088
|
+
return void 0;
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
function listRuns() {
|
|
2092
|
+
const base = runsHome();
|
|
2093
|
+
if (!existsSync(base)) return [];
|
|
2094
|
+
const out = [];
|
|
2095
|
+
for (const id of readdirSync(base)) {
|
|
2096
|
+
const s = readRunStatus(id);
|
|
2097
|
+
if (s) out.push(s);
|
|
2098
|
+
}
|
|
2099
|
+
return out.sort((a, b) => b.startedAt - a.startedAt);
|
|
2100
|
+
}
|
|
2101
|
+
function runEventsPath(runId) {
|
|
2102
|
+
return join(runsHome(), runId, "events.jsonl");
|
|
2103
|
+
}
|
|
2104
|
+
function formatEvent(event) {
|
|
2105
|
+
const at = event.path.length ? `${event.path.join(" \u203A ")} ` : "";
|
|
2106
|
+
switch (event.kind) {
|
|
2107
|
+
case "loop:start":
|
|
2108
|
+
return `${at}\u25B8 loop${event.max ? ` (max ${event.max})` : ""}`;
|
|
2109
|
+
case "dag:start":
|
|
2110
|
+
return `${at}\u25B8 dag (${event.nodes.length} nodes)`;
|
|
2111
|
+
case "loop:iteration":
|
|
2112
|
+
return `${at}\xB7 iteration ${event.iteration}`;
|
|
2113
|
+
case "loop:condition":
|
|
2114
|
+
return `${at}\xB7 ${event.which} ${event.result.met ? "met" : "not met"}: ${event.result.reason}`;
|
|
2115
|
+
case "loop:review":
|
|
2116
|
+
return `${at}\xB7 review: ${event.outcome.status}`;
|
|
2117
|
+
case "loop:end":
|
|
2118
|
+
return `${at}\u25C2 ${event.outcome.status} (${event.iterations} iter)`;
|
|
2119
|
+
case "dag:node":
|
|
2120
|
+
return `${at}\xB7 node ${event.node}: ${event.phase}${event.outcome ? ` (${event.outcome.status})` : ""}`;
|
|
2121
|
+
case "dag:end":
|
|
2122
|
+
return `${at}\u25C2 dag ${event.outcome.status}`;
|
|
2123
|
+
case "job:start":
|
|
2124
|
+
return `${at}\u2022 ${event.label}`;
|
|
2125
|
+
case "job:end":
|
|
2126
|
+
return `${at}\u2022 ${event.label}: ${event.outcome.status}`;
|
|
2127
|
+
case "engine:tool":
|
|
2128
|
+
return `${at} tool ${event.name} ${event.phase}`;
|
|
2129
|
+
case "engine:usage":
|
|
2130
|
+
return `${at} ${event.model}: ${event.usage.inputTokens}/${event.usage.outputTokens} tok`;
|
|
2131
|
+
case "limit:wait":
|
|
2132
|
+
return `${at}\u23F8 limit ${event.code}: waiting ${Math.round(event.waitMs / 1e3)}s`;
|
|
2133
|
+
case "limit:pause":
|
|
2134
|
+
return `${at}\u23F8 paused (${event.code}): ${event.reason}`;
|
|
2135
|
+
case "log":
|
|
2136
|
+
return `${at}${event.message}`;
|
|
2137
|
+
case "error":
|
|
2138
|
+
return `${at}\u2717 ${event.code}: ${event.message}`;
|
|
2139
|
+
default:
|
|
2140
|
+
return `${at}${event.kind}`;
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
var NOISE2 = /* @__PURE__ */ new Set([
|
|
2144
|
+
"engine:text",
|
|
2145
|
+
"engine:thinking"
|
|
2146
|
+
]);
|
|
1978
2147
|
var CHECKPOINT_AT = /* @__PURE__ */ new Set([
|
|
1979
2148
|
"loop:iteration",
|
|
1980
2149
|
"loop:end",
|
|
@@ -1989,7 +2158,7 @@ function makeRecorder(path) {
|
|
|
1989
2158
|
ensureDir2(path);
|
|
1990
2159
|
writeFileSync(path, "");
|
|
1991
2160
|
return (event) => {
|
|
1992
|
-
if (
|
|
2161
|
+
if (NOISE2.has(event.kind)) return;
|
|
1993
2162
|
try {
|
|
1994
2163
|
appendFileSync(path, `${JSON.stringify(event)}
|
|
1995
2164
|
`);
|
|
@@ -2051,10 +2220,23 @@ async function run(job, options = {}) {
|
|
|
2051
2220
|
});
|
|
2052
2221
|
}
|
|
2053
2222
|
}
|
|
2223
|
+
const dir = options.cwd ?? process.cwd();
|
|
2054
2224
|
const sinks = [];
|
|
2055
2225
|
if (options.recordTo) sinks.push(makeRecorder(options.recordTo));
|
|
2056
2226
|
if (options.checkpoint)
|
|
2057
2227
|
sinks.push(makeCheckpointer(options.checkpoint, initialState));
|
|
2228
|
+
let supervisor;
|
|
2229
|
+
if (options.supervise) {
|
|
2230
|
+
const shape = jobMeta(job);
|
|
2231
|
+
const title = shape?.name ?? "run";
|
|
2232
|
+
supervisor = startSupervisor({
|
|
2233
|
+
runId: newRunId(title),
|
|
2234
|
+
cwd: dir,
|
|
2235
|
+
title,
|
|
2236
|
+
shape
|
|
2237
|
+
});
|
|
2238
|
+
sinks.push(supervisor.sink);
|
|
2239
|
+
}
|
|
2058
2240
|
const emit = (event) => {
|
|
2059
2241
|
stats.record(event);
|
|
2060
2242
|
if (budget && event.kind === "engine:usage")
|
|
@@ -2063,7 +2245,6 @@ async function run(job, options = {}) {
|
|
|
2063
2245
|
for (const sink of sinks) sink(event);
|
|
2064
2246
|
};
|
|
2065
2247
|
const resolveEngine = (ref) => registry.create(ref, defaultEngine);
|
|
2066
|
-
const dir = options.cwd ?? process.cwd();
|
|
2067
2248
|
const workspace = {
|
|
2068
2249
|
dir,
|
|
2069
2250
|
branch: await currentBranch({ cwd: dir, signal: controller.signal })
|
|
@@ -2081,18 +2262,21 @@ async function run(job, options = {}) {
|
|
|
2081
2262
|
message: `environment "${options.environment.name}" failed to start: ${error.message}`,
|
|
2082
2263
|
code: error.code
|
|
2083
2264
|
});
|
|
2265
|
+
const failOutcome = {
|
|
2266
|
+
status: "fail",
|
|
2267
|
+
summary: `environment failed to start: ${error.message}`,
|
|
2268
|
+
error
|
|
2269
|
+
};
|
|
2270
|
+
supervisor?.finish(failOutcome);
|
|
2084
2271
|
return {
|
|
2085
|
-
outcome:
|
|
2086
|
-
status: "fail",
|
|
2087
|
-
summary: `environment failed to start: ${error.message}`,
|
|
2088
|
-
error
|
|
2089
|
-
},
|
|
2272
|
+
outcome: failOutcome,
|
|
2090
2273
|
stats: stats.snapshot(),
|
|
2091
2274
|
budget: budget ? {
|
|
2092
2275
|
limit: budget.limit,
|
|
2093
2276
|
spent: budget.spent(),
|
|
2094
2277
|
remaining: budget.remaining()
|
|
2095
|
-
} : void 0
|
|
2278
|
+
} : void 0,
|
|
2279
|
+
runId: supervisor?.runId
|
|
2096
2280
|
};
|
|
2097
2281
|
}
|
|
2098
2282
|
}
|
|
@@ -2133,6 +2317,7 @@ async function run(job, options = {}) {
|
|
|
2133
2317
|
}
|
|
2134
2318
|
if (outcome.status === "paused" && options.checkpoint)
|
|
2135
2319
|
flushCheckpoint(options.checkpoint, initialState);
|
|
2320
|
+
supervisor?.finish(outcome);
|
|
2136
2321
|
return {
|
|
2137
2322
|
outcome,
|
|
2138
2323
|
stats: stats.snapshot(),
|
|
@@ -2140,7 +2325,8 @@ async function run(job, options = {}) {
|
|
|
2140
2325
|
limit: budget.limit,
|
|
2141
2326
|
spent: budget.spent(),
|
|
2142
2327
|
remaining: budget.remaining()
|
|
2143
|
-
} : void 0
|
|
2328
|
+
} : void 0,
|
|
2329
|
+
runId: supervisor?.runId
|
|
2144
2330
|
};
|
|
2145
2331
|
}
|
|
2146
2332
|
function exitCodeFor(outcome) {
|
|
@@ -2158,6 +2344,6 @@ function exitCodeFor(outcome) {
|
|
|
2158
2344
|
}
|
|
2159
2345
|
}
|
|
2160
2346
|
|
|
2161
|
-
export { Budget, EXIT_PAUSED, EngineRegistry, GhForge, MockForge, Stats, addWorktree, agentCheck, agentJob, all, always, any, appendLedger, appendPrompt, bodyPassed, buildChecksArgs, buildCreateArgs, buildEditArgs, buildMergeArgs, buildViewArgs, childContext, commandSucceeds, commit, commitJob, compactLedger, composeCommitBody, conflictedFiles, consolidate, consolidateJob, currentBranch, defineAgent, defineSkill, deleteBranch, describeConditions, ensureIgnored, exitCodeFor, fnJob, forgeChecks, fromFile, gateJob, groundingText, hasStagedChanges, headSha, isDirty, isForge, isRepo, jobMeta, kickback, ledgerPath, log, loop, mergeAbort, mergeBranch, mergeNoCommit, minConfidence, never, not, predicate, promptPath, push, quorum, readLedger, readPrompt, removeWorktree, renderPlan, resetLedger, resetPrompt, resolveSystem, retrieveLedger, run, setMeta, stageAll, toCondition };
|
|
2162
|
-
//# sourceMappingURL=chunk-
|
|
2163
|
-
//# sourceMappingURL=chunk-
|
|
2347
|
+
export { Budget, EXIT_PAUSED, EngineRegistry, GhForge, MockForge, Stats, addWorktree, agentCheck, agentJob, all, always, any, appendLedger, appendPrompt, bodyPassed, buildChecksArgs, buildCreateArgs, buildEditArgs, buildMergeArgs, buildViewArgs, childContext, commandSucceeds, commit, commitJob, compactLedger, composeCommitBody, conflictedFiles, consolidate, consolidateJob, currentBranch, defineAgent, defineSkill, deleteBranch, describeConditions, ensureIgnored, exitCodeFor, fnJob, forgeChecks, formatEvent, fromFile, gateJob, groundingText, hasStagedChanges, headSha, isDirty, isForge, isRepo, jobMeta, kickback, ledgerPath, listRuns, log, loop, mergeAbort, mergeBranch, mergeNoCommit, minConfidence, never, not, predicate, promptPath, push, quorum, readLedger, readPrompt, readRunStatus, removeWorktree, renderPlan, resetLedger, resetPrompt, resolveSystem, retrieveLedger, run, runEventsPath, runsHome, setMeta, stageAll, toCondition };
|
|
2348
|
+
//# sourceMappingURL=chunk-6BDWTFOS.js.map
|
|
2349
|
+
//# sourceMappingURL=chunk-6BDWTFOS.js.map
|