loreli 0.0.0 → 1.0.0
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/LICENSE +1 -1
- package/README.md +670 -97
- package/bin/loreli.js +89 -0
- package/package.json +74 -14
- package/packages/README.md +101 -0
- package/packages/action/README.md +98 -0
- package/packages/action/src/index.js +656 -0
- package/packages/agent/README.md +517 -0
- package/packages/agent/src/backends/claude.js +287 -0
- package/packages/agent/src/backends/codex.js +278 -0
- package/packages/agent/src/backends/cursor.js +294 -0
- package/packages/agent/src/backends/index.js +329 -0
- package/packages/agent/src/base.js +138 -0
- package/packages/agent/src/cli.js +198 -0
- package/packages/agent/src/factory.js +119 -0
- package/packages/agent/src/index.js +12 -0
- package/packages/agent/src/models.js +141 -0
- package/packages/agent/src/output.js +62 -0
- package/packages/agent/src/session.js +162 -0
- package/packages/agent/src/trace.js +186 -0
- package/packages/config/README.md +833 -0
- package/packages/config/src/defaults.js +134 -0
- package/packages/config/src/index.js +192 -0
- package/packages/config/src/schema.js +273 -0
- package/packages/config/src/validate.js +160 -0
- package/packages/context/README.md +165 -0
- package/packages/context/src/index.js +198 -0
- package/packages/hub/README.md +338 -0
- package/packages/hub/src/base.js +154 -0
- package/packages/hub/src/github.js +1558 -0
- package/packages/hub/src/index.js +79 -0
- package/packages/hub/src/labels.js +48 -0
- package/packages/identity/README.md +288 -0
- package/packages/identity/src/index.js +620 -0
- package/packages/identity/src/themes/avatar.js +217 -0
- package/packages/identity/src/themes/digimon.js +217 -0
- package/packages/identity/src/themes/dragonball.js +217 -0
- package/packages/identity/src/themes/lotr.js +217 -0
- package/packages/identity/src/themes/marvel.js +217 -0
- package/packages/identity/src/themes/pokemon.js +217 -0
- package/packages/identity/src/themes/starwars.js +217 -0
- package/packages/identity/src/themes/transformers.js +217 -0
- package/packages/identity/src/themes/zelda.js +217 -0
- package/packages/knowledge/README.md +237 -0
- package/packages/knowledge/src/index.js +412 -0
- package/packages/log/README.md +93 -0
- package/packages/log/src/index.js +252 -0
- package/packages/marker/README.md +200 -0
- package/packages/marker/src/index.js +184 -0
- package/packages/mcp/README.md +279 -0
- package/packages/mcp/instructions.md +121 -0
- package/packages/mcp/scaffolding/.agents/skills/loreli-context/SKILL.md +89 -0
- package/packages/mcp/scaffolding/ISSUE_TEMPLATE/config.yml +2 -0
- package/packages/mcp/scaffolding/ISSUE_TEMPLATE/loreli.yml +83 -0
- package/packages/mcp/scaffolding/loreli.yml +453 -0
- package/packages/mcp/scaffolding/mcp-configs/.codex/config.toml +3 -0
- package/packages/mcp/scaffolding/mcp-configs/.cursor/mcp.json +11 -0
- package/packages/mcp/scaffolding/mcp-configs/.mcp.json +11 -0
- package/packages/mcp/scaffolding/pull-request.md +23 -0
- package/packages/mcp/src/index.js +571 -0
- package/packages/mcp/src/tools/agents.js +429 -0
- package/packages/mcp/src/tools/context.js +199 -0
- package/packages/mcp/src/tools/github.js +1199 -0
- package/packages/mcp/src/tools/hitl.js +149 -0
- package/packages/mcp/src/tools/index.js +17 -0
- package/packages/mcp/src/tools/start.js +835 -0
- package/packages/mcp/src/tools/status.js +146 -0
- package/packages/mcp/src/tools/work.js +124 -0
- package/packages/orchestrator/README.md +192 -0
- package/packages/orchestrator/src/index.js +1226 -0
- package/packages/planner/README.md +168 -0
- package/packages/planner/src/index.js +1166 -0
- package/packages/review/README.md +129 -0
- package/packages/review/src/index.js +1283 -0
- package/packages/risk/README.md +119 -0
- package/packages/risk/src/index.js +428 -0
- package/packages/session/README.md +165 -0
- package/packages/session/src/index.js +215 -0
- package/packages/test-utils/README.md +96 -0
- package/packages/test-utils/src/index.js +354 -0
- package/packages/tmux/README.md +261 -0
- package/packages/tmux/src/index.js +452 -0
- package/packages/workflow/README.md +313 -0
- package/packages/workflow/src/index.js +481 -0
- package/packages/workflow/src/proof-of-life.js +74 -0
- package/packages/workspace/README.md +143 -0
- package/packages/workspace/src/index.js +1076 -0
- package/index.js +0 -8
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Valid session states.
|
|
3
|
+
*
|
|
4
|
+
* @type {Set<string>}
|
|
5
|
+
*/
|
|
6
|
+
export const STATES = new Set([
|
|
7
|
+
'spawned', 'working', 'standby', 'reviewing', 'awaiting_hitl', 'dormant'
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Explicit state transition map.
|
|
12
|
+
*
|
|
13
|
+
* Each key is a current state, and the value is a Set of states
|
|
14
|
+
* it can transition to. Transitions not in this map are rejected.
|
|
15
|
+
*
|
|
16
|
+
* Uses an explicit graph instead of linear ordering because agent
|
|
17
|
+
* states have branching paths (e.g. working -> awaiting_hitl vs
|
|
18
|
+
* working -> dormant).
|
|
19
|
+
*
|
|
20
|
+
* @type {Record<string, Set<string>>}
|
|
21
|
+
*/
|
|
22
|
+
export const TRANSITIONS = {
|
|
23
|
+
spawned: new Set(['working', 'standby', 'dormant']),
|
|
24
|
+
working: new Set(['standby', 'reviewing', 'awaiting_hitl', 'dormant']),
|
|
25
|
+
standby: new Set(['working', 'reviewing', 'awaiting_hitl', 'dormant']),
|
|
26
|
+
reviewing: new Set(['working', 'standby', 'awaiting_hitl', 'dormant']),
|
|
27
|
+
awaiting_hitl: new Set(['working', 'standby', 'dormant']),
|
|
28
|
+
dormant: new Set([])
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Tracks an active agent's runtime state.
|
|
33
|
+
*
|
|
34
|
+
* Sessions are persisted to disk so agents survive orchestrator shutdown.
|
|
35
|
+
* State transitions are validated against the TRANSITIONS map to catch
|
|
36
|
+
* bugs where agents are reactivated without proper re-initialization.
|
|
37
|
+
*/
|
|
38
|
+
export class Session {
|
|
39
|
+
/**
|
|
40
|
+
* @param {object} opts
|
|
41
|
+
* @param {object} opts.identity - Agent identity object.
|
|
42
|
+
* @param {string} opts.role - Agent role ('planner' | 'action' | 'reviewer').
|
|
43
|
+
* @param {string} opts.backend - Backend name ('claude', 'codex', etc.).
|
|
44
|
+
* @param {string} [opts.paneId] - Tmux pane ID.
|
|
45
|
+
* @param {number} [opts.pid] - Process ID.
|
|
46
|
+
* @param {string} [opts.repo] - Target repository (owner/name).
|
|
47
|
+
*/
|
|
48
|
+
constructor({ identity, role, backend, paneId, pid, repo }) {
|
|
49
|
+
/** @type {object} Agent identity. */
|
|
50
|
+
this.identity = identity;
|
|
51
|
+
|
|
52
|
+
/** @type {string} Agent role. */
|
|
53
|
+
this.role = role;
|
|
54
|
+
|
|
55
|
+
/** @type {string} Backend name. */
|
|
56
|
+
this.backend = backend;
|
|
57
|
+
|
|
58
|
+
/** @type {string|null} Tmux pane ID. */
|
|
59
|
+
this.paneId = paneId ?? null;
|
|
60
|
+
|
|
61
|
+
/** @type {number|null} Process ID. */
|
|
62
|
+
this.pid = pid ?? null;
|
|
63
|
+
|
|
64
|
+
/** @type {string|null} Target repository. */
|
|
65
|
+
this.repo = repo ?? null;
|
|
66
|
+
|
|
67
|
+
/** @type {string} Current state. */
|
|
68
|
+
this.state = 'spawned';
|
|
69
|
+
|
|
70
|
+
/** @type {string} When the session started. */
|
|
71
|
+
this.startedAt = new Date().toISOString();
|
|
72
|
+
|
|
73
|
+
/** @type {string} Last activity timestamp. */
|
|
74
|
+
this.lastActivity = this.startedAt;
|
|
75
|
+
|
|
76
|
+
/** @type {number|null} Claimed issue number. */
|
|
77
|
+
this.claimedIssue = null;
|
|
78
|
+
|
|
79
|
+
/** @type {object|null} Dynamic task context from latest dispatch. */
|
|
80
|
+
this.task = null;
|
|
81
|
+
|
|
82
|
+
/** @type {string[]} Human reviewers assigned during HITL. */
|
|
83
|
+
this.reviewers = [];
|
|
84
|
+
|
|
85
|
+
/** @type {Array<{name: string, provider: string, timestamp: string}>} Agent approval records. */
|
|
86
|
+
this.agentApprovals = [];
|
|
87
|
+
|
|
88
|
+
/** @type {string|null} ISO timestamp when HITL was activated. */
|
|
89
|
+
this.hitlAt = null;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Token usage accumulator for the current task.
|
|
93
|
+
* Reset when a new task is assigned via dispatch.
|
|
94
|
+
*
|
|
95
|
+
* @type {{ input: number, output: number, model: string|null }}
|
|
96
|
+
*/
|
|
97
|
+
this.usage = { input: 0, output: 0, model: null };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Transition to a new state with validation.
|
|
102
|
+
*
|
|
103
|
+
* Validates the transition against the TRANSITIONS map. Invalid
|
|
104
|
+
* transitions throw an error to catch bugs where agents are moved
|
|
105
|
+
* to impossible states (e.g. dormant -> working without re-spawn).
|
|
106
|
+
*
|
|
107
|
+
* @param {string} next - New state (spawned | working | standby | reviewing | awaiting_hitl | dormant).
|
|
108
|
+
* @throws {Error} If the transition is invalid.
|
|
109
|
+
*/
|
|
110
|
+
transition(next) {
|
|
111
|
+
if (!STATES.has(next)) {
|
|
112
|
+
throw new Error(`Invalid state: "${next}". Valid: ${[...STATES].join(', ')}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const allowed = TRANSITIONS[this.state];
|
|
116
|
+
if (!allowed?.has(next)) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Invalid transition: "${this.state}" -> "${next}". ` +
|
|
119
|
+
`Allowed from "${this.state}": ${allowed ? [...allowed].join(', ') || 'none (terminal)' : 'unknown'}`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
this.state = next;
|
|
124
|
+
this.lastActivity = new Date().toISOString();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check whether a transition to the given state is valid.
|
|
129
|
+
*
|
|
130
|
+
* @param {string} next - Target state to check.
|
|
131
|
+
* @returns {boolean} True if the transition is allowed.
|
|
132
|
+
*/
|
|
133
|
+
canTransition(next) {
|
|
134
|
+
return TRANSITIONS[this.state]?.has(next) ?? false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Serialize the session to a plain object for JSON storage.
|
|
139
|
+
*
|
|
140
|
+
* @returns {object} Plain object representation.
|
|
141
|
+
*/
|
|
142
|
+
toJSON() {
|
|
143
|
+
return {
|
|
144
|
+
identity: this.identity,
|
|
145
|
+
role: this.role,
|
|
146
|
+
backend: this.backend,
|
|
147
|
+
paneId: this.paneId,
|
|
148
|
+
pid: this.pid,
|
|
149
|
+
repo: this.repo,
|
|
150
|
+
state: this.state,
|
|
151
|
+
startedAt: this.startedAt,
|
|
152
|
+
lastActivity: this.lastActivity,
|
|
153
|
+
claimedIssue: this.claimedIssue,
|
|
154
|
+
task: this.task,
|
|
155
|
+
reviewers: this.reviewers,
|
|
156
|
+
agentApprovals: this.agentApprovals,
|
|
157
|
+
hitlAt: this.hitlAt,
|
|
158
|
+
usage: this.usage
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent trace formatting and token parsing utilities.
|
|
3
|
+
*
|
|
4
|
+
* Produces marker-wrapped `<details>` blocks for embedding agent
|
|
5
|
+
* reasoning, terminal output, and token usage in PR bodies and
|
|
6
|
+
* review comments. Uses the loreli marker system for programmatic
|
|
7
|
+
* detection and stripping.
|
|
8
|
+
*
|
|
9
|
+
* @module loreli/agent/trace
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { mark } from 'loreli/marker';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Registry of regex patterns for extracting token usage from CLI output.
|
|
16
|
+
* Each entry has a `name`, a `pattern` regex, and an `extract` function
|
|
17
|
+
* that receives the match and returns `{ input, output }`.
|
|
18
|
+
*
|
|
19
|
+
* New backends or updated CLI versions can be supported by adding
|
|
20
|
+
* entries here without changing the `tokens()` function itself.
|
|
21
|
+
*
|
|
22
|
+
* @type {Array<{name: string, pattern: RegExp, extract: function}>}
|
|
23
|
+
*/
|
|
24
|
+
const TOKEN_PATTERNS = [
|
|
25
|
+
{
|
|
26
|
+
name: 'codex',
|
|
27
|
+
pattern: /tokens?\s+used\s*\n?\s*(\d[\d,]*)/i,
|
|
28
|
+
extract(m) {
|
|
29
|
+
const total = parseInt(m[1].replaceAll(',', ''), 10);
|
|
30
|
+
return { input: 0, output: total };
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'codex-split',
|
|
35
|
+
pattern: /input[:\s]+(\d[\d,]*)\s*(?:tokens?)?\s*[,|/]\s*output[:\s]+(\d[\d,]*)/i,
|
|
36
|
+
extract(m) {
|
|
37
|
+
return {
|
|
38
|
+
input: parseInt(m[1].replaceAll(',', ''), 10),
|
|
39
|
+
output: parseInt(m[2].replaceAll(',', ''), 10)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'claude',
|
|
45
|
+
pattern: /total\s+tokens?[:\s]+input\s*=\s*(\d[\d,]*)\s+output\s*=\s*(\d[\d,]*)/i,
|
|
46
|
+
extract(m) {
|
|
47
|
+
return {
|
|
48
|
+
input: parseInt(m[1].replaceAll(',', ''), 10),
|
|
49
|
+
output: parseInt(m[2].replaceAll(',', ''), 10)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Parse token usage from CLI terminal output.
|
|
57
|
+
*
|
|
58
|
+
* Iterates the pattern registry and returns the first match.
|
|
59
|
+
* Returns `null` when no recognized pattern is found — this is
|
|
60
|
+
* expected for backends like cursor-agent that do not print
|
|
61
|
+
* token info.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} text - Cleaned terminal output.
|
|
64
|
+
* @returns {{ input: number, output: number } | null}
|
|
65
|
+
*/
|
|
66
|
+
export function tokens(text) {
|
|
67
|
+
if (!text) return null;
|
|
68
|
+
|
|
69
|
+
for (const entry of TOKEN_PATTERNS) {
|
|
70
|
+
const m = text.match(entry.pattern);
|
|
71
|
+
if (m) return entry.extract(m);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Escape captured output so it cannot interfere with the loreli
|
|
79
|
+
* marker system or break markdown rendering.
|
|
80
|
+
*
|
|
81
|
+
* Strips:
|
|
82
|
+
* - HTML comments that look like loreli markers (`<!-- loreli:... -->`)
|
|
83
|
+
* - Null bytes and other ASCII control characters (except newline/tab)
|
|
84
|
+
*
|
|
85
|
+
* @param {string} text - Raw captured output.
|
|
86
|
+
* @returns {string} Sanitized text safe for embedding in code fences.
|
|
87
|
+
*/
|
|
88
|
+
export function escape(text) {
|
|
89
|
+
if (!text) return '';
|
|
90
|
+
return text
|
|
91
|
+
.replace(/<!-- loreli:[^>]*-->/g, '')
|
|
92
|
+
// eslint-disable-next-line no-control-regex
|
|
93
|
+
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Determine the minimum number of backticks needed for a code fence
|
|
98
|
+
* that will not be broken by backtick sequences in the content.
|
|
99
|
+
*
|
|
100
|
+
* @param {string} text - Content to be fenced.
|
|
101
|
+
* @returns {number} Fence length (at least 3).
|
|
102
|
+
*/
|
|
103
|
+
function fenceLength(text) {
|
|
104
|
+
let max = 0;
|
|
105
|
+
const re = /`+/g;
|
|
106
|
+
let m;
|
|
107
|
+
while ((m = re.exec(text)) !== null) {
|
|
108
|
+
if (m[0].length > max) max = m[0].length;
|
|
109
|
+
}
|
|
110
|
+
return Math.max(3, max + 1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Format a complete agent trace block.
|
|
115
|
+
*
|
|
116
|
+
* Produces a marker-delimited `<details>` section suitable for
|
|
117
|
+
* embedding in PR bodies or review comments. The block is wrapped
|
|
118
|
+
* with `loreli:trace` / `loreli:trace-end` markers so downstream
|
|
119
|
+
* consumers can strip it via `excise(body, 'trace')`.
|
|
120
|
+
*
|
|
121
|
+
* Sections are omitted when their data is absent:
|
|
122
|
+
* - `reasoning` — agent-provided summary of approach
|
|
123
|
+
* - `output` — captured terminal output (in a code fence)
|
|
124
|
+
* - `usage` — token counts from `tokens()`
|
|
125
|
+
*
|
|
126
|
+
* @param {string} name - Agent identity name (e.g. 'bumblebee-0').
|
|
127
|
+
* @param {object} data - Trace data.
|
|
128
|
+
* @param {string} [data.reasoning] - Agent-provided reasoning summary.
|
|
129
|
+
* @param {string} [data.output] - Captured terminal output.
|
|
130
|
+
* @param {{ input: number, output: number } | null} [data.usage] - Token usage.
|
|
131
|
+
* @param {string} [data.model] - Model identifier for the usage table.
|
|
132
|
+
* @param {number} [data.duration] - Duration in milliseconds.
|
|
133
|
+
* @returns {string} Complete trace block with markers.
|
|
134
|
+
*/
|
|
135
|
+
export function format(name, data = {}) {
|
|
136
|
+
const { reasoning, output: raw, usage, model, duration } = data;
|
|
137
|
+
const sections = [];
|
|
138
|
+
|
|
139
|
+
if (reasoning) {
|
|
140
|
+
sections.push('### Reasoning\n');
|
|
141
|
+
sections.push(reasoning);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (raw) {
|
|
145
|
+
const cleaned = escape(raw);
|
|
146
|
+
const len = fenceLength(cleaned);
|
|
147
|
+
const fence = '`'.repeat(len);
|
|
148
|
+
sections.push('### Output\n');
|
|
149
|
+
sections.push(`${fence}\n${cleaned}\n${fence}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (usage || model || duration != null) {
|
|
153
|
+
const rows = [];
|
|
154
|
+
if (usage?.input != null) rows.push(`| Input tokens | ${usage.input.toLocaleString()} |`);
|
|
155
|
+
if (usage?.output != null) rows.push(`| Output tokens | ${usage.output.toLocaleString()} |`);
|
|
156
|
+
if (model) rows.push(`| Model | ${model} |`);
|
|
157
|
+
if (duration != null) {
|
|
158
|
+
const secs = Math.round(duration / 1000);
|
|
159
|
+
const mins = Math.floor(secs / 60);
|
|
160
|
+
const rem = secs % 60;
|
|
161
|
+
const display = mins > 0 ? `${mins}m ${rem}s` : `${secs}s`;
|
|
162
|
+
rows.push(`| Duration | ${display} |`);
|
|
163
|
+
}
|
|
164
|
+
if (rows.length) {
|
|
165
|
+
sections.push('### Usage\n');
|
|
166
|
+
sections.push(`| Metric | Value |\n|--------|-------|\n${rows.join('\n')}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!sections.length) return '';
|
|
171
|
+
|
|
172
|
+
const body = sections.join('\n\n');
|
|
173
|
+
const startMarker = mark('trace', { agent: name });
|
|
174
|
+
const endMarker = `<!-- loreli:trace-end -->`;
|
|
175
|
+
|
|
176
|
+
return [
|
|
177
|
+
startMarker,
|
|
178
|
+
`<details>`,
|
|
179
|
+
`<summary>Agent Trace (${name})</summary>`,
|
|
180
|
+
'',
|
|
181
|
+
body,
|
|
182
|
+
'',
|
|
183
|
+
`</details>`,
|
|
184
|
+
endMarker
|
|
185
|
+
].join('\n');
|
|
186
|
+
}
|