@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 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.
@@ -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.5";
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 CONTEXT_TOOL_DENIAL = [
840577
- "GoodVibes Agent does not expose non-Agent runtime context through model tools in the main conversation.",
840578
- "The runtime context tool can describe assumptions that are outside the Agent product boundary.",
840579
- "Use explicit Agent CLI/slash commands such as status, compat, setup, and isolated Agent Knowledge instead."
840580
- ].join(" ");
840581
- function wrapBlockedContextToolForAgentPolicy(tool2) {
840582
- tool2.definition.description = "Blocked in GoodVibes Agent: non-Agent runtime context.";
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 () => ({ success: false, error: CONTEXT_TOOL_DENIAL });
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
- wrapBlockedContextToolForAgentPolicy(tool2);
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 settings.", kind: "settings-import", safety: "safe" },
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
- if (values2.size === 0) {
896209
- const detail = parseErrors.length > 0 ? `No importable settings found. ${parseErrors.join("; ")}` : "No GoodVibes TUI settings file with importable Agent-owned settings was found.";
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: imported.length > 0 ? `Imported ${imported.length} GoodVibes TUI setting(s).` : "No GoodVibes TUI settings changed.",
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 && imported.length === 0 ? "error" : imported.length > 0 ? "refreshed" : "guidance",
896244
- title: imported.length > 0 ? "GoodVibes TUI settings imported" : "No settings changed",
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
- unchanged.length > 0 ? `${unchanged.length} setting(s) already matched.` : "",
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.5",
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 settings.', kind: 'settings-import', safety: 'safe' },
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 file with importable Agent-owned settings was found.';
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: imported.length > 0 ? `Imported ${imported.length} GoodVibes TUI setting(s).` : 'No GoodVibes TUI settings changed.',
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 && imported.length === 0 ? 'error' : imported.length > 0 ? 'refreshed' : 'guidance',
275
- title: imported.length > 0 ? 'GoodVibes TUI settings imported' : 'No settings changed',
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
- unchanged.length > 0 ? `${unchanged.length} setting(s) already matched.` : '',
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 CONTEXT_TOOL_DENIAL = [
4
- 'GoodVibes Agent does not expose non-Agent runtime context through model tools in the main conversation.',
5
- 'The runtime context tool can describe assumptions that are outside the Agent product boundary.',
6
- 'Use explicit Agent CLI/slash commands such as status, compat, setup, and isolated Agent Knowledge instead.',
7
- ].join(' ');
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
- export function wrapBlockedContextToolForAgentPolicy(tool: Tool): void {
10
- tool.definition.description = 'Blocked in GoodVibes Agent: non-Agent runtime context.';
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 () => ({ success: false, error: CONTEXT_TOOL_DENIAL });
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
- export const AGENT_CONTEXT_TOOL_DENIAL_MESSAGE = CONTEXT_TOOL_DENIAL;
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
- wrapBlockedContextToolForAgentPolicy,
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
- wrapBlockedContextToolForAgentPolicy(tool);
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
- AGENT_CONTEXT_TOOL_DENIAL_MESSAGE,
542
- wrapBlockedContextToolForAgentPolicy,
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.5';
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;