@agent-loom/loom 1.0.2 → 1.0.3
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 +69 -0
- package/dist/acp/client.d.ts +182 -0
- package/dist/acp/client.d.ts.map +1 -0
- package/dist/acp/client.js +432 -0
- package/dist/acp/client.js.map +1 -0
- package/dist/acp/index.d.ts +5 -0
- package/dist/acp/index.d.ts.map +1 -0
- package/dist/acp/index.js +3 -0
- package/dist/acp/index.js.map +1 -0
- package/dist/acp/run.d.ts +41 -0
- package/dist/acp/run.d.ts.map +1 -0
- package/dist/acp/run.js +32 -0
- package/dist/acp/run.js.map +1 -0
- package/dist/apply.d.ts +15 -6
- package/dist/apply.d.ts.map +1 -1
- package/dist/apply.js +78 -49
- package/dist/apply.js.map +1 -1
- package/dist/chat/chat.d.ts +108 -0
- package/dist/chat/chat.d.ts.map +1 -0
- package/dist/chat/chat.js +221 -0
- package/dist/chat/chat.js.map +1 -0
- package/dist/chat/discovery.d.ts +30 -0
- package/dist/chat/discovery.d.ts.map +1 -0
- package/dist/chat/discovery.js +68 -0
- package/dist/chat/discovery.js.map +1 -0
- package/dist/chat/frontmatter.d.ts +12 -0
- package/dist/chat/frontmatter.d.ts.map +1 -0
- package/dist/chat/frontmatter.js +11 -0
- package/dist/chat/frontmatter.js.map +1 -0
- package/dist/chat/index.d.ts +16 -0
- package/dist/chat/index.d.ts.map +1 -0
- package/dist/chat/index.js +11 -0
- package/dist/chat/index.js.map +1 -0
- package/dist/chat/registry.d.ts +73 -0
- package/dist/chat/registry.d.ts.map +1 -0
- package/dist/chat/registry.js +118 -0
- package/dist/chat/registry.js.map +1 -0
- package/dist/chat/resolve-agent.d.ts +39 -0
- package/dist/chat/resolve-agent.d.ts.map +1 -0
- package/dist/chat/resolve-agent.js +36 -0
- package/dist/chat/resolve-agent.js.map +1 -0
- package/dist/chat/suggest.d.ts +20 -0
- package/dist/chat/suggest.d.ts.map +1 -0
- package/dist/chat/suggest.js +55 -0
- package/dist/chat/suggest.js.map +1 -0
- package/dist/cli.js +627 -75
- package/dist/cli.js.map +1 -1
- package/dist/clone.d.ts +21 -3
- package/dist/clone.d.ts.map +1 -1
- package/dist/clone.js +240 -12
- package/dist/clone.js.map +1 -1
- package/dist/copilot/mcp.d.ts +48 -0
- package/dist/copilot/mcp.d.ts.map +1 -0
- package/dist/copilot/mcp.js +146 -0
- package/dist/copilot/mcp.js.map +1 -0
- package/dist/copilot/resolve.d.ts +33 -0
- package/dist/copilot/resolve.d.ts.map +1 -0
- package/dist/copilot/resolve.js +96 -0
- package/dist/copilot/resolve.js.map +1 -0
- package/dist/copilot/spawn.d.ts +51 -0
- package/dist/copilot/spawn.d.ts.map +1 -0
- package/dist/copilot/spawn.js +132 -0
- package/dist/copilot/spawn.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/launch/index.d.ts +10 -0
- package/dist/launch/index.d.ts.map +1 -0
- package/dist/launch/index.js +9 -0
- package/dist/launch/index.js.map +1 -0
- package/dist/launch/stage.d.ts +62 -0
- package/dist/launch/stage.d.ts.map +1 -0
- package/dist/launch/stage.js +108 -0
- package/dist/launch/stage.js.map +1 -0
- package/dist/manifest.d.ts +165 -18
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +980 -225
- package/dist/manifest.js.map +1 -1
- package/dist/renderers/claude.d.ts +5 -0
- package/dist/renderers/claude.d.ts.map +1 -1
- package/dist/renderers/claude.js +17 -3
- package/dist/renderers/claude.js.map +1 -1
- package/dist/renderers/copilot.d.ts +1 -1
- package/dist/renderers/copilot.d.ts.map +1 -1
- package/dist/renderers/copilot.js +205 -22
- package/dist/renderers/copilot.js.map +1 -1
- package/dist/repo-clone.js +17 -11
- package/dist/repo-clone.js.map +1 -1
- package/dist/resolve-template.d.ts +12 -4
- package/dist/resolve-template.d.ts.map +1 -1
- package/dist/resolve-template.js +39 -8
- package/dist/resolve-template.js.map +1 -1
- package/dist/run/index.d.ts +4 -0
- package/dist/run/index.d.ts.map +1 -0
- package/dist/run/index.js +2 -0
- package/dist/run/index.js.map +1 -0
- package/dist/run/run.d.ts +143 -0
- package/dist/run/run.d.ts.map +1 -0
- package/dist/run/run.js +406 -0
- package/dist/run/run.js.map +1 -0
- package/dist/search-registry.d.ts +10 -3
- package/dist/search-registry.d.ts.map +1 -1
- package/dist/search-registry.js +16 -16
- package/dist/search-registry.js.map +1 -1
- package/dist/sessions/index.d.ts +16 -0
- package/dist/sessions/index.d.ts.map +1 -0
- package/dist/sessions/index.js +15 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/sessions/store.d.ts +56 -0
- package/dist/sessions/store.d.ts.map +1 -0
- package/dist/sessions/store.js +220 -0
- package/dist/sessions/store.js.map +1 -0
- package/dist/sessions/types.d.ts +62 -0
- package/dist/sessions/types.d.ts.map +1 -0
- package/dist/sessions/types.js +5 -0
- package/dist/sessions/types.js.map +1 -0
- package/dist/skill-fetcher.d.ts.map +1 -1
- package/dist/skill-fetcher.js +5 -6
- package/dist/skill-fetcher.js.map +1 -1
- package/dist/types.d.ts +123 -41
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -1
- package/dist/util/binary-cache.d.ts +53 -0
- package/dist/util/binary-cache.d.ts.map +1 -0
- package/dist/util/binary-cache.js +211 -0
- package/dist/util/binary-cache.js.map +1 -0
- package/dist/util/frontmatter.d.ts +53 -0
- package/dist/util/frontmatter.d.ts.map +1 -0
- package/dist/util/frontmatter.js +85 -0
- package/dist/util/frontmatter.js.map +1 -0
- package/dist/util/loom-home.d.ts +19 -0
- package/dist/util/loom-home.d.ts.map +1 -0
- package/dist/util/loom-home.js +37 -0
- package/dist/util/loom-home.js.map +1 -0
- package/dist/util/workspace-folder.d.ts +29 -0
- package/dist/util/workspace-folder.d.ts.map +1 -0
- package/dist/util/workspace-folder.js +43 -0
- package/dist/util/workspace-folder.js.map +1 -0
- package/dist/validate.d.ts +7 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +90 -17
- package/dist/validate.js.map +1 -1
- package/package.json +31 -2
package/dist/run/run.js
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `loom run` — headless single-agent execution.
|
|
3
|
+
*
|
|
4
|
+
* Designed for orchestrators (scripts, CI, supervisor processes) that need to:
|
|
5
|
+
* 1. Apply a template + agent into a workspace
|
|
6
|
+
* 2. Run the agent non-interactively against a prompt
|
|
7
|
+
* 3. Collect artifacts (report / session / progress) and an exit code
|
|
8
|
+
*
|
|
9
|
+
* This is the headless counterpart to `loom <agent>` (interactive chat).
|
|
10
|
+
* Where chat inherits the parent tty and lets a human talk to Copilot,
|
|
11
|
+
* `run` captures stdout, honors a hard timeout, and returns structured results.
|
|
12
|
+
*/
|
|
13
|
+
import { spawn, execSync } from 'node:child_process';
|
|
14
|
+
import { existsSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
|
|
15
|
+
import { mkdir, writeFile, readFile, readdir, rm } from 'node:fs/promises';
|
|
16
|
+
import { join, resolve } from 'node:path';
|
|
17
|
+
import { platform } from 'node:os';
|
|
18
|
+
import { applyTemplate } from '../apply.js';
|
|
19
|
+
import { resolveTemplateSource } from '../resolve-template.js';
|
|
20
|
+
import { resolveCopilotBin, resolveAgentDisplayNameFromPath } from '../copilot/resolve.js';
|
|
21
|
+
import { buildCopilotMcpConfigJson, writeMcpConfig } from '../copilot/mcp.js';
|
|
22
|
+
import { spawnCaptured } from '../copilot/spawn.js';
|
|
23
|
+
/**
|
|
24
|
+
* Default timeout for `run()` when callers don't supply one (5 minutes).
|
|
25
|
+
* Exported so `loom run` and SDK consumers stay in sync — programmatic
|
|
26
|
+
* callers can omit `timeoutMs` and still get a sane bound.
|
|
27
|
+
*/
|
|
28
|
+
export const DEFAULT_RUN_TIMEOUT_MS = 5 * 60 * 1000;
|
|
29
|
+
/**
|
|
30
|
+
* Filename of the cooperative lock dropped into `workDir` for the duration
|
|
31
|
+
* of a `run()` invocation. Contains the holder's PID + start time so callers
|
|
32
|
+
* can diagnose conflicts. Removed on success AND on failure.
|
|
33
|
+
*/
|
|
34
|
+
const RUN_LOCK_FILENAME = '.loom-run.lock';
|
|
35
|
+
/**
|
|
36
|
+
* Run a single agent headlessly and return structured results.
|
|
37
|
+
*
|
|
38
|
+
* Steps: workspace prep -> shared-repo junctions -> applyTemplate ->
|
|
39
|
+
* mcp config merge -> task data + prompt -> spawn copilot (captured stdio)
|
|
40
|
+
* -> artifact collection -> optional cleanup.
|
|
41
|
+
*
|
|
42
|
+
* Per-`workDir` exclusivity is enforced by a cooperative lock file
|
|
43
|
+
* (`.loom-run.lock`). Concurrent invocations against the same `workDir`
|
|
44
|
+
* fail fast instead of racing on `prompt.txt` / `mcp-config.json` /
|
|
45
|
+
* `SESSION-*.md` writes. The lock is released in a `finally` so a crashed
|
|
46
|
+
* process doesn't strand the workspace; stale locks (PID no longer alive)
|
|
47
|
+
* are detected and reclaimed automatically.
|
|
48
|
+
*/
|
|
49
|
+
export async function run(options) {
|
|
50
|
+
const startTime = Date.now();
|
|
51
|
+
const workDir = resolve(options.workDir);
|
|
52
|
+
const stdoutCap = options.stdoutTailBytes ?? 16 * 1024;
|
|
53
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_RUN_TIMEOUT_MS;
|
|
54
|
+
const warnings = [];
|
|
55
|
+
// 1. Workspace setup
|
|
56
|
+
await mkdir(workDir, { recursive: true });
|
|
57
|
+
// 1a. Acquire the per-workDir lock BEFORE we touch anything else, so two
|
|
58
|
+
// concurrent runs don't cross-contaminate `prompt.txt` / `mcp-config.json`
|
|
59
|
+
// / `SESSION-*.md`. Wrap the rest of the function in try/finally so the
|
|
60
|
+
// lock always releases.
|
|
61
|
+
acquireRunLock(workDir, warnings, options.writeFn);
|
|
62
|
+
try {
|
|
63
|
+
// `git init` is opt-in: forced via `initGit`, or auto-enabled when shared
|
|
64
|
+
// repos are present (some agents walk parent dirs for git context).
|
|
65
|
+
// Skipping by default keeps `run()` from hard-failing on machines without
|
|
66
|
+
// a `git` binary on PATH, and from leaving an empty .git/ behind.
|
|
67
|
+
const wantsGit = options.initGit === true ||
|
|
68
|
+
(options.initGit !== false && Object.keys(options.sharedRepos ?? {}).length > 0);
|
|
69
|
+
if (wantsGit && !existsSync(join(workDir, '.git'))) {
|
|
70
|
+
execSync('git init', { cwd: workDir, stdio: 'pipe' });
|
|
71
|
+
}
|
|
72
|
+
// 2. Shared repos (junctioned in before apply, so --clone-repos auto skips them)
|
|
73
|
+
for (const [name, target] of Object.entries(options.sharedRepos ?? {})) {
|
|
74
|
+
await junctionShared(target, join(workDir, name));
|
|
75
|
+
}
|
|
76
|
+
// 3. Apply template (resolves registry if `template` is a name)
|
|
77
|
+
const resolveFn = options.resolveFn ?? resolveTemplateSource;
|
|
78
|
+
const applyFn = options.applyFn ?? applyTemplate;
|
|
79
|
+
const resolved = await resolveFn(options.template, workDir, undefined);
|
|
80
|
+
await applyFn({
|
|
81
|
+
templateDir: resolved.templateDir,
|
|
82
|
+
registryRoot: resolved.registryRoot,
|
|
83
|
+
outputDir: workDir,
|
|
84
|
+
target: 'copilot',
|
|
85
|
+
cloneRepos: 'auto',
|
|
86
|
+
agents: [options.agent],
|
|
87
|
+
linkFn: junctionShared,
|
|
88
|
+
});
|
|
89
|
+
// 3a. Resolve copilot's --agent value from the rendered agent file's
|
|
90
|
+
// frontmatter `name:`. Copilot CLI identifies agents by their declared
|
|
91
|
+
// display name, NOT by the manifest agent id we passed to applyTemplate.
|
|
92
|
+
// Without this lookup copilot fails with "No such agent: <id>".
|
|
93
|
+
const agentDisplayName = await resolveAgentDisplayName(workDir, options.agent);
|
|
94
|
+
// 4. MCP config: render base + merge extras + remove
|
|
95
|
+
const mcpConfigPath = await composeMcpConfig(workDir, options.mcpExtras, options.mcpRemove);
|
|
96
|
+
// 5. Task data file
|
|
97
|
+
if (options.taskId && options.taskData) {
|
|
98
|
+
await writeFile(join(workDir, `task-${options.taskId}.json`), options.taskData, 'utf-8');
|
|
99
|
+
}
|
|
100
|
+
// 6. Prompt: write to a file so we can pass `-p @<path>` (avoids quoting hell).
|
|
101
|
+
const promptPath = join(workDir, 'prompt.txt');
|
|
102
|
+
const promptText = options.prompt.startsWith('@')
|
|
103
|
+
? await readFile(options.prompt.slice(1), 'utf-8')
|
|
104
|
+
: options.prompt;
|
|
105
|
+
await writeFile(promptPath, promptText, 'utf-8');
|
|
106
|
+
// 7. Spawn copilot with captured stdio + hard timeout
|
|
107
|
+
const sessionPath = options.taskId
|
|
108
|
+
? join(workDir, `SESSION-${options.taskId}.md`)
|
|
109
|
+
: join(workDir, 'SESSION.md');
|
|
110
|
+
const args = [
|
|
111
|
+
'-p', `@${promptPath}`,
|
|
112
|
+
// --allow-all is the umbrella flag: --allow-all-tools + --allow-all-paths
|
|
113
|
+
// + --allow-all-urls. Without --allow-all-paths, copilot denies file reads
|
|
114
|
+
// in subdirectories (e.g. shared-repo junctions) and, with --no-ask-user,
|
|
115
|
+
// cannot prompt — so the agent silently fails. This is the standard
|
|
116
|
+
// setting for headless orchestrated runs.
|
|
117
|
+
'--allow-all',
|
|
118
|
+
'--no-ask-user',
|
|
119
|
+
'--add-dir', workDir,
|
|
120
|
+
'--agent', agentDisplayName,
|
|
121
|
+
'--share', sessionPath,
|
|
122
|
+
];
|
|
123
|
+
if (mcpConfigPath)
|
|
124
|
+
args.push('--additional-mcp-config', `@${mcpConfigPath}`);
|
|
125
|
+
if (options.model)
|
|
126
|
+
args.push('--model', options.model);
|
|
127
|
+
const spawnImpl = options.spawnFn ?? spawn;
|
|
128
|
+
const bin = resolveCopilotBin();
|
|
129
|
+
const { exitCode, stdout, timedOut, stdoutTail, stderrTail } = await spawnCaptured({ bin, args, cwd: workDir }, {
|
|
130
|
+
timeoutMs: timeoutMs > 0 ? timeoutMs : undefined,
|
|
131
|
+
bufferCapBytes: options.captureBytesCap,
|
|
132
|
+
tailBytes: stdoutCap,
|
|
133
|
+
spawnFn: spawnImpl,
|
|
134
|
+
});
|
|
135
|
+
// 8. Artifact collection
|
|
136
|
+
const artifacts = await collectArtifacts(workDir, options.taskId, sessionPath, stdout, options.reportSearchDirs);
|
|
137
|
+
const result = {
|
|
138
|
+
exitCode,
|
|
139
|
+
durationMs: Date.now() - startTime,
|
|
140
|
+
workspacePath: workDir,
|
|
141
|
+
timedOut,
|
|
142
|
+
session: artifacts.session,
|
|
143
|
+
report: artifacts.report,
|
|
144
|
+
progress: artifacts.progress,
|
|
145
|
+
artifactPaths: {
|
|
146
|
+
session: existsSync(sessionPath) ? sessionPath : undefined,
|
|
147
|
+
report: artifacts.reportPath,
|
|
148
|
+
progress: artifacts.progressPath,
|
|
149
|
+
mcpConfig: mcpConfigPath ?? undefined,
|
|
150
|
+
prompt: promptPath,
|
|
151
|
+
},
|
|
152
|
+
stdoutTail,
|
|
153
|
+
stderrTail,
|
|
154
|
+
warnings,
|
|
155
|
+
};
|
|
156
|
+
// 9. Cleanup (only on success — failures are kept for inspection)
|
|
157
|
+
if (options.cleanup && exitCode === 0 && !timedOut) {
|
|
158
|
+
// Release the lock BEFORE rm()ing the workdir so we don't wipe a file
|
|
159
|
+
// we still hold. (rm-recursive would delete it anyway, but explicit
|
|
160
|
+
// release keeps the contract clean.)
|
|
161
|
+
releaseRunLock(workDir);
|
|
162
|
+
await rm(workDir, { recursive: true, force: true });
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
// Idempotent — safe to call even if cleanup already removed the lock.
|
|
169
|
+
releaseRunLock(workDir);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Maximum age of a lock file (in ms) before we consider it stale even if
|
|
174
|
+
* the holder PID still appears alive. Defends against PID reuse on
|
|
175
|
+
* long-uptime hosts (N2): a fresh, unrelated process happening to land on
|
|
176
|
+
* the same pid would otherwise block us indefinitely.
|
|
177
|
+
*
|
|
178
|
+
* Tuned generously — 24h is well above the longest plausible legitimate
|
|
179
|
+
* `run()` (the in-product `timeoutMs` defaults to 5 minutes; even the most
|
|
180
|
+
* patient orchestrator caps out long before a day).
|
|
181
|
+
*/
|
|
182
|
+
const LOCK_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
183
|
+
/**
|
|
184
|
+
* Read and parse the lock holder JSON. Returns `raw: ''` when the file is
|
|
185
|
+
* missing (ENOENT); returns a partially-populated holder when the file is
|
|
186
|
+
* corrupt (callers treat missing `pid` as "stale" and reclaim). Other I/O
|
|
187
|
+
* errors (EACCES, EBUSY, etc.) are rethrown so callers fail loudly rather
|
|
188
|
+
* than mistakenly treating an unreadable lock as absent.
|
|
189
|
+
*/
|
|
190
|
+
function readHolder(lockPath) {
|
|
191
|
+
let raw = '';
|
|
192
|
+
try {
|
|
193
|
+
raw = readFileSync(lockPath, 'utf-8');
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
if (err.code === 'ENOENT') {
|
|
197
|
+
return { raw: '' };
|
|
198
|
+
}
|
|
199
|
+
throw err;
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
const parsed = JSON.parse(raw);
|
|
203
|
+
return {
|
|
204
|
+
raw,
|
|
205
|
+
pid: typeof parsed.pid === 'number' ? parsed.pid : undefined,
|
|
206
|
+
startedAt: typeof parsed.startedAt === 'string' ? parsed.startedAt : undefined,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return { raw };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Acquire the per-workDir cooperative lock. Throws a descriptive error
|
|
215
|
+
* (including the holder's PID) if another live `run()` already holds it.
|
|
216
|
+
* Detects and reclaims stale locks left by crashed processes; also
|
|
217
|
+
* reclaims locks older than `LOCK_MAX_AGE_MS` even when the PID looks
|
|
218
|
+
* alive (PID-reuse defense, N2).
|
|
219
|
+
*
|
|
220
|
+
* Non-fatal advisories (e.g. "reclaimed stale lock") are pushed to the
|
|
221
|
+
* shared `warnings` array. SDK consumers see them via `RunResult.warnings`;
|
|
222
|
+
* if `LOOM_RUN_DEBUG` is truthy they're also mirrored to stderr.
|
|
223
|
+
*
|
|
224
|
+
* `writeFn` is an optional injection point for tests to drive the EEXIST
|
|
225
|
+
* retry branch deterministically.
|
|
226
|
+
*/
|
|
227
|
+
function acquireRunLock(workDir, warnings, writeFn = writeFileSync) {
|
|
228
|
+
const lockPath = join(workDir, RUN_LOCK_FILENAME);
|
|
229
|
+
// Use writeFileSync with `wx` to atomically create-and-fail-if-exists.
|
|
230
|
+
const tryWrite = () => {
|
|
231
|
+
try {
|
|
232
|
+
writeFn(lockPath, JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString() }), { flag: 'wx', encoding: 'utf-8' });
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
if (err.code === 'EEXIST')
|
|
237
|
+
return false;
|
|
238
|
+
throw err;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
if (tryWrite())
|
|
242
|
+
return;
|
|
243
|
+
// EEXIST on first attempt. Reconcile the observed holder once, then retry
|
|
244
|
+
// atomically. If the file vanished before we could read it, do not remove
|
|
245
|
+
// anything; just retry and let `wx` decide whether the lock is still free.
|
|
246
|
+
const holder = readHolder(lockPath);
|
|
247
|
+
if (holder.raw === '') {
|
|
248
|
+
if (tryWrite())
|
|
249
|
+
return;
|
|
250
|
+
throwConcurrentLockError(workDir, lockPath);
|
|
251
|
+
}
|
|
252
|
+
const pidAlive = holder.pid !== undefined && isProcessAlive(holder.pid);
|
|
253
|
+
const lockAgeMs = holder.startedAt ? Date.now() - Date.parse(holder.startedAt) : Infinity;
|
|
254
|
+
const lockAgedOut = !Number.isFinite(lockAgeMs) || lockAgeMs > LOCK_MAX_AGE_MS;
|
|
255
|
+
if (pidAlive && !lockAgedOut) {
|
|
256
|
+
throw new Error(`loom run: another run is already in progress for ${workDir} ` +
|
|
257
|
+
`(holder PID ${holder.pid}). Wait for it to finish or remove ` +
|
|
258
|
+
`${RUN_LOCK_FILENAME} manually if you're certain it's stale.`);
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
rmSync(lockPath, { force: true });
|
|
262
|
+
}
|
|
263
|
+
catch { /* ignore */ }
|
|
264
|
+
const reason = pidAlive
|
|
265
|
+
? `lock older than ${Math.round(LOCK_MAX_AGE_MS / 3_600_000)}h, treating PID as reused`
|
|
266
|
+
: 'holder PID no longer alive';
|
|
267
|
+
emitWarning(warnings, `loom run: reclaiming stale lock at ${lockPath} (${reason}; previous holder: ${holder.raw.trim()})`);
|
|
268
|
+
if (tryWrite())
|
|
269
|
+
return;
|
|
270
|
+
throwConcurrentLockError(workDir, lockPath);
|
|
271
|
+
}
|
|
272
|
+
function throwConcurrentLockError(workDir, lockPath) {
|
|
273
|
+
const holder = readHolder(lockPath);
|
|
274
|
+
const holderDetails = holder.pid !== undefined ? ` (holder PID ${holder.pid})` : '';
|
|
275
|
+
throw new Error(`loom run: another run grabbed the lock for ${workDir} concurrently${holderDetails}. ` +
|
|
276
|
+
`Retry once it finishes.`);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Push an advisory onto the warnings buffer. Mirror to stderr only when
|
|
280
|
+
* `LOOM_RUN_DEBUG` is set so SDK consumers don't get unsolicited stderr
|
|
281
|
+
* from the library.
|
|
282
|
+
*/
|
|
283
|
+
function emitWarning(warnings, message) {
|
|
284
|
+
warnings.push(message);
|
|
285
|
+
if (process.env.LOOM_RUN_DEBUG) {
|
|
286
|
+
console.error(message);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/** Idempotent — best-effort removal of the per-workDir lock. */
|
|
290
|
+
function releaseRunLock(workDir) {
|
|
291
|
+
const lockPath = join(workDir, RUN_LOCK_FILENAME);
|
|
292
|
+
try {
|
|
293
|
+
rmSync(lockPath, { force: true });
|
|
294
|
+
}
|
|
295
|
+
catch { /* ignore */ }
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* `process.kill(pid, 0)` is the canonical liveness probe — it sends no
|
|
299
|
+
* signal but throws ESRCH when the process is gone. We additionally treat
|
|
300
|
+
* EPERM as "alive" (we can see it but lack permission to signal it).
|
|
301
|
+
*/
|
|
302
|
+
function isProcessAlive(pid) {
|
|
303
|
+
if (pid <= 0)
|
|
304
|
+
return false;
|
|
305
|
+
try {
|
|
306
|
+
process.kill(pid, 0);
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
catch (err) {
|
|
310
|
+
return err.code === 'EPERM';
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
async function junctionShared(target, linkPath) {
|
|
314
|
+
if (existsSync(linkPath))
|
|
315
|
+
return;
|
|
316
|
+
if (!existsSync(target))
|
|
317
|
+
return;
|
|
318
|
+
if (platform() === 'win32') {
|
|
319
|
+
execSync(`cmd /c mklink /J "${linkPath}" "${target}"`, { stdio: 'pipe' });
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
const { symlink } = await import('node:fs/promises');
|
|
323
|
+
await symlink(target, linkPath, 'junction');
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Resolve the value to pass to copilot's `--agent` flag.
|
|
328
|
+
*
|
|
329
|
+
* Copilot CLI identifies agents by the frontmatter `name:` field, not by
|
|
330
|
+
* the manifest agent id we passed to applyTemplate. After applyTemplate
|
|
331
|
+
* runs, exactly one .agent.md file should exist under .github/agents/
|
|
332
|
+
* (because we filter via `agents: [options.agent]`). Read its frontmatter
|
|
333
|
+
* `name:` and return that.
|
|
334
|
+
*
|
|
335
|
+
* Fall back to the manifest id if anything goes wrong -- the spawn will
|
|
336
|
+
* fail with copilot's own "No such agent" error, now visible via the
|
|
337
|
+
* stderr capture added in this PR.
|
|
338
|
+
*/
|
|
339
|
+
async function resolveAgentDisplayName(workDir, agentId) {
|
|
340
|
+
const agentsDir = join(workDir, '.github', 'agents');
|
|
341
|
+
if (!existsSync(agentsDir))
|
|
342
|
+
return agentId;
|
|
343
|
+
const entries = await readdir(agentsDir);
|
|
344
|
+
const agentFiles = entries.filter((e) => e.endsWith('.agent.md'));
|
|
345
|
+
if (agentFiles.length === 0)
|
|
346
|
+
return agentId;
|
|
347
|
+
// Prefer a file whose slug matches the agent id; otherwise take the first.
|
|
348
|
+
const slug = agentId.toLowerCase();
|
|
349
|
+
const preferred = agentFiles.find((f) => f.toLowerCase().startsWith(`${slug}.`)) ??
|
|
350
|
+
agentFiles.find((f) => f.toLowerCase().includes(slug)) ??
|
|
351
|
+
agentFiles[0];
|
|
352
|
+
// Reuse the shared copilot/resolve helper to read frontmatter and pick a
|
|
353
|
+
// display name. (Keeping the file-discovery logic local since it depends
|
|
354
|
+
// on workDir layout produced by applyTemplate.)
|
|
355
|
+
return resolveAgentDisplayNameFromPath(join(agentsDir, preferred));
|
|
356
|
+
}
|
|
357
|
+
async function composeMcpConfig(workDir, extras, remove) {
|
|
358
|
+
const baseJson = buildCopilotMcpConfigJson(workDir);
|
|
359
|
+
const path = join(workDir, 'mcp-config.json');
|
|
360
|
+
const wrote = await writeMcpConfig(path, {
|
|
361
|
+
baseConfig: baseJson,
|
|
362
|
+
extras,
|
|
363
|
+
removeServers: remove,
|
|
364
|
+
});
|
|
365
|
+
return wrote ? path : null;
|
|
366
|
+
}
|
|
367
|
+
async function collectArtifacts(workDir, taskId, sessionSharePath, stdoutFallback, extraReportDirs) {
|
|
368
|
+
const result = {};
|
|
369
|
+
// Session: prefer --share file, fall back to stdout.
|
|
370
|
+
try {
|
|
371
|
+
result.session = await readFile(sessionSharePath, 'utf-8');
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
result.session = stdoutFallback || undefined;
|
|
375
|
+
}
|
|
376
|
+
if (!taskId)
|
|
377
|
+
return result;
|
|
378
|
+
// Report: default location is `workDir/REPORT-<taskId>.md`. Callers may
|
|
379
|
+
// pass extra search dirs (`reportSearchDirs`) to honor their own naming
|
|
380
|
+
// conventions; the first match wins.
|
|
381
|
+
const reportCandidates = [
|
|
382
|
+
join(workDir, `REPORT-${taskId}.md`),
|
|
383
|
+
...(extraReportDirs ?? []).map((dir) => join(resolve(workDir, dir), `REPORT-${taskId}.md`)),
|
|
384
|
+
];
|
|
385
|
+
for (const candidate of reportCandidates) {
|
|
386
|
+
try {
|
|
387
|
+
result.report = await readFile(candidate, 'utf-8');
|
|
388
|
+
result.reportPath = candidate;
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
// try next
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// Progress
|
|
396
|
+
const progressPath = join(workDir, `PROGRESS-${taskId}.md`);
|
|
397
|
+
try {
|
|
398
|
+
result.progress = await readFile(progressPath, 'utf-8');
|
|
399
|
+
result.progressPath = progressPath;
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
// not produced
|
|
403
|
+
}
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
406
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/run/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Workflow:
|
|
5
5
|
* 1. Read ~/.loom/config.yaml for registered registries
|
|
6
|
-
* 2.
|
|
6
|
+
* 2. Refresh safe cached clones under .loom/registries/<name>/
|
|
7
7
|
* 3. If not cached, auto-clone (shallow) and cache
|
|
8
8
|
* 4. Return paths for search
|
|
9
9
|
*/
|
|
10
|
+
import { type CloneOptions, type GitExecFn, type LegacyExecFn } from './clone.js';
|
|
10
11
|
export interface RegistryResolutionOptions {
|
|
11
12
|
/** Search a specific registry by name (from config.yaml). If omitted, resolves all registries. */
|
|
12
13
|
registryName?: string;
|
|
@@ -14,8 +15,14 @@ export interface RegistryResolutionOptions {
|
|
|
14
15
|
workspaceDir?: string;
|
|
15
16
|
/** Override config directory (for testing — defaults to ~/.loom) */
|
|
16
17
|
configDir?: string;
|
|
17
|
-
/** Inject
|
|
18
|
-
|
|
18
|
+
/** Inject git function for testing (used for auto-clone/refresh) */
|
|
19
|
+
gitFn?: GitExecFn;
|
|
20
|
+
/** @deprecated Use gitFn for argument-array based injection. */
|
|
21
|
+
execFn?: LegacyExecFn;
|
|
22
|
+
/** Inject function to list available gh accounts (for testing clone retry behavior) */
|
|
23
|
+
listGhAccountsFn?: CloneOptions['listGhAccountsFn'];
|
|
24
|
+
/** Inject function to switch gh account (for testing clone retry behavior) */
|
|
25
|
+
switchGhAccountFn?: CloneOptions['switchGhAccountFn'];
|
|
19
26
|
/** Also check workspace root for index.yaml (workspace is itself a registry) */
|
|
20
27
|
includeWorkspaceRoot?: boolean;
|
|
21
28
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-registry.d.ts","sourceRoot":"","sources":["../src/search-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"search-registry.d.ts","sourceRoot":"","sources":["../src/search-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,EAAiB,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAOjG,MAAM,WAAW,yBAAyB;IACxC,kGAAkG;IAClG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,gEAAgE;IAChE,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,uFAAuF;IACvF,gBAAgB,CAAC,EAAE,YAAY,CAAC,kBAAkB,CAAC,CAAC;IACpD,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,YAAY,CAAC,mBAAmB,CAAC,CAAC;IACtD,gFAAgF;IAChF,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAMD;;;;GAIG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CA2ChD"}
|
package/dist/search-registry.js
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Workflow:
|
|
5
5
|
* 1. Read ~/.loom/config.yaml for registered registries
|
|
6
|
-
* 2.
|
|
6
|
+
* 2. Refresh safe cached clones under .loom/registries/<name>/
|
|
7
7
|
* 3. If not cached, auto-clone (shallow) and cache
|
|
8
8
|
* 4. Return paths for search
|
|
9
9
|
*/
|
|
10
|
-
import { readFile, stat
|
|
10
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import { homedir } from 'node:os';
|
|
13
|
-
import { execSync } from 'node:child_process';
|
|
14
13
|
import yaml from 'js-yaml';
|
|
14
|
+
import { cloneRegistry } from './clone.js';
|
|
15
15
|
// =============================================================================
|
|
16
16
|
// Public API
|
|
17
17
|
// =============================================================================
|
|
@@ -21,7 +21,7 @@ import yaml from 'js-yaml';
|
|
|
21
21
|
* Returns an array of { name, path } suitable for passing to searchTemplates().
|
|
22
22
|
*/
|
|
23
23
|
export async function resolveRegistryIndexPaths(options) {
|
|
24
|
-
const { registryName, workspaceDir = process.cwd(), configDir = join(homedir(), '.loom'), execFn, includeWorkspaceRoot, } = options;
|
|
24
|
+
const { registryName, workspaceDir = process.cwd(), configDir = join(homedir(), '.loom'), gitFn, execFn, listGhAccountsFn, switchGhAccountFn, includeWorkspaceRoot, } = options;
|
|
25
25
|
const results = [];
|
|
26
26
|
// Load config
|
|
27
27
|
const config = await loadConfigFrom(configDir);
|
|
@@ -29,7 +29,7 @@ export async function resolveRegistryIndexPaths(options) {
|
|
|
29
29
|
? config.registries.filter((r) => r.name === registryName)
|
|
30
30
|
: config.registries;
|
|
31
31
|
for (const reg of registries) {
|
|
32
|
-
const indexPath = await resolveOneRegistry(reg, workspaceDir, execFn);
|
|
32
|
+
const indexPath = await resolveOneRegistry(reg, workspaceDir, gitFn, execFn, listGhAccountsFn, switchGhAccountFn);
|
|
33
33
|
if (indexPath) {
|
|
34
34
|
results.push({ name: reg.name, path: indexPath });
|
|
35
35
|
}
|
|
@@ -50,19 +50,19 @@ export async function resolveRegistryIndexPaths(options) {
|
|
|
50
50
|
* Try to resolve a single registry to a local index.yaml path.
|
|
51
51
|
* Returns the path if found/cloned, or undefined if unavailable.
|
|
52
52
|
*/
|
|
53
|
-
async function resolveOneRegistry(reg, workspaceDir, execFn) {
|
|
54
|
-
// Check cached clone in .loom/registries/<name>/
|
|
55
|
-
const cachedDir = join(workspaceDir, '.loom', 'registries', reg.name);
|
|
56
|
-
const cachedIndex = join(cachedDir, 'index.yaml');
|
|
57
|
-
if (await fileExists(cachedIndex)) {
|
|
58
|
-
return cachedIndex;
|
|
59
|
-
}
|
|
60
|
-
// Not cached — try to auto-clone
|
|
61
|
-
const exec = execFn ?? ((cmd) => { execSync(cmd, { stdio: 'pipe' }); });
|
|
53
|
+
async function resolveOneRegistry(reg, workspaceDir, gitFn, execFn, listGhAccountsFn, switchGhAccountFn) {
|
|
62
54
|
if (reg.url) {
|
|
63
55
|
try {
|
|
64
|
-
|
|
65
|
-
|
|
56
|
+
const cloneResult = await cloneRegistry({
|
|
57
|
+
workspaceDir,
|
|
58
|
+
url: reg.url,
|
|
59
|
+
name: reg.name,
|
|
60
|
+
gitFn,
|
|
61
|
+
execFn,
|
|
62
|
+
listGhAccountsFn,
|
|
63
|
+
switchGhAccountFn,
|
|
64
|
+
});
|
|
65
|
+
const cachedIndex = join(cloneResult.registryDir, 'index.yaml');
|
|
66
66
|
if (await fileExists(cachedIndex)) {
|
|
67
67
|
return cachedIndex;
|
|
68
68
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-registry.js","sourceRoot":"","sources":["../src/search-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"search-registry.js","sourceRoot":"","sources":["../src/search-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session module — named session directories.
|
|
3
|
+
*
|
|
4
|
+
* Layout:
|
|
5
|
+
* $LOOM_HOME/sessions/<id>/
|
|
6
|
+
* meta.yaml -- typed session metadata (see SessionMeta)
|
|
7
|
+
* transcript.jsonl -- (reserved) append-only turn log; not yet populated
|
|
8
|
+
* workspace/ -- (optional) staged workspace, when launched from a
|
|
9
|
+
* registry-resolved agent rather than an in-tree one
|
|
10
|
+
*
|
|
11
|
+
* `$LOOM_HOME` defaults to `~/.loom` but can be overridden with the
|
|
12
|
+
* `LOOM_HOME` env var (useful for tests + power users).
|
|
13
|
+
*/
|
|
14
|
+
export type { SessionMeta, SessionStatus, SessionRuntime, CreateSessionInput, FinalizeSessionInput, Session, } from './types.js';
|
|
15
|
+
export { sessionsRoot, loomHome, generateSessionId, createSession, finalizeSession, listSessions, readSession, updateSessionMeta, sessionWorkspaceDir, } from './store.js';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sessions/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,YAAY,EACV,WAAW,EACX,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,oBAAoB,EACpB,OAAO,GACR,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,YAAY,EACZ,WAAW,EACX,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session module — named session directories.
|
|
3
|
+
*
|
|
4
|
+
* Layout:
|
|
5
|
+
* $LOOM_HOME/sessions/<id>/
|
|
6
|
+
* meta.yaml -- typed session metadata (see SessionMeta)
|
|
7
|
+
* transcript.jsonl -- (reserved) append-only turn log; not yet populated
|
|
8
|
+
* workspace/ -- (optional) staged workspace, when launched from a
|
|
9
|
+
* registry-resolved agent rather than an in-tree one
|
|
10
|
+
*
|
|
11
|
+
* `$LOOM_HOME` defaults to `~/.loom` but can be overridden with the
|
|
12
|
+
* `LOOM_HOME` env var (useful for tests + power users).
|
|
13
|
+
*/
|
|
14
|
+
export { sessionsRoot, loomHome, generateSessionId, createSession, finalizeSession, listSessions, readSession, updateSessionMeta, sessionWorkspaceDir, } from './store.js';
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sessions/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAUH,OAAO,EACL,YAAY,EACZ,QAAQ,EACR,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,YAAY,EACZ,WAAW,EACX,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session store — filesystem CRUD for session directories.
|
|
3
|
+
*/
|
|
4
|
+
import type { CreateSessionInput, FinalizeSessionInput, Session, SessionMeta } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Resolve `$LOOM_HOME`. Honors `LOOM_HOME`, else `~/.loom`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function loomHome(): string;
|
|
9
|
+
/**
|
|
10
|
+
* Directory containing all sessions. Created lazily by callers.
|
|
11
|
+
*/
|
|
12
|
+
export declare function sessionsRoot(): string;
|
|
13
|
+
/**
|
|
14
|
+
* Session workspace dir — for staged sessions this is populated by the
|
|
15
|
+
* staging code before `chat()` runs.
|
|
16
|
+
*/
|
|
17
|
+
export declare function sessionWorkspaceDir(sessionDir: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Generate an id of the form `YYYYMMDD-HHMMSS-<agentId>[-<slug>]-<rand4>`.
|
|
20
|
+
* Uses UTC to keep comparisons deterministic across TZ changes.
|
|
21
|
+
*
|
|
22
|
+
* The trailing 4-char random suffix prevents collisions when two sessions
|
|
23
|
+
* are created within the same second (the timestamp resolution). Without
|
|
24
|
+
* it, parallel `loom <agent>` invocations could overwrite each other's
|
|
25
|
+
* `meta.yaml`. Callers that supply their own `id` (tests, replays) bypass
|
|
26
|
+
* this entirely.
|
|
27
|
+
*/
|
|
28
|
+
export declare function generateSessionId(agentId: string, options?: {
|
|
29
|
+
slug?: string;
|
|
30
|
+
now?: Date;
|
|
31
|
+
rand?: () => string;
|
|
32
|
+
}): string;
|
|
33
|
+
/**
|
|
34
|
+
* Create a new session on disk. Returns the handle.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createSession(input: CreateSessionInput): Session;
|
|
37
|
+
/**
|
|
38
|
+
* Stamp a session as finished. Idempotent — calling twice overwrites with
|
|
39
|
+
* the latest values.
|
|
40
|
+
*/
|
|
41
|
+
export declare function finalizeSession(session: Session, input: FinalizeSessionInput): Session;
|
|
42
|
+
/**
|
|
43
|
+
* Update a subset of a session's mutable meta fields.
|
|
44
|
+
* Returns null if the session doesn't exist / is unreadable.
|
|
45
|
+
*/
|
|
46
|
+
export declare function updateSessionMeta(id: string, patch: Partial<Pick<SessionMeta, 'name' | 'note' | 'tags' | 'status'>>): Session | null;
|
|
47
|
+
/**
|
|
48
|
+
* Read a single session by id. Returns null if the dir or meta is missing.
|
|
49
|
+
*/
|
|
50
|
+
export declare function readSession(id: string): Session | null;
|
|
51
|
+
/**
|
|
52
|
+
* List all sessions, sorted by startedAt descending (newest first).
|
|
53
|
+
* Silently skips malformed session dirs.
|
|
54
|
+
*/
|
|
55
|
+
export declare function listSessions(): Session[];
|
|
56
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/sessions/store.ts"],"names":[],"mappings":"AAAA;;GAEG;AAcH,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EACpB,OAAO,EACP,WAAW,EACZ,MAAM,YAAY,CAAC;AAKpB;;GAEG;AACH,wBAAgB,QAAQ,IAAI,MAAM,CAIjC;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,MAAM,CAAA;CAAO,GAC/D,MAAM,CAmBR;AAgDD;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAwChE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAUT;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC,GACrE,OAAO,GAAG,IAAI,CAMhB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAOtD;AAED;;;GAGG;AACH,wBAAgB,YAAY,IAAI,OAAO,EAAE,CA0BxC"}
|