iosm-cli 0.2.15 → 0.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +23 -1
  3. package/dist/cli/args.d.ts +2 -1
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +16 -3
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/core/agent-profiles.d.ts.map +1 -1
  8. package/dist/core/agent-profiles.js +3 -0
  9. package/dist/core/agent-profiles.js.map +1 -1
  10. package/dist/core/agent-session.d.ts +2 -0
  11. package/dist/core/agent-session.d.ts.map +1 -1
  12. package/dist/core/agent-session.js +26 -10
  13. package/dist/core/agent-session.js.map +1 -1
  14. package/dist/core/agent-teams.d.ts.map +1 -1
  15. package/dist/core/agent-teams.js +12 -9
  16. package/dist/core/agent-teams.js.map +1 -1
  17. package/dist/core/checkpoint/fs-checkpoint.d.ts +14 -0
  18. package/dist/core/checkpoint/fs-checkpoint.d.ts.map +1 -0
  19. package/dist/core/checkpoint/fs-checkpoint.js +211 -0
  20. package/dist/core/checkpoint/fs-checkpoint.js.map +1 -0
  21. package/dist/core/command-dispatcher.d.ts +2 -0
  22. package/dist/core/command-dispatcher.d.ts.map +1 -1
  23. package/dist/core/command-dispatcher.js +78 -13
  24. package/dist/core/command-dispatcher.js.map +1 -1
  25. package/dist/core/mcp/cli.d.ts.map +1 -1
  26. package/dist/core/mcp/cli.js +26 -0
  27. package/dist/core/mcp/cli.js.map +1 -1
  28. package/dist/core/mcp/config.d.ts.map +1 -1
  29. package/dist/core/mcp/config.js +55 -0
  30. package/dist/core/mcp/config.js.map +1 -1
  31. package/dist/core/mcp/index.d.ts +1 -1
  32. package/dist/core/mcp/index.d.ts.map +1 -1
  33. package/dist/core/mcp/index.js.map +1 -1
  34. package/dist/core/mcp/runtime.d.ts +3 -1
  35. package/dist/core/mcp/runtime.d.ts.map +1 -1
  36. package/dist/core/mcp/runtime.js +21 -2
  37. package/dist/core/mcp/runtime.js.map +1 -1
  38. package/dist/core/mcp/types.d.ts +30 -2
  39. package/dist/core/mcp/types.d.ts.map +1 -1
  40. package/dist/core/mcp/types.js.map +1 -1
  41. package/dist/core/package-manager.d.ts +10 -0
  42. package/dist/core/package-manager.d.ts.map +1 -1
  43. package/dist/core/package-manager.js +100 -2
  44. package/dist/core/package-manager.js.map +1 -1
  45. package/dist/core/policy/engine.d.ts +77 -0
  46. package/dist/core/policy/engine.d.ts.map +1 -0
  47. package/dist/core/policy/engine.js +614 -0
  48. package/dist/core/policy/engine.js.map +1 -0
  49. package/dist/core/policy/index.d.ts +2 -0
  50. package/dist/core/policy/index.d.ts.map +1 -0
  51. package/dist/core/policy/index.js +2 -0
  52. package/dist/core/policy/index.js.map +1 -0
  53. package/dist/core/sandbox/executor.d.ts +13 -0
  54. package/dist/core/sandbox/executor.d.ts.map +1 -0
  55. package/dist/core/sandbox/executor.js +64 -0
  56. package/dist/core/sandbox/executor.js.map +1 -0
  57. package/dist/core/sdk.d.ts +2 -2
  58. package/dist/core/sdk.d.ts.map +1 -1
  59. package/dist/core/sdk.js +3 -3
  60. package/dist/core/sdk.js.map +1 -1
  61. package/dist/core/security/index.d.ts +3 -0
  62. package/dist/core/security/index.d.ts.map +1 -0
  63. package/dist/core/security/index.js +3 -0
  64. package/dist/core/security/index.js.map +1 -0
  65. package/dist/core/security/source-security.d.ts +43 -0
  66. package/dist/core/security/source-security.d.ts.map +1 -0
  67. package/dist/core/security/source-security.js +94 -0
  68. package/dist/core/security/source-security.js.map +1 -0
  69. package/dist/core/security/trust-ledger.d.ts +24 -0
  70. package/dist/core/security/trust-ledger.d.ts.map +1 -0
  71. package/dist/core/security/trust-ledger.js +66 -0
  72. package/dist/core/security/trust-ledger.js.map +1 -0
  73. package/dist/core/session-manager.d.ts.map +1 -1
  74. package/dist/core/session-manager.js +128 -15
  75. package/dist/core/session-manager.js.map +1 -1
  76. package/dist/core/settings-manager.d.ts +6 -0
  77. package/dist/core/settings-manager.d.ts.map +1 -1
  78. package/dist/core/settings-manager.js +36 -1
  79. package/dist/core/settings-manager.js.map +1 -1
  80. package/dist/core/settings.schema.json +71 -0
  81. package/dist/core/system-prompt.d.ts.map +1 -1
  82. package/dist/core/system-prompt.js +9 -2
  83. package/dist/core/system-prompt.js.map +1 -1
  84. package/dist/core/task-plan.d.ts +1 -0
  85. package/dist/core/task-plan.d.ts.map +1 -1
  86. package/dist/core/task-plan.js +103 -0
  87. package/dist/core/task-plan.js.map +1 -1
  88. package/dist/core/tools/apply-patch.d.ts +29 -0
  89. package/dist/core/tools/apply-patch.d.ts.map +1 -0
  90. package/dist/core/tools/apply-patch.js +167 -0
  91. package/dist/core/tools/apply-patch.js.map +1 -0
  92. package/dist/core/tools/bash.d.ts.map +1 -1
  93. package/dist/core/tools/bash.js +15 -1
  94. package/dist/core/tools/bash.js.map +1 -1
  95. package/dist/core/tools/git-common.d.ts.map +1 -1
  96. package/dist/core/tools/git-common.js +15 -1
  97. package/dist/core/tools/git-common.js.map +1 -1
  98. package/dist/core/tools/index.d.ts +20 -2
  99. package/dist/core/tools/index.d.ts.map +1 -1
  100. package/dist/core/tools/index.js +85 -25
  101. package/dist/core/tools/index.js.map +1 -1
  102. package/dist/core/tools/permissions.d.ts +16 -0
  103. package/dist/core/tools/permissions.d.ts.map +1 -1
  104. package/dist/core/tools/permissions.js +34 -1
  105. package/dist/core/tools/permissions.js.map +1 -1
  106. package/dist/core/tools/task.d.ts.map +1 -1
  107. package/dist/core/tools/task.js +68 -24
  108. package/dist/core/tools/task.js.map +1 -1
  109. package/dist/core/tools/tool-search.d.ts +24 -0
  110. package/dist/core/tools/tool-search.d.ts.map +1 -0
  111. package/dist/core/tools/tool-search.js +85 -0
  112. package/dist/core/tools/tool-search.js.map +1 -0
  113. package/dist/core/tools/tool-suggest.d.ts +18 -0
  114. package/dist/core/tools/tool-suggest.d.ts.map +1 -0
  115. package/dist/core/tools/tool-suggest.js +94 -0
  116. package/dist/core/tools/tool-suggest.js.map +1 -0
  117. package/dist/core/tools/verification-runner.d.ts.map +1 -1
  118. package/dist/core/tools/verification-runner.js +15 -1
  119. package/dist/core/tools/verification-runner.js.map +1 -1
  120. package/dist/core/unified-exec.d.ts +39 -0
  121. package/dist/core/unified-exec.d.ts.map +1 -0
  122. package/dist/core/unified-exec.js +286 -0
  123. package/dist/core/unified-exec.js.map +1 -0
  124. package/dist/main.d.ts.map +1 -1
  125. package/dist/main.js +93 -11
  126. package/dist/main.js.map +1 -1
  127. package/dist/modes/acp/acp-mode.d.ts +17 -0
  128. package/dist/modes/acp/acp-mode.d.ts.map +1 -0
  129. package/dist/modes/acp/acp-mode.js +352 -0
  130. package/dist/modes/acp/acp-mode.js.map +1 -0
  131. package/dist/modes/index.d.ts +2 -1
  132. package/dist/modes/index.d.ts.map +1 -1
  133. package/dist/modes/index.js +1 -0
  134. package/dist/modes/index.js.map +1 -1
  135. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  136. package/dist/modes/interactive/components/tool-execution.js +217 -0
  137. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  138. package/dist/modes/interactive/interactive-mode.d.ts +8 -0
  139. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  140. package/dist/modes/interactive/interactive-mode.js +159 -72
  141. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  142. package/dist/modes/rpc/rpc-client.d.ts +25 -1
  143. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  144. package/dist/modes/rpc/rpc-client.js +33 -0
  145. package/dist/modes/rpc/rpc-client.js.map +1 -1
  146. package/dist/modes/rpc/rpc-mode.d.ts +13 -1
  147. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  148. package/dist/modes/rpc/rpc-mode.js +189 -28
  149. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  150. package/dist/modes/rpc/rpc-types.d.ts +54 -1
  151. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  152. package/dist/modes/rpc/rpc-types.js.map +1 -1
  153. package/dist/utils/tools-manager.d.ts.map +1 -1
  154. package/dist/utils/tools-manager.js +96 -9
  155. package/dist/utils/tools-manager.js.map +1 -1
  156. package/docs/README.md +2 -0
  157. package/docs/acp-rpc-mapping.md +38 -0
  158. package/docs/configuration.generated.md +81 -0
  159. package/docs/configuration.md +7 -0
  160. package/package.json +6 -3
@@ -35,13 +35,15 @@ import { createAgentSession } from "../../core/sdk.js";
35
35
  import { createTeamRun, getTeamRun, listTeamRuns } from "../../core/agent-teams.js";
36
36
  import { buildSwarmPlanFromSingular, buildSwarmPlanFromTask, runSwarmScheduler, SwarmStateStore, } from "../../core/swarm/index.js";
37
37
  import { loadCustomSubagents, resolveCustomSubagentReference, } from "../../core/subagents.js";
38
+ import { evaluatePermissionWithPolicy } from "../../core/policy/index.js";
38
39
  import { getSubagentRun, listSubagentRuns } from "../../core/subagent-runs.js";
39
40
  import { getBackgroundProcess, listBackgroundProcesses, pruneBackgroundProcesses, readBackgroundProcessLogTail, stopBackgroundProcess, } from "../../core/background-processes.js";
40
41
  import { getSubagentBackgroundRun, listSubagentBackgroundRuns, pruneSubagentBackgroundRuns, readSubagentBackgroundRunLogTail, requestStopAllSubagentBackgroundRuns, requestStopSubagentBackgroundRun, } from "../../core/subagent-background-runs.js";
41
42
  import { SessionManager } from "../../core/session-manager.js";
42
43
  import { SettingsManager } from "../../core/settings-manager.js";
43
44
  import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
44
- import { isTaskPlanSnapshot, TASK_PLAN_CUSTOM_TYPE } from "../../core/task-plan.js";
45
+ import { getToolPermissionSignature } from "../../core/tools/index.js";
46
+ import { coerceTaskPlanSnapshot, TASK_PLAN_CUSTOM_TYPE, } from "../../core/task-plan.js";
45
47
  import { buildIosmAutomationPrompt, buildIosmAgentVerificationPrompt, buildIosmGuideAuthoringPrompt, buildIosmPriorityChecklist, createMetricSnapshot, extractAssistantText, evaluateIosmAutomationProgress, formatMetricSnapshot, hasReachedIosmTarget, getIosmGuidePath, initIosmWorkspace, inspectIosmCycle, listIosmCycles, loadIosmConfig, planIosmCycle, readIosmCycleReport, recordIosmCycleHistory, resolveIosmAutomationSettings, summarizeMetricDelta, normalizeIosmGuideMarkdown, writeIosmGuideDocument, } from "../../iosm/index.js";
46
48
  import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
47
49
  import { copyToClipboard } from "../../utils/clipboard.js";
@@ -414,9 +416,12 @@ async function promptMetaWithParallelismGuard(input) {
414
416
  };
415
417
  const unsubscribe = input.session.subscribe((event) => {
416
418
  if (event.type === "message_end" && event.message.role === "custom") {
417
- if (event.message.customType === TASK_PLAN_CUSTOM_TYPE && isTaskPlanSnapshot(event.message.details)) {
418
- taskPlanSnapshot = event.message.details;
419
- maybeScheduleCorrection();
419
+ if (event.message.customType === TASK_PLAN_CUSTOM_TYPE) {
420
+ const snapshot = coerceTaskPlanSnapshot(event.message.details);
421
+ if (snapshot) {
422
+ taskPlanSnapshot = snapshot;
423
+ maybeScheduleCorrection();
424
+ }
420
425
  }
421
426
  return;
422
427
  }
@@ -932,6 +937,7 @@ export class InteractiveMode {
932
937
  this.permissionAllowRules = [];
933
938
  this.permissionDenyRules = [];
934
939
  this.permissionPromptLock = Promise.resolve();
940
+ this.turnAllowedToolSignatures = new Set();
935
941
  this.sessionAllowedToolSignatures = new Set();
936
942
  this.singularLastEffectiveContract = {};
937
943
  this.swarmActiveRunId = undefined;
@@ -1046,6 +1052,7 @@ export class InteractiveMode {
1046
1052
  this.activeProfileName = profile.name;
1047
1053
  this.profilePromptSuffix = profile.systemPromptAppend || undefined;
1048
1054
  this.mcpRuntime = options.mcpRuntime;
1055
+ this.policyEngine = options.policyEngine;
1049
1056
  this.contractService = new ContractService({ cwd: this.sessionManager.getCwd() });
1050
1057
  this.singularService = new SingularService({ cwd: this.sessionManager.getCwd() });
1051
1058
  // Apply plan mode and profile badges immediately if set
@@ -1056,8 +1063,9 @@ export class InteractiveMode {
1056
1063
  this.session.setIosmAutopilotEnabled(this.activeProfileName === "iosm");
1057
1064
  this.syncRuntimePromptSuffix();
1058
1065
  this.permissionMode = this.settingsManager.getPermissionMode();
1059
- this.permissionAllowRules = this.settingsManager.getPermissionAllowRules();
1060
- this.permissionDenyRules = this.settingsManager.getPermissionDenyRules();
1066
+ this.policyEngine?.refresh();
1067
+ this.permissionAllowRules = this.policyEngine?.getLegacyRules("allow") ?? this.settingsManager.getPermissionAllowRules();
1068
+ this.permissionDenyRules = this.policyEngine?.getLegacyRules("deny") ?? this.settingsManager.getPermissionDenyRules();
1061
1069
  this.session.setToolPermissionHandler((request) => this.requestToolPermission(request));
1062
1070
  this.mcpRuntime?.setPermissionGuard((request) => this.requestToolPermission(request));
1063
1071
  // Load hide thinking block setting
@@ -1105,8 +1113,7 @@ export class InteractiveMode {
1105
1113
  return [...new Set(nextActiveTools)];
1106
1114
  }
1107
1115
  getToolPermissionSignature(request) {
1108
- const summary = request.summary.trim().replace(/\s+/g, " ");
1109
- return `${request.toolName}:${summary}`;
1116
+ return getToolPermissionSignature(request);
1110
1117
  }
1111
1118
  matchesPermissionRule(rule, request) {
1112
1119
  const [ruleToolRaw, ...rest] = rule.split(":");
@@ -1137,54 +1144,77 @@ export class InteractiveMode {
1137
1144
  : false;
1138
1145
  if (this.activeProfileName === "meta" &&
1139
1146
  !this.currentTurnSawTaskToolCall &&
1140
- (request.toolName === "bash" || request.toolName === "edit" || request.toolName === "write")) {
1147
+ (request.toolName === "bash" || request.toolName === "edit" || request.toolName === "write" || request.toolName === "apply_patch")) {
1141
1148
  this.showWarning(`META mode orchestration guard: direct ${request.toolName} is blocked before the first task call in a turn. Launch subagents via task or switch profile to full.`);
1142
1149
  return false;
1143
1150
  }
1144
1151
  if (isReadOnlyProfileName(this.activeProfileName) &&
1145
- (request.toolName === "bash" || request.toolName === "edit" || request.toolName === "write")) {
1152
+ (request.toolName === "bash" || request.toolName === "edit" || request.toolName === "write" || request.toolName === "apply_patch")) {
1146
1153
  this.showWarning(`Tool ${request.toolName} is disabled in ${this.activeProfileName} profile. Switch to full/meta/iosm for mutating operations.`);
1147
1154
  return false;
1148
1155
  }
1149
- for (const rule of this.permissionDenyRules) {
1150
- if (this.matchesPermissionRule(rule, request)) {
1151
- this.showWarning(`Denied by rule: ${rule}`);
1156
+ if (this.policyEngine) {
1157
+ this.policyEngine.refresh();
1158
+ const evaluated = evaluatePermissionWithPolicy(this.policyEngine, request, {
1159
+ profile: this.activeProfileName,
1160
+ runtimeMode: "interactive",
1161
+ permissionMode: this.permissionMode,
1162
+ strictExtensionToolEnforcement,
1163
+ });
1164
+ if (evaluated.outcome === "deny") {
1165
+ this.showWarning(evaluated.reason);
1152
1166
  return false;
1153
1167
  }
1154
- }
1155
- for (const rule of this.permissionAllowRules) {
1156
- if (this.matchesPermissionRule(rule, request)) {
1168
+ if (evaluated.outcome === "allow") {
1157
1169
  return true;
1158
1170
  }
1159
1171
  }
1160
- if (strictExtensionToolEnforcement && request.toolSource === "extension") {
1161
- if (this.permissionMode === "auto") {
1162
- if (request.requiredPermission === "read-only") {
1172
+ else {
1173
+ for (const rule of this.permissionDenyRules) {
1174
+ if (this.matchesPermissionRule(rule, request)) {
1175
+ this.showWarning(`Denied by rule: ${rule}`);
1176
+ return false;
1177
+ }
1178
+ }
1179
+ for (const rule of this.permissionAllowRules) {
1180
+ if (this.matchesPermissionRule(rule, request)) {
1163
1181
  return true;
1164
1182
  }
1165
- if (!request.requiredPermission) {
1166
- this.showWarning(`Extension tool ${request.toolName} is missing requiredPermission metadata. Add an allow rule or switch to ask/yolo mode.`);
1167
- return false;
1183
+ }
1184
+ if (strictExtensionToolEnforcement && request.toolSource === "extension") {
1185
+ if (this.permissionMode === "auto") {
1186
+ if (request.requiredPermission === "read-only") {
1187
+ return true;
1188
+ }
1189
+ if (!request.requiredPermission) {
1190
+ this.showWarning(`Extension tool ${request.toolName} is missing requiredPermission metadata. Add an allow rule or switch to ask/yolo mode.`);
1191
+ return false;
1192
+ }
1168
1193
  }
1169
1194
  }
1170
- }
1171
- if (this.permissionMode === "yolo") {
1172
- return true;
1173
- }
1174
- if (this.permissionMode === "auto") {
1175
- // Auto mode mirrors "accept edits": allow edit/write without prompts,
1176
- // still ask for shell execution.
1177
- if (request.toolName === "edit" || request.toolName === "write") {
1195
+ if (this.permissionMode === "yolo") {
1178
1196
  return true;
1179
1197
  }
1198
+ if (this.permissionMode === "auto") {
1199
+ // Auto mode mirrors "accept edits": allow edit/write without prompts,
1200
+ // still ask for shell execution.
1201
+ if (request.toolName === "edit" || request.toolName === "write" || request.toolName === "apply_patch") {
1202
+ return true;
1203
+ }
1204
+ }
1180
1205
  }
1181
1206
  const signature = this.getToolPermissionSignature(request);
1207
+ if (this.turnAllowedToolSignatures?.has(signature)) {
1208
+ return true;
1209
+ }
1182
1210
  if (this.sessionAllowedToolSignatures.has(signature)) {
1183
1211
  return true;
1184
1212
  }
1185
1213
  return this.withPermissionDialogLock(async () => {
1186
1214
  if (this.permissionMode === "yolo")
1187
1215
  return true;
1216
+ if (this.turnAllowedToolSignatures?.has(signature))
1217
+ return true;
1188
1218
  if (this.sessionAllowedToolSignatures.has(signature))
1189
1219
  return true;
1190
1220
  const tierLabel = request.requiredPermission ? ` [${request.requiredPermission}]` : "";
@@ -1192,6 +1222,7 @@ export class InteractiveMode {
1192
1222
  const label = `${request.toolName}${tierLabel}${sourceLabel}: ${request.summary}`;
1193
1223
  const choice = await this.showExtensionSelector("Permission required", [
1194
1224
  "Allow once",
1225
+ "Allow this turn",
1195
1226
  "Deny",
1196
1227
  "Always allow this command (session)",
1197
1228
  ]);
@@ -1199,6 +1230,12 @@ export class InteractiveMode {
1199
1230
  this.showWarning(`Permission denied: ${label}`);
1200
1231
  return false;
1201
1232
  }
1233
+ if (choice === "Allow this turn") {
1234
+ if (!this.turnAllowedToolSignatures) {
1235
+ this.turnAllowedToolSignatures = new Set();
1236
+ }
1237
+ this.turnAllowedToolSignatures.add(signature);
1238
+ }
1202
1239
  if (choice === "Always allow this command (session)") {
1203
1240
  this.sessionAllowedToolSignatures.add(signature);
1204
1241
  }
@@ -1251,6 +1288,59 @@ export class InteractiveMode {
1251
1288
  : "";
1252
1289
  return `Permissions: ${this.permissionMode}${this.permissionAllowRules.length > 0 ? ` · allow rules: ${this.permissionAllowRules.length}` : ""}${this.permissionDenyRules.length > 0 ? ` · deny rules: ${this.permissionDenyRules.length}` : ""}${hookSegment}`;
1253
1290
  }
1291
+ refreshPermissionRulesFromPolicy() {
1292
+ if (!this.policyEngine)
1293
+ return;
1294
+ this.policyEngine.refresh();
1295
+ this.permissionAllowRules = this.policyEngine.getLegacyRules("allow");
1296
+ this.permissionDenyRules = this.policyEngine.getLegacyRules("deny");
1297
+ }
1298
+ addPermissionRule(kind, rawRule) {
1299
+ const normalized = rawRule.trim();
1300
+ if (!normalized || !normalized.includes(":"))
1301
+ return false;
1302
+ if (this.policyEngine) {
1303
+ const added = this.policyEngine.addLegacyRule(kind, normalized);
1304
+ this.refreshPermissionRulesFromPolicy();
1305
+ return added;
1306
+ }
1307
+ if (kind === "allow") {
1308
+ if (this.permissionAllowRules.includes(normalized))
1309
+ return false;
1310
+ this.permissionAllowRules.push(normalized);
1311
+ this.settingsManager.setPermissionAllowRules(this.permissionAllowRules);
1312
+ return true;
1313
+ }
1314
+ if (this.permissionDenyRules.includes(normalized))
1315
+ return false;
1316
+ this.permissionDenyRules.push(normalized);
1317
+ this.settingsManager.setPermissionDenyRules(this.permissionDenyRules);
1318
+ return true;
1319
+ }
1320
+ removePermissionRule(kind, rawRule) {
1321
+ const normalized = rawRule.trim();
1322
+ if (!normalized)
1323
+ return false;
1324
+ if (this.policyEngine) {
1325
+ const removed = this.policyEngine.removeLegacyRule(kind, normalized);
1326
+ this.refreshPermissionRulesFromPolicy();
1327
+ return removed;
1328
+ }
1329
+ if (kind === "allow") {
1330
+ const next = this.permissionAllowRules.filter((rule) => rule !== normalized);
1331
+ if (next.length === this.permissionAllowRules.length)
1332
+ return false;
1333
+ this.permissionAllowRules = next;
1334
+ this.settingsManager.setPermissionAllowRules(this.permissionAllowRules);
1335
+ return true;
1336
+ }
1337
+ const next = this.permissionDenyRules.filter((rule) => rule !== normalized);
1338
+ if (next.length === this.permissionDenyRules.length)
1339
+ return false;
1340
+ this.permissionDenyRules = next;
1341
+ this.settingsManager.setPermissionDenyRules(this.permissionDenyRules);
1342
+ return true;
1343
+ }
1254
1344
  async runPermissionRulesMenu(kind) {
1255
1345
  while (true) {
1256
1346
  const isAllow = kind === "allow";
@@ -1281,18 +1371,7 @@ export class InteractiveMode {
1281
1371
  this.showWarning(`Invalid rule "${rawRule || "(empty)"}". Expected <tool:match>.`);
1282
1372
  continue;
1283
1373
  }
1284
- if (isAllow) {
1285
- if (!this.permissionAllowRules.includes(rawRule)) {
1286
- this.permissionAllowRules.push(rawRule);
1287
- this.settingsManager.setPermissionAllowRules(this.permissionAllowRules);
1288
- }
1289
- }
1290
- else {
1291
- if (!this.permissionDenyRules.includes(rawRule)) {
1292
- this.permissionDenyRules.push(rawRule);
1293
- this.settingsManager.setPermissionDenyRules(this.permissionDenyRules);
1294
- }
1295
- }
1374
+ this.addPermissionRule(isAllow ? "allow" : "deny", rawRule);
1296
1375
  this.showStatus(`Added ${kind} rule: ${rawRule}`);
1297
1376
  continue;
1298
1377
  }
@@ -1304,14 +1383,7 @@ export class InteractiveMode {
1304
1383
  const pickedRule = await this.showExtensionSelector(`/permissions ${kind}: remove rule`, rules.map((rule) => rule));
1305
1384
  if (!pickedRule)
1306
1385
  continue;
1307
- if (isAllow) {
1308
- this.permissionAllowRules = this.permissionAllowRules.filter((rule) => rule !== pickedRule);
1309
- this.settingsManager.setPermissionAllowRules(this.permissionAllowRules);
1310
- }
1311
- else {
1312
- this.permissionDenyRules = this.permissionDenyRules.filter((rule) => rule !== pickedRule);
1313
- this.settingsManager.setPermissionDenyRules(this.permissionDenyRules);
1314
- }
1386
+ this.removePermissionRule(isAllow ? "allow" : "deny", pickedRule);
1315
1387
  this.showStatus(`Removed ${kind} rule: ${pickedRule}`);
1316
1388
  }
1317
1389
  }
@@ -1380,6 +1452,7 @@ export class InteractiveMode {
1380
1452
  }
1381
1453
  }
1382
1454
  async handlePermissionsCommand(text) {
1455
+ this.refreshPermissionRulesFromPolicy();
1383
1456
  const args = this.parseSlashArgs(text).slice(1);
1384
1457
  const value = args[0]?.toLowerCase();
1385
1458
  if (!value) {
@@ -1426,10 +1499,7 @@ export class InteractiveMode {
1426
1499
  this.showWarning("Usage: /permissions allow add <tool:match>");
1427
1500
  return;
1428
1501
  }
1429
- if (!this.permissionAllowRules.includes(rawRule)) {
1430
- this.permissionAllowRules.push(rawRule);
1431
- this.settingsManager.setPermissionAllowRules(this.permissionAllowRules);
1432
- }
1502
+ this.addPermissionRule("allow", rawRule);
1433
1503
  this.showStatus(`Added allow rule: ${rawRule}`);
1434
1504
  return;
1435
1505
  }
@@ -1439,8 +1509,7 @@ export class InteractiveMode {
1439
1509
  this.showWarning("Usage: /permissions allow remove <tool:match>");
1440
1510
  return;
1441
1511
  }
1442
- this.permissionAllowRules = this.permissionAllowRules.filter((r) => r !== rawRule);
1443
- this.settingsManager.setPermissionAllowRules(this.permissionAllowRules);
1512
+ this.removePermissionRule("allow", rawRule);
1444
1513
  this.showStatus(`Removed allow rule: ${rawRule}`);
1445
1514
  return;
1446
1515
  }
@@ -1463,10 +1532,7 @@ export class InteractiveMode {
1463
1532
  this.showWarning("Usage: /permissions deny add <tool:match>");
1464
1533
  return;
1465
1534
  }
1466
- if (!this.permissionDenyRules.includes(rawRule)) {
1467
- this.permissionDenyRules.push(rawRule);
1468
- this.settingsManager.setPermissionDenyRules(this.permissionDenyRules);
1469
- }
1535
+ this.addPermissionRule("deny", rawRule);
1470
1536
  this.showStatus(`Added deny rule: ${rawRule}`);
1471
1537
  return;
1472
1538
  }
@@ -1476,8 +1542,7 @@ export class InteractiveMode {
1476
1542
  this.showWarning("Usage: /permissions deny remove <tool:match>");
1477
1543
  return;
1478
1544
  }
1479
- this.permissionDenyRules = this.permissionDenyRules.filter((r) => r !== rawRule);
1480
- this.settingsManager.setPermissionDenyRules(this.permissionDenyRules);
1545
+ this.removePermissionRule("deny", rawRule);
1481
1546
  this.showStatus(`Removed deny rule: ${rawRule}`);
1482
1547
  return;
1483
1548
  }
@@ -3822,6 +3887,7 @@ export class InteractiveMode {
3822
3887
  case "agent_start":
3823
3888
  this.currentTurnSawAssistantMessage = false;
3824
3889
  this.currentTurnSawTaskToolCall = false;
3890
+ this.turnAllowedToolSignatures.clear();
3825
3891
  // Restore main escape handler if retry handler is still active
3826
3892
  // (retry success event fires later, but we need main handler now)
3827
3893
  if (this.retryEscapeHandler) {
@@ -4383,17 +4449,19 @@ export class InteractiveMode {
4383
4449
  case "custom": {
4384
4450
  this.handleInternalUiMetaMessage(message);
4385
4451
  if (message.display) {
4386
- if (message.customType === TASK_PLAN_CUSTOM_TYPE && isTaskPlanSnapshot(message.details)) {
4387
- const component = new TaskPlanMessageComponent(message.details);
4388
- component.setExpanded(this.toolOutputExpanded);
4389
- this.chatContainer.addChild(component);
4390
- }
4391
- else {
4392
- const renderer = this.session.extensionRunner?.getMessageRenderer(message.customType);
4393
- const component = new CustomMessageComponent(message, renderer, this.getMarkdownThemeWithSettings());
4394
- component.setExpanded(this.toolOutputExpanded);
4395
- this.chatContainer.addChild(component);
4452
+ if (message.customType === TASK_PLAN_CUSTOM_TYPE) {
4453
+ const snapshot = coerceTaskPlanSnapshot(message.details);
4454
+ if (snapshot) {
4455
+ const component = new TaskPlanMessageComponent(snapshot);
4456
+ component.setExpanded(this.toolOutputExpanded);
4457
+ this.chatContainer.addChild(component);
4458
+ break;
4459
+ }
4396
4460
  }
4461
+ const renderer = this.session.extensionRunner?.getMessageRenderer(message.customType);
4462
+ const component = new CustomMessageComponent(message, renderer, this.getMarkdownThemeWithSettings());
4463
+ component.setExpanded(this.toolOutputExpanded);
4464
+ this.chatContainer.addChild(component);
4397
4465
  }
4398
4466
  break;
4399
4467
  }
@@ -13182,6 +13250,25 @@ export class InteractiveMode {
13182
13250
  cwd,
13183
13251
  agentDir: getAgentDir(),
13184
13252
  settingsManager: this.settingsManager,
13253
+ allowedSourceHosts: this.policyEngine?.getAllowedSourceHosts(),
13254
+ trustConsentProvider: async (request) => {
13255
+ const details = [
13256
+ `Source: ${request.source}`,
13257
+ `Host: ${request.host}`,
13258
+ `Identity: ${request.identity}`,
13259
+ `Fingerprint: ${request.fingerprint}`,
13260
+ ];
13261
+ if (request.previousFingerprint) {
13262
+ details.push(`Previous fingerprint: ${request.previousFingerprint}`);
13263
+ }
13264
+ const answer = await this.showExtensionSelector(`Trust ${request.sourceType} ${request.action}?`, ["Trust and continue", "Deny"]);
13265
+ if (answer !== "Trust and continue") {
13266
+ this.showWarning(`Blocked untrusted source: ${request.source}`);
13267
+ return false;
13268
+ }
13269
+ this.showCommandTextBlock("Trust Decision", details.join("\n"));
13270
+ return true;
13271
+ },
13185
13272
  });
13186
13273
  }
13187
13274
  normalizeExtensionOverrideTarget(target) {