@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.
@@ -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 (NOISE.has(event.kind)) return;
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-3BPU34DE.js.map
2163
- //# sourceMappingURL=chunk-3BPU34DE.js.map
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