kira-glasses-bridge 2.5.0 → 2.6.0
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/package.json +1 -1
- package/src/agents/claude.mjs +13 -1
- package/src/index.mjs +7 -3
package/package.json
CHANGED
package/src/agents/claude.mjs
CHANGED
|
@@ -133,7 +133,7 @@ function applyAnswer(name, input, request, answer, suggestions) {
|
|
|
133
133
|
/** "Always allow" answers, kept for the rest of the process per session: sessionId -> Set of rules. */
|
|
134
134
|
const sessionRules = new Map()
|
|
135
135
|
|
|
136
|
-
export async function run({ prompt, sessionId, cwd, policy }, io) {
|
|
136
|
+
export async function run({ prompt, sessionId, cwd, policy, persona }, io) {
|
|
137
137
|
let session = sessionId ?? null
|
|
138
138
|
let text = ''
|
|
139
139
|
let error = null
|
|
@@ -143,8 +143,17 @@ export async function run({ prompt, sessionId, cwd, policy }, io) {
|
|
|
143
143
|
// Pre-tool hook: the policy is authoritative. It forces the prompt for what
|
|
144
144
|
// it does not allow (so a permissive local config cannot bypass the person)
|
|
145
145
|
// and refuses outright what it denies.
|
|
146
|
+
// A member with a restricted shell (a designer, a researcher) runs only what its preset allows.
|
|
147
|
+
const shellAllow = persona?.shellAllow ? new RegExp(persona.shellAllow) : null
|
|
146
148
|
const preToolUse = async input => {
|
|
147
149
|
if (input.hook_event_name !== 'PreToolUse') return {}
|
|
150
|
+
// Every command in a chain ("ls; rm -rf x", "a && b", "a | b") has to be allowed, not only the first.
|
|
151
|
+
const segments = c => String(c ?? '').split(/&&|\|\||;|\|/).map(x => x.trim()).filter(Boolean)
|
|
152
|
+
if (shellAllow && input.tool_name === 'Bash' && !segments(input.tool_input?.command).every(seg => shellAllow.test(seg))) {
|
|
153
|
+
const reason = `${persona.name} uses the shell only for git and for reading files; run that another way or leave it.`
|
|
154
|
+
io.emit({ kind: 'request_resolved', id: `persona-${Date.now().toString(36)}`, answer: { optionIds: ['deny'] }, by: 'policy', tool: 'Bash', reason })
|
|
155
|
+
return { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: reason } }
|
|
156
|
+
}
|
|
148
157
|
const { decision, reason } = judge(input.tool_name, input.tool_input)
|
|
149
158
|
if (decision === 'deny') io.emit({ kind: 'request_resolved', id: `policy-${Date.now().toString(36)}`, answer: { optionIds: ['deny'] }, by: 'policy', tool: input.tool_name, reason })
|
|
150
159
|
return { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: decision, permissionDecisionReason: `Kira: ${reason}` } }
|
|
@@ -178,6 +187,9 @@ export async function run({ prompt, sessionId, cwd, policy }, io) {
|
|
|
178
187
|
cwd,
|
|
179
188
|
...(sessionId ? { resume: sessionId } : {}),
|
|
180
189
|
permissionMode: 'default',
|
|
190
|
+
// A team member's soul, skills and conventions ride on top of Claude Code's own prompt; its preset says which tools it does without.
|
|
191
|
+
...(persona?.systemPrompt ? { systemPrompt: { type: 'preset', preset: 'claude_code', append: persona.systemPrompt } } : {}),
|
|
192
|
+
...(persona?.disallowedTools?.length ? { disallowedTools: persona.disallowedTools } : {}),
|
|
181
193
|
canUseTool,
|
|
182
194
|
hooks: { PreToolUse: [{ hooks: [preToolUse] }] },
|
|
183
195
|
...(process.env.KIRA_CLAUDE_BIN ? { pathToClaudeCodeExecutable: process.env.KIRA_CLAUDE_BIN } : {}),
|
package/src/index.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import WebSocket from 'ws'
|
|
5
5
|
import { listProjects, listSessions, readSession } from './claude-sessions.mjs'
|
|
6
6
|
import { snapshot, changesSince, display, setupProjectFolder } from './git.mjs'
|
|
7
|
-
import { resolveCwd } from './claude-run.mjs'
|
|
7
|
+
import { resolveCwd, folderExists } from './claude-run.mjs'
|
|
8
8
|
import { agentById, AGENTS } from './agents/index.mjs'
|
|
9
9
|
import { normalizeAnswer } from './agents/protocol.mjs'
|
|
10
10
|
import { openApp, installedApps } from './apps.mjs'
|
|
@@ -81,7 +81,11 @@ const tools = {
|
|
|
81
81
|
if (s?.cwd) cwd = s.cwd
|
|
82
82
|
}
|
|
83
83
|
const dir = resolveCwd(cwd)
|
|
84
|
-
|
|
84
|
+
if (!folderExists(dir)) {
|
|
85
|
+
// The SDK would report a spawn failure as a broken binary; the folder is simply not there.
|
|
86
|
+
return { error: `The folder ${display(dir)} does not exist on this Mac. Link the project's real folder in the portal, or clone its repository there first (setup_project_folder); never guess a path.` }
|
|
87
|
+
}
|
|
88
|
+
log(`${agent.id} job=${jobId.slice(0, 8)}${args.persona?.name ? ` as ${args.persona.name}` : ''} session=${args.session_id ?? 'new'} in ${display(dir)}: ${prompt.slice(0, 80)}`)
|
|
85
89
|
const before = await snapshot(dir).catch(() => null)
|
|
86
90
|
let seq = 0
|
|
87
91
|
const io = {
|
|
@@ -104,7 +108,7 @@ const tools = {
|
|
|
104
108
|
}, { once: true })
|
|
105
109
|
}),
|
|
106
110
|
}
|
|
107
|
-
void agent.run({ prompt, sessionId: args.session_id, cwd: dir, policy: args.policy ?? {} }, io).then(async outcome => {
|
|
111
|
+
void agent.run({ prompt, sessionId: args.session_id, cwd: dir, policy: args.policy ?? {}, persona: args.persona ?? null }, io).then(async outcome => {
|
|
108
112
|
for (const key of [...answerWaits.keys()]) if (key.startsWith(`${jobId}:`)) answerWaits.delete(key)
|
|
109
113
|
let change = null
|
|
110
114
|
if (before) change = await changesSince(before).catch(err => (log(`git after job failed: ${err.message}`), null))
|