Merge upstream/dev into fix/auto-updater-paths
This commit is contained in:
@@ -213,8 +213,8 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
npm publish --access public --provenance $TAG_ARG
|
npm publish --access public --provenance $TAG_ARG
|
||||||
env:
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
|
||||||
NPM_CONFIG_PROVENANCE: true
|
NPM_CONFIG_PROVENANCE: true
|
||||||
|
|
||||||
- name: Publish oh-my-openagent
|
- name: Publish oh-my-openagent
|
||||||
if: steps.check.outputs.skip != 'true'
|
if: steps.check.outputs.skip != 'true'
|
||||||
run: |
|
run: |
|
||||||
@@ -242,8 +242,8 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
npm publish --access public --provenance $TAG_ARG || echo "oh-my-openagent publish may have failed (package may already exist)"
|
npm publish --access public --provenance $TAG_ARG || echo "oh-my-openagent publish may have failed (package may already exist)"
|
||||||
env:
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
|
||||||
NPM_CONFIG_PROVENANCE: true
|
NPM_CONFIG_PROVENANCE: true
|
||||||
|
|
||||||
- name: Restore package.json
|
- name: Restore package.json
|
||||||
if: steps.check.outputs.skip != 'true'
|
if: steps.check.outputs.skip != 'true'
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -2015,6 +2015,14 @@
|
|||||||
"created_at": "2026-03-07T13:53:56Z",
|
"created_at": "2026-03-07T13:53:56Z",
|
||||||
"repoId": 1108837393,
|
"repoId": 1108837393,
|
||||||
"pullRequestNo": 2360
|
"pullRequestNo": 2360
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "crazyrabbit0",
|
||||||
|
"id": 5244848,
|
||||||
|
"comment_id": 3936744393,
|
||||||
|
"created_at": "2026-02-20T19:40:05Z",
|
||||||
|
"repoId": 1108837393,
|
||||||
|
"pullRequestNo": 2012
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as fs from "node:fs"
|
import * as fs from "node:fs"
|
||||||
import * as path from "node:path"
|
import * as path from "node:path"
|
||||||
import { CACHE_DIR, PACKAGE_NAME } from "./constants"
|
import { CACHE_DIR, PACKAGE_NAME, USER_CONFIG_DIR } from "./constants"
|
||||||
import { log } from "../../shared/logger"
|
import { log } from "../../shared/logger"
|
||||||
|
|
||||||
interface BunLockfile {
|
interface BunLockfile {
|
||||||
@@ -23,7 +23,7 @@ function removeFromBunLock(packageName: string): boolean {
|
|||||||
try {
|
try {
|
||||||
const content = fs.readFileSync(lockPath, "utf-8")
|
const content = fs.readFileSync(lockPath, "utf-8")
|
||||||
const lock = JSON.parse(stripTrailingCommas(content)) as BunLockfile
|
const lock = JSON.parse(stripTrailingCommas(content)) as BunLockfile
|
||||||
let modified = false
|
let modified = false
|
||||||
|
|
||||||
if (lock.packages?.[packageName]) {
|
if (lock.packages?.[packageName]) {
|
||||||
delete lock.packages[packageName]
|
delete lock.packages[packageName]
|
||||||
@@ -43,15 +43,20 @@ function removeFromBunLock(packageName: string): boolean {
|
|||||||
|
|
||||||
export function invalidatePackage(packageName: string = PACKAGE_NAME): boolean {
|
export function invalidatePackage(packageName: string = PACKAGE_NAME): boolean {
|
||||||
try {
|
try {
|
||||||
const pkgDir = path.join(CACHE_DIR, "node_modules", packageName)
|
const pkgDirs = [
|
||||||
|
path.join(USER_CONFIG_DIR, "node_modules", packageName),
|
||||||
|
path.join(CACHE_DIR, "node_modules", packageName),
|
||||||
|
]
|
||||||
|
|
||||||
let packageRemoved = false
|
let packageRemoved = false
|
||||||
let lockRemoved = false
|
let lockRemoved = false
|
||||||
|
|
||||||
if (fs.existsSync(pkgDir)) {
|
for (const pkgDir of pkgDirs) {
|
||||||
fs.rmSync(pkgDir, { recursive: true, force: true })
|
if (fs.existsSync(pkgDir)) {
|
||||||
log(`[auto-update-checker] Package removed: ${pkgDir}`)
|
fs.rmSync(pkgDir, { recursive: true, force: true })
|
||||||
packageRemoved = true
|
log(`[auto-update-checker] Package removed: ${pkgDir}`)
|
||||||
|
packageRemoved = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lockRemoved = removeFromBunLock(packageName)
|
lockRemoved = removeFromBunLock(packageName)
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ type SessionNotificationConfig = {
|
|||||||
idleConfirmationDelay: number
|
idleConfirmationDelay: number
|
||||||
skipIfIncompleteTodos: boolean
|
skipIfIncompleteTodos: boolean
|
||||||
maxTrackedSessions: number
|
maxTrackedSessions: number
|
||||||
|
/** Grace period in ms to ignore late-arriving activity events after scheduling (default: 100) */
|
||||||
|
activityGracePeriodMs?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createIdleNotificationScheduler(options: {
|
export function createIdleNotificationScheduler(options: {
|
||||||
@@ -24,6 +26,9 @@ export function createIdleNotificationScheduler(options: {
|
|||||||
const sessionActivitySinceIdle = new Set<string>()
|
const sessionActivitySinceIdle = new Set<string>()
|
||||||
const notificationVersions = new Map<string, number>()
|
const notificationVersions = new Map<string, number>()
|
||||||
const executingNotifications = new Set<string>()
|
const executingNotifications = new Set<string>()
|
||||||
|
const scheduledAt = new Map<string, number>()
|
||||||
|
|
||||||
|
const activityGracePeriodMs = options.config.activityGracePeriodMs ?? 100
|
||||||
|
|
||||||
function cleanupOldSessions(): void {
|
function cleanupOldSessions(): void {
|
||||||
const maxSessions = options.config.maxTrackedSessions
|
const maxSessions = options.config.maxTrackedSessions
|
||||||
@@ -43,6 +48,10 @@ export function createIdleNotificationScheduler(options: {
|
|||||||
const sessionsToRemove = Array.from(executingNotifications).slice(0, executingNotifications.size - maxSessions)
|
const sessionsToRemove = Array.from(executingNotifications).slice(0, executingNotifications.size - maxSessions)
|
||||||
sessionsToRemove.forEach((id) => executingNotifications.delete(id))
|
sessionsToRemove.forEach((id) => executingNotifications.delete(id))
|
||||||
}
|
}
|
||||||
|
if (scheduledAt.size > maxSessions) {
|
||||||
|
const sessionsToRemove = Array.from(scheduledAt.keys()).slice(0, scheduledAt.size - maxSessions)
|
||||||
|
sessionsToRemove.forEach((id) => scheduledAt.delete(id))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancelPendingNotification(sessionID: string): void {
|
function cancelPendingNotification(sessionID: string): void {
|
||||||
@@ -51,11 +60,17 @@ export function createIdleNotificationScheduler(options: {
|
|||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
pendingTimers.delete(sessionID)
|
pendingTimers.delete(sessionID)
|
||||||
}
|
}
|
||||||
|
scheduledAt.delete(sessionID)
|
||||||
sessionActivitySinceIdle.add(sessionID)
|
sessionActivitySinceIdle.add(sessionID)
|
||||||
notificationVersions.set(sessionID, (notificationVersions.get(sessionID) ?? 0) + 1)
|
notificationVersions.set(sessionID, (notificationVersions.get(sessionID) ?? 0) + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
function markSessionActivity(sessionID: string): void {
|
function markSessionActivity(sessionID: string): void {
|
||||||
|
const scheduledTime = scheduledAt.get(sessionID)
|
||||||
|
if (scheduledTime && Date.now() - scheduledTime < activityGracePeriodMs) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
cancelPendingNotification(sessionID)
|
cancelPendingNotification(sessionID)
|
||||||
if (!executingNotifications.has(sessionID)) {
|
if (!executingNotifications.has(sessionID)) {
|
||||||
notifiedSessions.delete(sessionID)
|
notifiedSessions.delete(sessionID)
|
||||||
@@ -65,22 +80,26 @@ export function createIdleNotificationScheduler(options: {
|
|||||||
async function executeNotification(sessionID: string, version: number): Promise<void> {
|
async function executeNotification(sessionID: string, version: number): Promise<void> {
|
||||||
if (executingNotifications.has(sessionID)) {
|
if (executingNotifications.has(sessionID)) {
|
||||||
pendingTimers.delete(sessionID)
|
pendingTimers.delete(sessionID)
|
||||||
|
scheduledAt.delete(sessionID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notificationVersions.get(sessionID) !== version) {
|
if (notificationVersions.get(sessionID) !== version) {
|
||||||
pendingTimers.delete(sessionID)
|
pendingTimers.delete(sessionID)
|
||||||
|
scheduledAt.delete(sessionID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sessionActivitySinceIdle.has(sessionID)) {
|
if (sessionActivitySinceIdle.has(sessionID)) {
|
||||||
sessionActivitySinceIdle.delete(sessionID)
|
sessionActivitySinceIdle.delete(sessionID)
|
||||||
pendingTimers.delete(sessionID)
|
pendingTimers.delete(sessionID)
|
||||||
|
scheduledAt.delete(sessionID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notifiedSessions.has(sessionID)) {
|
if (notifiedSessions.has(sessionID)) {
|
||||||
pendingTimers.delete(sessionID)
|
pendingTimers.delete(sessionID)
|
||||||
|
scheduledAt.delete(sessionID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +132,7 @@ export function createIdleNotificationScheduler(options: {
|
|||||||
} finally {
|
} finally {
|
||||||
executingNotifications.delete(sessionID)
|
executingNotifications.delete(sessionID)
|
||||||
pendingTimers.delete(sessionID)
|
pendingTimers.delete(sessionID)
|
||||||
|
scheduledAt.delete(sessionID)
|
||||||
if (sessionActivitySinceIdle.has(sessionID)) {
|
if (sessionActivitySinceIdle.has(sessionID)) {
|
||||||
notifiedSessions.delete(sessionID)
|
notifiedSessions.delete(sessionID)
|
||||||
sessionActivitySinceIdle.delete(sessionID)
|
sessionActivitySinceIdle.delete(sessionID)
|
||||||
@@ -126,6 +146,7 @@ export function createIdleNotificationScheduler(options: {
|
|||||||
if (executingNotifications.has(sessionID)) return
|
if (executingNotifications.has(sessionID)) return
|
||||||
|
|
||||||
sessionActivitySinceIdle.delete(sessionID)
|
sessionActivitySinceIdle.delete(sessionID)
|
||||||
|
scheduledAt.set(sessionID, Date.now())
|
||||||
|
|
||||||
const currentVersion = (notificationVersions.get(sessionID) ?? 0) + 1
|
const currentVersion = (notificationVersions.get(sessionID) ?? 0) + 1
|
||||||
notificationVersions.set(sessionID, currentVersion)
|
notificationVersions.set(sessionID, currentVersion)
|
||||||
@@ -144,6 +165,7 @@ export function createIdleNotificationScheduler(options: {
|
|||||||
sessionActivitySinceIdle.delete(sessionID)
|
sessionActivitySinceIdle.delete(sessionID)
|
||||||
notificationVersions.delete(sessionID)
|
notificationVersions.delete(sessionID)
|
||||||
executingNotifications.delete(sessionID)
|
executingNotifications.delete(sessionID)
|
||||||
|
scheduledAt.delete(sessionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -195,8 +195,9 @@ describe("session-notification", () => {
|
|||||||
setMainSession(mainSessionID)
|
setMainSession(mainSessionID)
|
||||||
|
|
||||||
const hook = createSessionNotification(createMockPluginInput(), {
|
const hook = createSessionNotification(createMockPluginInput(), {
|
||||||
idleConfirmationDelay: 100, // Long delay
|
idleConfirmationDelay: 100,
|
||||||
skipIfIncompleteTodos: false,
|
skipIfIncompleteTodos: false,
|
||||||
|
activityGracePeriodMs: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
// when - session goes idle
|
// when - session goes idle
|
||||||
@@ -272,6 +273,7 @@ describe("session-notification", () => {
|
|||||||
const hook = createSessionNotification(createMockPluginInput(), {
|
const hook = createSessionNotification(createMockPluginInput(), {
|
||||||
idleConfirmationDelay: 50,
|
idleConfirmationDelay: 50,
|
||||||
skipIfIncompleteTodos: false,
|
skipIfIncompleteTodos: false,
|
||||||
|
activityGracePeriodMs: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
// when - session goes idle, then message.updated fires
|
// when - session goes idle, then message.updated fires
|
||||||
@@ -306,6 +308,7 @@ describe("session-notification", () => {
|
|||||||
const hook = createSessionNotification(createMockPluginInput(), {
|
const hook = createSessionNotification(createMockPluginInput(), {
|
||||||
idleConfirmationDelay: 50,
|
idleConfirmationDelay: 50,
|
||||||
skipIfIncompleteTodos: false,
|
skipIfIncompleteTodos: false,
|
||||||
|
activityGracePeriodMs: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
// when - session goes idle, then tool.execute.before fires
|
// when - session goes idle, then tool.execute.before fires
|
||||||
@@ -509,4 +512,75 @@ describe("session-notification", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("should ignore activity events within grace period", async () => {
|
||||||
|
// given - main session is set
|
||||||
|
const mainSessionID = "main-grace"
|
||||||
|
setMainSession(mainSessionID)
|
||||||
|
|
||||||
|
const hook = createSessionNotification(createMockPluginInput(), {
|
||||||
|
idleConfirmationDelay: 50,
|
||||||
|
skipIfIncompleteTodos: false,
|
||||||
|
activityGracePeriodMs: 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
// when - session goes idle
|
||||||
|
await hook({
|
||||||
|
event: {
|
||||||
|
type: "session.idle",
|
||||||
|
properties: { sessionID: mainSessionID },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// when - activity happens immediately (within grace period)
|
||||||
|
await hook({
|
||||||
|
event: {
|
||||||
|
type: "tool.execute.before",
|
||||||
|
properties: { sessionID: mainSessionID },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Wait for idle delay to pass
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||||
|
|
||||||
|
// then - notification SHOULD be sent (activity was within grace period, ignored)
|
||||||
|
expect(notificationCalls.length).toBeGreaterThanOrEqual(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("should cancel notification for activity after grace period", async () => {
|
||||||
|
// given - main session is set
|
||||||
|
const mainSessionID = "main-grace-cancel"
|
||||||
|
setMainSession(mainSessionID)
|
||||||
|
|
||||||
|
const hook = createSessionNotification(createMockPluginInput(), {
|
||||||
|
idleConfirmationDelay: 200,
|
||||||
|
skipIfIncompleteTodos: false,
|
||||||
|
activityGracePeriodMs: 50,
|
||||||
|
})
|
||||||
|
|
||||||
|
// when - session goes idle
|
||||||
|
await hook({
|
||||||
|
event: {
|
||||||
|
type: "session.idle",
|
||||||
|
properties: { sessionID: mainSessionID },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// when - wait for grace period to pass
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 60))
|
||||||
|
|
||||||
|
// when - activity happens after grace period
|
||||||
|
await hook({
|
||||||
|
event: {
|
||||||
|
type: "tool.execute.before",
|
||||||
|
properties: { sessionID: mainSessionID },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Wait for original delay to pass
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 200))
|
||||||
|
|
||||||
|
// then - notification should NOT be sent (activity cancelled it after grace period)
|
||||||
|
expect(notificationCalls).toHaveLength(0)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ interface SessionNotificationConfig {
|
|||||||
/** Maximum number of sessions to track before cleanup (default: 100) */
|
/** Maximum number of sessions to track before cleanup (default: 100) */
|
||||||
maxTrackedSessions?: number
|
maxTrackedSessions?: number
|
||||||
enforceMainSessionFilter?: boolean
|
enforceMainSessionFilter?: boolean
|
||||||
|
/** Grace period in ms to ignore late-arriving activity events after scheduling (default: 100) */
|
||||||
|
activityGracePeriodMs?: number
|
||||||
}
|
}
|
||||||
export function createSessionNotification(
|
export function createSessionNotification(
|
||||||
ctx: PluginInput,
|
ctx: PluginInput,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { describe, it, expect } from "bun:test"
|
import { describe, it, expect, beforeEach, afterEach } from "bun:test"
|
||||||
import { applyToolConfig } from "./tool-config-handler"
|
import { applyToolConfig } from "./tool-config-handler"
|
||||||
import type { OhMyOpenCodeConfig } from "../config"
|
import type { OhMyOpenCodeConfig } from "../config"
|
||||||
|
|
||||||
@@ -56,6 +56,109 @@ describe("applyToolConfig", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("#given OPENCODE_CONFIG_CONTENT has question set to deny", () => {
|
||||||
|
let originalConfigContent: string | undefined
|
||||||
|
let originalCliRunMode: string | undefined
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
originalConfigContent = process.env.OPENCODE_CONFIG_CONTENT
|
||||||
|
originalCliRunMode = process.env.OPENCODE_CLI_RUN_MODE
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (originalConfigContent === undefined) {
|
||||||
|
delete process.env.OPENCODE_CONFIG_CONTENT
|
||||||
|
} else {
|
||||||
|
process.env.OPENCODE_CONFIG_CONTENT = originalConfigContent
|
||||||
|
}
|
||||||
|
if (originalCliRunMode === undefined) {
|
||||||
|
delete process.env.OPENCODE_CLI_RUN_MODE
|
||||||
|
} else {
|
||||||
|
process.env.OPENCODE_CLI_RUN_MODE = originalCliRunMode
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#when config explicitly denies question permission", () => {
|
||||||
|
it.each(["sisyphus", "hephaestus", "prometheus"])(
|
||||||
|
"#then should deny question for %s even without CLI_RUN_MODE",
|
||||||
|
(agentName) => {
|
||||||
|
process.env.OPENCODE_CONFIG_CONTENT = JSON.stringify({
|
||||||
|
permission: { question: "deny" },
|
||||||
|
})
|
||||||
|
delete process.env.OPENCODE_CLI_RUN_MODE
|
||||||
|
const params = createParams({ agents: [agentName] })
|
||||||
|
|
||||||
|
applyToolConfig(params)
|
||||||
|
|
||||||
|
const agent = params.agentResult[agentName] as {
|
||||||
|
permission: Record<string, unknown>
|
||||||
|
}
|
||||||
|
expect(agent.permission.question).toBe("deny")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#when config does not deny question permission", () => {
|
||||||
|
it.each(["sisyphus", "hephaestus", "prometheus"])(
|
||||||
|
"#then should allow question for %s in interactive mode",
|
||||||
|
(agentName) => {
|
||||||
|
process.env.OPENCODE_CONFIG_CONTENT = JSON.stringify({
|
||||||
|
permission: { question: "allow" },
|
||||||
|
})
|
||||||
|
delete process.env.OPENCODE_CLI_RUN_MODE
|
||||||
|
const params = createParams({ agents: [agentName] })
|
||||||
|
|
||||||
|
applyToolConfig(params)
|
||||||
|
|
||||||
|
const agent = params.agentResult[agentName] as {
|
||||||
|
permission: Record<string, unknown>
|
||||||
|
}
|
||||||
|
expect(agent.permission.question).toBe("allow")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#when CLI_RUN_MODE is true and config does not deny", () => {
|
||||||
|
it.each(["sisyphus", "hephaestus", "prometheus"])(
|
||||||
|
"#then should deny question for %s via CLI_RUN_MODE",
|
||||||
|
(agentName) => {
|
||||||
|
process.env.OPENCODE_CONFIG_CONTENT = JSON.stringify({
|
||||||
|
permission: {},
|
||||||
|
})
|
||||||
|
process.env.OPENCODE_CLI_RUN_MODE = "true"
|
||||||
|
const params = createParams({ agents: [agentName] })
|
||||||
|
|
||||||
|
applyToolConfig(params)
|
||||||
|
|
||||||
|
const agent = params.agentResult[agentName] as {
|
||||||
|
permission: Record<string, unknown>
|
||||||
|
}
|
||||||
|
expect(agent.permission.question).toBe("deny")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("#when config deny overrides CLI_RUN_MODE allow", () => {
|
||||||
|
it.each(["sisyphus", "hephaestus", "prometheus"])(
|
||||||
|
"#then should deny question for %s when config says deny regardless of CLI_RUN_MODE",
|
||||||
|
(agentName) => {
|
||||||
|
process.env.OPENCODE_CONFIG_CONTENT = JSON.stringify({
|
||||||
|
permission: { question: "deny" },
|
||||||
|
})
|
||||||
|
process.env.OPENCODE_CLI_RUN_MODE = "false"
|
||||||
|
const params = createParams({ agents: [agentName] })
|
||||||
|
|
||||||
|
applyToolConfig(params)
|
||||||
|
|
||||||
|
const agent = params.agentResult[agentName] as {
|
||||||
|
permission: Record<string, unknown>
|
||||||
|
}
|
||||||
|
expect(agent.permission.question).toBe("deny")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("#given task_system is disabled", () => {
|
describe("#given task_system is disabled", () => {
|
||||||
describe("#when applying tool config", () => {
|
describe("#when applying tool config", () => {
|
||||||
it.each([
|
it.each([
|
||||||
|
|||||||
@@ -3,6 +3,17 @@ import { getAgentDisplayName } from "../shared/agent-display-names";
|
|||||||
|
|
||||||
type AgentWithPermission = { permission?: Record<string, unknown> };
|
type AgentWithPermission = { permission?: Record<string, unknown> };
|
||||||
|
|
||||||
|
function getConfigQuestionPermission(): string | null {
|
||||||
|
const configContent = process.env.OPENCODE_CONFIG_CONTENT;
|
||||||
|
if (!configContent) return null;
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(configContent);
|
||||||
|
return parsed?.permission?.question ?? null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function agentByKey(agentResult: Record<string, unknown>, key: string): AgentWithPermission | undefined {
|
function agentByKey(agentResult: Record<string, unknown>, key: string): AgentWithPermission | undefined {
|
||||||
return (agentResult[key] ?? agentResult[getAgentDisplayName(key)]) as
|
return (agentResult[key] ?? agentResult[getAgentDisplayName(key)]) as
|
||||||
| AgentWithPermission
|
| AgentWithPermission
|
||||||
@@ -32,7 +43,11 @@ export function applyToolConfig(params: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isCliRunMode = process.env.OPENCODE_CLI_RUN_MODE === "true";
|
const isCliRunMode = process.env.OPENCODE_CLI_RUN_MODE === "true";
|
||||||
const questionPermission = isCliRunMode ? "deny" : "allow";
|
const configQuestionPermission = getConfigQuestionPermission();
|
||||||
|
const questionPermission =
|
||||||
|
configQuestionPermission === "deny" ? "deny" :
|
||||||
|
isCliRunMode ? "deny" :
|
||||||
|
"allow";
|
||||||
|
|
||||||
const librarian = agentByKey(params.agentResult, "librarian");
|
const librarian = agentByKey(params.agentResult, "librarian");
|
||||||
if (librarian) {
|
if (librarian) {
|
||||||
|
|||||||
Reference in New Issue
Block a user