loopengine 0.1.59 → 0.1.61
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/dist/bin/ability-manager.d.ts +11 -0
- package/dist/core/gateway-tools.d.ts +19 -0
- package/dist/core/gateway-tools.js +32 -1
- package/dist/core/system-skills/system-skills/composio-large-outputs/SKILL.md +25 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/web/agents-config-page.js +54 -0
- package/dist/web/env-admin.d.ts +10 -0
- package/dist/web/env-admin.js +46 -18
- package/package.json +1 -1
|
@@ -26,6 +26,17 @@ export interface AbilityEnvDecl {
|
|
|
26
26
|
* env-admin.ts's own serializeEnvValue doc comment). Omit for anything
|
|
27
27
|
* genuinely single-line. */
|
|
28
28
|
multiline?: boolean;
|
|
29
|
+
/** True when this setting is meaningfully different per agent (e.g.
|
|
30
|
+
* which Slack channel to post to) rather than one real shared
|
|
31
|
+
* credential every installed copy should agree on (e.g. an API key
|
|
32
|
+
* for the one external service they all call). The Admin UI's
|
|
33
|
+
* Environment tab then also offers a per-agent override — a second,
|
|
34
|
+
* separate var named `<AGENT_NAME>_<this name>` (env-admin.ts's own
|
|
35
|
+
* agentScopedEnvVarName) — that an ability's own tool code can check
|
|
36
|
+
* first, falling back to this bare name when the override isn't set.
|
|
37
|
+
* Omit (the default, false) for anything that's actually fine shared
|
|
38
|
+
* across every agent that installs this ability. */
|
|
39
|
+
perAgent?: boolean;
|
|
29
40
|
}
|
|
30
41
|
/** `name`/`version` deliberately aren't declared here — they're read off
|
|
31
42
|
* the ability's own sibling `package.json` instead (see readManifest),
|
|
@@ -35,6 +35,25 @@ export type GatewayToolEntry = ComposioGatewayToolEntry;
|
|
|
35
35
|
* module's own registry read/writes do, rather than re-deriving it (e.g.
|
|
36
36
|
* from process.cwd()) and risking the two drifting apart. */
|
|
37
37
|
export declare function agentDir(agentName: string): string;
|
|
38
|
+
/** agentDir's own inverse, for a tool file that needs to know which
|
|
39
|
+
* agent it was installed under without an env var to tell it — an
|
|
40
|
+
* ability's own tool file (copied to agents/<name>/tools/<file>.ts by
|
|
41
|
+
* add-ability) calls this with its own `import.meta.url`, and gets
|
|
42
|
+
* `<name>` back, by walking back up from the file's real on-disk
|
|
43
|
+
* location rather than trusting anything passed in (there's nothing
|
|
44
|
+
* else to trust: this runs from inside the tool file itself, before
|
|
45
|
+
* any agent-scoped state exists). Returns undefined for a tool file
|
|
46
|
+
* that isn't actually at that expected agents/<name>/tools/ depth (a
|
|
47
|
+
* test harness loading the file from somewhere else, say) — a caller
|
|
48
|
+
* needing a default when this can't be determined should fall back
|
|
49
|
+
* explicitly, not treat undefined as a crash. Mirrors the inferAgentName
|
|
50
|
+
* helper lp-task-scheduler's own tool files (schedule_task.ts et al.)
|
|
51
|
+
* already hand-duplicate per the ability system's own "no shared
|
|
52
|
+
* modules between tool files" rule — this is the one place that's
|
|
53
|
+
* actually safe to centralize, since every ability's tool file already
|
|
54
|
+
* imports real functions from this package (see e.g. lp-task-scheduler's
|
|
55
|
+
* own createCheckpointStore/runAgent imports), just not yet this. */
|
|
56
|
+
export declare function inferAgentNameFromToolUrl(toolFileUrl: string): string | undefined;
|
|
38
57
|
/** `dir` is the agent's real folder — `agents/<name>` for a top-level
|
|
39
58
|
* agent, or the nested `agents/<parent>/subagents/<child>` a subagent
|
|
40
59
|
* actually lives in (see run-agent.ts's resolveSubagentConfig, which
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
// the only intended writer of that file; loadGatewayToolsFromDir is what
|
|
9
9
|
// run-agent.ts calls to actually resolve it into ToolDefinitions.
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
11
|
-
import { join } from 'node:path';
|
|
11
|
+
import { basename, dirname, join } from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
12
13
|
import { execFile } from 'node:child_process';
|
|
13
14
|
import { promisify } from 'node:util';
|
|
14
15
|
import { parse as parseYaml, parseDocument, stringify as stringifyYaml } from 'yaml';
|
|
@@ -34,6 +35,36 @@ function gatewayToolsPath(dir) {
|
|
|
34
35
|
export function agentDir(agentName) {
|
|
35
36
|
return join(agentsRootDir, agentName);
|
|
36
37
|
}
|
|
38
|
+
/** agentDir's own inverse, for a tool file that needs to know which
|
|
39
|
+
* agent it was installed under without an env var to tell it — an
|
|
40
|
+
* ability's own tool file (copied to agents/<name>/tools/<file>.ts by
|
|
41
|
+
* add-ability) calls this with its own `import.meta.url`, and gets
|
|
42
|
+
* `<name>` back, by walking back up from the file's real on-disk
|
|
43
|
+
* location rather than trusting anything passed in (there's nothing
|
|
44
|
+
* else to trust: this runs from inside the tool file itself, before
|
|
45
|
+
* any agent-scoped state exists). Returns undefined for a tool file
|
|
46
|
+
* that isn't actually at that expected agents/<name>/tools/ depth (a
|
|
47
|
+
* test harness loading the file from somewhere else, say) — a caller
|
|
48
|
+
* needing a default when this can't be determined should fall back
|
|
49
|
+
* explicitly, not treat undefined as a crash. Mirrors the inferAgentName
|
|
50
|
+
* helper lp-task-scheduler's own tool files (schedule_task.ts et al.)
|
|
51
|
+
* already hand-duplicate per the ability system's own "no shared
|
|
52
|
+
* modules between tool files" rule — this is the one place that's
|
|
53
|
+
* actually safe to centralize, since every ability's tool file already
|
|
54
|
+
* imports real functions from this package (see e.g. lp-task-scheduler's
|
|
55
|
+
* own createCheckpointStore/runAgent imports), just not yet this. */
|
|
56
|
+
export function inferAgentNameFromToolUrl(toolFileUrl) {
|
|
57
|
+
try {
|
|
58
|
+
const toolsDir = dirname(fileURLToPath(toolFileUrl));
|
|
59
|
+
const agentDir = dirname(toolsDir);
|
|
60
|
+
if (basename(dirname(agentDir)) !== 'agents')
|
|
61
|
+
return undefined;
|
|
62
|
+
return basename(agentDir);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
37
68
|
/** `dir` is the agent's real folder — `agents/<name>` for a top-level
|
|
38
69
|
* agent, or the nested `agents/<parent>/subagents/<child>` a subagent
|
|
39
70
|
* actually lives in (see run-agent.ts's resolveSubagentConfig, which
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: composio-large-outputs
|
|
3
|
+
description: How to retrieve a gateway tool's real output when its result says storedInFile is true instead of returning the data inline.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Gateway tools with a large result
|
|
7
|
+
|
|
8
|
+
Some gateway tools (Composio-sourced ones in particular) return a small
|
|
9
|
+
pointer object instead of their real output when the actual result is
|
|
10
|
+
too large to return inline:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
{
|
|
14
|
+
"successful": true,
|
|
15
|
+
"storedInFile": true,
|
|
16
|
+
"tokenCount": 10234,
|
|
17
|
+
"outputFilePath": "/tmp/.../SOME_TOOL_OUTPUT_xxxxx.json"
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
When you see `storedInFile: true` in a tool result, the real data is
|
|
22
|
+
**not** in that object — it's in the file at `outputFilePath`. Call
|
|
23
|
+
`system_read_file` with that exact path to retrieve the actual content
|
|
24
|
+
before answering. Don't guess at an answer, summarize from the pointer
|
|
25
|
+
alone, or claim you don't have the data — read the file first.
|
package/dist/index.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export { LLMSummarizer } from './core/llm-summarizer.js';
|
|
|
36
36
|
export type { LLMSummarizerOptions } from './core/llm-summarizer.js';
|
|
37
37
|
export { createCheckpointStore } from './core/durable-approvals.js';
|
|
38
38
|
export type { TurnCheckpoint, CheckpointStore, OutstandingItem } from './core/durable-approvals.js';
|
|
39
|
-
export { addGatewayTool, agentDir, disconnectComposioAccount, describeGatewayTools, listComposioConnections, listComposioTools, loadGatewayToolsFromDir, removeGatewayTool, removeGatewayToolSlug, GatewayToolExistsError, GatewayToolNotFoundError, } from './core/gateway-tools.js';
|
|
39
|
+
export { addGatewayTool, agentDir, inferAgentNameFromToolUrl, disconnectComposioAccount, describeGatewayTools, listComposioConnections, listComposioTools, loadGatewayToolsFromDir, removeGatewayTool, removeGatewayToolSlug, GatewayToolExistsError, GatewayToolNotFoundError, } from './core/gateway-tools.js';
|
|
40
40
|
export type { GatewayToolEntry, GatewayToolDecision } from './core/gateway-tools.js';
|
|
41
41
|
export { connectComposioSource } from './core/mcpplug.js';
|
|
42
42
|
export type { ToolSource, ComposioSourceOptions } from './core/mcpplug.js';
|
|
@@ -55,11 +55,11 @@ export { globalConfigPageHtml } from './web/global-config-page.js';
|
|
|
55
55
|
export { playgroundHtml } from './web/playground.js';
|
|
56
56
|
export { readSkill, writeSkill, deleteSkill, SkillInvalidIdError, SkillNotFoundError } from './web/skills-admin.js';
|
|
57
57
|
export type { SkillContent } from './web/skills-admin.js';
|
|
58
|
-
export { listDeclaredEnvVars, setEnvVar, unsetEnvVar, EnvVarNameError } from './web/env-admin.js';
|
|
58
|
+
export { listDeclaredEnvVars, setEnvVar, unsetEnvVar, agentScopedEnvVarName, EnvVarNameError } from './web/env-admin.js';
|
|
59
59
|
export type { DeclaredEnvVar } from './web/env-admin.js';
|
|
60
60
|
export { readActauthConfig, addActauthRule, updateActauthRule, removeActauthRule, setDefaultDecision, ActauthRuleExistsError, ActauthRuleNotFoundError, } from './web/actauth-admin.js';
|
|
61
61
|
export type { ActauthRuleInput, ActauthConfigView } from './web/actauth-admin.js';
|
|
62
|
-
export { installAbility, upgradeAbility, listInstalledAbilities, backfillDependencies, AbilityManifestError, AbilityVersionError, AbilityCollisionError, AbilityAlreadyInstalledError, AbilityNotInstalledError, } from './bin/ability-manager.js';
|
|
62
|
+
export { installAbility, upgradeAbility, removeAbility, listInstalledAbilities, backfillDependencies, AbilityManifestError, AbilityVersionError, AbilityCollisionError, AbilityAlreadyInstalledError, AbilityNotInstalledError, } from './bin/ability-manager.js';
|
|
63
63
|
export type { InstalledAbilityRecord, AbilityEnvDecl, UpgradeFileResult } from './bin/ability-manager.js';
|
|
64
64
|
export { searchPublicAbilities, fetchLatestAbilityVersion } from './web/abilities-admin.js';
|
|
65
65
|
export type { PublicAbilitySearchResult } from './web/abilities-admin.js';
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ export { resumeAgent, loadRules, loadDefaultTools, loadSubagentAsTools, systemTo
|
|
|
35
35
|
export { Compactor, TruncatingSummarizer } from './core/compaction.js';
|
|
36
36
|
export { LLMSummarizer } from './core/llm-summarizer.js';
|
|
37
37
|
export { createCheckpointStore } from './core/durable-approvals.js';
|
|
38
|
-
export { addGatewayTool, agentDir, disconnectComposioAccount, describeGatewayTools, listComposioConnections, listComposioTools, loadGatewayToolsFromDir, removeGatewayTool, removeGatewayToolSlug, GatewayToolExistsError, GatewayToolNotFoundError, } from './core/gateway-tools.js';
|
|
38
|
+
export { addGatewayTool, agentDir, inferAgentNameFromToolUrl, disconnectComposioAccount, describeGatewayTools, listComposioConnections, listComposioTools, loadGatewayToolsFromDir, removeGatewayTool, removeGatewayToolSlug, GatewayToolExistsError, GatewayToolNotFoundError, } from './core/gateway-tools.js';
|
|
39
39
|
export { connectComposioSource } from './core/mcpplug.js';
|
|
40
40
|
export { ToolLane, buildLanes, runLanes } from './core/toollane.js';
|
|
41
41
|
export { WebhookNotifier } from './core/http-notify-triggers/webhook.js';
|
|
@@ -50,9 +50,9 @@ export { agentsListPageHtml } from './web/agents-list-page.js';
|
|
|
50
50
|
export { globalConfigPageHtml } from './web/global-config-page.js';
|
|
51
51
|
export { playgroundHtml } from './web/playground.js';
|
|
52
52
|
export { readSkill, writeSkill, deleteSkill, SkillInvalidIdError, SkillNotFoundError } from './web/skills-admin.js';
|
|
53
|
-
export { listDeclaredEnvVars, setEnvVar, unsetEnvVar, EnvVarNameError } from './web/env-admin.js';
|
|
53
|
+
export { listDeclaredEnvVars, setEnvVar, unsetEnvVar, agentScopedEnvVarName, EnvVarNameError } from './web/env-admin.js';
|
|
54
54
|
export { readActauthConfig, addActauthRule, updateActauthRule, removeActauthRule, setDefaultDecision, ActauthRuleExistsError, ActauthRuleNotFoundError, } from './web/actauth-admin.js';
|
|
55
|
-
export { installAbility, upgradeAbility, listInstalledAbilities, backfillDependencies, AbilityManifestError, AbilityVersionError, AbilityCollisionError, AbilityAlreadyInstalledError, AbilityNotInstalledError, } from './bin/ability-manager.js';
|
|
55
|
+
export { installAbility, upgradeAbility, removeAbility, listInstalledAbilities, backfillDependencies, AbilityManifestError, AbilityVersionError, AbilityCollisionError, AbilityAlreadyInstalledError, AbilityNotInstalledError, } from './bin/ability-manager.js';
|
|
56
56
|
export { searchPublicAbilities, fetchLatestAbilityVersion } from './web/abilities-admin.js';
|
|
57
57
|
export { describeModelProviders, describeGateways } from './web/global-config.js';
|
|
58
58
|
export { createTrackedApprover, listApprovals, decideApproval, findApproval } from './web/web-approver.js';
|
|
@@ -1379,6 +1379,7 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1379
1379
|
if (deps.length) {
|
|
1380
1380
|
actionHtml += ' <button type="button" class="ability-install-deps-btn" data-name="' + escapeHtml(a.name) + '" title="npm install ' + escapeHtml(deps.join(' ')) + '">Install deps</button>';
|
|
1381
1381
|
}
|
|
1382
|
+
actionHtml += ' <button type="button" class="ability-uninstall-btn" data-name="' + escapeHtml(a.name) + '">Uninstall</button>';
|
|
1382
1383
|
return '<tr>' +
|
|
1383
1384
|
'<td><code>' + escapeHtml(a.name) + '</code></td>' +
|
|
1384
1385
|
'<td>' + escapeHtml(a.version) + '</td>' +
|
|
@@ -1544,6 +1545,49 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1544
1545
|
});
|
|
1545
1546
|
}
|
|
1546
1547
|
|
|
1548
|
+
// Deletes an ability's tool/skill files and actauth rules
|
|
1549
|
+
// (handleAbilityUninstallDelete / bin/ability-manager.ts's own
|
|
1550
|
+
// removeAbility). A file hand-modified since install is left in place
|
|
1551
|
+
// and reported back as refused rather than silently deleted — the
|
|
1552
|
+
// confirm() below offers to retry with force once the operator has
|
|
1553
|
+
// seen exactly which files those are, same two-step the CLI itself
|
|
1554
|
+
// takes with --force. Always reloads the tab afterward: a partial
|
|
1555
|
+
// uninstall (some files refused) still changes what is installed,
|
|
1556
|
+
// it just does not remove the ability entirely from provenance.
|
|
1557
|
+
function uninstallAbility(name, abilityName, statusEl, btn, force) {
|
|
1558
|
+
if (btn) { btn.disabled = true; }
|
|
1559
|
+
statusEl.className = 'hint';
|
|
1560
|
+
statusEl.textContent = 'Uninstalling ' + abilityName + '...';
|
|
1561
|
+
var url = '/agents/' + encodeURIComponent(name) + '/abilities/' + encodeURIComponent(abilityName) + (force ? '?force=true' : '');
|
|
1562
|
+
fetch(url, { method: 'DELETE' })
|
|
1563
|
+
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, body: j }; }); })
|
|
1564
|
+
.then(function (result) {
|
|
1565
|
+
if (!result.ok) throw new Error(result.body.error || 'request failed');
|
|
1566
|
+
var removed = result.body.removed || [];
|
|
1567
|
+
var refused = result.body.refused || [];
|
|
1568
|
+
if (refused.length && !force) {
|
|
1569
|
+
var retry = confirm(
|
|
1570
|
+
abilityName + ': ' + refused.length + ' file(s) were hand-modified since install and left in place (' + refused.join(', ') + '). Remove them anyway?',
|
|
1571
|
+
);
|
|
1572
|
+
if (retry) {
|
|
1573
|
+
uninstallAbility(name, abilityName, statusEl, btn, true);
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
statusEl.className = refused.length ? 'error' : 'hint';
|
|
1578
|
+
statusEl.textContent = removed.length
|
|
1579
|
+
? 'Removed ' + removed.join(', ') + '.' + (refused.length ? ' Left in place (hand-modified): ' + refused.join(', ') + '.' : '')
|
|
1580
|
+
: 'Nothing removed.';
|
|
1581
|
+
if (btn) { btn.disabled = false; }
|
|
1582
|
+
loadAbilitiesTab(name);
|
|
1583
|
+
})
|
|
1584
|
+
.catch(function (err) {
|
|
1585
|
+
statusEl.className = 'error';
|
|
1586
|
+
statusEl.textContent = 'Could not uninstall: ' + err.message;
|
|
1587
|
+
if (btn) { btn.disabled = false; }
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1547
1591
|
// Shared by the initial (empty-query) load right after the tab opens
|
|
1548
1592
|
// and the search form's own submit — the empty-query case is just
|
|
1549
1593
|
// "list every published ability" (the keyword filter alone, no extra
|
|
@@ -1593,6 +1637,16 @@ export const agentsConfigPageHtml = `<!doctype html>
|
|
|
1593
1637
|
});
|
|
1594
1638
|
}
|
|
1595
1639
|
|
|
1640
|
+
var uninstallBtns = content.querySelectorAll('.ability-uninstall-btn');
|
|
1641
|
+
for (var r = 0; r < uninstallBtns.length; r++) {
|
|
1642
|
+
uninstallBtns[r].addEventListener('click', function (ev) {
|
|
1643
|
+
var btn = ev.currentTarget;
|
|
1644
|
+
var abilityName = btn.dataset.name;
|
|
1645
|
+
if (!confirm('Uninstall ' + abilityName + '? This deletes its tool and skill files and actauth rules from this agent.')) return;
|
|
1646
|
+
uninstallAbility(name, abilityName, upgradeStatusEl, btn, false);
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1596
1650
|
var searchForm = content.querySelector('#abilitySearchForm');
|
|
1597
1651
|
var resultsEl = content.querySelector('#abilitySearchResults');
|
|
1598
1652
|
if (searchForm && resultsEl) {
|
package/dist/web/env-admin.d.ts
CHANGED
|
@@ -48,6 +48,16 @@ export interface DeclaredEnvVar {
|
|
|
48
48
|
* live off `process.env`, not cached, so it reflects whatever the last
|
|
49
49
|
* `setEnvVar` call — or a plain restart picking up `.env` — actually
|
|
50
50
|
* did. */
|
|
51
|
+
/** `varName`'s own per-agent override name (see AbilityEnvDecl.perAgent)
|
|
52
|
+
* — `<AGENT_NAME>_<varName>`, with `agentName` upper-cased and anything
|
|
53
|
+
* that isn't `[A-Z0-9_]` (a hyphen, most likely — agent directory names
|
|
54
|
+
* are kebab-case) turned into `_`, same alphabet ENV_VAR_NAME_PATTERN
|
|
55
|
+
* already requires. Guards against a result starting with a digit (a
|
|
56
|
+
* from-digits agent name, or an agent name that's entirely punctuation)
|
|
57
|
+
* the same way that pattern would otherwise reject — by prefixing one
|
|
58
|
+
* more `_` — though in practice agent names are never actually bare
|
|
59
|
+
* digits. */
|
|
60
|
+
export declare function agentScopedEnvVarName(agentName: string, varName: string): string;
|
|
51
61
|
export declare function listDeclaredEnvVars(agentName: string): DeclaredEnvVar[];
|
|
52
62
|
/** Upserts `NAME=VALUE` into the project's `.env` file — preserving
|
|
53
63
|
* every other line (comments, blank lines, unrelated keys) — and
|
package/dist/web/env-admin.js
CHANGED
|
@@ -41,33 +41,61 @@ function provenancePath(agentName) {
|
|
|
41
41
|
* live off `process.env`, not cached, so it reflects whatever the last
|
|
42
42
|
* `setEnvVar` call — or a plain restart picking up `.env` — actually
|
|
43
43
|
* did. */
|
|
44
|
+
/** `varName`'s own per-agent override name (see AbilityEnvDecl.perAgent)
|
|
45
|
+
* — `<AGENT_NAME>_<varName>`, with `agentName` upper-cased and anything
|
|
46
|
+
* that isn't `[A-Z0-9_]` (a hyphen, most likely — agent directory names
|
|
47
|
+
* are kebab-case) turned into `_`, same alphabet ENV_VAR_NAME_PATTERN
|
|
48
|
+
* already requires. Guards against a result starting with a digit (a
|
|
49
|
+
* from-digits agent name, or an agent name that's entirely punctuation)
|
|
50
|
+
* the same way that pattern would otherwise reject — by prefixing one
|
|
51
|
+
* more `_` — though in practice agent names are never actually bare
|
|
52
|
+
* digits. */
|
|
53
|
+
export function agentScopedEnvVarName(agentName, varName) {
|
|
54
|
+
const prefix = agentName.toUpperCase().replace(/[^A-Z0-9_]/g, '_');
|
|
55
|
+
return `${/^[0-9]/.test(prefix) ? '_' : ''}${prefix}_${varName}`;
|
|
56
|
+
}
|
|
44
57
|
export function listDeclaredEnvVars(agentName) {
|
|
45
58
|
const path = provenancePath(agentName);
|
|
46
59
|
if (!existsSync(path))
|
|
47
60
|
return [];
|
|
48
61
|
const provenance = JSON.parse(readFileSync(path, 'utf8'));
|
|
49
62
|
const byName = new Map();
|
|
63
|
+
function upsert(name, abilityName, decl, description) {
|
|
64
|
+
const existing = byName.get(name);
|
|
65
|
+
if (existing) {
|
|
66
|
+
existing.abilityNames.push(abilityName);
|
|
67
|
+
existing.secret = existing.secret || decl.secret === true;
|
|
68
|
+
if (existing.secret)
|
|
69
|
+
existing.value = undefined;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const rawValue = process.env[name];
|
|
73
|
+
byName.set(name, {
|
|
74
|
+
name,
|
|
75
|
+
description,
|
|
76
|
+
secret: decl.secret === true,
|
|
77
|
+
abilityNames: [abilityName],
|
|
78
|
+
set: rawValue !== undefined,
|
|
79
|
+
value: decl.secret === true ? undefined : rawValue,
|
|
80
|
+
options: decl.options,
|
|
81
|
+
multiline: decl.multiline,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
50
84
|
for (const [abilityName, record] of Object.entries(provenance)) {
|
|
51
85
|
for (const decl of record.env) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
86
|
+
upsert(decl.name, abilityName, decl, decl.description);
|
|
87
|
+
// A perAgent var also gets its own separate, independently
|
|
88
|
+
// set/unset row for this one agent's override — see
|
|
89
|
+
// AbilityEnvDecl.perAgent's own doc comment for why this is a
|
|
90
|
+
// second row, not a replacement for the shared one above (an
|
|
91
|
+
// ability's own tool code checks the override first and falls
|
|
92
|
+
// back to the shared value, so both need to stay independently
|
|
93
|
+
// visible and settable here).
|
|
94
|
+
if (decl.perAgent) {
|
|
95
|
+
const scopedName = agentScopedEnvVarName(agentName, decl.name);
|
|
96
|
+
const overrideDescription = `Overrides ${decl.name} for this agent only — if unset, falls back to the shared value above.${decl.description ? ` ${decl.description}` : ''}`;
|
|
97
|
+
upsert(scopedName, abilityName, decl, overrideDescription);
|
|
59
98
|
}
|
|
60
|
-
const rawValue = process.env[decl.name];
|
|
61
|
-
byName.set(decl.name, {
|
|
62
|
-
name: decl.name,
|
|
63
|
-
description: decl.description,
|
|
64
|
-
secret: decl.secret === true,
|
|
65
|
-
abilityNames: [abilityName],
|
|
66
|
-
set: rawValue !== undefined,
|
|
67
|
-
value: decl.secret === true ? undefined : rawValue,
|
|
68
|
-
options: decl.options,
|
|
69
|
-
multiline: decl.multiline,
|
|
70
|
-
});
|
|
71
99
|
}
|
|
72
100
|
}
|
|
73
101
|
return [...byName.values()];
|