loreli 1.0.0 → 2.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.
Files changed (63) hide show
  1. package/README.md +66 -26
  2. package/package.json +17 -14
  3. package/packages/action/prompts/action.md +172 -0
  4. package/packages/action/src/index.js +33 -5
  5. package/packages/agent/README.md +107 -18
  6. package/packages/agent/src/backends/claude.js +111 -11
  7. package/packages/agent/src/backends/codex.js +78 -5
  8. package/packages/agent/src/backends/cursor.js +104 -27
  9. package/packages/agent/src/backends/index.js +162 -5
  10. package/packages/agent/src/cli.js +80 -3
  11. package/packages/agent/src/discover.js +396 -0
  12. package/packages/agent/src/factory.js +39 -34
  13. package/packages/agent/src/models.js +24 -6
  14. package/packages/classify/README.md +136 -0
  15. package/packages/classify/prompts/blocker.md +12 -0
  16. package/packages/classify/prompts/feedback.md +14 -0
  17. package/packages/classify/prompts/pane-state.md +20 -0
  18. package/packages/classify/src/index.js +81 -0
  19. package/packages/config/README.md +156 -91
  20. package/packages/config/src/defaults.js +32 -21
  21. package/packages/config/src/index.js +33 -2
  22. package/packages/config/src/schema.js +57 -39
  23. package/packages/hub/src/github.js +59 -20
  24. package/packages/identity/README.md +1 -1
  25. package/packages/identity/src/index.js +2 -2
  26. package/packages/knowledge/README.md +86 -106
  27. package/packages/knowledge/src/index.js +56 -225
  28. package/packages/mcp/README.md +51 -7
  29. package/packages/mcp/instructions.md +6 -1
  30. package/packages/mcp/scaffolding/loreli.yml +115 -77
  31. package/packages/mcp/scaffolding/mcp-configs/.codex/config.toml +1 -0
  32. package/packages/mcp/scaffolding/mcp-configs/.cursor/mcp.json +4 -1
  33. package/packages/mcp/scaffolding/mcp-configs/.mcp.json +4 -1
  34. package/packages/mcp/src/index.js +45 -16
  35. package/packages/mcp/src/tools/agent-context.js +44 -0
  36. package/packages/mcp/src/tools/agents.js +34 -13
  37. package/packages/mcp/src/tools/context.js +3 -2
  38. package/packages/mcp/src/tools/github.js +11 -47
  39. package/packages/mcp/src/tools/hitl.js +19 -6
  40. package/packages/mcp/src/tools/index.js +2 -1
  41. package/packages/mcp/src/tools/refactor.js +227 -0
  42. package/packages/mcp/src/tools/repo.js +44 -0
  43. package/packages/mcp/src/tools/start.js +159 -90
  44. package/packages/mcp/src/tools/status.js +5 -2
  45. package/packages/mcp/src/tools/work.js +18 -8
  46. package/packages/orchestrator/src/index.js +345 -79
  47. package/packages/planner/README.md +84 -1
  48. package/packages/planner/prompts/plan-reviewer.md +109 -0
  49. package/packages/planner/prompts/planner.md +191 -0
  50. package/packages/planner/prompts/tiebreaker-reviewer.md +71 -0
  51. package/packages/planner/src/index.js +326 -111
  52. package/packages/review/README.md +2 -2
  53. package/packages/review/prompts/reviewer.md +158 -0
  54. package/packages/review/src/index.js +196 -76
  55. package/packages/risk/README.md +81 -22
  56. package/packages/risk/prompts/risk.md +272 -0
  57. package/packages/risk/src/index.js +44 -33
  58. package/packages/tmux/src/index.js +61 -12
  59. package/packages/workflow/README.md +18 -14
  60. package/packages/workflow/prompts/preamble.md +14 -0
  61. package/packages/workflow/src/index.js +191 -12
  62. package/packages/workspace/README.md +2 -2
  63. package/packages/workspace/src/index.js +69 -18
@@ -1,5 +1,17 @@
1
1
  import ms from 'ms';
2
2
 
3
+ /**
4
+ * Valid merge methods supported by GitHub.
5
+ * @type {Set<string>}
6
+ */
7
+ const MERGE_METHODS = new Set(['merge', 'squash', 'rebase']);
8
+
9
+ /**
10
+ * Pattern for valid "owner/name" repository format.
11
+ * @type {RegExp}
12
+ */
13
+ const REPO_RE = /^[^/]+\/[^/]+$/;
14
+
3
15
  /**
4
16
  * Validates and normalizes a raw config object parsed from loreli.yml.
5
17
  * Ensures types are correct and unknown keys are stripped.
@@ -15,6 +27,8 @@ export function validate(raw) {
15
27
 
16
28
  const out = {};
17
29
 
30
+ if (typeof raw.repo === 'string' && REPO_RE.test(raw.repo)) out.repo = raw.repo;
31
+
18
32
  if (typeof raw.theme === 'string') {
19
33
  out.theme = raw.theme;
20
34
  } else if (Array.isArray(raw.theme)) {
@@ -32,7 +46,7 @@ export function validate(raw) {
32
46
 
33
47
  if (raw.merge && typeof raw.merge === 'object') {
34
48
  out.merge = {};
35
- if (typeof raw.merge.method === 'string') out.merge.method = raw.merge.method;
49
+ if (typeof raw.merge.method === 'string' && MERGE_METHODS.has(raw.merge.method)) out.merge.method = raw.merge.method;
36
50
  if (typeof raw.merge.base === 'string') out.merge.base = raw.merge.base;
37
51
 
38
52
  if (typeof raw.merge.hitl === 'boolean') {
@@ -78,7 +92,7 @@ export function validate(raw) {
78
92
 
79
93
  if (raw.timeouts && typeof raw.timeouts === 'object') {
80
94
  out.timeouts = {};
81
- for (const key of ['stall', 'shutdown', 'poll', 'rapidDeath']) {
95
+ for (const key of ['stall', 'shutdown', 'poll', 'rapidDeath', 'proxyDiscovery']) {
82
96
  const v = raw.timeouts[key];
83
97
  if (v == null) continue;
84
98
  try { out.timeouts[key] = typeof v === 'number' ? v : ms(v); } catch { /* invalid — falls to default */ }
@@ -113,13 +127,6 @@ export function validate(raw) {
113
127
  if (typeof raw.watch.maxClaims === 'number' && raw.watch.maxClaims > 0) out.watch.maxClaims = raw.watch.maxClaims;
114
128
  }
115
129
 
116
- if (raw.review && typeof raw.review === 'object') {
117
- out.review = {};
118
- if (typeof raw.review.skipRiskAssessment === 'boolean') {
119
- out.review.skipRiskAssessment = raw.review.skipRiskAssessment;
120
- }
121
- }
122
-
123
130
  if (raw.scaling && typeof raw.scaling === 'object') {
124
131
  out.scaling = {};
125
132
  if (typeof raw.scaling.maxAgents === 'number') out.scaling.maxAgents = raw.scaling.maxAgents;
@@ -130,16 +137,6 @@ export function validate(raw) {
130
137
  try { out.scaling.cooldown = typeof cd === 'number' ? cd : ms(cd); } catch { /* invalid — falls to default */ }
131
138
  }
132
139
 
133
- if (raw.scaling.maxPerRole && typeof raw.scaling.maxPerRole === 'object') {
134
- const mpr = {};
135
- for (const role of ['action', 'reviewer', 'risk', 'planner']) {
136
- if (typeof raw.scaling.maxPerRole[role] === 'number') {
137
- mpr[role] = raw.scaling.maxPerRole[role];
138
- }
139
- }
140
- if (Object.keys(mpr).length) out.scaling.maxPerRole = mpr;
141
- }
142
-
143
140
  if (!Object.keys(out.scaling).length) delete out.scaling;
144
141
  }
145
142
 
@@ -190,25 +187,23 @@ export function validate(raw) {
190
187
  if (!Object.keys(out.backends).length) delete out.backends;
191
188
  }
192
189
 
190
+ if (raw.classify && typeof raw.classify === 'object') {
191
+ out.classify = {};
192
+ if (typeof raw.classify.model === 'string') out.classify.model = raw.classify.model;
193
+ if (typeof raw.classify.maxLines === 'number' && raw.classify.maxLines > 0) out.classify.maxLines = raw.classify.maxLines;
194
+ if (typeof raw.classify.maxRetries === 'number' && raw.classify.maxRetries > 0) out.classify.maxRetries = raw.classify.maxRetries;
195
+ const t = raw.classify.timeout;
196
+ if (t != null) {
197
+ try { out.classify.timeout = typeof t === 'number' ? t : ms(t); } catch { /* invalid — falls to default */ }
198
+ }
199
+ if (!Object.keys(out.classify).length) delete out.classify;
200
+ }
201
+
193
202
  if (raw.trace && typeof raw.trace === 'object') {
194
203
  out.trace = {};
195
204
  if (typeof raw.trace.enabled === 'boolean') out.trace.enabled = raw.trace.enabled;
196
205
  if (typeof raw.trace.includeOutput === 'boolean') out.trace.includeOutput = raw.trace.includeOutput;
197
206
  if (typeof raw.trace.maxOutputChars === 'number') out.trace.maxOutputChars = raw.trace.maxOutputChars;
198
-
199
- if (raw.trace.workflows && typeof raw.trace.workflows === 'object') {
200
- const workflows = {};
201
- for (const role of ['planner', 'reviewer', 'risk']) {
202
- const wf = raw.trace.workflows[role];
203
- if (wf && typeof wf === 'object') {
204
- const entry = {};
205
- if (typeof wf.enabled === 'boolean') entry.enabled = wf.enabled;
206
- if (typeof wf.maxOutputChars === 'number') entry.maxOutputChars = wf.maxOutputChars;
207
- if (Object.keys(entry).length) workflows[role] = entry;
208
- }
209
- }
210
- if (Object.keys(workflows).length) out.trace.workflows = workflows;
211
- }
212
207
  }
213
208
 
214
209
  if (raw.agents && typeof raw.agents === 'object') {
@@ -240,6 +235,13 @@ export function validate(raw) {
240
235
  function isString(c) { return typeof c === 'string'; }
241
236
  );
242
237
  }
238
+ if (typeof raw.feedback.hitl === 'boolean') {
239
+ out.feedback.hitl = raw.feedback.hitl;
240
+ } else if (Array.isArray(raw.feedback.hitl)) {
241
+ out.feedback.hitl = raw.feedback.hitl.filter(
242
+ function isString(c) { return typeof c === 'string'; }
243
+ );
244
+ }
243
245
  }
244
246
 
245
247
  if (raw.workspace && typeof raw.workspace === 'object') {
@@ -256,17 +258,33 @@ export function validate(raw) {
256
258
  if (typeof raw.cleanup.autoprune === 'boolean') out.cleanup.autoprune = raw.cleanup.autoprune;
257
259
  }
258
260
 
259
- if (raw.prompts && typeof raw.prompts === 'object') {
260
- const prompts = {};
261
- for (const role of ['action', 'reviewer', 'planner', 'risk']) {
262
- if (typeof raw.prompts[role] === 'string') prompts[role] = raw.prompts[role];
261
+ if (raw.workflows && typeof raw.workflows === 'object') {
262
+ const workflows = {};
263
+ for (const role of ['action', 'reviewer', 'risk', 'planner']) {
264
+ const wf = raw.workflows[role];
265
+ if (!wf || typeof wf !== 'object') continue;
266
+
267
+ const entry = {};
268
+ if (typeof wf.model === 'string') entry.model = wf.model;
269
+ if (typeof wf.maxAgents === 'number') entry.maxAgents = wf.maxAgents;
270
+ if (typeof wf.prompt === 'string') entry.prompt = wf.prompt;
271
+ if (typeof wf.skip === 'boolean') entry.skip = wf.skip;
272
+
273
+ if (wf.trace && typeof wf.trace === 'object') {
274
+ const trace = {};
275
+ if (typeof wf.trace.enabled === 'boolean') trace.enabled = wf.trace.enabled;
276
+ if (typeof wf.trace.maxOutputChars === 'number') trace.maxOutputChars = wf.trace.maxOutputChars;
277
+ if (Object.keys(trace).length) entry.trace = trace;
278
+ }
279
+
280
+ if (Object.keys(entry).length) workflows[role] = entry;
263
281
  }
264
- if (Object.keys(prompts).length) out.prompts = prompts;
282
+ if (Object.keys(workflows).length) out.workflows = workflows;
265
283
  }
266
284
 
267
285
  if (raw.github && typeof raw.github === 'object') {
268
286
  out.github = {};
269
- if (typeof raw.github.token === 'string') out.github.token = raw.github.token;
287
+ if (typeof raw.github.token === 'string' && raw.github.token.length > 0) out.github.token = raw.github.token;
270
288
  }
271
289
 
272
290
  return out;
@@ -12,11 +12,19 @@ const log = logger('hub');
12
12
  const RATE_WARN_THRESHOLD = 0.2;
13
13
 
14
14
  /**
15
- * Maximum retry attempts for rate-limited (429) responses.
15
+ * Maximum retry attempts for transient failures.
16
16
  * @type {number}
17
17
  */
18
18
  const MAX_RETRIES = 3;
19
19
 
20
+ /**
21
+ * HTTP status codes eligible for automatic retry with backoff.
22
+ * 429 = secondary rate limit, 500/502/503 = transient server errors.
23
+ *
24
+ * @type {Set<number>}
25
+ */
26
+ const RETRYABLE = new Set([429, 500, 502, 503]);
27
+
20
28
  /**
21
29
  * Calculate exponential backoff with jitter.
22
30
  *
@@ -125,7 +133,7 @@ const normalize = {
125
133
  * Normalize a GitHub pull request object.
126
134
  *
127
135
  * @param {object} raw - Raw GitHub PR API response.
128
- * @returns {{number: number, title: string, body: string, state: string, head: string, headSha: string, base: string, author: string, url: string, labels: string[], merged: boolean, created: string, updated: string}}
136
+ * @returns {{number: number, title: string, body: string, state: string, head: string, headSha: string, base: string, author: string, url: string, labels: string[], merged: boolean, mergeable: boolean|null, mergeableState: string|null, created: string, updated: string}}
129
137
  */
130
138
  pull(raw) {
131
139
  return {
@@ -142,6 +150,8 @@ const normalize = {
142
150
  return typeof l === 'string' ? l : l.name;
143
151
  }),
144
152
  merged: raw.merged ?? false,
153
+ mergeable: raw.mergeable ?? null,
154
+ mergeableState: raw.mergeable_state ?? null,
145
155
  created: raw.created_at,
146
156
  updated: raw.updated_at
147
157
  };
@@ -226,9 +236,9 @@ export class GitHubHub extends BaseHub {
226
236
  try {
227
237
  response = await request(options);
228
238
  } catch (err) {
229
- // Octokit throws on non-2xx; check for 429 (secondary rate limit)
230
- if (err.status === 429 && attempt < MAX_RETRIES) {
239
+ if (RETRYABLE.has(err.status) && attempt < MAX_RETRIES) {
231
240
  const wait = parseRetryAfter(err.response?.headers) ?? backoff(attempt);
241
+ log.warn(`retrying ${options.method} ${options.url} (${err.status}, attempt ${attempt + 1}/${MAX_RETRIES})`);
232
242
  await new Promise(function delay(r) { setTimeout(r, wait); });
233
243
  continue;
234
244
  }
@@ -249,6 +259,29 @@ export class GitHubHub extends BaseHub {
249
259
  });
250
260
  }
251
261
 
262
+ /**
263
+ * Execute a GraphQL query/mutation with error checking and rate limit tracking.
264
+ *
265
+ * GraphQL responses can return partial data alongside an `errors` array.
266
+ * This method throws on errors instead of silently using partial data.
267
+ * Also extracts `x-ratelimit-*` headers that the REST hook misses.
268
+ *
269
+ * @param {string} query - GraphQL query or mutation string.
270
+ * @param {object} [variables] - Query variables.
271
+ * @returns {Promise<object>} The response data.
272
+ * @throws {Error} When the response contains GraphQL errors.
273
+ */
274
+ async _graphql(query, variables) {
275
+ const result = await this.client.graphql(query, variables);
276
+
277
+ if (result.errors?.length) {
278
+ const messages = result.errors.map(function msg(e) { return e.message; }).join('; ');
279
+ throw new Error(`GraphQL errors: ${messages}`);
280
+ }
281
+
282
+ return result;
283
+ }
284
+
252
285
  /**
253
286
  * Parse rate limit headers from a GitHub API response and update internal state.
254
287
  * Emits a warning when remaining requests drop below the configured threshold.
@@ -357,8 +390,10 @@ export class GitHubHub extends BaseHub {
357
390
  * @returns {[string, string]} Tuple of [owner, repo].
358
391
  */
359
392
  parse(repo) {
360
- const [owner, name] = repo.split('/');
361
- return [owner, name];
393
+ const slash = repo.indexOf('/');
394
+ if (slash <= 0 || slash === repo.length - 1)
395
+ throw new Error(`Invalid repo format "${repo}": expected "owner/name"`);
396
+ return [repo.slice(0, slash), repo.slice(slash + 1)];
362
397
  }
363
398
 
364
399
  /**
@@ -438,6 +473,8 @@ export class GitHubHub extends BaseHub {
438
473
  owner, repo: name,
439
474
  state: opts.state ?? 'open',
440
475
  labels: opts.labels?.join(','),
476
+ sort: 'created',
477
+ direction: 'asc',
441
478
  per_page: 100
442
479
  });
443
480
  return data.filter(function notPR(i) { return !i.pull_request; }).map(normalize.issue);
@@ -575,6 +612,8 @@ export class GitHubHub extends BaseHub {
575
612
  const data = await this.client.paginate(this.client.pulls.list, {
576
613
  owner, repo: name,
577
614
  state: opts.state ?? 'open',
615
+ sort: 'created',
616
+ direction: 'asc',
578
617
  per_page: 100
579
618
  });
580
619
  return data.map(normalize.pull);
@@ -585,7 +624,7 @@ export class GitHubHub extends BaseHub {
585
624
  *
586
625
  * @param {string} repo - "owner/name" repository.
587
626
  * @param {number} number - Pull request number.
588
- * @returns {Promise<{number: number, title: string, body: string, state: string, head: string, base: string, author: string, url: string, labels: string[], merged: boolean, created: string, updated: string}>}
627
+ * @returns {Promise<{number: number, title: string, body: string, state: string, head: string, base: string, author: string, url: string, labels: string[], merged: boolean, mergeable: boolean|null, mergeableState: string|null, created: string, updated: string}>}
589
628
  */
590
629
  async pull(repo, number) {
591
630
  const [owner, name] = this.parse(repo);
@@ -1077,7 +1116,7 @@ export class GitHubHub extends BaseHub {
1077
1116
  }
1078
1117
  }`;
1079
1118
 
1080
- const result = await this.client.graphql(query, { owner, name: repoName });
1119
+ const result = await this._graphql(query, { owner, name: repoName });
1081
1120
  const cats = result.repository?.discussionCategories?.nodes ?? [];
1082
1121
  const match = cats.find(function byName(c) { return c.name === name; });
1083
1122
  if (!match) {
@@ -1113,7 +1152,7 @@ export class GitHubHub extends BaseHub {
1113
1152
  }
1114
1153
  }`;
1115
1154
 
1116
- const result = await this.client.graphql(mutation, {
1155
+ const result = await this._graphql(mutation, {
1117
1156
  repositoryId: opts.repositoryId,
1118
1157
  categoryId: opts.categoryId,
1119
1158
  title: opts.title,
@@ -1152,7 +1191,7 @@ export class GitHubHub extends BaseHub {
1152
1191
  const [owner, repoName] = this.parse(repo);
1153
1192
  const query = `query($owner: String!, $name: String!, $categoryId: ID!) {
1154
1193
  repository(owner: $owner, name: $name) {
1155
- discussions(first: 100, categoryId: $categoryId) {
1194
+ discussions(first: 100, categoryId: $categoryId, orderBy: { field: CREATED_AT, direction: ASC }) {
1156
1195
  nodes {
1157
1196
  id number title body url closed
1158
1197
  author { login }
@@ -1162,7 +1201,7 @@ export class GitHubHub extends BaseHub {
1162
1201
  }
1163
1202
  }`;
1164
1203
 
1165
- const result = await this.client.graphql(query, { owner, name: repoName, categoryId });
1204
+ const result = await this._graphql(query, { owner, name: repoName, categoryId });
1166
1205
  const nodes = result.repository?.discussions?.nodes ?? [];
1167
1206
  return nodes.map(function norm(d) {
1168
1207
  return {
@@ -1203,7 +1242,7 @@ export class GitHubHub extends BaseHub {
1203
1242
  }
1204
1243
  }`;
1205
1244
 
1206
- const result = await this.client.graphql(query, { owner, name: repoName, number });
1245
+ const result = await this._graphql(query, { owner, name: repoName, number });
1207
1246
  const d = result.repository?.discussion;
1208
1247
  if (!d) throw new Error(`Discussion #${number} not found in ${repo}`);
1209
1248
  return {
@@ -1241,7 +1280,7 @@ export class GitHubHub extends BaseHub {
1241
1280
  }
1242
1281
  }`;
1243
1282
 
1244
- const result = await this.client.graphql(query, { id: discussionId });
1283
+ const result = await this._graphql(query, { id: discussionId });
1245
1284
  const nodes = result.node?.comments?.nodes ?? [];
1246
1285
  return nodes.map(function norm(c) {
1247
1286
  return { id: c.id, body: c.body ?? '', author: c.author?.login ?? '', created: c.createdAt };
@@ -1265,7 +1304,7 @@ export class GitHubHub extends BaseHub {
1265
1304
  }
1266
1305
  }`;
1267
1306
 
1268
- const result = await this.client.graphql(mutation, {
1307
+ const result = await this._graphql(mutation, {
1269
1308
  discussionId,
1270
1309
  body: this._stamp(body)
1271
1310
  });
@@ -1300,7 +1339,7 @@ export class GitHubHub extends BaseHub {
1300
1339
  }
1301
1340
  }`;
1302
1341
 
1303
- const result = await this.client.graphql(mutation, input);
1342
+ const result = await this._graphql(mutation, input);
1304
1343
  return {
1305
1344
  id: result.updateDiscussion.discussion.id,
1306
1345
  title: result.updateDiscussion.discussion.title
@@ -1319,14 +1358,14 @@ export class GitHubHub extends BaseHub {
1319
1358
  discussion { id }
1320
1359
  }
1321
1360
  }`;
1322
- await this.client.graphql(closeMutation, { id: discussionId });
1361
+ await this._graphql(closeMutation, { id: discussionId });
1323
1362
 
1324
1363
  const lockMutation = `mutation($id: ID!) {
1325
1364
  lockLockable(input: { lockableId: $id }) {
1326
1365
  lockedRecord { locked }
1327
1366
  }
1328
1367
  }`;
1329
- await this.client.graphql(lockMutation, { id: discussionId });
1368
+ await this._graphql(lockMutation, { id: discussionId });
1330
1369
  }
1331
1370
 
1332
1371
  /**
@@ -1341,7 +1380,7 @@ export class GitHubHub extends BaseHub {
1341
1380
  discussion { id }
1342
1381
  }
1343
1382
  }`;
1344
- const result = await this.client.graphql(mutation, { id: discussionId });
1383
+ const result = await this._graphql(mutation, { id: discussionId });
1345
1384
  const deletedId = result.deleteDiscussion?.discussion?.id;
1346
1385
 
1347
1386
  // Verify the discussion is no longer fetchable by node ID
@@ -1398,7 +1437,7 @@ export class GitHubHub extends BaseHub {
1398
1437
  labelable { ... on Discussion { id } }
1399
1438
  }
1400
1439
  }`;
1401
- await this.client.graphql(mutation, { labelableId: discussionId, labelIds });
1440
+ await this._graphql(mutation, { labelableId: discussionId, labelIds });
1402
1441
  }
1403
1442
 
1404
1443
  /**
@@ -1431,7 +1470,7 @@ export class GitHubHub extends BaseHub {
1431
1470
  labelable { ... on Discussion { id } }
1432
1471
  }
1433
1472
  }`;
1434
- await this.client.graphql(mutation, { labelableId: discussionId, labelIds });
1473
+ await this._graphql(mutation, { labelableId: discussionId, labelIds });
1435
1474
  }
1436
1475
 
1437
1476
  // --- Search & Commits ---
@@ -151,7 +151,7 @@ Autobots, roll out! — **optimus-0**
151
151
 
152
152
  | Agent | Model | Provider | Faction | Role | Version |
153
153
  |-------|-------|----------|---------|------|---------|
154
- | optimus-0 | gpt-4o | openai | autobots | action | loreli@0.0.0 |
154
+ | optimus-0 | gpt-4o | openai | autobots | action | `loreli@0.0.0` |
155
155
  ```
156
156
 
157
157
  This is automatically appended by claim, signoff, and relay comments in the orchestration workflow.
@@ -439,7 +439,7 @@ export class Identity {
439
439
  *
440
440
  * | Agent | Model | Provider | Faction | Role | Version |
441
441
  * |-------|-------|----------|---------|------|---------|
442
- * | {name} | {displayModel} | {provider} | {faction} | {role} | loreli@{version} |
442
+ * | {name} | {displayModel} | {provider} | {faction} | {role} | `loreli@{version}` |
443
443
  * ```
444
444
  *
445
445
  * @param {string} role - The agent's current role.
@@ -460,7 +460,7 @@ export class Identity {
460
460
  '',
461
461
  '| Agent | Model | Provider | Faction | Role | Version |',
462
462
  '|-------|-------|----------|---------|------|---------|',
463
- `| ${this.name} | ${model} | ${this.provider} | ${this.faction} | ${role} | loreli@${ver} |`
463
+ `| ${this.name} | ${model} | ${this.provider} | ${this.faction} | ${role} | \`loreli@${ver}\` |`
464
464
  ].join('\n');
465
465
  }
466
466
  }