clay-server 4.1.0-beta.16 → 4.1.0-beta.18
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/lib/issues-service.js +3 -1
- package/lib/notes.js +20 -3
- package/lib/os-users.js +6 -0
- package/lib/project-connection.js +5 -2
- package/lib/project-http.js +34 -37
- package/lib/project-sessions.js +1 -0
- package/lib/project-user-message.js +43 -0
- package/lib/project.js +21 -0
- package/lib/public/css/issues.css +17 -3
- package/lib/public/css/sticky-notes.css +29 -0
- package/lib/public/modules/app-messages.js +5 -0
- package/lib/public/modules/scheduled-tasks.js +1 -1
- package/lib/public/modules/sticky-notes-browser.js +126 -5
- package/lib/public/modules/sticky-notes.js +2 -3
- package/lib/sdk-bridge.js +63 -19
- package/lib/sdk-message-processor.js +19 -8
- package/lib/sdk-skill-discovery.js +6 -28
- package/lib/sessions.js +8 -1
- package/lib/yoke/adapters/acp.js +3 -2
- package/lib/yoke/adapters/antigravity.js +2 -2
- package/lib/yoke/adapters/claude.js +25 -7
- package/lib/yoke/adapters/codex.js +158 -16
- package/lib/yoke/adapters/kiro.js +6 -5
- package/lib/yoke/session-skill-catalog.js +45 -0
- package/lib/yoke/skill-discovery.js +60 -20
- package/package.json +1 -1
|
@@ -67,10 +67,28 @@ function applySystemPromptSnapshot(systemPrompt, enabled) {
|
|
|
67
67
|
return systemPrompt;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
function getSharedSkillPlugins(homeDir, existingPlugins) {
|
|
70
|
+
function getSharedSkillPlugins(homeDir, existingPlugins, cwd, env) {
|
|
71
71
|
var plugins = Array.isArray(existingPlugins) ? existingPlugins.slice() : [];
|
|
72
|
-
|
|
72
|
+
// undefined means normal in-process Claude and may use the configured
|
|
73
|
+
// daemon home; null means a failed mapped-user resolution and must fail
|
|
74
|
+
// closed without constructing any personal bridges.
|
|
75
|
+
if (homeDir === null) return plugins;
|
|
76
|
+
var effectiveHome = homeDir === undefined ? require("../../config").REAL_HOME || os.homedir() : homeDir;
|
|
77
|
+
var rootOptions = { homeDir: effectiveHome, env: env, scopedHome: !!homeDir };
|
|
78
|
+
var roots = skillDiscovery.getSkillRoots(cwd, rootOptions);
|
|
79
|
+
var codexRoot = roots.filter(function (root) { return root.source === "codex-user"; })[0];
|
|
80
|
+
var codexOpts = codexRoot ? { homeDir: effectiveHome, source: codexRoot.path, name: "codex" } : null;
|
|
81
|
+
if (codexRoot && codexRoot.path !== path.join(effectiveHome, ".codex", "skills")) {
|
|
82
|
+
codexOpts.name = "codex-" + crypto.createHash("sha256").update(codexRoot.path).digest("hex").slice(0, 12);
|
|
83
|
+
}
|
|
84
|
+
var bridgePath = skillDiscovery.ensureClaudeCodexPlugin(codexOpts);
|
|
73
85
|
if (bridgePath) plugins.push({ type: "local", path: bridgePath, skipMcpDiscovery: true });
|
|
86
|
+
for (var i = 0; i < roots.length; i++) {
|
|
87
|
+
if (roots[i].source === "codex-user" || roots[i].source === "claude-user" || roots[i].source === "claude-project") continue;
|
|
88
|
+
var rootId = crypto.createHash("sha256").update(roots[i].source + "\0" + path.resolve(roots[i].path)).digest("hex").slice(0, 12);
|
|
89
|
+
var sharedPath = skillDiscovery.ensureClaudeCodexPlugin({ homeDir: effectiveHome, source: roots[i].path, name: "shared-" + roots[i].source + "-" + rootId });
|
|
90
|
+
if (sharedPath) plugins.push({ type: "local", path: sharedPath, skipMcpDiscovery: true });
|
|
91
|
+
}
|
|
74
92
|
return plugins;
|
|
75
93
|
}
|
|
76
94
|
|
|
@@ -99,7 +117,7 @@ function buildWorkerQueryOptions(queryOpts, claudeOpts, workerCwd) {
|
|
|
99
117
|
queryOpts.systemPromptSnapshot
|
|
100
118
|
);
|
|
101
119
|
}
|
|
102
|
-
queryOptions.plugins = queryOpts.skipSkills ? (claudeOpts.plugins || []) : getSharedSkillPlugins(claudeOpts.originalHome, claudeOpts.plugins);
|
|
120
|
+
queryOptions.plugins = queryOpts.skipSkills ? (claudeOpts.plugins || []) : getSharedSkillPlugins(claudeOpts.originalHome, claudeOpts.plugins, workerCwd, queryOpts.env);
|
|
103
121
|
if (queryOpts.toolServerDescriptors) queryOptions.mcpServerDescriptors = queryOpts.toolServerDescriptors;
|
|
104
122
|
if (queryOpts.onUserDialog) queryOptions.supportedDialogKinds = SUPPORTED_DIALOG_KINDS;
|
|
105
123
|
queryOptions.perTaskStopAffordance = PER_TASK_STOP_AFFORDANCE;
|
|
@@ -1423,7 +1441,7 @@ function createClaudeAdapter(opts) {
|
|
|
1423
1441
|
abortController: ac,
|
|
1424
1442
|
settings: { disableAllHooks: true },
|
|
1425
1443
|
};
|
|
1426
|
-
warmupOptions.plugins = getSharedSkillPlugins(initOpts && initOpts.originalHome);
|
|
1444
|
+
warmupOptions.plugins = getSharedSkillPlugins(initOpts && initOpts.originalHome, null, initOpts && initOpts.cwd, initOpts && initOpts.env);
|
|
1427
1445
|
if (_claudeBinaryPath) warmupOptions.pathToClaudeCodeExecutable = _claudeBinaryPath;
|
|
1428
1446
|
|
|
1429
1447
|
if (initOpts && initOpts.dangerouslySkipPermissions) {
|
|
@@ -1637,7 +1655,7 @@ function createClaudeAdapter(opts) {
|
|
|
1637
1655
|
if (co.allowDangerouslySkipPermissions) sdkOptions.allowDangerouslySkipPermissions = true;
|
|
1638
1656
|
if (co.resumeSessionAt) sdkOptions.resumeSessionAt = co.resumeSessionAt;
|
|
1639
1657
|
if (co.settings) sdkOptions.settings = co.settings;
|
|
1640
|
-
sdkOptions.plugins = queryOpts.skipSkills ? (co.plugins || []) : getSharedSkillPlugins(co.originalHome, co.plugins);
|
|
1658
|
+
sdkOptions.plugins = queryOpts.skipSkills ? (co.plugins || []) : getSharedSkillPlugins(co.originalHome, co.plugins, queryOpts.cwd || _cwd, queryOpts.env);
|
|
1641
1659
|
|
|
1642
1660
|
claudeModelSwitch.attach(sdkOptions, queryOpts.onModelSwitch);
|
|
1643
1661
|
var rawQuery = sdk.query({ prompt: mq, options: sdkOptions });
|
|
@@ -1897,7 +1915,7 @@ function createClaudeAdapter(opts) {
|
|
|
1897
1915
|
abortController: ac,
|
|
1898
1916
|
settings: { disableAllHooks: true },
|
|
1899
1917
|
};
|
|
1900
|
-
warmupOptions.plugins = getSharedSkillPlugins(initOpts && initOpts.originalHome);
|
|
1918
|
+
warmupOptions.plugins = getSharedSkillPlugins(initOpts && initOpts.originalHome, null, initOpts && initOpts.cwd, initOpts && initOpts.env);
|
|
1901
1919
|
if (_claudeBinaryPath) warmupOptions.pathToClaudeCodeExecutable = _claudeBinaryPath;
|
|
1902
1920
|
|
|
1903
1921
|
if (initOpts && initOpts.dangerouslySkipPermissions) {
|
|
@@ -1979,7 +1997,7 @@ function createClaudeAdapter(opts) {
|
|
|
1979
1997
|
}
|
|
1980
1998
|
|
|
1981
1999
|
var warmupOptions = { cwd: workerCwd, settingSources: ["user", "project", "local"], settings: { disableAllHooks: true } };
|
|
1982
|
-
warmupOptions.plugins = getSharedSkillPlugins(initOpts && initOpts.originalHome);
|
|
2000
|
+
warmupOptions.plugins = getSharedSkillPlugins(initOpts && initOpts.originalHome, null, initOpts && initOpts.cwd, initOpts && initOpts.env);
|
|
1983
2001
|
if (_claudeBinaryPath) warmupOptions.pathToClaudeCodeExecutable = _claudeBinaryPath;
|
|
1984
2002
|
if (initOpts && initOpts.dangerouslySkipPermissions) {
|
|
1985
2003
|
warmupOptions.permissionMode = "bypassPermissions";
|
|
@@ -29,7 +29,7 @@ var CODEX_REASONING_DEFAULTS = {
|
|
|
29
29
|
model_supports_reasoning_summaries: true,
|
|
30
30
|
};
|
|
31
31
|
var DEFAULT_CODEX_MODEL = "gpt-6-astra";
|
|
32
|
-
var CODEX_QUERY_BOUND_TOOL_CAPABILITY = { mode: "dynamic", resume: "
|
|
32
|
+
var CODEX_QUERY_BOUND_TOOL_CAPABILITY = { mode: "dynamic", resume: "session-mcp-recovery" };
|
|
33
33
|
var CODEX_NOTE_TOOLS = {
|
|
34
34
|
list_notes: true,
|
|
35
35
|
write_note: true,
|
|
@@ -1163,16 +1163,25 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
1163
1163
|
|
|
1164
1164
|
var threadResult;
|
|
1165
1165
|
if (queryOpts.resumeSessionId) {
|
|
1166
|
-
// ThreadResumeParams
|
|
1167
|
-
//
|
|
1168
|
-
//
|
|
1169
|
-
|
|
1166
|
+
// ThreadResumeParams has no dynamicTools field, but its supported
|
|
1167
|
+
// config override can start an exact-query MCP bridge without making
|
|
1168
|
+
// a replacement thread or changing the saved conversation history.
|
|
1169
|
+
var resumeParams = {
|
|
1170
1170
|
threadId: queryOpts.resumeSessionId,
|
|
1171
1171
|
model: threadParams.model,
|
|
1172
1172
|
sandbox: threadParams.sandbox,
|
|
1173
1173
|
approvalPolicy: threadParams.approvalPolicy,
|
|
1174
1174
|
cwd: threadParams.cwd,
|
|
1175
|
-
}
|
|
1175
|
+
};
|
|
1176
|
+
if (queryOpts.sessionMcpServer && !queryOpts.isolatedRecovery) {
|
|
1177
|
+
resumeParams.config = { mcp_servers: {} };
|
|
1178
|
+
resumeParams.config.mcp_servers[queryOpts.sessionMcpServer.name] = {
|
|
1179
|
+
command: queryOpts.sessionMcpServer.command,
|
|
1180
|
+
args: queryOpts.sessionMcpServer.args || [],
|
|
1181
|
+
env: queryOpts.sessionMcpServer.env || {},
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
threadResult = await appServer.send("thread/resume", resumeParams, 60000);
|
|
1176
1185
|
} else {
|
|
1177
1186
|
threadResult = await appServer.send("thread/start", threadParams, 60000);
|
|
1178
1187
|
}
|
|
@@ -1213,7 +1222,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
1213
1222
|
|
|
1214
1223
|
// Explicit references use Codex's native skill input item regardless of
|
|
1215
1224
|
// which vendor owns the SKILL.md file.
|
|
1216
|
-
var availableSkills = queryOpts.skipSkills ? [] : skillDiscovery.discoverSkills(queryOpts.cwd);
|
|
1225
|
+
var availableSkills = queryOpts.skipSkills ? [] : skillDiscovery.discoverSkills(queryOpts.cwd, queryOpts.skillOptions || { env: queryOpts.env });
|
|
1217
1226
|
var skillItemsToInject = [];
|
|
1218
1227
|
var injected = {};
|
|
1219
1228
|
for (var ii = 0; ii < input.length; ii++) {
|
|
@@ -1427,11 +1436,25 @@ function createCodexAdapter(opts) {
|
|
|
1427
1436
|
for (var i = 0; i < listeners.length; i++) listeners[i]();
|
|
1428
1437
|
}
|
|
1429
1438
|
var _initPromise = null;
|
|
1439
|
+
var _initializing = false;
|
|
1430
1440
|
var _shutdownPromise = null;
|
|
1431
1441
|
var _refCount = 0;
|
|
1432
1442
|
var _lastActiveAt = Date.now();
|
|
1433
1443
|
var _shuttingDown = false;
|
|
1434
1444
|
var _activeQueries = [];
|
|
1445
|
+
var _recoveryHandoff = null;
|
|
1446
|
+
var _recoveryRuntimes = [];
|
|
1447
|
+
var _recoveryCleanup = Promise.resolve();
|
|
1448
|
+
var _recoveryCleanupTasks = [];
|
|
1449
|
+
|
|
1450
|
+
function scopedSkillOptions(env) {
|
|
1451
|
+
if (!_runtimeLinuxUser) return { env: env };
|
|
1452
|
+
try {
|
|
1453
|
+
return Object.assign(skillDiscovery.optionsForOsUser(_resolveOsUserInfo(_runtimeLinuxUser)), { env: env });
|
|
1454
|
+
} catch (e) {
|
|
1455
|
+
return { disable: true };
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1435
1458
|
|
|
1436
1459
|
function withoutLinuxUser(callOpts) {
|
|
1437
1460
|
var cleaned = Object.assign({}, callOpts || {});
|
|
@@ -1498,6 +1521,95 @@ function createCodexAdapter(opts) {
|
|
|
1498
1521
|
updateLastActiveAt();
|
|
1499
1522
|
}
|
|
1500
1523
|
|
|
1524
|
+
function waitForRecoveryHandoff() {
|
|
1525
|
+
return _recoveryHandoff || Promise.resolve();
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
function retainRecoveryRuntime(runtime) {
|
|
1529
|
+
_recoveryRuntimes.push(runtime);
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
function removeRecoveryRuntime(runtime) {
|
|
1533
|
+
var next = [];
|
|
1534
|
+
for (var i = 0; i < _recoveryRuntimes.length; i++) {
|
|
1535
|
+
if (_recoveryRuntimes[i] !== runtime) next.push(_recoveryRuntimes[i]);
|
|
1536
|
+
}
|
|
1537
|
+
_recoveryRuntimes = next;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
function cleanupRecoveryRuntime(runtime) {
|
|
1541
|
+
for (var i = 0; i < _recoveryCleanupTasks.length; i++) {
|
|
1542
|
+
if (_recoveryCleanupTasks[i].runtime === runtime) return _recoveryCleanupTasks[i].promise;
|
|
1543
|
+
}
|
|
1544
|
+
var task = { runtime: runtime, promise: null };
|
|
1545
|
+
_recoveryCleanupTasks.push(task);
|
|
1546
|
+
var priorCleanup = _recoveryCleanup;
|
|
1547
|
+
task.promise = Promise.resolve(priorCleanup).catch(function() {
|
|
1548
|
+
return null;
|
|
1549
|
+
}).then(function() {
|
|
1550
|
+
return runtime.shutdown();
|
|
1551
|
+
}).catch(function(error) {
|
|
1552
|
+
console.error("[yoke/codex] Isolated recovery runtime shutdown failed:", error.message || error);
|
|
1553
|
+
}).then(function() {
|
|
1554
|
+
removeRecoveryRuntime(runtime);
|
|
1555
|
+
var remainingTasks = [];
|
|
1556
|
+
for (var ti = 0; ti < _recoveryCleanupTasks.length; ti++) {
|
|
1557
|
+
if (_recoveryCleanupTasks[ti] !== task) remainingTasks.push(_recoveryCleanupTasks[ti]);
|
|
1558
|
+
}
|
|
1559
|
+
_recoveryCleanupTasks = remainingTasks;
|
|
1560
|
+
});
|
|
1561
|
+
_recoveryCleanup = task.promise;
|
|
1562
|
+
return task.promise;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
function shutdownRecoveryRuntimes() {
|
|
1566
|
+
var runtimes = _recoveryRuntimes.slice();
|
|
1567
|
+
if (!runtimes.length) return _recoveryCleanup;
|
|
1568
|
+
return Promise.all(runtimes.map(function(runtime) {
|
|
1569
|
+
return cleanupRecoveryRuntime(runtime);
|
|
1570
|
+
})).then(function() {
|
|
1571
|
+
_recoveryRuntimes = [];
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
async function createIsolatedRecoveryQuery(queryOpts) {
|
|
1576
|
+
var releaseHandoff;
|
|
1577
|
+
var priorHandoff = _recoveryHandoff;
|
|
1578
|
+
var myHandoff = new Promise(function(resolve) { releaseHandoff = resolve; });
|
|
1579
|
+
_recoveryHandoff = myHandoff;
|
|
1580
|
+
await (priorHandoff || Promise.resolve());
|
|
1581
|
+
try {
|
|
1582
|
+
await _recoveryCleanup;
|
|
1583
|
+
if (_refCount > 0 || _initializing) {
|
|
1584
|
+
throw new Error("Codex resumed-tool recovery is waiting for an active shared runtime; retry after its current queries finish.");
|
|
1585
|
+
}
|
|
1586
|
+
if (_appServer) {
|
|
1587
|
+
var stopped = await adapter.shutdownIfIdle(0);
|
|
1588
|
+
if (!stopped) throw new Error("Codex resumed-tool recovery could not safely release the shared runtime; retry after its current queries finish.");
|
|
1589
|
+
}
|
|
1590
|
+
var recoveryRuntime = createCodexAdapter(Object.assign({}, _defaultInitOpts, {
|
|
1591
|
+
recoveryRuntime: true,
|
|
1592
|
+
recoverySessionMcpServer: queryOpts.sessionMcpServer,
|
|
1593
|
+
}));
|
|
1594
|
+
retainRecoveryRuntime(recoveryRuntime);
|
|
1595
|
+
var recoveryOpts = Object.assign({}, queryOpts, {
|
|
1596
|
+
isolatedRecovery: true,
|
|
1597
|
+
onRuntimeFinished: function() {
|
|
1598
|
+
return cleanupRecoveryRuntime(recoveryRuntime);
|
|
1599
|
+
},
|
|
1600
|
+
});
|
|
1601
|
+
try {
|
|
1602
|
+
return await recoveryRuntime.createQuery(recoveryOpts);
|
|
1603
|
+
} catch (error) {
|
|
1604
|
+
await cleanupRecoveryRuntime(recoveryRuntime);
|
|
1605
|
+
throw error;
|
|
1606
|
+
}
|
|
1607
|
+
} finally {
|
|
1608
|
+
releaseHandoff();
|
|
1609
|
+
if (_recoveryHandoff === myHandoff) _recoveryHandoff = null;
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1501
1613
|
function buildReadyResponse(skillNames) {
|
|
1502
1614
|
return {
|
|
1503
1615
|
models: _cachedModels,
|
|
@@ -1530,6 +1642,7 @@ function createCodexAdapter(opts) {
|
|
|
1530
1642
|
function clearRuntimeState() {
|
|
1531
1643
|
_appServer = null;
|
|
1532
1644
|
_initPromise = null;
|
|
1645
|
+
_initializing = false;
|
|
1533
1646
|
_cachedModels = [];
|
|
1534
1647
|
_refCount = 0;
|
|
1535
1648
|
_activeQueries = [];
|
|
@@ -1584,7 +1697,7 @@ function createCodexAdapter(opts) {
|
|
|
1584
1697
|
var deadline = Date.now() + 5000;
|
|
1585
1698
|
var shouldAbort = !!force;
|
|
1586
1699
|
|
|
1587
|
-
if (_initPromise) {
|
|
1700
|
+
if (_initializing && _initPromise) {
|
|
1588
1701
|
try {
|
|
1589
1702
|
await Promise.race([
|
|
1590
1703
|
_initPromise.catch(function() { return null; }),
|
|
@@ -1649,9 +1762,10 @@ function createCodexAdapter(opts) {
|
|
|
1649
1762
|
}
|
|
1650
1763
|
|
|
1651
1764
|
// Deduplicate concurrent init calls
|
|
1652
|
-
if (_initPromise) return _initPromise;
|
|
1765
|
+
if (_initializing && _initPromise) return _initPromise;
|
|
1653
1766
|
|
|
1654
|
-
|
|
1767
|
+
_initializing = true;
|
|
1768
|
+
var initPromise = (async function() {
|
|
1655
1769
|
var serverOpts = { cwd: _cwd, env: effectiveInitOpts.env || null };
|
|
1656
1770
|
if (_runtimeLinuxUser) {
|
|
1657
1771
|
serverOpts.osUserInfo = _resolveOsUserInfo(_runtimeLinuxUser);
|
|
@@ -1671,7 +1785,7 @@ function createCodexAdapter(opts) {
|
|
|
1671
1785
|
|
|
1672
1786
|
// Track 1: Read local MCP server definitions from ~/.clay/mcp.json
|
|
1673
1787
|
// and inject into Codex config so Codex manages them natively.
|
|
1674
|
-
var mcpServerConfig = {};
|
|
1788
|
+
var mcpServerConfig = Object.assign({}, serverOpts.config.mcp_servers || {});
|
|
1675
1789
|
try {
|
|
1676
1790
|
var mcpLocal = require("../../mcp-local");
|
|
1677
1791
|
var localMcpServers = mcpLocal.readMergedServers();
|
|
@@ -1711,6 +1825,17 @@ function createCodexAdapter(opts) {
|
|
|
1711
1825
|
console.error("[codex] Failed to configure clay-tools bridge:", e.message);
|
|
1712
1826
|
}
|
|
1713
1827
|
|
|
1828
|
+
// Recovery uses a dedicated app-server so the query-local bridge can
|
|
1829
|
+
// coexist with every server already configured for this runtime.
|
|
1830
|
+
var recoveryMcpServer = effectiveInitOpts.recoverySessionMcpServer;
|
|
1831
|
+
if (recoveryMcpServer && recoveryMcpServer.name && recoveryMcpServer.command) {
|
|
1832
|
+
mcpServerConfig[recoveryMcpServer.name] = {
|
|
1833
|
+
command: recoveryMcpServer.command,
|
|
1834
|
+
args: recoveryMcpServer.args || [],
|
|
1835
|
+
env: recoveryMcpServer.env || {},
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1714
1839
|
if (Object.keys(mcpServerConfig).length > 0) {
|
|
1715
1840
|
serverOpts.config = Object.assign({}, serverOpts.config || {}, {
|
|
1716
1841
|
mcp_servers: mcpServerConfig,
|
|
@@ -1749,7 +1874,8 @@ function createCodexAdapter(opts) {
|
|
|
1749
1874
|
// triggering and skills/list use the same merged inventory.
|
|
1750
1875
|
var skillNames = [];
|
|
1751
1876
|
try {
|
|
1752
|
-
var
|
|
1877
|
+
var skillOptions = scopedSkillOptions(effectiveInitOpts.env);
|
|
1878
|
+
var roots = skillOptions.disable ? [] : skillDiscovery.getSkillRoots(_cwd, skillOptions);
|
|
1753
1879
|
var extraRootPaths = [];
|
|
1754
1880
|
for (var ri = 0; ri < roots.length; ri++) {
|
|
1755
1881
|
if (roots[ri].source !== "codex-user" && path.isAbsolute(roots[ri].path)) extraRootPaths.push(roots[ri].path);
|
|
@@ -1778,7 +1904,7 @@ function createCodexAdapter(opts) {
|
|
|
1778
1904
|
}
|
|
1779
1905
|
// Direct discovery remains the fallback for older Codex app-server
|
|
1780
1906
|
// versions and keeps the GUI inventory vendor-neutral.
|
|
1781
|
-
var sharedSkills = skillDiscovery.discoverSkills(_cwd);
|
|
1907
|
+
var sharedSkills = skillDiscovery.discoverSkills(_cwd, skillOptions);
|
|
1782
1908
|
for (var csn = 0; csn < sharedSkills.length; csn++) {
|
|
1783
1909
|
if (skillNames.indexOf(sharedSkills[csn].name) === -1) skillNames.push(sharedSkills[csn].name);
|
|
1784
1910
|
}
|
|
@@ -1792,13 +1918,20 @@ function createCodexAdapter(opts) {
|
|
|
1792
1918
|
throw createShutdownError();
|
|
1793
1919
|
}
|
|
1794
1920
|
|
|
1795
|
-
_initPromise = null;
|
|
1796
1921
|
updateLastActiveAt();
|
|
1797
1922
|
|
|
1798
1923
|
return buildReadyResponse(skillNames);
|
|
1799
1924
|
})();
|
|
1800
1925
|
|
|
1801
|
-
|
|
1926
|
+
_initPromise = initPromise;
|
|
1927
|
+
initPromise.then(function() {
|
|
1928
|
+
_initializing = false;
|
|
1929
|
+
if (_initPromise === initPromise) _initPromise = null;
|
|
1930
|
+
}, function() {
|
|
1931
|
+
_initializing = false;
|
|
1932
|
+
if (_initPromise === initPromise) _initPromise = null;
|
|
1933
|
+
});
|
|
1934
|
+
return initPromise;
|
|
1802
1935
|
},
|
|
1803
1936
|
|
|
1804
1937
|
supportedModels: function() {
|
|
@@ -1827,6 +1960,10 @@ function createCodexAdapter(opts) {
|
|
|
1827
1960
|
if (_shuttingDown) {
|
|
1828
1961
|
throw createShutdownError();
|
|
1829
1962
|
}
|
|
1963
|
+
if (queryOpts && queryOpts.sessionMcpServer && queryOpts.resumeSessionId && !queryOpts.isolatedRecovery) {
|
|
1964
|
+
return createIsolatedRecoveryQuery(queryOpts);
|
|
1965
|
+
}
|
|
1966
|
+
await waitForRecoveryHandoff();
|
|
1830
1967
|
|
|
1831
1968
|
if (!_appServer || !_appServer.started) {
|
|
1832
1969
|
await adapter.init(queryOpts || {});
|
|
@@ -1865,8 +2002,11 @@ function createCodexAdapter(opts) {
|
|
|
1865
2002
|
onElicitation: queryOpts.userInputMode === "fallback" ? null : (queryOpts.onElicitation || null),
|
|
1866
2003
|
onUserInputRequest: queryOpts.userInputMode === "fallback" ? null : (queryOpts.onUserInputRequest || null),
|
|
1867
2004
|
resumeSessionId: queryOpts.resumeSessionId || null,
|
|
2005
|
+
sessionMcpServer: queryOpts.sessionMcpServer || null,
|
|
2006
|
+
isolatedRecovery: queryOpts.isolatedRecovery === true,
|
|
1868
2007
|
registerBackgroundTaskReset: registerBackgroundTaskReset,
|
|
1869
2008
|
ephemeral: queryOpts.ephemeral === true,
|
|
2009
|
+
skillOptions: scopedSkillOptions(queryOpts.env),
|
|
1870
2010
|
};
|
|
1871
2011
|
|
|
1872
2012
|
// Reasoning effort
|
|
@@ -1899,6 +2039,7 @@ function createCodexAdapter(opts) {
|
|
|
1899
2039
|
handleOpts.onFinished = function() {
|
|
1900
2040
|
removeActiveQuery(activeEntry);
|
|
1901
2041
|
decrementRefCount();
|
|
2042
|
+
if (typeof queryOpts.onRuntimeFinished === "function") queryOpts.onRuntimeFinished();
|
|
1902
2043
|
};
|
|
1903
2044
|
handle = createCodexQueryHandle(_appServer, handleOpts);
|
|
1904
2045
|
} catch (e) {
|
|
@@ -2013,6 +2154,7 @@ function createCodexAdapter(opts) {
|
|
|
2013
2154
|
return Promise.all([
|
|
2014
2155
|
beginShutdown(true),
|
|
2015
2156
|
shutdownUserRuntimes("shutdown"),
|
|
2157
|
+
shutdownRecoveryRuntimes(),
|
|
2016
2158
|
]).then(function() { return true; });
|
|
2017
2159
|
},
|
|
2018
2160
|
|
|
@@ -2040,7 +2182,7 @@ function createCodexAdapter(opts) {
|
|
|
2040
2182
|
});
|
|
2041
2183
|
}
|
|
2042
2184
|
if (_shuttingDown || _shutdownPromise) return Promise.resolve(false);
|
|
2043
|
-
if (
|
|
2185
|
+
if (_initializing) return Promise.resolve(false);
|
|
2044
2186
|
if (!_appServer) return Promise.resolve(false);
|
|
2045
2187
|
if (_refCount > 0) return Promise.resolve(false);
|
|
2046
2188
|
if (Date.now() - _lastActiveAt < (idleMs || 0)) return Promise.resolve(false);
|
|
@@ -18,7 +18,7 @@ var INITIALIZE_TIMEOUT_MS = require("../interface").INITIALIZE_TIMEOUT_MS;
|
|
|
18
18
|
var skillDiscovery = require("../skill-discovery");
|
|
19
19
|
var { KIRO_DEFAULTS } = require("../../kiro-defaults");
|
|
20
20
|
|
|
21
|
-
function injectSharedSkillContent(message, skills) {
|
|
21
|
+
function injectSharedSkillContent(message, skills, readOpts) {
|
|
22
22
|
var text = "";
|
|
23
23
|
if (typeof message === "string") text = message;
|
|
24
24
|
else if (Array.isArray(message)) {
|
|
@@ -26,7 +26,7 @@ function injectSharedSkillContent(message, skills) {
|
|
|
26
26
|
if (message[i] && message[i].type === "text") text += "\n" + (message[i].text || "");
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
-
var content = skillDiscovery.readReferencedSkills(text, skills, 32768);
|
|
29
|
+
var content = skillDiscovery.readReferencedSkills(text, skills, 32768, readOpts);
|
|
30
30
|
if (!content) return message;
|
|
31
31
|
var appendix = "\n\nUse the following shared skill instructions for this turn:\n" + content;
|
|
32
32
|
if (typeof message === "string") return message + appendix;
|
|
@@ -682,7 +682,7 @@ function createKiroQueryHandle(acp, queryOpts) {
|
|
|
682
682
|
while (!isCancelled()) {
|
|
683
683
|
resetTurnState();
|
|
684
684
|
|
|
685
|
-
var messageWithSkills = injectSharedSkillContent(currentMessage, queryOpts.skills || []);
|
|
685
|
+
var messageWithSkills = injectSharedSkillContent(currentMessage, queryOpts.skills || [], queryOpts.skillOptions);
|
|
686
686
|
var input = typeof messageWithSkills === "string"
|
|
687
687
|
? [{ type: "text", text: messageWithSkills }]
|
|
688
688
|
: messageWithSkills;
|
|
@@ -1012,7 +1012,7 @@ function createKiroAdapter(opts) {
|
|
|
1012
1012
|
_defaultModel = "auto";
|
|
1013
1013
|
}
|
|
1014
1014
|
|
|
1015
|
-
var skillNames = skillDiscovery.discoverSkills(_cwd).map(function(skill) { return skill.name; });
|
|
1015
|
+
var skillNames = skillDiscovery.discoverSkills(_cwd, { env: (initOpts && initOpts.env) || null }).map(function(skill) { return skill.name; });
|
|
1016
1016
|
console.log("[yoke/kiro] ACP initialized, models: " + _cachedModels.length + ", skills: " + skillNames.length);
|
|
1017
1017
|
|
|
1018
1018
|
updateLastActiveAt();
|
|
@@ -1065,7 +1065,7 @@ function createKiroAdapter(opts) {
|
|
|
1065
1065
|
var model = queryOpts.model || _defaultModel || "auto";
|
|
1066
1066
|
var ac = queryOpts.abortController || new AbortController();
|
|
1067
1067
|
var kiroOpts = (queryOpts.adapterOptions && queryOpts.adapterOptions.KIRO) || {};
|
|
1068
|
-
var sharedSkills = queryOpts.skipSkills ? [] : skillDiscovery.discoverSkills(queryOpts.cwd || _cwd);
|
|
1068
|
+
var sharedSkills = queryOpts.skipSkills ? [] : skillDiscovery.discoverSkills(queryOpts.cwd || _cwd, queryOpts.skillOptions || { env: queryOpts.env || null });
|
|
1069
1069
|
var skillIndex = skillDiscovery.buildSkillIndex(sharedSkills);
|
|
1070
1070
|
|
|
1071
1071
|
var activeEntry = { abort: function() { try { ac.abort(); } catch (e) {} } };
|
|
@@ -1079,6 +1079,7 @@ function createKiroAdapter(opts) {
|
|
|
1079
1079
|
systemPrompt: queryOpts.systemPrompt || "",
|
|
1080
1080
|
appendSystemPrompt: [queryOpts.appendSystemPrompt, skillIndex].filter(function (part) { return !!part; }).join("\n\n"),
|
|
1081
1081
|
skills: sharedSkills,
|
|
1082
|
+
skillOptions: queryOpts.skillOptions || { env: queryOpts.env || null },
|
|
1082
1083
|
abortController: ac,
|
|
1083
1084
|
canUseTool: queryOpts.canUseTool || null,
|
|
1084
1085
|
resumeSessionId: queryOpts.resumeSessionId || null,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Session-owned shared skill catalog.
|
|
2
|
+
// ----------------------------------
|
|
3
|
+
// Keeps personal skill names out of project-wide session-manager state while
|
|
4
|
+
// retaining vendor commands separately for hydration and session switching.
|
|
5
|
+
|
|
6
|
+
var skillDiscovery = require("./skill-discovery");
|
|
7
|
+
|
|
8
|
+
function createSessionSkillCatalog(opts) {
|
|
9
|
+
opts = opts || {};
|
|
10
|
+
var cwd = opts.cwd;
|
|
11
|
+
var osUsers = !!opts.osUsers;
|
|
12
|
+
var getLinuxUser = opts.getLinuxUser || function () { return null; };
|
|
13
|
+
var resolveUser = opts.resolveUser || require("../os-users").resolveOsUserInfo;
|
|
14
|
+
|
|
15
|
+
function optionsFor(session) {
|
|
16
|
+
if (!osUsers) return opts.homeDir ? { homeDir: opts.homeDir, env: opts.env } : {};
|
|
17
|
+
var linuxUser = getLinuxUser(session);
|
|
18
|
+
if (!linuxUser) return { disable: true };
|
|
19
|
+
try {
|
|
20
|
+
return skillDiscovery.optionsForOsUser(resolveUser(linuxUser));
|
|
21
|
+
} catch (e) {
|
|
22
|
+
return { disable: true };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function hydrate(session) {
|
|
27
|
+
if (!session) return [];
|
|
28
|
+
var discovered = skillDiscovery.discoverSkills(cwd, optionsFor(session));
|
|
29
|
+
var names = discovered.map(function (skill) { return skill.name; });
|
|
30
|
+
session.skillNames = names;
|
|
31
|
+
var vendorCommands = Array.isArray(session.vendorSlashCommands) ? session.vendorSlashCommands : [];
|
|
32
|
+
var combined = vendorCommands.slice();
|
|
33
|
+
for (var i = 0; i < names.length; i++) {
|
|
34
|
+
if (combined.indexOf(names[i]) === -1) combined.push(names[i]);
|
|
35
|
+
}
|
|
36
|
+
session.slashCommands = combined;
|
|
37
|
+
session.slashCommandsByVendor = session.slashCommandsByVendor || {};
|
|
38
|
+
session.slashCommandsByVendor[session.vendor || "claude"] = combined;
|
|
39
|
+
return combined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { hydrate: hydrate, optionsFor: optionsFor };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { createSessionSkillCatalog: createSessionSkillCatalog };
|
|
@@ -11,6 +11,18 @@ function defaultHomeDir() {
|
|
|
11
11
|
try { return require("../config").REAL_HOME; } catch (e) { return os.homedir(); }
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
function optionsForOsUser(userInfo, fsAsUser) {
|
|
15
|
+
if (!userInfo || typeof userInfo.home !== "string" || !userInfo.home) return { disable: true };
|
|
16
|
+
fsAsUser = fsAsUser || require("../os-users").fsAsUser;
|
|
17
|
+
return {
|
|
18
|
+
homeDir: userInfo.home,
|
|
19
|
+
scopedHome: true,
|
|
20
|
+
readDir: function (dir) { return fsAsUser("list", { dir: dir }, userInfo).map(function (entry) { return { name: entry.name, isDir: entry.isDir }; }); },
|
|
21
|
+
readFile: function (file) { return fsAsUser("read", { file: file, readContent: true }, userInfo).content; },
|
|
22
|
+
realpath: function (file) { return fsAsUser("realpath", { file: file }, userInfo).path; },
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
14
26
|
function unquote(value) {
|
|
15
27
|
var text = String(value || "").trim();
|
|
16
28
|
if ((text[0] === '"' && text[text.length - 1] === '"') ||
|
|
@@ -20,61 +32,83 @@ function unquote(value) {
|
|
|
20
32
|
return text;
|
|
21
33
|
}
|
|
22
34
|
|
|
23
|
-
function readMetadata(skillPath, fallbackName) {
|
|
35
|
+
function readMetadata(skillPath, fallbackName, opts) {
|
|
24
36
|
var content;
|
|
25
|
-
try { content = fs.readFileSync(skillPath, "utf8"); } catch (e) { return null; }
|
|
37
|
+
try { content = opts && opts.readFile ? opts.readFile(skillPath) : fs.readFileSync(skillPath, "utf8"); } catch (e) { return null; }
|
|
26
38
|
var name = fallbackName;
|
|
27
39
|
var description = "";
|
|
40
|
+
var version = "";
|
|
28
41
|
var frontmatter = content.match(/^---\s*\r?\n([\s\S]*?)\r?\n---(?:\s*\r?\n|$)/);
|
|
29
42
|
if (frontmatter) {
|
|
30
43
|
var nameMatch = frontmatter[1].match(/^name:\s*(.+)$/m);
|
|
31
44
|
var descriptionMatch = frontmatter[1].match(/^description:\s*(.+)$/m);
|
|
32
45
|
if (nameMatch) name = unquote(nameMatch[1]) || fallbackName;
|
|
33
46
|
if (descriptionMatch) description = unquote(descriptionMatch[1]);
|
|
47
|
+
var versionMatch = frontmatter[1].match(/^version:\s*(.+)$/m);
|
|
48
|
+
if (versionMatch) version = unquote(versionMatch[1]);
|
|
34
49
|
}
|
|
35
50
|
if (!description) {
|
|
36
51
|
var heading = content.match(/^#\s+(.+)$/m);
|
|
37
52
|
if (heading) description = heading[1].trim();
|
|
38
53
|
}
|
|
39
|
-
return { name: name, description: description };
|
|
54
|
+
return { name: name, description: description, version: version };
|
|
40
55
|
}
|
|
41
56
|
|
|
42
|
-
function scanRoot(root, source, merged) {
|
|
57
|
+
function scanRoot(root, source, merged, opts) {
|
|
43
58
|
var entries;
|
|
44
|
-
try { entries = fs.readdirSync(root, { withFileTypes: true }); } catch (e) { return; }
|
|
59
|
+
try { entries = opts && opts.readDir ? opts.readDir(root) : fs.readdirSync(root, { withFileTypes: true }); } catch (e) { return; }
|
|
45
60
|
entries.sort(function(a, b) { return a.name.localeCompare(b.name); });
|
|
46
61
|
for (var i = 0; i < entries.length; i++) {
|
|
47
62
|
var entry = entries[i];
|
|
48
|
-
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
63
|
+
if ((!opts || !opts.readDir) && (entry.isDir === false || (typeof entry.isDirectory === "function" && !entry.isDirectory() && !entry.isSymbolicLink()))) continue;
|
|
49
64
|
var skillPath = path.join(root, entry.name, "SKILL.md");
|
|
50
|
-
var
|
|
65
|
+
var realSkillPath;
|
|
66
|
+
try { realSkillPath = opts && opts.realpath ? opts.realpath(skillPath) : fs.realpathSync(skillPath); } catch (e) { continue; }
|
|
67
|
+
var metadata = readMetadata(realSkillPath, entry.name, opts);
|
|
51
68
|
if (!metadata) continue;
|
|
52
|
-
|
|
69
|
+
var key = opts && opts.includeDuplicates ? source + "\0" + metadata.name : metadata.name;
|
|
70
|
+
merged[key] = {
|
|
53
71
|
name: metadata.name,
|
|
54
|
-
path:
|
|
72
|
+
path: realSkillPath,
|
|
55
73
|
description: metadata.description,
|
|
74
|
+
version: metadata.version,
|
|
56
75
|
source: source,
|
|
57
76
|
};
|
|
58
77
|
}
|
|
59
78
|
}
|
|
60
79
|
|
|
61
80
|
function getSkillRoots(cwd, opts) {
|
|
62
|
-
|
|
81
|
+
opts = opts || {};
|
|
82
|
+
var env = opts.env || (opts.homeDir ? {} : process.env);
|
|
83
|
+
if (opts.scopedHome) {
|
|
84
|
+
env = Object.assign({}, env);
|
|
85
|
+
delete env.CODEX_HOME;
|
|
86
|
+
delete env.CLAUDE_CONFIG_DIR;
|
|
87
|
+
}
|
|
88
|
+
var homeDir = opts.homeDir || env.HOME || defaultHomeDir();
|
|
89
|
+
var codexHome = env.CODEX_HOME ? path.resolve(homeDir, env.CODEX_HOME) : path.join(homeDir, ".codex");
|
|
90
|
+
var claudeConfigDir = env.CLAUDE_CONFIG_DIR ? path.resolve(homeDir, env.CLAUDE_CONFIG_DIR) : path.join(homeDir, ".claude");
|
|
63
91
|
var roots = [
|
|
64
|
-
{ path: path.join(homeDir, ".
|
|
65
|
-
{ path: path.join(
|
|
92
|
+
{ path: path.join(homeDir, ".agents", "skills"), source: "agents-user" },
|
|
93
|
+
{ path: path.join(codexHome, "skills"), source: "codex-user" },
|
|
94
|
+
{ path: path.join(codexHome, "skills", ".system"), source: "codex-system-user" },
|
|
95
|
+
{ path: path.join(claudeConfigDir, "skills"), source: "claude-user" },
|
|
66
96
|
];
|
|
67
97
|
if (cwd) {
|
|
68
98
|
roots.push({ path: path.join(cwd, "skills"), source: "project" });
|
|
99
|
+
roots.push({ path: path.join(cwd, ".agents", "skills"), source: "agents-project" });
|
|
100
|
+
roots.push({ path: path.join(cwd, ".codex", "skills"), source: "codex-project" });
|
|
101
|
+
roots.push({ path: path.join(cwd, ".codex", "skills", ".system"), source: "codex-system-project" });
|
|
69
102
|
roots.push({ path: path.join(cwd, ".claude", "skills"), source: "claude-project" });
|
|
70
103
|
}
|
|
71
104
|
return roots;
|
|
72
105
|
}
|
|
73
106
|
|
|
74
107
|
function discoverSkills(cwd, opts) {
|
|
108
|
+
if (opts && opts.disable) return [];
|
|
75
109
|
var merged = {};
|
|
76
110
|
var roots = getSkillRoots(cwd, opts);
|
|
77
|
-
for (var i = 0; i < roots.length; i++) scanRoot(roots[i].path, roots[i].source, merged);
|
|
111
|
+
for (var i = 0; i < roots.length; i++) scanRoot(roots[i].path, roots[i].source, merged, opts);
|
|
78
112
|
return Object.keys(merged).sort().map(function(name) { return merged[name]; });
|
|
79
113
|
}
|
|
80
114
|
|
|
@@ -112,13 +146,13 @@ function buildSkillIndex(skills) {
|
|
|
112
146
|
return lines.join("\n");
|
|
113
147
|
}
|
|
114
148
|
|
|
115
|
-
function readReferencedSkills(text, skills, maxChars) {
|
|
149
|
+
function readReferencedSkills(text, skills, maxChars, readOpts) {
|
|
116
150
|
var references = findSkillReferences(text, skills);
|
|
117
151
|
var remaining = maxChars || 32768;
|
|
118
152
|
var blocks = [];
|
|
119
153
|
for (var i = 0; i < references.length && remaining > 0; i++) {
|
|
120
154
|
var content;
|
|
121
|
-
try { content = fs.readFileSync(references[i].path, "utf8"); } catch (e) { continue; }
|
|
155
|
+
try { content = readOpts && readOpts.readFile ? readOpts.readFile(references[i].path) : fs.readFileSync(references[i].path, "utf8"); } catch (e) { continue; }
|
|
122
156
|
var header = "<shared-skill name=\"" + references[i].name + "\" source=\"" + references[i].source + "\">\n";
|
|
123
157
|
var footer = "\n</shared-skill>";
|
|
124
158
|
var available = Math.max(0, remaining - header.length - footer.length);
|
|
@@ -142,18 +176,20 @@ function lstatOrNull(target) {
|
|
|
142
176
|
}
|
|
143
177
|
|
|
144
178
|
function ensureClaudeCodexPlugin(opts) {
|
|
145
|
-
|
|
146
|
-
var
|
|
179
|
+
opts = opts || {};
|
|
180
|
+
var homeDir = opts.homeDir || defaultHomeDir();
|
|
181
|
+
var source = opts.source || path.join(homeDir, ".codex", "skills");
|
|
182
|
+
var bridgeName = opts.name || "codex";
|
|
147
183
|
try {
|
|
148
184
|
if (!fs.statSync(source).isDirectory()) return null;
|
|
149
185
|
} catch (e) { return null; }
|
|
150
186
|
|
|
151
|
-
var pluginDir = path.join(homeDir, ".clay", "skill-bridge",
|
|
187
|
+
var pluginDir = path.join(homeDir, ".clay", "skill-bridge", bridgeName);
|
|
152
188
|
var bridgeRoot = path.dirname(pluginDir);
|
|
153
189
|
var manifestDir = path.join(pluginDir, ".claude-plugin");
|
|
154
190
|
var manifestPath = path.join(manifestDir, "plugin.json");
|
|
155
191
|
var skillsLink = path.join(pluginDir, "skills");
|
|
156
|
-
var manifest = JSON.stringify({ name:
|
|
192
|
+
var manifest = JSON.stringify({ name: bridgeName, version: "1.0.0", description: "Shared skills provided by Clay" }, null, 2) + "\n";
|
|
157
193
|
|
|
158
194
|
try {
|
|
159
195
|
fs.mkdirSync(path.join(homeDir, ".clay"), { recursive: true });
|
|
@@ -168,7 +204,10 @@ function ensureClaudeCodexPlugin(opts) {
|
|
|
168
204
|
if (!manifestDirStat) fs.mkdirSync(manifestDir);
|
|
169
205
|
var manifestStat = lstatOrNull(manifestPath);
|
|
170
206
|
if (manifestStat) {
|
|
171
|
-
if (!manifestStat.isFile()
|
|
207
|
+
if (!manifestStat.isFile()) return null;
|
|
208
|
+
var existingManifest = fs.readFileSync(manifestPath, "utf8");
|
|
209
|
+
var legacyManifest = JSON.stringify({ name: "codex", version: "1.0.0", description: "Codex skills shared by Clay" }, null, 2) + "\n";
|
|
210
|
+
if (existingManifest !== manifest && existingManifest !== legacyManifest) return null;
|
|
172
211
|
} else {
|
|
173
212
|
fs.writeFileSync(manifestPath, manifest, { mode: 0o600, flag: "wx" });
|
|
174
213
|
}
|
|
@@ -192,5 +231,6 @@ module.exports = {
|
|
|
192
231
|
findSkillReferences: findSkillReferences,
|
|
193
232
|
getSkillRoots: getSkillRoots,
|
|
194
233
|
indexSkills: indexSkills,
|
|
234
|
+
optionsForOsUser: optionsForOsUser,
|
|
195
235
|
readReferencedSkills: readReferencedSkills,
|
|
196
236
|
};
|
package/package.json
CHANGED