@pellux/goodvibes-agent 1.1.5 → 1.1.7
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/CHANGELOG.md +12 -0
- package/dist/package/main.js +310 -19
- package/package.json +1 -1
- package/src/input/agent-workspace-categories.ts +1 -1
- package/src/input/agent-workspace-settings.ts +94 -8
- package/src/runtime/bootstrap-core.ts +4 -0
- package/src/runtime/tool-permission-safety.ts +93 -0
- package/src/tools/agent-context-policy.ts +137 -10
- package/src/tools/agent-tool-policy-guard.ts +4 -4
- package/src/tools/tool-execution-safety.ts +41 -0
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Product-facing release notes for GoodVibes Agent.
|
|
4
4
|
|
|
5
|
+
## 1.1.7 - 2026-06-05
|
|
6
|
+
|
|
7
|
+
- Hardened the Agent model tool surface so goodvibes_context routes to the Agent harness instead of dead-ending.
|
|
8
|
+
- Added tool execution and permission safety guards so registered model tools return structured failures instead of aborting turns.
|
|
9
|
+
- Added registered-tool smoke coverage for the Agent-guarded platform tool roster.
|
|
10
|
+
|
|
11
|
+
## 1.1.6 - 2026-06-05
|
|
12
|
+
|
|
13
|
+
- Fixed Import GoodVibes settings so it also imports active and pending provider subscriptions from the GoodVibes TUI user store into Agent-owned subscription state.
|
|
14
|
+
- Preserved existing Agent-only subscriptions while merging imported provider sessions by provider id.
|
|
15
|
+
- Updated onboarding copy and added regression coverage for subscription import.
|
|
16
|
+
|
|
5
17
|
## 1.1.5 - 2026-06-05
|
|
6
18
|
|
|
7
19
|
- Replaced onboarding with the real Agent setup flow for subscription login, provider/model selection, settings persistence, channels, voice, local context, automation, and finish.
|
package/dist/package/main.js
CHANGED
|
@@ -817034,7 +817034,7 @@ var createStyledCell = (char, overrides = {}) => ({
|
|
|
817034
817034
|
// src/version.ts
|
|
817035
817035
|
import { readFileSync } from "fs";
|
|
817036
817036
|
import { join } from "path";
|
|
817037
|
-
var _version = "1.1.
|
|
817037
|
+
var _version = "1.1.7";
|
|
817038
817038
|
try {
|
|
817039
817039
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, "..", "package.json"), "utf-8"));
|
|
817040
817040
|
_version = typeof pkg.version === "string" ? pkg.version : _version;
|
|
@@ -840573,20 +840573,132 @@ function isRecord17(value) {
|
|
|
840573
840573
|
}
|
|
840574
840574
|
|
|
840575
840575
|
// src/tools/agent-context-policy.ts
|
|
840576
|
-
var
|
|
840577
|
-
"
|
|
840578
|
-
"
|
|
840579
|
-
"
|
|
840580
|
-
|
|
840581
|
-
|
|
840582
|
-
|
|
840576
|
+
var AGENT_CONTEXT_TOOL_MODES = [
|
|
840577
|
+
"summary",
|
|
840578
|
+
"capabilities",
|
|
840579
|
+
"modes",
|
|
840580
|
+
"tools",
|
|
840581
|
+
"tool",
|
|
840582
|
+
"settings",
|
|
840583
|
+
"setting",
|
|
840584
|
+
"commands",
|
|
840585
|
+
"command",
|
|
840586
|
+
"workspace",
|
|
840587
|
+
"status"
|
|
840588
|
+
];
|
|
840589
|
+
var CONTEXT_TOOL_FALLBACK = {
|
|
840590
|
+
runtime: "GoodVibes Agent",
|
|
840591
|
+
preferredTool: "agent_harness",
|
|
840592
|
+
routes: {
|
|
840593
|
+
summary: 'agent_harness mode:"summary"',
|
|
840594
|
+
capabilities: 'agent_harness mode:"summary" includeParameters:true',
|
|
840595
|
+
tools: 'agent_harness mode:"tools"',
|
|
840596
|
+
tool: 'agent_harness mode:"tool"',
|
|
840597
|
+
settings: 'agent_harness mode:"settings"',
|
|
840598
|
+
commands: 'agent_harness mode:"commands"',
|
|
840599
|
+
workspace: 'agent_harness mode:"workspace"',
|
|
840600
|
+
status: 'agent_harness mode:"connected_host_status"'
|
|
840601
|
+
}
|
|
840602
|
+
};
|
|
840603
|
+
function wrapAgentContextToolForAgentPolicy(tool2, registry5) {
|
|
840604
|
+
tool2.definition.description = "Inspect GoodVibes Agent harness capabilities.";
|
|
840583
840605
|
tool2.definition.sideEffects = [];
|
|
840584
840606
|
tool2.definition.parameters = {
|
|
840585
840607
|
type: "object",
|
|
840586
|
-
properties: {
|
|
840608
|
+
properties: {
|
|
840609
|
+
mode: { type: "string", enum: [...AGENT_CONTEXT_TOOL_MODES] },
|
|
840610
|
+
query: { type: "string" },
|
|
840611
|
+
target: { type: "string" },
|
|
840612
|
+
toolName: { type: "string" },
|
|
840613
|
+
command: { type: "string" },
|
|
840614
|
+
commandName: { type: "string" },
|
|
840615
|
+
key: { type: "string" },
|
|
840616
|
+
category: { type: "string" },
|
|
840617
|
+
prefix: { type: "string" },
|
|
840618
|
+
includeHidden: { type: "boolean" },
|
|
840619
|
+
includeParameters: { type: "boolean" },
|
|
840620
|
+
limit: { type: "number" }
|
|
840621
|
+
},
|
|
840587
840622
|
additionalProperties: false
|
|
840588
840623
|
};
|
|
840589
|
-
tool2.execute = async () =>
|
|
840624
|
+
tool2.execute = async (rawArgs) => {
|
|
840625
|
+
const args2 = rawArgs;
|
|
840626
|
+
const mode = readAgentContextMode(args2.mode);
|
|
840627
|
+
const harnessMode = agentContextModeToHarnessMode(mode);
|
|
840628
|
+
const harnessTool = registry5?.list().find((entry) => entry.definition.name === "agent_harness");
|
|
840629
|
+
if (!harnessTool) {
|
|
840630
|
+
return ok3({
|
|
840631
|
+
...CONTEXT_TOOL_FALLBACK,
|
|
840632
|
+
status: "agent_harness_not_registered_yet",
|
|
840633
|
+
requestedMode: mode,
|
|
840634
|
+
route: `agent_harness mode:"${harnessMode}"`
|
|
840635
|
+
});
|
|
840636
|
+
}
|
|
840637
|
+
const harnessArgs = {
|
|
840638
|
+
mode: harnessMode,
|
|
840639
|
+
query: args2.query,
|
|
840640
|
+
target: args2.target,
|
|
840641
|
+
toolName: args2.toolName,
|
|
840642
|
+
command: args2.command,
|
|
840643
|
+
commandName: args2.commandName,
|
|
840644
|
+
key: args2.key,
|
|
840645
|
+
category: args2.category,
|
|
840646
|
+
prefix: args2.prefix,
|
|
840647
|
+
includeHidden: args2.includeHidden,
|
|
840648
|
+
includeParameters: mode === "capabilities" ? true : args2.includeParameters,
|
|
840649
|
+
limit: args2.limit
|
|
840650
|
+
};
|
|
840651
|
+
const result2 = await harnessTool.execute(dropUndefined(harnessArgs));
|
|
840652
|
+
if (!result2.success)
|
|
840653
|
+
return result2;
|
|
840654
|
+
return ok3({
|
|
840655
|
+
source: "agent_harness",
|
|
840656
|
+
requestedMode: mode,
|
|
840657
|
+
route: `agent_harness mode:"${harnessMode}"`,
|
|
840658
|
+
result: parseToolOutput2(result2.output)
|
|
840659
|
+
});
|
|
840660
|
+
};
|
|
840661
|
+
}
|
|
840662
|
+
function readAgentContextMode(value) {
|
|
840663
|
+
return typeof value === "string" && AGENT_CONTEXT_TOOL_MODES.includes(value) ? value : "summary";
|
|
840664
|
+
}
|
|
840665
|
+
function agentContextModeToHarnessMode(mode) {
|
|
840666
|
+
if (mode === "capabilities")
|
|
840667
|
+
return "summary";
|
|
840668
|
+
if (mode === "modes")
|
|
840669
|
+
return "modes";
|
|
840670
|
+
if (mode === "tools")
|
|
840671
|
+
return "tools";
|
|
840672
|
+
if (mode === "tool")
|
|
840673
|
+
return "tool";
|
|
840674
|
+
if (mode === "settings")
|
|
840675
|
+
return "settings";
|
|
840676
|
+
if (mode === "setting")
|
|
840677
|
+
return "get_setting";
|
|
840678
|
+
if (mode === "commands")
|
|
840679
|
+
return "commands";
|
|
840680
|
+
if (mode === "command")
|
|
840681
|
+
return "command";
|
|
840682
|
+
if (mode === "workspace")
|
|
840683
|
+
return "workspace";
|
|
840684
|
+
if (mode === "status")
|
|
840685
|
+
return "connected_host_status";
|
|
840686
|
+
return "summary";
|
|
840687
|
+
}
|
|
840688
|
+
function dropUndefined(value) {
|
|
840689
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
840690
|
+
}
|
|
840691
|
+
function parseToolOutput2(value) {
|
|
840692
|
+
if (typeof value !== "string")
|
|
840693
|
+
return value ?? null;
|
|
840694
|
+
try {
|
|
840695
|
+
return JSON.parse(value);
|
|
840696
|
+
} catch {
|
|
840697
|
+
return value;
|
|
840698
|
+
}
|
|
840699
|
+
}
|
|
840700
|
+
function ok3(value) {
|
|
840701
|
+
return { success: true, output: JSON.stringify(value, null, 2) };
|
|
840590
840702
|
}
|
|
840591
840703
|
|
|
840592
840704
|
// src/tools/agent-find-policy.ts
|
|
@@ -841057,7 +841169,7 @@ function installAgentToolPolicyGuard(registry5, options = {}) {
|
|
|
841057
841169
|
} else if (tool2.definition.name === "state") {
|
|
841058
841170
|
wrapStateToolForAgentPolicy(tool2);
|
|
841059
841171
|
} else if (tool2.definition.name === "goodvibes_context") {
|
|
841060
|
-
|
|
841172
|
+
wrapAgentContextToolForAgentPolicy(tool2, registry5);
|
|
841061
841173
|
} else if (tool2.definition.name === "goodvibes_settings") {
|
|
841062
841174
|
wrapBlockedSettingsToolForAgentPolicy(tool2);
|
|
841063
841175
|
} else if (tool2.definition.name === "inspect") {
|
|
@@ -848549,6 +848661,119 @@ function compactRegisteredToolDefinitions(toolRegistry) {
|
|
|
848549
848661
|
}
|
|
848550
848662
|
}
|
|
848551
848663
|
|
|
848664
|
+
// src/tools/tool-execution-safety.ts
|
|
848665
|
+
var TOOL_SAFETY_MARKER = Symbol.for("goodvibes-agent.tool-execution-safety-wrapped");
|
|
848666
|
+
var REGISTRY_SAFETY_MARKER = Symbol.for("goodvibes-agent.tool-registry-safety-installed");
|
|
848667
|
+
function installToolExecutionSafetyGuard(registry5) {
|
|
848668
|
+
const marked = registry5;
|
|
848669
|
+
if (marked[REGISTRY_SAFETY_MARKER])
|
|
848670
|
+
return;
|
|
848671
|
+
marked[REGISTRY_SAFETY_MARKER] = true;
|
|
848672
|
+
for (const tool2 of registry5.list())
|
|
848673
|
+
wrapToolExecutionSafety(tool2);
|
|
848674
|
+
const originalRegister = registry5.register.bind(registry5);
|
|
848675
|
+
registry5.register = (tool2) => {
|
|
848676
|
+
wrapToolExecutionSafety(tool2);
|
|
848677
|
+
originalRegister(tool2);
|
|
848678
|
+
};
|
|
848679
|
+
}
|
|
848680
|
+
function wrapToolExecutionSafety(tool2) {
|
|
848681
|
+
const marked = tool2;
|
|
848682
|
+
if (marked[TOOL_SAFETY_MARKER])
|
|
848683
|
+
return;
|
|
848684
|
+
marked[TOOL_SAFETY_MARKER] = true;
|
|
848685
|
+
const originalExecute = tool2.execute.bind(tool2);
|
|
848686
|
+
tool2.execute = async (args2) => {
|
|
848687
|
+
try {
|
|
848688
|
+
return await originalExecute(args2);
|
|
848689
|
+
} catch (error51) {
|
|
848690
|
+
return {
|
|
848691
|
+
success: false,
|
|
848692
|
+
error: summarizeError(error51)
|
|
848693
|
+
};
|
|
848694
|
+
}
|
|
848695
|
+
};
|
|
848696
|
+
}
|
|
848697
|
+
|
|
848698
|
+
// src/runtime/tool-permission-safety.ts
|
|
848699
|
+
var SAFETY_MARKER = Symbol.for("goodvibes-agent.permission-safety-installed");
|
|
848700
|
+
var READ_TOOL_NAMES = new Set([
|
|
848701
|
+
"read",
|
|
848702
|
+
"find",
|
|
848703
|
+
"fetch",
|
|
848704
|
+
"analyze",
|
|
848705
|
+
"inspect",
|
|
848706
|
+
"state",
|
|
848707
|
+
"registry",
|
|
848708
|
+
"goodvibes_context",
|
|
848709
|
+
"agent_harness",
|
|
848710
|
+
"agent_knowledge",
|
|
848711
|
+
"agent_operator_briefing"
|
|
848712
|
+
]);
|
|
848713
|
+
var WRITE_TOOL_NAMES = new Set([
|
|
848714
|
+
"write",
|
|
848715
|
+
"edit",
|
|
848716
|
+
"goodvibes_settings",
|
|
848717
|
+
"agent_knowledge_ingest",
|
|
848718
|
+
"agent_local_registry",
|
|
848719
|
+
"agent_work_plan"
|
|
848720
|
+
]);
|
|
848721
|
+
var EXECUTE_TOOL_NAMES = new Set(["exec", "repl"]);
|
|
848722
|
+
function installPermissionManagerSafetyGuard(manager5) {
|
|
848723
|
+
const marked = manager5;
|
|
848724
|
+
if (marked[SAFETY_MARKER])
|
|
848725
|
+
return;
|
|
848726
|
+
marked[SAFETY_MARKER] = true;
|
|
848727
|
+
const originalGetCategory = manager5.getCategory.bind(manager5);
|
|
848728
|
+
const originalCheck = manager5.check.bind(manager5);
|
|
848729
|
+
const originalCheckDetailed = manager5.checkDetailed?.bind(manager5);
|
|
848730
|
+
manager5.getCategory = (toolName, args2 = {}) => {
|
|
848731
|
+
try {
|
|
848732
|
+
return originalGetCategory(toolName, args2);
|
|
848733
|
+
} catch {
|
|
848734
|
+
return fallbackPermissionCategory(toolName);
|
|
848735
|
+
}
|
|
848736
|
+
};
|
|
848737
|
+
manager5.check = async (toolName, args2) => {
|
|
848738
|
+
try {
|
|
848739
|
+
return await originalCheck(toolName, args2);
|
|
848740
|
+
} catch {
|
|
848741
|
+
return fallbackPermissionCategory(toolName) === "read";
|
|
848742
|
+
}
|
|
848743
|
+
};
|
|
848744
|
+
if (originalCheckDetailed) {
|
|
848745
|
+
manager5.checkDetailed = async (toolName, args2) => {
|
|
848746
|
+
try {
|
|
848747
|
+
return await originalCheckDetailed(toolName, args2);
|
|
848748
|
+
} catch (error51) {
|
|
848749
|
+
const category = fallbackPermissionCategory(toolName);
|
|
848750
|
+
const approved = category === "read";
|
|
848751
|
+
return {
|
|
848752
|
+
approved,
|
|
848753
|
+
persisted: false,
|
|
848754
|
+
sourceLayer: "runtime_mode",
|
|
848755
|
+
reasonCode: approved ? "config_allow" : "config_deny",
|
|
848756
|
+
analysis: {
|
|
848757
|
+
classification: "generic",
|
|
848758
|
+
riskLevel: category === "read" ? "low" : "high",
|
|
848759
|
+
summary: `Permission fallback for ${toolName}: ${summarizeError(error51)}`,
|
|
848760
|
+
reasons: ["permission-manager-exception"]
|
|
848761
|
+
}
|
|
848762
|
+
};
|
|
848763
|
+
}
|
|
848764
|
+
};
|
|
848765
|
+
}
|
|
848766
|
+
}
|
|
848767
|
+
function fallbackPermissionCategory(toolName) {
|
|
848768
|
+
if (READ_TOOL_NAMES.has(toolName))
|
|
848769
|
+
return "read";
|
|
848770
|
+
if (WRITE_TOOL_NAMES.has(toolName))
|
|
848771
|
+
return "write";
|
|
848772
|
+
if (EXECUTE_TOOL_NAMES.has(toolName))
|
|
848773
|
+
return "execute";
|
|
848774
|
+
return "delegate";
|
|
848775
|
+
}
|
|
848776
|
+
|
|
848552
848777
|
// src/runtime/agent-runtime-events.ts
|
|
848553
848778
|
var AGENT_STATUS_INTERVAL_MS2 = 30000;
|
|
848554
848779
|
function withRouter2(getSystemMessageRouter, action2) {
|
|
@@ -848805,6 +849030,7 @@ async function initializeBootstrapCore(stdout, options, getControlPlaneRecentEve
|
|
|
848805
849030
|
installAgentToolPolicyGuard(toolRegistry, {
|
|
848806
849031
|
getLastUserMessage: () => conversation.getLastUserMessage()
|
|
848807
849032
|
});
|
|
849033
|
+
installToolExecutionSafetyGuard(toolRegistry);
|
|
848808
849034
|
compactRegisteredToolDefinitions(toolRegistry);
|
|
848809
849035
|
services.agentOrchestrator.setDependencies({
|
|
848810
849036
|
surfaceRoot: GOODVIBES_AGENT_SURFACE_ROOT,
|
|
@@ -848943,6 +849169,7 @@ async function initializeBootstrapCore(stdout, options, getControlPlaneRecentEve
|
|
|
848943
849169
|
sessionId: runtimeSessionIdRef.value,
|
|
848944
849170
|
localPrompt: permissionPromptRef.requestPermission
|
|
848945
849171
|
}), createPermissionConfigReader(configManager), policyRuntimeState, services.hookDispatcher, featureFlags);
|
|
849172
|
+
installPermissionManagerSafetyGuard(permissionManager);
|
|
848946
849173
|
await hookWorkbench.loadAndApplyManagedHooks();
|
|
848947
849174
|
const runtime2 = {
|
|
848948
849175
|
model: configManager.get("provider.model"),
|
|
@@ -890625,7 +890852,7 @@ var AGENT_WORKSPACE_CATEGORIES = [
|
|
|
890625
890852
|
summary: "Import preferences, sign in, and choose the main model.",
|
|
890626
890853
|
detail: "Start here on a fresh install. Every row either saves state, opens the shared model picker, or opens a confirmed in-modal form.",
|
|
890627
890854
|
actions: [
|
|
890628
|
-
{ id: "import-goodvibes-tui-settings", label: "Import GoodVibes settings", detail: "Copy existing display, provider, behavior, permission, TTS, channel, helper, tool, release, and automation values into Agent-owned
|
|
890855
|
+
{ id: "import-goodvibes-tui-settings", label: "Import GoodVibes settings", detail: "Copy existing display, provider, subscription, behavior, permission, TTS, channel, helper, tool, release, and automation values into Agent-owned state.", kind: "settings-import", safety: "safe" },
|
|
890629
890856
|
{ id: "subscription-login-start", label: "Start subscription login", detail: "Start one provider sign-in flow, save pending state, and return here.", editorKind: "subscription-login-start", kind: "editor", safety: "safe" },
|
|
890630
890857
|
{ id: "subscription-login-finish", label: "Finish subscription login", detail: "Exchange a code or redirect URL and save the provider subscription session.", editorKind: "subscription-login-finish", kind: "editor", safety: "safe" },
|
|
890631
890858
|
{ id: "setup-provider-model", label: "Choose main model", detail: "Open the shared provider/model picker for normal assistant turns.", kind: "model-picker", modelPickerFlow: "providerModel", modelPickerTarget: "main", safety: "safe" },
|
|
@@ -896068,6 +896295,62 @@ function canImportTuiSetting(key) {
|
|
|
896068
896295
|
function valuesMatch(left, right) {
|
|
896069
896296
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
896070
896297
|
}
|
|
896298
|
+
function readRecordMap(record2, key) {
|
|
896299
|
+
const value = record2[key];
|
|
896300
|
+
return isRecord45(value) ? Object.values(value) : [];
|
|
896301
|
+
}
|
|
896302
|
+
function isProviderSubscription(value) {
|
|
896303
|
+
return isRecord45(value) && typeof value.provider === "string" && typeof value.accessToken === "string" && typeof value.tokenType === "string" && value.authMode === "oauth" && typeof value.overrideAmbientApiKeys === "boolean" && typeof value.createdAt === "number" && typeof value.updatedAt === "number";
|
|
896304
|
+
}
|
|
896305
|
+
function isPendingSubscriptionLogin(value) {
|
|
896306
|
+
return isRecord45(value) && typeof value.provider === "string" && typeof value.state === "string" && typeof value.verifier === "string" && typeof value.redirectUri === "string" && typeof value.createdAt === "number";
|
|
896307
|
+
}
|
|
896308
|
+
function importTuiSubscriptions(context, parseErrors) {
|
|
896309
|
+
const shellPaths3 = context.workspace.shellPaths;
|
|
896310
|
+
const manager5 = context.platform.subscriptionManager;
|
|
896311
|
+
const result2 = {
|
|
896312
|
+
importedActive: [],
|
|
896313
|
+
importedPending: [],
|
|
896314
|
+
unchangedActive: [],
|
|
896315
|
+
unchangedPending: [],
|
|
896316
|
+
skipped: []
|
|
896317
|
+
};
|
|
896318
|
+
if (!shellPaths3 || !manager5)
|
|
896319
|
+
return result2;
|
|
896320
|
+
const sourcePath = shellPaths3.resolveUserPath(GOODVIBES_TUI_SURFACE_ROOT, "subscriptions.json");
|
|
896321
|
+
try {
|
|
896322
|
+
const store2 = readJsonRecord(sourcePath);
|
|
896323
|
+
if (!store2)
|
|
896324
|
+
return result2;
|
|
896325
|
+
for (const entry of readRecordMap(store2, "subscriptions")) {
|
|
896326
|
+
if (!isProviderSubscription(entry)) {
|
|
896327
|
+
result2.skipped.push("active subscription with invalid shape");
|
|
896328
|
+
continue;
|
|
896329
|
+
}
|
|
896330
|
+
if (valuesMatch(manager5.get(entry.provider), entry)) {
|
|
896331
|
+
result2.unchangedActive.push(entry.provider);
|
|
896332
|
+
continue;
|
|
896333
|
+
}
|
|
896334
|
+
manager5.saveSubscription(entry);
|
|
896335
|
+
result2.importedActive.push(entry.provider);
|
|
896336
|
+
}
|
|
896337
|
+
for (const entry of readRecordMap(store2, "pending")) {
|
|
896338
|
+
if (!isPendingSubscriptionLogin(entry)) {
|
|
896339
|
+
result2.skipped.push("pending subscription with invalid shape");
|
|
896340
|
+
continue;
|
|
896341
|
+
}
|
|
896342
|
+
if (valuesMatch(manager5.getPending(entry.provider), entry)) {
|
|
896343
|
+
result2.unchangedPending.push(entry.provider);
|
|
896344
|
+
continue;
|
|
896345
|
+
}
|
|
896346
|
+
manager5.savePending(entry);
|
|
896347
|
+
result2.importedPending.push(entry.provider);
|
|
896348
|
+
}
|
|
896349
|
+
} catch (error53) {
|
|
896350
|
+
parseErrors.push(`subscriptions: ${error53 instanceof Error ? error53.message : String(error53)}`);
|
|
896351
|
+
}
|
|
896352
|
+
return result2;
|
|
896353
|
+
}
|
|
896071
896354
|
function agentWorkspaceSettingSchema(context, key) {
|
|
896072
896355
|
return context?.platform?.configManager?.getSchema().find((setting) => setting.key === key) ?? null;
|
|
896073
896356
|
}
|
|
@@ -896171,7 +896454,7 @@ async function applyAgentWorkspaceSettingValue(context, setting, value) {
|
|
|
896171
896454
|
async function importAgentWorkspaceTuiSettings(context) {
|
|
896172
896455
|
const shellPaths3 = context?.workspace?.shellPaths;
|
|
896173
896456
|
const configManager = context?.platform?.configManager;
|
|
896174
|
-
if (!shellPaths3 || !configManager) {
|
|
896457
|
+
if (!context || !shellPaths3 || !configManager) {
|
|
896175
896458
|
return {
|
|
896176
896459
|
status: "GoodVibes TUI settings import is unavailable in this runtime.",
|
|
896177
896460
|
runtimeSnapshot: null,
|
|
@@ -896205,8 +896488,11 @@ async function importAgentWorkspaceTuiSettings(context) {
|
|
|
896205
896488
|
parseErrors.push(`${source.label}: ${error53 instanceof Error ? error53.message : String(error53)}`);
|
|
896206
896489
|
}
|
|
896207
896490
|
}
|
|
896208
|
-
|
|
896209
|
-
|
|
896491
|
+
const subscriptions = importTuiSubscriptions(context, parseErrors);
|
|
896492
|
+
const subscriptionImports = subscriptions.importedActive.length + subscriptions.importedPending.length;
|
|
896493
|
+
const subscriptionUnchanged = subscriptions.unchangedActive.length + subscriptions.unchangedPending.length;
|
|
896494
|
+
if (values2.size === 0 && subscriptionImports === 0 && subscriptionUnchanged === 0 && subscriptions.skipped.length === 0) {
|
|
896495
|
+
const detail = parseErrors.length > 0 ? `No importable settings or subscriptions found. ${parseErrors.join("; ")}` : "No GoodVibes TUI settings or subscription store with importable Agent-owned state was found.";
|
|
896210
896496
|
return {
|
|
896211
896497
|
status: "No GoodVibes TUI settings imported.",
|
|
896212
896498
|
runtimeSnapshot: null,
|
|
@@ -896236,15 +896522,20 @@ async function importAgentWorkspaceTuiSettings(context) {
|
|
|
896236
896522
|
skipped.push(`${setting.key}: ${error53 instanceof Error ? error53.message : String(error53)}`);
|
|
896237
896523
|
}
|
|
896238
896524
|
}
|
|
896525
|
+
skipped.push(...subscriptions.skipped);
|
|
896526
|
+
const changedCount = imported.length + subscriptionImports;
|
|
896527
|
+
const unchangedCount = unchanged.length + subscriptionUnchanged;
|
|
896239
896528
|
return {
|
|
896240
|
-
status:
|
|
896529
|
+
status: changedCount > 0 ? `Imported ${changedCount} GoodVibes TUI item(s).` : "No GoodVibes TUI settings changed.",
|
|
896241
896530
|
runtimeSnapshot: context ? buildAgentWorkspaceRuntimeSnapshot(context) : null,
|
|
896242
896531
|
result: {
|
|
896243
|
-
kind: skipped.length > 0 &&
|
|
896244
|
-
title:
|
|
896532
|
+
kind: skipped.length > 0 && changedCount === 0 ? "error" : changedCount > 0 ? "refreshed" : "guidance",
|
|
896533
|
+
title: changedCount > 0 ? "GoodVibes TUI settings imported" : "No settings changed",
|
|
896245
896534
|
detail: [
|
|
896246
896535
|
imported.length > 0 ? `Imported: ${imported.slice(0, 10).join(", ")}${imported.length > 10 ? `, +${imported.length - 10} more` : ""}.` : "",
|
|
896247
|
-
|
|
896536
|
+
subscriptions.importedActive.length > 0 ? `Imported active subscription(s): ${subscriptions.importedActive.join(", ")}.` : "",
|
|
896537
|
+
subscriptions.importedPending.length > 0 ? `Imported pending subscription(s): ${subscriptions.importedPending.join(", ")}.` : "",
|
|
896538
|
+
unchangedCount > 0 ? `${unchangedCount} item(s) already matched.` : "",
|
|
896248
896539
|
skipped.length > 0 ? `Skipped: ${skipped.slice(0, 5).join("; ")}${skipped.length > 5 ? `; +${skipped.length - 5} more` : ""}.` : "",
|
|
896249
896540
|
parseErrors.length > 0 ? `Parse issues: ${parseErrors.join("; ")}.` : ""
|
|
896250
896541
|
].filter((line2) => line2.length > 0).join(" "),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-agent",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "GoodVibes personal operator assistant TUI with a proactive Agent product brain, isolated Agent Knowledge, local profiles, routines, skills, personas, and explicit build delegation.",
|
|
6
6
|
"type": "module",
|
|
@@ -43,7 +43,7 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
|
|
|
43
43
|
summary: 'Import preferences, sign in, and choose the main model.',
|
|
44
44
|
detail: 'Start here on a fresh install. Every row either saves state, opens the shared model picker, or opens a confirmed in-modal form.',
|
|
45
45
|
actions: [
|
|
46
|
-
{ id: 'import-goodvibes-tui-settings', label: 'Import GoodVibes settings', detail: 'Copy existing display, provider, behavior, permission, TTS, channel, helper, tool, release, and automation values into Agent-owned
|
|
46
|
+
{ id: 'import-goodvibes-tui-settings', label: 'Import GoodVibes settings', detail: 'Copy existing display, provider, subscription, behavior, permission, TTS, channel, helper, tool, release, and automation values into Agent-owned state.', kind: 'settings-import', safety: 'safe' },
|
|
47
47
|
{ id: 'subscription-login-start', label: 'Start subscription login', detail: 'Start one provider sign-in flow, save pending state, and return here.', editorKind: 'subscription-login-start', kind: 'editor', safety: 'safe' },
|
|
48
48
|
{ id: 'subscription-login-finish', label: 'Finish subscription login', detail: 'Exchange a code or redirect URL and save the provider subscription session.', editorKind: 'subscription-login-finish', kind: 'editor', safety: 'safe' },
|
|
49
49
|
{ id: 'setup-provider-model', label: 'Choose main model', detail: 'Open the shared provider/model picker for normal assistant turns.', kind: 'model-picker', modelPickerFlow: 'providerModel', modelPickerTarget: 'main', safety: 'safe' },
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import type { ConfigKey, ConfigSetting } from '@pellux/goodvibes-sdk/platform/config';
|
|
3
|
+
import type { PendingSubscriptionLogin, ProviderSubscription } from '@pellux/goodvibes-sdk/platform/config';
|
|
3
4
|
import { setHarnessSetting } from '../agent/harness-control.ts';
|
|
4
5
|
import { isExternalHostOwnedSettingKey } from '../config/agent-settings-policy.ts';
|
|
5
6
|
import { buildAgentWorkspaceRuntimeSnapshot } from './agent-workspace-snapshot.ts';
|
|
@@ -83,6 +84,83 @@ function valuesMatch(left: unknown, right: unknown): boolean {
|
|
|
83
84
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
function readRecordMap(record: Record<string, unknown>, key: string): readonly unknown[] {
|
|
88
|
+
const value = record[key];
|
|
89
|
+
return isRecord(value) ? Object.values(value) : [];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isProviderSubscription(value: unknown): value is ProviderSubscription {
|
|
93
|
+
return isRecord(value)
|
|
94
|
+
&& typeof value.provider === 'string'
|
|
95
|
+
&& typeof value.accessToken === 'string'
|
|
96
|
+
&& typeof value.tokenType === 'string'
|
|
97
|
+
&& value.authMode === 'oauth'
|
|
98
|
+
&& typeof value.overrideAmbientApiKeys === 'boolean'
|
|
99
|
+
&& typeof value.createdAt === 'number'
|
|
100
|
+
&& typeof value.updatedAt === 'number';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isPendingSubscriptionLogin(value: unknown): value is PendingSubscriptionLogin {
|
|
104
|
+
return isRecord(value)
|
|
105
|
+
&& typeof value.provider === 'string'
|
|
106
|
+
&& typeof value.state === 'string'
|
|
107
|
+
&& typeof value.verifier === 'string'
|
|
108
|
+
&& typeof value.redirectUri === 'string'
|
|
109
|
+
&& typeof value.createdAt === 'number';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function importTuiSubscriptions(context: CommandContext, parseErrors: string[]): {
|
|
113
|
+
readonly importedActive: string[];
|
|
114
|
+
readonly importedPending: string[];
|
|
115
|
+
readonly unchangedActive: string[];
|
|
116
|
+
readonly unchangedPending: string[];
|
|
117
|
+
readonly skipped: string[];
|
|
118
|
+
} {
|
|
119
|
+
const shellPaths = context.workspace.shellPaths;
|
|
120
|
+
const manager = context.platform.subscriptionManager;
|
|
121
|
+
const result = {
|
|
122
|
+
importedActive: [] as string[],
|
|
123
|
+
importedPending: [] as string[],
|
|
124
|
+
unchangedActive: [] as string[],
|
|
125
|
+
unchangedPending: [] as string[],
|
|
126
|
+
skipped: [] as string[],
|
|
127
|
+
};
|
|
128
|
+
if (!shellPaths || !manager) return result;
|
|
129
|
+
|
|
130
|
+
const sourcePath = shellPaths.resolveUserPath(GOODVIBES_TUI_SURFACE_ROOT, 'subscriptions.json');
|
|
131
|
+
try {
|
|
132
|
+
const store = readJsonRecord(sourcePath);
|
|
133
|
+
if (!store) return result;
|
|
134
|
+
for (const entry of readRecordMap(store, 'subscriptions')) {
|
|
135
|
+
if (!isProviderSubscription(entry)) {
|
|
136
|
+
result.skipped.push('active subscription with invalid shape');
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (valuesMatch(manager.get(entry.provider), entry)) {
|
|
140
|
+
result.unchangedActive.push(entry.provider);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
manager.saveSubscription(entry);
|
|
144
|
+
result.importedActive.push(entry.provider);
|
|
145
|
+
}
|
|
146
|
+
for (const entry of readRecordMap(store, 'pending')) {
|
|
147
|
+
if (!isPendingSubscriptionLogin(entry)) {
|
|
148
|
+
result.skipped.push('pending subscription with invalid shape');
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (valuesMatch(manager.getPending(entry.provider), entry)) {
|
|
152
|
+
result.unchangedPending.push(entry.provider);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
manager.savePending(entry);
|
|
156
|
+
result.importedPending.push(entry.provider);
|
|
157
|
+
}
|
|
158
|
+
} catch (error) {
|
|
159
|
+
parseErrors.push(`subscriptions: ${error instanceof Error ? error.message : String(error)}`);
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
|
|
86
164
|
export function agentWorkspaceSettingSchema(context: CommandContext | null, key: string): ConfigSetting | null {
|
|
87
165
|
return context?.platform?.configManager
|
|
88
166
|
?.getSchema()
|
|
@@ -200,7 +278,7 @@ export async function applyAgentWorkspaceSettingValue(
|
|
|
200
278
|
export async function importAgentWorkspaceTuiSettings(context: CommandContext | null): Promise<TuiSettingsImportOutcome> {
|
|
201
279
|
const shellPaths = context?.workspace?.shellPaths;
|
|
202
280
|
const configManager = context?.platform?.configManager;
|
|
203
|
-
if (!shellPaths || !configManager) {
|
|
281
|
+
if (!context || !shellPaths || !configManager) {
|
|
204
282
|
return {
|
|
205
283
|
status: 'GoodVibes TUI settings import is unavailable in this runtime.',
|
|
206
284
|
runtimeSnapshot: null,
|
|
@@ -232,11 +310,14 @@ export async function importAgentWorkspaceTuiSettings(context: CommandContext |
|
|
|
232
310
|
parseErrors.push(`${source.label}: ${error instanceof Error ? error.message : String(error)}`);
|
|
233
311
|
}
|
|
234
312
|
}
|
|
313
|
+
const subscriptions = importTuiSubscriptions(context, parseErrors);
|
|
314
|
+
const subscriptionImports = subscriptions.importedActive.length + subscriptions.importedPending.length;
|
|
315
|
+
const subscriptionUnchanged = subscriptions.unchangedActive.length + subscriptions.unchangedPending.length;
|
|
235
316
|
|
|
236
|
-
if (values.size === 0) {
|
|
317
|
+
if (values.size === 0 && subscriptionImports === 0 && subscriptionUnchanged === 0 && subscriptions.skipped.length === 0) {
|
|
237
318
|
const detail = parseErrors.length > 0
|
|
238
|
-
? `No importable settings found. ${parseErrors.join('; ')}`
|
|
239
|
-
: 'No GoodVibes TUI settings
|
|
319
|
+
? `No importable settings or subscriptions found. ${parseErrors.join('; ')}`
|
|
320
|
+
: 'No GoodVibes TUI settings or subscription store with importable Agent-owned state was found.';
|
|
240
321
|
return {
|
|
241
322
|
status: 'No GoodVibes TUI settings imported.',
|
|
242
323
|
runtimeSnapshot: null,
|
|
@@ -266,16 +347,21 @@ export async function importAgentWorkspaceTuiSettings(context: CommandContext |
|
|
|
266
347
|
skipped.push(`${setting.key}: ${error instanceof Error ? error.message : String(error)}`);
|
|
267
348
|
}
|
|
268
349
|
}
|
|
350
|
+
skipped.push(...subscriptions.skipped);
|
|
351
|
+
const changedCount = imported.length + subscriptionImports;
|
|
352
|
+
const unchangedCount = unchanged.length + subscriptionUnchanged;
|
|
269
353
|
|
|
270
354
|
return {
|
|
271
|
-
status:
|
|
355
|
+
status: changedCount > 0 ? `Imported ${changedCount} GoodVibes TUI item(s).` : 'No GoodVibes TUI settings changed.',
|
|
272
356
|
runtimeSnapshot: context ? buildAgentWorkspaceRuntimeSnapshot(context) : null,
|
|
273
357
|
result: {
|
|
274
|
-
kind: skipped.length > 0 &&
|
|
275
|
-
title:
|
|
358
|
+
kind: skipped.length > 0 && changedCount === 0 ? 'error' : changedCount > 0 ? 'refreshed' : 'guidance',
|
|
359
|
+
title: changedCount > 0 ? 'GoodVibes TUI settings imported' : 'No settings changed',
|
|
276
360
|
detail: [
|
|
277
361
|
imported.length > 0 ? `Imported: ${imported.slice(0, 10).join(', ')}${imported.length > 10 ? `, +${imported.length - 10} more` : ''}.` : '',
|
|
278
|
-
|
|
362
|
+
subscriptions.importedActive.length > 0 ? `Imported active subscription(s): ${subscriptions.importedActive.join(', ')}.` : '',
|
|
363
|
+
subscriptions.importedPending.length > 0 ? `Imported pending subscription(s): ${subscriptions.importedPending.join(', ')}.` : '',
|
|
364
|
+
unchangedCount > 0 ? `${unchangedCount} item(s) already matched.` : '',
|
|
279
365
|
skipped.length > 0 ? `Skipped: ${skipped.slice(0, 5).join('; ')}${skipped.length > 5 ? `; +${skipped.length - 5} more` : ''}.` : '',
|
|
280
366
|
parseErrors.length > 0 ? `Parse issues: ${parseErrors.join('; ')}.` : '',
|
|
281
367
|
].filter((line) => line.length > 0).join(' '),
|
|
@@ -41,6 +41,8 @@ import { registerAgentReminderScheduleTool } from '../tools/agent-reminder-sched
|
|
|
41
41
|
import { getTerminalSize } from '../shell/terminal-size.ts';
|
|
42
42
|
import { registerAgentWorkPlanTool } from '../tools/agent-work-plan-tool.ts';
|
|
43
43
|
import { compactRegisteredToolDefinitions } from '../tools/tool-definition-compaction.ts';
|
|
44
|
+
import { installToolExecutionSafetyGuard } from '../tools/tool-execution-safety.ts';
|
|
45
|
+
import { installPermissionManagerSafetyGuard } from './tool-permission-safety.ts';
|
|
44
46
|
import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
|
|
45
47
|
import { registerAgentRuntimeEvents } from './agent-runtime-events.ts';
|
|
46
48
|
|
|
@@ -251,6 +253,7 @@ export async function initializeBootstrapCore(
|
|
|
251
253
|
installAgentToolPolicyGuard(toolRegistry, {
|
|
252
254
|
getLastUserMessage: () => conversation.getLastUserMessage(),
|
|
253
255
|
});
|
|
256
|
+
installToolExecutionSafetyGuard(toolRegistry);
|
|
254
257
|
compactRegisteredToolDefinitions(toolRegistry);
|
|
255
258
|
services.agentOrchestrator.setDependencies({
|
|
256
259
|
surfaceRoot: GOODVIBES_AGENT_SURFACE_ROOT,
|
|
@@ -432,6 +435,7 @@ export async function initializeBootstrapCore(
|
|
|
432
435
|
services.hookDispatcher,
|
|
433
436
|
featureFlags,
|
|
434
437
|
);
|
|
438
|
+
installPermissionManagerSafetyGuard(permissionManager);
|
|
435
439
|
await hookWorkbench.loadAndApplyManagedHooks();
|
|
436
440
|
|
|
437
441
|
const runtime: MutableRuntimeState = {
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { PermissionCategory, PermissionCheckResult } from '@pellux/goodvibes-sdk/platform/permissions';
|
|
2
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
3
|
+
|
|
4
|
+
type PermissionManagerLike = {
|
|
5
|
+
check(toolName: string, args: Record<string, unknown>): Promise<boolean>;
|
|
6
|
+
checkDetailed?: (toolName: string, args: Record<string, unknown>) => Promise<PermissionCheckResult>;
|
|
7
|
+
getCategory(toolName: string, args?: Record<string, unknown>): PermissionCategory;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const SAFETY_MARKER = Symbol.for('goodvibes-agent.permission-safety-installed');
|
|
11
|
+
|
|
12
|
+
const READ_TOOL_NAMES = new Set([
|
|
13
|
+
'read',
|
|
14
|
+
'find',
|
|
15
|
+
'fetch',
|
|
16
|
+
'analyze',
|
|
17
|
+
'inspect',
|
|
18
|
+
'state',
|
|
19
|
+
'registry',
|
|
20
|
+
'goodvibes_context',
|
|
21
|
+
'agent_harness',
|
|
22
|
+
'agent_knowledge',
|
|
23
|
+
'agent_operator_briefing',
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
const WRITE_TOOL_NAMES = new Set([
|
|
27
|
+
'write',
|
|
28
|
+
'edit',
|
|
29
|
+
'goodvibes_settings',
|
|
30
|
+
'agent_knowledge_ingest',
|
|
31
|
+
'agent_local_registry',
|
|
32
|
+
'agent_work_plan',
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const EXECUTE_TOOL_NAMES = new Set(['exec', 'repl']);
|
|
36
|
+
|
|
37
|
+
type MarkedPermissionManager = PermissionManagerLike & { [SAFETY_MARKER]?: true };
|
|
38
|
+
|
|
39
|
+
export function installPermissionManagerSafetyGuard(manager: PermissionManagerLike): void {
|
|
40
|
+
const marked = manager as MarkedPermissionManager;
|
|
41
|
+
if (marked[SAFETY_MARKER]) return;
|
|
42
|
+
marked[SAFETY_MARKER] = true;
|
|
43
|
+
|
|
44
|
+
const originalGetCategory = manager.getCategory.bind(manager);
|
|
45
|
+
const originalCheck = manager.check.bind(manager);
|
|
46
|
+
const originalCheckDetailed = manager.checkDetailed?.bind(manager);
|
|
47
|
+
|
|
48
|
+
manager.getCategory = (toolName, args = {}) => {
|
|
49
|
+
try {
|
|
50
|
+
return originalGetCategory(toolName, args);
|
|
51
|
+
} catch {
|
|
52
|
+
return fallbackPermissionCategory(toolName);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
manager.check = async (toolName, args) => {
|
|
57
|
+
try {
|
|
58
|
+
return await originalCheck(toolName, args);
|
|
59
|
+
} catch {
|
|
60
|
+
return fallbackPermissionCategory(toolName) === 'read';
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
if (originalCheckDetailed) {
|
|
65
|
+
manager.checkDetailed = async (toolName, args) => {
|
|
66
|
+
try {
|
|
67
|
+
return await originalCheckDetailed(toolName, args);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
const category = fallbackPermissionCategory(toolName);
|
|
70
|
+
const approved = category === 'read';
|
|
71
|
+
return {
|
|
72
|
+
approved,
|
|
73
|
+
persisted: false,
|
|
74
|
+
sourceLayer: 'runtime_mode',
|
|
75
|
+
reasonCode: approved ? 'config_allow' : 'config_deny',
|
|
76
|
+
analysis: {
|
|
77
|
+
classification: 'generic',
|
|
78
|
+
riskLevel: category === 'read' ? 'low' : 'high',
|
|
79
|
+
summary: `Permission fallback for ${toolName}: ${summarizeError(error)}`,
|
|
80
|
+
reasons: ['permission-manager-exception'],
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function fallbackPermissionCategory(toolName: string): PermissionCategory {
|
|
89
|
+
if (READ_TOOL_NAMES.has(toolName)) return 'read';
|
|
90
|
+
if (WRITE_TOOL_NAMES.has(toolName)) return 'write';
|
|
91
|
+
if (EXECUTE_TOOL_NAMES.has(toolName)) return 'execute';
|
|
92
|
+
return 'delegate';
|
|
93
|
+
}
|
|
@@ -1,20 +1,147 @@
|
|
|
1
1
|
import type { Tool } from '@pellux/goodvibes-sdk/platform/types';
|
|
2
|
+
import type { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools';
|
|
2
3
|
|
|
3
|
-
const
|
|
4
|
-
'
|
|
5
|
-
'
|
|
6
|
-
'
|
|
7
|
-
|
|
4
|
+
const AGENT_CONTEXT_TOOL_MODES = [
|
|
5
|
+
'summary',
|
|
6
|
+
'capabilities',
|
|
7
|
+
'modes',
|
|
8
|
+
'tools',
|
|
9
|
+
'tool',
|
|
10
|
+
'settings',
|
|
11
|
+
'setting',
|
|
12
|
+
'commands',
|
|
13
|
+
'command',
|
|
14
|
+
'workspace',
|
|
15
|
+
'status',
|
|
16
|
+
] as const;
|
|
8
17
|
|
|
9
|
-
|
|
10
|
-
|
|
18
|
+
type AgentContextMode = typeof AGENT_CONTEXT_TOOL_MODES[number];
|
|
19
|
+
|
|
20
|
+
type AgentContextArgs = {
|
|
21
|
+
readonly mode?: unknown;
|
|
22
|
+
readonly query?: unknown;
|
|
23
|
+
readonly target?: unknown;
|
|
24
|
+
readonly toolName?: unknown;
|
|
25
|
+
readonly command?: unknown;
|
|
26
|
+
readonly commandName?: unknown;
|
|
27
|
+
readonly key?: unknown;
|
|
28
|
+
readonly category?: unknown;
|
|
29
|
+
readonly prefix?: unknown;
|
|
30
|
+
readonly includeHidden?: unknown;
|
|
31
|
+
readonly includeParameters?: unknown;
|
|
32
|
+
readonly limit?: unknown;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const CONTEXT_TOOL_FALLBACK = {
|
|
36
|
+
runtime: 'GoodVibes Agent',
|
|
37
|
+
preferredTool: 'agent_harness',
|
|
38
|
+
routes: {
|
|
39
|
+
summary: 'agent_harness mode:"summary"',
|
|
40
|
+
capabilities: 'agent_harness mode:"summary" includeParameters:true',
|
|
41
|
+
tools: 'agent_harness mode:"tools"',
|
|
42
|
+
tool: 'agent_harness mode:"tool"',
|
|
43
|
+
settings: 'agent_harness mode:"settings"',
|
|
44
|
+
commands: 'agent_harness mode:"commands"',
|
|
45
|
+
workspace: 'agent_harness mode:"workspace"',
|
|
46
|
+
status: 'agent_harness mode:"connected_host_status"',
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export function wrapAgentContextToolForAgentPolicy(tool: Tool, registry?: ToolRegistry): void {
|
|
51
|
+
tool.definition.description = 'Inspect GoodVibes Agent harness capabilities.';
|
|
11
52
|
tool.definition.sideEffects = [];
|
|
12
53
|
tool.definition.parameters = {
|
|
13
54
|
type: 'object',
|
|
14
|
-
properties: {
|
|
55
|
+
properties: {
|
|
56
|
+
mode: { type: 'string', enum: [...AGENT_CONTEXT_TOOL_MODES] },
|
|
57
|
+
query: { type: 'string' },
|
|
58
|
+
target: { type: 'string' },
|
|
59
|
+
toolName: { type: 'string' },
|
|
60
|
+
command: { type: 'string' },
|
|
61
|
+
commandName: { type: 'string' },
|
|
62
|
+
key: { type: 'string' },
|
|
63
|
+
category: { type: 'string' },
|
|
64
|
+
prefix: { type: 'string' },
|
|
65
|
+
includeHidden: { type: 'boolean' },
|
|
66
|
+
includeParameters: { type: 'boolean' },
|
|
67
|
+
limit: { type: 'number' },
|
|
68
|
+
},
|
|
15
69
|
additionalProperties: false,
|
|
16
70
|
};
|
|
17
|
-
tool.execute = async () =>
|
|
71
|
+
tool.execute = async (rawArgs) => {
|
|
72
|
+
const args = rawArgs as AgentContextArgs;
|
|
73
|
+
const mode = readAgentContextMode(args.mode);
|
|
74
|
+
const harnessMode = agentContextModeToHarnessMode(mode);
|
|
75
|
+
const harnessTool = registry?.list().find((entry) => entry.definition.name === 'agent_harness');
|
|
76
|
+
if (!harnessTool) {
|
|
77
|
+
return ok({
|
|
78
|
+
...CONTEXT_TOOL_FALLBACK,
|
|
79
|
+
status: 'agent_harness_not_registered_yet',
|
|
80
|
+
requestedMode: mode,
|
|
81
|
+
route: `agent_harness mode:"${harnessMode}"`,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const harnessArgs = {
|
|
86
|
+
mode: harnessMode,
|
|
87
|
+
query: args.query,
|
|
88
|
+
target: args.target,
|
|
89
|
+
toolName: args.toolName,
|
|
90
|
+
command: args.command,
|
|
91
|
+
commandName: args.commandName,
|
|
92
|
+
key: args.key,
|
|
93
|
+
category: args.category,
|
|
94
|
+
prefix: args.prefix,
|
|
95
|
+
includeHidden: args.includeHidden,
|
|
96
|
+
includeParameters: mode === 'capabilities' ? true : args.includeParameters,
|
|
97
|
+
limit: args.limit,
|
|
98
|
+
};
|
|
99
|
+
const result = await harnessTool.execute(dropUndefined(harnessArgs));
|
|
100
|
+
if (!result.success) return result;
|
|
101
|
+
return ok({
|
|
102
|
+
source: 'agent_harness',
|
|
103
|
+
requestedMode: mode,
|
|
104
|
+
route: `agent_harness mode:"${harnessMode}"`,
|
|
105
|
+
result: parseToolOutput(result.output),
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const AGENT_CONTEXT_TOOL_COMPATIBILITY_MODES = AGENT_CONTEXT_TOOL_MODES;
|
|
111
|
+
|
|
112
|
+
function readAgentContextMode(value: unknown): AgentContextMode {
|
|
113
|
+
return typeof value === 'string' && AGENT_CONTEXT_TOOL_MODES.includes(value as AgentContextMode)
|
|
114
|
+
? value as AgentContextMode
|
|
115
|
+
: 'summary';
|
|
18
116
|
}
|
|
19
117
|
|
|
20
|
-
|
|
118
|
+
function agentContextModeToHarnessMode(mode: AgentContextMode): string {
|
|
119
|
+
if (mode === 'capabilities') return 'summary';
|
|
120
|
+
if (mode === 'modes') return 'modes';
|
|
121
|
+
if (mode === 'tools') return 'tools';
|
|
122
|
+
if (mode === 'tool') return 'tool';
|
|
123
|
+
if (mode === 'settings') return 'settings';
|
|
124
|
+
if (mode === 'setting') return 'get_setting';
|
|
125
|
+
if (mode === 'commands') return 'commands';
|
|
126
|
+
if (mode === 'command') return 'command';
|
|
127
|
+
if (mode === 'workspace') return 'workspace';
|
|
128
|
+
if (mode === 'status') return 'connected_host_status';
|
|
129
|
+
return 'summary';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function dropUndefined<T extends Record<string, unknown>>(value: T): Record<string, unknown> {
|
|
133
|
+
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function parseToolOutput(value: unknown): unknown {
|
|
137
|
+
if (typeof value !== 'string') return value ?? null;
|
|
138
|
+
try {
|
|
139
|
+
return JSON.parse(value);
|
|
140
|
+
} catch {
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function ok(value: unknown): { readonly success: true; readonly output: string } {
|
|
146
|
+
return { success: true, output: JSON.stringify(value, null, 2) };
|
|
147
|
+
}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
wrapRegistryToolForAgentPolicy,
|
|
6
6
|
} from './agent-analysis-registry-policy.ts';
|
|
7
7
|
import {
|
|
8
|
-
|
|
8
|
+
wrapAgentContextToolForAgentPolicy,
|
|
9
9
|
} from './agent-context-policy.ts';
|
|
10
10
|
import { wrapFindToolForAgentPolicy } from './agent-find-policy.ts';
|
|
11
11
|
import { wrapReadToolForAgentPolicy } from './agent-read-policy.ts';
|
|
@@ -223,7 +223,7 @@ export function installAgentToolPolicyGuard(registry: ToolRegistry, options: Age
|
|
|
223
223
|
} else if (tool.definition.name === 'state') {
|
|
224
224
|
wrapStateToolForAgentPolicy(tool);
|
|
225
225
|
} else if (tool.definition.name === 'goodvibes_context') {
|
|
226
|
-
|
|
226
|
+
wrapAgentContextToolForAgentPolicy(tool, registry);
|
|
227
227
|
} else if (tool.definition.name === 'goodvibes_settings') {
|
|
228
228
|
wrapBlockedSettingsToolForAgentPolicy(tool);
|
|
229
229
|
} else if (tool.definition.name === 'inspect') {
|
|
@@ -538,8 +538,8 @@ export const AGENT_DURABLE_WORKFLOW_MUTATION_DENIAL_MESSAGE = DURABLE_WORKFLOW_M
|
|
|
538
538
|
export const AGENT_CONTROL_MUTATION_DENIAL_MESSAGE = CONTROL_MUTATION_DENIAL;
|
|
539
539
|
|
|
540
540
|
export {
|
|
541
|
-
|
|
542
|
-
|
|
541
|
+
AGENT_CONTEXT_TOOL_COMPATIBILITY_MODES,
|
|
542
|
+
wrapAgentContextToolForAgentPolicy,
|
|
543
543
|
} from './agent-context-policy.ts';
|
|
544
544
|
|
|
545
545
|
export {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Tool } from '@pellux/goodvibes-sdk/platform/types';
|
|
2
|
+
import type { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools';
|
|
3
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
4
|
+
|
|
5
|
+
const TOOL_SAFETY_MARKER = Symbol.for('goodvibes-agent.tool-execution-safety-wrapped');
|
|
6
|
+
const REGISTRY_SAFETY_MARKER = Symbol.for('goodvibes-agent.tool-registry-safety-installed');
|
|
7
|
+
|
|
8
|
+
type MarkedTool = Tool & { [TOOL_SAFETY_MARKER]?: true };
|
|
9
|
+
type MarkedRegistry = ToolRegistry & { [REGISTRY_SAFETY_MARKER]?: true };
|
|
10
|
+
|
|
11
|
+
export function installToolExecutionSafetyGuard(registry: ToolRegistry): void {
|
|
12
|
+
const marked = registry as MarkedRegistry;
|
|
13
|
+
if (marked[REGISTRY_SAFETY_MARKER]) return;
|
|
14
|
+
marked[REGISTRY_SAFETY_MARKER] = true;
|
|
15
|
+
|
|
16
|
+
for (const tool of registry.list()) wrapToolExecutionSafety(tool);
|
|
17
|
+
|
|
18
|
+
const originalRegister = registry.register.bind(registry);
|
|
19
|
+
registry.register = (tool: Tool): void => {
|
|
20
|
+
wrapToolExecutionSafety(tool);
|
|
21
|
+
originalRegister(tool);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function wrapToolExecutionSafety(tool: Tool): void {
|
|
26
|
+
const marked = tool as MarkedTool;
|
|
27
|
+
if (marked[TOOL_SAFETY_MARKER]) return;
|
|
28
|
+
marked[TOOL_SAFETY_MARKER] = true;
|
|
29
|
+
|
|
30
|
+
const originalExecute = tool.execute.bind(tool);
|
|
31
|
+
tool.execute = async (args) => {
|
|
32
|
+
try {
|
|
33
|
+
return await originalExecute(args);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
return {
|
|
36
|
+
success: false,
|
|
37
|
+
error: summarizeError(error),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
// The prebuild script updates the fallback value before compilation.
|
|
7
7
|
// Uses import.meta.dir (Bun) to locate package.json relative to this file,
|
|
8
8
|
// which is correct regardless of the process working directory.
|
|
9
|
-
let _version = '1.1.
|
|
9
|
+
let _version = '1.1.7';
|
|
10
10
|
try {
|
|
11
11
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {
|
|
12
12
|
readonly version?: unknown;
|