loopengine 0.1.15 → 0.1.22

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 (35) hide show
  1. package/README.md +124 -299
  2. package/dist/core/agent-config.d.ts +1 -1
  3. package/dist/core/gateway-tools.js +4 -4
  4. package/dist/core/mcpplug.d.ts +23 -0
  5. package/dist/core/mcpplug.js +64 -0
  6. package/dist/core/run-agent.js +2 -2
  7. package/dist/core/session-store.d.ts +1 -1
  8. package/dist/core/session-store.js +1 -1
  9. package/dist/core/sessionknit.d.ts +87 -0
  10. package/dist/core/sessionknit.js +189 -0
  11. package/dist/core/skillgarden/activation.d.ts +4 -0
  12. package/dist/core/skillgarden/activation.js +16 -0
  13. package/dist/core/skillgarden/budget.d.ts +20 -0
  14. package/dist/core/skillgarden/budget.js +28 -0
  15. package/dist/core/skillgarden/discovery.d.ts +11 -0
  16. package/dist/core/skillgarden/discovery.js +40 -0
  17. package/dist/core/skillgarden/frontmatter.d.ts +10 -0
  18. package/dist/core/skillgarden/frontmatter.js +35 -0
  19. package/dist/core/skillgarden/index.d.ts +8 -0
  20. package/dist/core/skillgarden/index.js +12 -0
  21. package/dist/core/skillgarden/loader.d.ts +31 -0
  22. package/dist/core/skillgarden/loader.js +70 -0
  23. package/dist/core/skillgarden/substitute.d.ts +4 -0
  24. package/dist/core/skillgarden/substitute.js +13 -0
  25. package/dist/core/skillgarden/types.d.ts +23 -0
  26. package/dist/core/skillgarden/types.js +1 -0
  27. package/dist/core/toollane.d.ts +46 -0
  28. package/dist/core/toollane.js +69 -0
  29. package/dist/index.d.ts +8 -2
  30. package/dist/index.js +4 -1
  31. package/dist/web/agents-config-page.js +1 -197
  32. package/dist/web/skills-admin.js +1 -1
  33. package/package.json +8 -5
  34. package/dist/web/skillgarden-admin.d.ts +0 -24
  35. package/dist/web/skillgarden-admin.js +0 -76
@@ -0,0 +1,46 @@
1
+ /** A tool call is just a callable with identity — this module doesn't
2
+ * know or care what a "tool" is beyond that. */
3
+ export interface ToolCall {
4
+ id: string;
5
+ name: string;
6
+ execute: () => Promise<unknown>;
7
+ }
8
+ /** Host-provided: does this specific call belong in a parallel lane?
9
+ * Mirrors Claude Code's own isReadOnly()/isConcurrencySafe() per-tool
10
+ * declarations. */
11
+ export type SafetyClassifier = (call: ToolCall) => boolean;
12
+ export interface Lane {
13
+ mode: 'parallel' | 'solo';
14
+ calls: ToolCall[];
15
+ }
16
+ export interface LaneResult {
17
+ id: string;
18
+ name: string;
19
+ status: 'fulfilled' | 'rejected';
20
+ value?: unknown;
21
+ error?: unknown;
22
+ }
23
+ /** Groups a batch of tool calls into lanes: consecutive safety-declared
24
+ * calls merge into one parallel lane; anything else gets its own solo
25
+ * lane. Batch order is preserved across lanes — this never reorders
26
+ * calls, only decides what runs together. */
27
+ export declare function buildLanes(calls: ToolCall[], isSafe: SafetyClassifier): Lane[];
28
+ /** Runs lanes in order; calls within a parallel lane run concurrently.
29
+ * Every call resolves to a LaneResult regardless of success or failure —
30
+ * one call throwing never rejects the batch, it just carries the error
31
+ * in its own slot. */
32
+ export declare function runLanes(lanes: Lane[]): AsyncGenerator<LaneResult>;
33
+ export interface ToolLaneOptions {
34
+ isSafe: SafetyClassifier;
35
+ }
36
+ /** Ties classification, scheduling, and execution together: given a
37
+ * batch of approved tool calls, decide a safe execution plan and run
38
+ * it, streaming each call's result as it completes. */
39
+ export declare class ToolLane {
40
+ private readonly isSafe;
41
+ constructor(options: ToolLaneOptions);
42
+ /** Read-only — returns the lane plan without executing anything. */
43
+ plan(calls: ToolCall[]): Lane[];
44
+ /** Builds the plan and executes it. */
45
+ run(calls: ToolCall[]): AsyncGenerator<LaneResult>;
46
+ }
@@ -0,0 +1,69 @@
1
+ // Formerly the standalone `toollane` package — folded in here for the
2
+ // same reason mcpplug.ts was: no consumer besides loopengine itself ever
3
+ // actually materialized, so the separate-package/portable-to-any-host
4
+ // framing was aspirational rather than realized. A concurrency-safety-
5
+ // aware scheduler for a batch of already-approved tool calls: consecutive
6
+ // safety-declared calls run together in a parallel lane, anything else
7
+ // gets its own solo lane, order preserved, one failure isolated to its
8
+ // own slot rather than rejecting the whole batch.
9
+ /** Groups a batch of tool calls into lanes: consecutive safety-declared
10
+ * calls merge into one parallel lane; anything else gets its own solo
11
+ * lane. Batch order is preserved across lanes — this never reorders
12
+ * calls, only decides what runs together. */
13
+ export function buildLanes(calls, isSafe) {
14
+ const lanes = [];
15
+ for (const call of calls) {
16
+ const safe = isSafe(call);
17
+ const lastLane = lanes.at(-1);
18
+ if (safe && lastLane?.mode === 'parallel') {
19
+ lastLane.calls.push(call);
20
+ continue;
21
+ }
22
+ lanes.push({ mode: safe ? 'parallel' : 'solo', calls: [call] });
23
+ }
24
+ return lanes;
25
+ }
26
+ async function runCall(call) {
27
+ try {
28
+ const value = await call.execute();
29
+ return { id: call.id, name: call.name, status: 'fulfilled', value };
30
+ }
31
+ catch (error) {
32
+ return { id: call.id, name: call.name, status: 'rejected', error };
33
+ }
34
+ }
35
+ /** Runs lanes in order; calls within a parallel lane run concurrently.
36
+ * Every call resolves to a LaneResult regardless of success or failure —
37
+ * one call throwing never rejects the batch, it just carries the error
38
+ * in its own slot. */
39
+ export async function* runLanes(lanes) {
40
+ for (const lane of lanes) {
41
+ if (lane.mode === 'solo') {
42
+ const call = lane.calls[0];
43
+ if (call)
44
+ yield await runCall(call);
45
+ continue;
46
+ }
47
+ const results = await Promise.all(lane.calls.map(runCall));
48
+ for (const result of results) {
49
+ yield result;
50
+ }
51
+ }
52
+ }
53
+ /** Ties classification, scheduling, and execution together: given a
54
+ * batch of approved tool calls, decide a safe execution plan and run
55
+ * it, streaming each call's result as it completes. */
56
+ export class ToolLane {
57
+ isSafe;
58
+ constructor(options) {
59
+ this.isSafe = options.isSafe;
60
+ }
61
+ /** Read-only — returns the lane plan without executing anything. */
62
+ plan(calls) {
63
+ return buildLanes(calls, this.isSafe);
64
+ }
65
+ /** Builds the plan and executes it. */
66
+ async *run(calls) {
67
+ yield* runLanes(this.plan(calls));
68
+ }
69
+ }
package/dist/index.d.ts CHANGED
@@ -6,6 +6,10 @@ export { streamMessage, streamMessageWithCallbacks, sendMessage, approveCall, de
6
6
  export type { RequestOptions, SendMessageResult, PendingApprovalResult, PendingQuestionResult, PendingResult, MessageResult, StreamMessageCallbacks, } from './core/client.js';
7
7
  export { FileSessionStore, RedisSessionStore, createSessionStore } from './core/session-store.js';
8
8
  export type { SessionStore, SessionResult, RedisSessionStoreOptions } from './core/session-store.js';
9
+ export { SessionKnit, FileStorage, MemoryStorage, reconstructChain } from './core/sessionknit.js';
10
+ export type { Storage, SessionEntry, ResumeResult, SessionKnitOptions, ReconstructResult } from './core/sessionknit.js';
11
+ export { SkillGarden, parseSkillFile, discoverSkillFiles, estimateTokens as estimateSkillIndexTokens, buildBudgetedIndex, substituteArguments, matchesActivationPaths, } from './core/skillgarden/index.js';
12
+ export type { SkillGardenOptions, SkillFrontmatter, SkillIndexEntry, LoadedSkill, DiscoveredSkillFile, BuildIndexOptions, BudgetedIndex, } from './core/skillgarden/index.js';
9
13
  export { VectorIndex, embed, cosineSimilarity } from './core/vector-index.js';
10
14
  export type { Document, ScoredDocument } from './core/vector-index.js';
11
15
  export { createAnthropicModelCall } from './core/model-calls/anthropic-model-call.js';
@@ -32,6 +36,10 @@ export { createCheckpointStore } from './core/durable-approvals.js';
32
36
  export type { TurnCheckpoint, CheckpointStore, OutstandingItem } from './core/durable-approvals.js';
33
37
  export { addGatewayTool, agentDir, disconnectComposioAccount, describeGatewayTools, listComposioConnections, listComposioTools, loadGatewayToolsFromDir, removeGatewayTool, removeGatewayToolSlug, GatewayToolExistsError, GatewayToolNotFoundError, } from './core/gateway-tools.js';
34
38
  export type { GatewayToolEntry, GatewayToolDecision } from './core/gateway-tools.js';
39
+ export { connectComposioSource } from './core/mcpplug.js';
40
+ export type { ToolSource, ComposioSourceOptions } from './core/mcpplug.js';
41
+ export { ToolLane, buildLanes, runLanes } from './core/toollane.js';
42
+ export type { ToolLaneOptions, SafetyClassifier, ToolCall, Lane, LaneResult } from './core/toollane.js';
35
43
  export { WebhookNotifier } from './core/http-notify-triggers/webhook.js';
36
44
  export { listQuestions, answerQuestion as resolvePendingQuestion, findQuestion, createAskUserTool } from './core/system-tools/index.js';
37
45
  export type { PendingQuestion } from './core/system-tools/index.js';
@@ -45,8 +53,6 @@ export { globalConfigPageHtml } from './web/global-config-page.js';
45
53
  export { playgroundHtml } from './web/playground.js';
46
54
  export { readSkill, writeSkill, deleteSkill, SkillInvalidIdError, SkillNotFoundError } from './web/skills-admin.js';
47
55
  export type { SkillContent } from './web/skills-admin.js';
48
- export { listSkillgardenCatalog, readSkillgardenCatalogEntry, addSkillgardenSkillToAgent, SkillgardenUnavailableError } from './web/skillgarden-admin.js';
49
- export type { SkillgardenCatalogEntry, SkillgardenCatalogDetail } from './web/skillgarden-admin.js';
50
56
  export { listDeclaredEnvVars, setEnvVar, EnvVarNameError } from './web/env-admin.js';
51
57
  export type { DeclaredEnvVar } from './web/env-admin.js';
52
58
  export { readActauthConfig, addActauthRule, updateActauthRule, removeActauthRule, setDefaultDecision, ActauthRuleExistsError, ActauthRuleNotFoundError, } from './web/actauth-admin.js';
package/dist/index.js CHANGED
@@ -12,6 +12,8 @@ export { runAgent } from '#core/run-agent.js';
12
12
  // exception.
13
13
  export { streamMessage, streamMessageWithCallbacks, sendMessage, approveCall, denyCall, answerQuestion, getSessionHistory, } from './core/client.js';
14
14
  export { FileSessionStore, RedisSessionStore, createSessionStore } from './core/session-store.js';
15
+ export { SessionKnit, FileStorage, MemoryStorage, reconstructChain } from './core/sessionknit.js';
16
+ export { SkillGarden, parseSkillFile, discoverSkillFiles, estimateTokens as estimateSkillIndexTokens, buildBudgetedIndex, substituteArguments, matchesActivationPaths, } from './core/skillgarden/index.js';
15
17
  export { VectorIndex, embed, cosineSimilarity } from './core/vector-index.js';
16
18
  export { createAnthropicModelCall } from './core/model-calls/anthropic-model-call.js';
17
19
  export { createOpenAIModelCall } from './core/model-calls/openai-model-call.js';
@@ -33,6 +35,8 @@ export { Compactor, TruncatingSummarizer } from './core/compaction.js';
33
35
  export { LLMSummarizer } from './core/llm-summarizer.js';
34
36
  export { createCheckpointStore } from './core/durable-approvals.js';
35
37
  export { addGatewayTool, agentDir, disconnectComposioAccount, describeGatewayTools, listComposioConnections, listComposioTools, loadGatewayToolsFromDir, removeGatewayTool, removeGatewayToolSlug, GatewayToolExistsError, GatewayToolNotFoundError, } from './core/gateway-tools.js';
38
+ export { connectComposioSource } from './core/mcpplug.js';
39
+ export { ToolLane, buildLanes, runLanes } from './core/toollane.js';
36
40
  export { WebhookNotifier } from './core/http-notify-triggers/webhook.js';
37
41
  // answerQuestion above (core/client.ts) is the HTTP client-side helper —
38
42
  // this is the server-side function that actually resolves a pending
@@ -45,7 +49,6 @@ export { agentsListPageHtml } from './web/agents-list-page.js';
45
49
  export { globalConfigPageHtml } from './web/global-config-page.js';
46
50
  export { playgroundHtml } from './web/playground.js';
47
51
  export { readSkill, writeSkill, deleteSkill, SkillInvalidIdError, SkillNotFoundError } from './web/skills-admin.js';
48
- export { listSkillgardenCatalog, readSkillgardenCatalogEntry, addSkillgardenSkillToAgent, SkillgardenUnavailableError } from './web/skillgarden-admin.js';
49
52
  export { listDeclaredEnvVars, setEnvVar, EnvVarNameError } from './web/env-admin.js';
50
53
  export { readActauthConfig, addActauthRule, updateActauthRule, removeActauthRule, setDefaultDecision, ActauthRuleExistsError, ActauthRuleNotFoundError, } from './web/actauth-admin.js';
51
54
  export { describeModelProviders, describeGateways } from './web/global-config.js';
@@ -367,26 +367,6 @@ export const agentsConfigPageHtml = `<!doctype html>
367
367
  #httpToolAddFieldBtn, #httpToolAddHeaderBtn {
368
368
  padding: 6px 12px;
369
369
  }
370
- .skillgarden-category-badge {
371
- display: inline-block;
372
- font-family: ui-sans-serif, system-ui, sans-serif;
373
- font-size: 10px;
374
- font-weight: 600;
375
- text-transform: uppercase;
376
- letter-spacing: 0.03em;
377
- padding: 1px 7px;
378
- border-radius: 999px;
379
- background: light-dark(#eef1ec, #1e2519);
380
- color: light-dark(#3f6b4a, #7fbf8f);
381
- margin-right: 6px;
382
- }
383
- #skillgardenFilterInput { margin-bottom: 8px; }
384
- .skillgarden-catalog-body {
385
- margin-top: 8px;
386
- padding-top: 8px;
387
- border-top: 1px solid light-dark(#eee, #2a2a2e);
388
- font-size: 13px;
389
- }
390
370
  </style>
391
371
  </head>
392
372
  <body>
@@ -1270,194 +1250,18 @@ export const agentsConfigPageHtml = `<!doctype html>
1270
1250
  '<button type="submit" id="skillSubmitBtn">Add</button>' +
1271
1251
  '<button type="button" id="skillCancelBtn" style="display:none">Cancel</button>' +
1272
1252
  '<div id="skillFormError" class="error"></div>' +
1273
- '</form></section>' +
1274
- '<section><h3>Browse skillgarden catalog</h3>' +
1275
- '<input type="text" id="skillgardenFilterInput" placeholder="Filter by name or category&hellip;">' +
1276
- '<div id="skillgardenCatalogList"><p class="hint">Loading&hellip;</p></div>' +
1277
- '</section>';
1253
+ '</form></section>';
1278
1254
  }
1279
1255
 
1280
1256
  function skillsPanelEl() {
1281
1257
  return detail.querySelector('[data-tab-panel="skills"]');
1282
1258
  }
1283
1259
 
1284
- // ---- Skills tab's "Browse skillgarden catalog" section: a
1285
- // read-only, agent-independent list of everything GET /skillgarden-
1286
- // catalog reports (see web/skillgarden-admin.ts) — cached in this one
1287
- // module-level variable, not per-agent like gatewayLoadedFor, since
1288
- // the catalog itself never varies by agent. renderSkillsTabHtml
1289
- // regenerates the whole Skills panel (including this section's own
1290
- // markup) on every agent switch/refresh, so loadSkillgardenCatalogSection
1291
- // re-renders from this cache instead of re-fetching every time — only
1292
- // the very first time this tab is opened in the page's lifetime hits
1293
- // the network at all. ----
1294
- var skillgardenCatalog = null;
1295
-
1296
- function skillgardenCatalogListEl() {
1297
- var panel = skillsPanelEl();
1298
- return panel ? panel.querySelector('#skillgardenCatalogList') : null;
1299
- }
1300
-
1301
- function renderSkillgardenCatalogEntry(entry) {
1302
- return '<div class="source" data-category="' + escapeHtml(entry.category) + '" data-id="' + escapeHtml(entry.id) + '">' +
1303
- '<div class="source-head">' +
1304
- '<h4><span class="skillgarden-category-badge">' + escapeHtml(entry.category) + '</span>' + escapeHtml(entry.id) + '</h4>' +
1305
- '<span>' +
1306
- '<button type="button" class="skillgarden-view-btn" data-category="' + escapeHtml(entry.category) + '" data-id="' + escapeHtml(entry.id) + '">View</button> ' +
1307
- '<button type="button" class="skillgarden-add-btn" data-category="' + escapeHtml(entry.category) + '" data-id="' + escapeHtml(entry.id) + '">Add to this agent</button>' +
1308
- '</span>' +
1309
- '</div>' +
1310
- '<p class="hint">' + escapeHtml(entry.description) + '</p>' +
1311
- '<div class="skillgarden-catalog-body" style="display:none"></div>' +
1312
- '</div>';
1313
- }
1314
-
1315
- function renderSkillgardenCatalogList(entries) {
1316
- return entries.length ? entries.map(renderSkillgardenCatalogEntry).join('') : '<p class="hint">No skills match.</p>';
1317
- }
1318
-
1319
- function applySkillgardenCatalog(name, entries) {
1320
- var listEl = skillgardenCatalogListEl();
1321
- if (!listEl) return;
1322
- listEl.innerHTML = renderSkillgardenCatalogList(entries);
1323
- wireSkillgardenCatalogHandlers(name);
1324
- }
1325
-
1326
- function filteredSkillgardenCatalog(filterText) {
1327
- if (!filterText) return skillgardenCatalog;
1328
- var needle = filterText.toLowerCase();
1329
- return skillgardenCatalog.filter(function (entry) {
1330
- return entry.category.toLowerCase().indexOf(needle) !== -1 || entry.id.toLowerCase().indexOf(needle) !== -1;
1331
- });
1332
- }
1333
-
1334
- function loadSkillgardenCatalogSection(name) {
1335
- var listEl = skillgardenCatalogListEl();
1336
- if (!listEl) return;
1337
- if (skillgardenCatalog) {
1338
- var panel = skillsPanelEl();
1339
- var filterInput = panel ? panel.querySelector('#skillgardenFilterInput') : null;
1340
- applySkillgardenCatalog(name, filteredSkillgardenCatalog(filterInput ? filterInput.value.trim() : ''));
1341
- return;
1342
- }
1343
- listEl.innerHTML = '<p class="hint">Loading&hellip;</p>';
1344
- fetch('/skillgarden-catalog')
1345
- .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
1346
- .then(function (result) {
1347
- if (!result.ok) throw new Error(result.body.error || 'request failed');
1348
- skillgardenCatalog = result.body;
1349
- if (currentName !== name) return;
1350
- applySkillgardenCatalog(name, skillgardenCatalog);
1351
- })
1352
- .catch(function (err) {
1353
- if (currentName !== name) return;
1354
- listEl.innerHTML = '<p class="error">Could not load skillgarden catalog: ' + escapeHtml(err.message) + '</p>';
1355
- });
1356
- }
1357
-
1358
- // force=true retries as an explicit overwrite after the operator
1359
- // confirms — same "ask once a plain add collides, don't ask up front"
1360
- // flow the rest of this page never has to think about since nothing
1361
- // else lets an operator add something whose id they don't already
1362
- // control the uniqueness of themselves.
1363
- function performSkillgardenAdd(name, category, id, btn, originalText, force) {
1364
- fetch('/agents/' + encodeURIComponent(name) + '/skillgarden-catalog/' + encodeURIComponent(category) + '/' + encodeURIComponent(id), {
1365
- method: 'POST',
1366
- headers: { 'content-type': 'application/json' },
1367
- body: JSON.stringify({ force: !!force }),
1368
- })
1369
- .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
1370
- .then(function (result) {
1371
- if (result.ok) {
1372
- refreshSkillsDependentPanels(name);
1373
- return;
1374
- }
1375
- if (!force && /already exists/.test(result.body.error || '')) {
1376
- if (confirm('A skill named "' + id + '" already exists for this agent. Overwrite it?')) {
1377
- performSkillgardenAdd(name, category, id, btn, originalText, true);
1378
- return;
1379
- }
1380
- btn.disabled = false;
1381
- btn.textContent = originalText;
1382
- return;
1383
- }
1384
- throw new Error(result.body.error || 'request failed');
1385
- })
1386
- .catch(function (err) {
1387
- alert('Could not add "' + id + '": ' + err.message);
1388
- btn.disabled = false;
1389
- btn.textContent = originalText;
1390
- });
1391
- }
1392
-
1393
- function wireSkillgardenCatalogHandlers(name) {
1394
- var listEl = skillgardenCatalogListEl();
1395
- if (!listEl) return;
1396
-
1397
- var viewButtons = listEl.querySelectorAll('.skillgarden-view-btn');
1398
- for (var i = 0; i < viewButtons.length; i++) {
1399
- viewButtons[i].addEventListener('click', function (ev) {
1400
- var btn = ev.currentTarget;
1401
- var category = btn.getAttribute('data-category');
1402
- var id = btn.getAttribute('data-id');
1403
- var card = btn.closest('.source');
1404
- var bodyEl = card.querySelector('.skillgarden-catalog-body');
1405
- if (bodyEl.style.display !== 'none') {
1406
- bodyEl.style.display = 'none';
1407
- btn.textContent = 'View';
1408
- return;
1409
- }
1410
- if (bodyEl.getAttribute('data-loaded') === '1') {
1411
- bodyEl.style.display = 'block';
1412
- btn.textContent = 'Hide';
1413
- return;
1414
- }
1415
- btn.disabled = true;
1416
- fetch('/skillgarden-catalog/' + encodeURIComponent(category) + '/' + encodeURIComponent(id))
1417
- .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
1418
- .then(function (result) {
1419
- if (!result.ok) throw new Error(result.body.error || 'request failed');
1420
- bodyEl.innerHTML = renderMarkdownPreview(result.body.body);
1421
- bodyEl.setAttribute('data-loaded', '1');
1422
- bodyEl.style.display = 'block';
1423
- btn.textContent = 'Hide';
1424
- })
1425
- .catch(function (err) {
1426
- bodyEl.innerHTML = '<p class="error">' + escapeHtml(err.message) + '</p>';
1427
- bodyEl.style.display = 'block';
1428
- })
1429
- .then(function () { btn.disabled = false; });
1430
- });
1431
- }
1432
-
1433
- var addButtons = listEl.querySelectorAll('.skillgarden-add-btn');
1434
- for (var j = 0; j < addButtons.length; j++) {
1435
- addButtons[j].addEventListener('click', function (ev) {
1436
- var btn = ev.currentTarget;
1437
- var category = btn.getAttribute('data-category');
1438
- var id = btn.getAttribute('data-id');
1439
- var originalText = btn.textContent;
1440
- btn.disabled = true;
1441
- btn.textContent = 'Adding…';
1442
- performSkillgardenAdd(name, category, id, btn, originalText, false);
1443
- });
1444
- }
1445
- }
1446
-
1447
1260
  function wireSkillsHandlers(name) {
1448
1261
  var panel = skillsPanelEl();
1449
1262
  if (!panel) return;
1450
1263
  var editingSkillId = null;
1451
1264
 
1452
- loadSkillgardenCatalogSection(name);
1453
- var skillgardenFilterInput = panel.querySelector('#skillgardenFilterInput');
1454
- if (skillgardenFilterInput) {
1455
- skillgardenFilterInput.addEventListener('input', function () {
1456
- if (!skillgardenCatalog) return;
1457
- applySkillgardenCatalog(name, filteredSkillgardenCatalog(skillgardenFilterInput.value.trim()));
1458
- });
1459
- }
1460
-
1461
1265
  // Write/Preview toggle for the Body field — same idea as GitHub's own
1462
1266
  // issue/PR editor: the textarea and the rendered preview show/hide
1463
1267
  // each other, the textarea's value is the only source of truth (the
@@ -11,7 +11,7 @@
11
11
  // only adding/editing/removing one through this UI is out of scope.
12
12
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
13
13
  import { dirname, join } from 'node:path';
14
- import { parseSkillFile } from 'skillgarden';
14
+ import { parseSkillFile } from '../core/skillgarden/index.js';
15
15
  import { agentDir } from '../core/gateway-tools.js';
16
16
  export class SkillInvalidIdError extends Error {
17
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopengine",
3
- "version": "0.1.15",
3
+ "version": "0.1.22",
4
4
  "type": "module",
5
5
  "description": "Runtime for tool-using AI agents with a transparent ReAct loop.",
6
6
  "license": "MIT",
@@ -55,6 +55,12 @@
55
55
  "dist/core/agent-as-tool.d.ts",
56
56
  "dist/core/gateway-tools.js",
57
57
  "dist/core/gateway-tools.d.ts",
58
+ "dist/core/mcpplug.js",
59
+ "dist/core/mcpplug.d.ts",
60
+ "dist/core/toollane.js",
61
+ "dist/core/toollane.d.ts",
62
+ "dist/core/sessionknit.js",
63
+ "dist/core/sessionknit.d.ts",
58
64
  "dist/core/budget.js",
59
65
  "dist/core/budget.d.ts",
60
66
  "dist/core/compaction.js",
@@ -68,6 +74,7 @@
68
74
  "dist/core/http-notify-triggers",
69
75
  "dist/core/system-tools",
70
76
  "dist/core/system-skills",
77
+ "dist/core/skillgarden",
71
78
  "dist/core/durable-approvals.js",
72
79
  "dist/core/durable-approvals.d.ts",
73
80
  "dist/web",
@@ -85,12 +92,8 @@
85
92
  "@anthropic-ai/sdk": "^0.116.0",
86
93
  "actauth": "^0.0.12",
87
94
  "ioredis": "^6.0.0",
88
- "mcpplug": "^0.0.1",
89
95
  "openai": "^7.4.0",
90
96
  "semver": "^7.8.5",
91
- "sessionknit": "^0.0.2",
92
- "skillgarden": "^0.0.9",
93
- "toollane": "^0.0.1",
94
97
  "typescript": "^5.7.0",
95
98
  "yaml": "^2.6.0"
96
99
  },
@@ -1,24 +0,0 @@
1
- export declare class SkillgardenUnavailableError extends Error {
2
- }
3
- export interface SkillgardenCatalogEntry {
4
- category: string;
5
- id: string;
6
- description: string;
7
- }
8
- export interface SkillgardenCatalogDetail extends SkillgardenCatalogEntry {
9
- body: string;
10
- }
11
- export declare function listSkillgardenCatalog(): SkillgardenCatalogEntry[];
12
- export declare function readSkillgardenCatalogEntry(category: string, id: string): SkillgardenCatalogDetail;
13
- /** Copies `registry/<category>/<id>/` into `agents/<agentName>/skills/<id>/`
14
- * — delegates entirely to skillgarden's own `addSkill`, which already
15
- * knows this exact `agents/<agent>/skills` destination convention (see
16
- * its own doc comment) and already refuses (or, with `force`,
17
- * overwrites) an existing destination. Returns the bare skill id actually
18
- * installed — never category-qualified, matching `addSkill`'s own
19
- * "category is a registry-organization concept, not forced onto what a
20
- * caller now owns" convention. */
21
- export declare function addSkillgardenSkillToAgent(agentName: string, category: string, id: string, force?: boolean): {
22
- id: string;
23
- path: string;
24
- };
@@ -1,76 +0,0 @@
1
- // Lets an operator browse skillgarden's own bundled skill registry from
2
- // the Skills tab in /agents/config, and add one straight into an agent's
3
- // agents/<name>/skills/ folder — a shortcut for the same thing
4
- // `npx skillgarden add <skill> --agent <name>` already does from a
5
- // terminal, surfaced in the UI instead. This module only ever *reads*
6
- // the registry and delegates the actual copy to skillgarden's own
7
- // exported `addSkill` (see below) — it never re-implements that logic.
8
- import { createRequire } from 'node:module';
9
- import { dirname, join } from 'node:path';
10
- import { SkillGarden, addSkill as skillgardenAddSkill } from 'skillgarden';
11
- export class SkillgardenUnavailableError extends Error {
12
- }
13
- // skillgarden's own CLI resolves its bundled registry/ relative to its
14
- // own compiled dist/cli.js location (see its cli.ts's own
15
- // DEFAULT_REGISTRY_DIR), but doesn't export that path — `addSkill`
16
- // itself defaults to it internally, which is enough for *installing* a
17
- // skill, but browsing/previewing the catalog needs the directory path
18
- // itself. Re-derived here the same way Node's own module resolution
19
- // would: from package.json's real installed location, not assumed
20
- // relative to this file (skillgarden is a real npm dependency, not a
21
- // vendored copy).
22
- function registryDir() {
23
- const require = createRequire(import.meta.url);
24
- let pkgJsonPath;
25
- try {
26
- pkgJsonPath = require.resolve('skillgarden/package.json');
27
- }
28
- catch {
29
- throw new SkillgardenUnavailableError('skillgarden is not installed — run `npm install skillgarden` to enable the skill catalog.');
30
- }
31
- return join(dirname(pkgJsonPath), 'registry');
32
- }
33
- // A large, fixed budget, not run-agent.ts's own live skillIndexBudgetTokens
34
- // default — this indexes a small, fixed, bundled catalog for browsing,
35
- // not an agent's own live context, so nothing here should ever silently
36
- // truncate the list an operator sees.
37
- const CATALOG_INDEX_BUDGET_TOKENS = 1_000_000;
38
- // discoverSkillFiles namespaces registry/<category>/<skill>/SKILL.md as
39
- // "<category>:<skill>" (colon-joined, one level of nesting) — split on
40
- // just the first colon, not skillId itself, in case a future entry ever
41
- // nests deeper than one category level.
42
- function splitNamespacedName(namespacedName) {
43
- const colon = namespacedName.indexOf(':');
44
- return { category: namespacedName.slice(0, colon), id: namespacedName.slice(colon + 1) };
45
- }
46
- function catalogGarden() {
47
- return new SkillGarden({ dirs: [registryDir()], indexBudgetTokens: CATALOG_INDEX_BUDGET_TOKENS });
48
- }
49
- export function listSkillgardenCatalog() {
50
- const { included } = catalogGarden().buildIndex();
51
- return included
52
- .map((entry) => ({ ...splitNamespacedName(entry.name), description: entry.description }))
53
- .sort((a, b) => (a.category === b.category ? a.id.localeCompare(b.id) : a.category.localeCompare(b.category)));
54
- }
55
- export function readSkillgardenCatalogEntry(category, id) {
56
- const garden = catalogGarden();
57
- garden.buildIndex();
58
- // load() throws a plain Error for an unindexed name — same "not found"
59
- // shape a bad category/id combination should surface as; the caller
60
- // (adapters/http.ts) maps any throw here to 404, so there's nothing
61
- // more specific to catch and re-throw.
62
- const loaded = garden.load(`${category}:${id}`);
63
- return { category, id, description: loaded.description, body: loaded.body };
64
- }
65
- /** Copies `registry/<category>/<id>/` into `agents/<agentName>/skills/<id>/`
66
- * — delegates entirely to skillgarden's own `addSkill`, which already
67
- * knows this exact `agents/<agent>/skills` destination convention (see
68
- * its own doc comment) and already refuses (or, with `force`,
69
- * overwrites) an existing destination. Returns the bare skill id actually
70
- * installed — never category-qualified, matching `addSkill`'s own
71
- * "category is a registry-organization concept, not forced onto what a
72
- * caller now owns" convention. */
73
- export function addSkillgardenSkillToAgent(agentName, category, id, force = false) {
74
- const result = skillgardenAddSkill({ skill: `${category}/${id}`, agent: agentName, registryDir: registryDir(), force });
75
- return { id, path: join(result.destination, 'SKILL.md') };
76
- }