kira-glasses-bridge 2.40.0 → 2.41.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/auto.mjs +36 -0
- package/src/agents/claude-calls.mjs +18 -3
- package/src/agents/claude.mjs +1 -1
- package/src/agents/codex-forms.mjs +2 -2
- package/src/agents/codex-requests.mjs +2 -2
- package/src/agents/protocol.mjs +3 -0
- package/src/agents/settings.mjs +7 -2
- package/src/job-tools.mjs +5 -3
package/package.json
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Auto mode: the person is not asked anything, ever. Every request a job's agent would put to them
|
|
2
|
+
// (a permission, a plan to approve, a question, an MCP server's form) is answered here at once, for
|
|
3
|
+
// Claude Code and Codex alike (job-tools.mjs wraps io.ask and io.emit with autoIo). Kira's deny rules
|
|
4
|
+
// still refuse before anything is asked (checks.mjs): a refusal is not a question.
|
|
5
|
+
import { modeOf } from './settings.mjs'
|
|
6
|
+
|
|
7
|
+
/** What a question gets in Auto mode, as its answer. */
|
|
8
|
+
export const AUTO_WORDS =
|
|
9
|
+
'Auto mode: the person is not taking questions. ' +
|
|
10
|
+
'Go with the sensible default, say your assumption in one line, and carry on.'
|
|
11
|
+
|
|
12
|
+
/** Whether the person chose Auto mode for this job. */
|
|
13
|
+
export const isAuto = settings => modeOf(settings) === 'auto'
|
|
14
|
+
|
|
15
|
+
/** Auto mode's answer to a request: go ahead; a question gets AUTO_WORDS. Never "always" (that writes a rule). */
|
|
16
|
+
export function autoAnswer(request) {
|
|
17
|
+
if (request?.type === 'plan') return { optionIds: ['approve'] }
|
|
18
|
+
if (request?.type === 'choice' || request?.type === 'text') return { optionIds: [], text: AUTO_WORDS }
|
|
19
|
+
return { optionIds: ['allow', 'accept'] }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** A request, or its answer by Auto mode: neither leaves the Mac, so no card and nothing for the API to file. */
|
|
23
|
+
const unseen = event => event?.kind === 'request' || (event?.kind === 'request_resolved' && event.by === 'auto')
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The job's io for Auto mode: a request is never shown (no card, no push to the phone or glasses)
|
|
27
|
+
* and is answered at once, by 'auto'. Other modes get io back as it is.
|
|
28
|
+
*/
|
|
29
|
+
export function autoIo(io, settings) {
|
|
30
|
+
if (!isAuto(settings)) return io
|
|
31
|
+
return {
|
|
32
|
+
...io,
|
|
33
|
+
emit: event => (unseen(event) ? undefined : io.emit(event)),
|
|
34
|
+
ask: async request => ({ answer: autoAnswer(request), by: 'auto' }),
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// SDK expects.
|
|
4
4
|
import { homedir } from 'node:os'
|
|
5
5
|
import { relative, resolve, sep } from 'node:path'
|
|
6
|
-
import { PLAN_OPTIONS, permissionOptions, requestId } from './protocol.mjs'
|
|
6
|
+
import { PLAN_OPTIONS, permissionOptions, requestId, unanswered } from './protocol.mjs'
|
|
7
7
|
|
|
8
8
|
/** What an agent is told when it may not do something. */
|
|
9
9
|
export const NOT_ANOTHER_WAY =
|
|
@@ -122,8 +122,23 @@ export function toRequest(name, input, cwd, verdict) {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
/**
|
|
126
|
-
|
|
125
|
+
/**
|
|
126
|
+
* What the agent is told when nobody answered a card in time, by the card's type. Said as no refusal,
|
|
127
|
+
* so it neither gives up on the task nor reads the timeout's words as the person's.
|
|
128
|
+
*/
|
|
129
|
+
const UNANSWERED = {
|
|
130
|
+
permission: 'Nobody answered the permission card in time. This was not a refusal: ' +
|
|
131
|
+
'carry on without this step, or try again later.',
|
|
132
|
+
choice: 'Nobody answered the question in time. This was not a refusal: ' +
|
|
133
|
+
'carry on with your best judgment and say what you assumed.',
|
|
134
|
+
plan: 'Nobody answered about the plan in time. This was not a refusal: ' +
|
|
135
|
+
'stay in plan mode and say the plan is waiting for their go-ahead.',
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Turn a protocol answer (`{ answer, by }`) back into what the SDK expects. */
|
|
139
|
+
export function applyAnswer(name, input, request, resolved) {
|
|
140
|
+
if (unanswered(resolved)) return { behavior: 'deny', message: UNANSWERED[request.type] ?? UNANSWERED.permission }
|
|
141
|
+
const answer = resolved.answer ?? {}
|
|
127
142
|
const chosen = answer.optionIds ?? []
|
|
128
143
|
if (request.type === 'choice') {
|
|
129
144
|
const answers = {}
|
package/src/agents/claude.mjs
CHANGED
|
@@ -86,7 +86,7 @@ async function run({ prompt, sessionId, cwd, persona, settings, abort }, io) {
|
|
|
86
86
|
resolved = { answer: { optionIds: [], text: err.message }, by: 'timeout' }
|
|
87
87
|
}
|
|
88
88
|
io.emit({ kind: 'request_resolved', id: request.id, answer: resolved.answer, by: resolved.by })
|
|
89
|
-
return applyAnswer(toolName, input, request, resolved
|
|
89
|
+
return applyAnswer(toolName, input, request, resolved)
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
// Background work (subagents, `run_in_background` shell commands, monitors)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// a link to open or a plain yes/no as a choice; and the answer back as MCP's accept, decline or cancel
|
|
4
4
|
// with the form's content. Pure.
|
|
5
5
|
import { short } from './claude-calls.mjs'
|
|
6
|
-
import { requestId } from './protocol.mjs'
|
|
6
|
+
import { requestId, unanswered } from './protocol.mjs'
|
|
7
7
|
|
|
8
8
|
const ACCEPT = [
|
|
9
9
|
{ id: 'accept', label: 'Accept' },
|
|
@@ -93,7 +93,7 @@ function valueOf(f, a) {
|
|
|
93
93
|
*/
|
|
94
94
|
export function formResponse(params, request, resolved) {
|
|
95
95
|
if (!request) return { action: 'decline' }
|
|
96
|
-
if (resolved
|
|
96
|
+
if (unanswered(resolved)) return { action: 'cancel' }
|
|
97
97
|
const answer = resolved?.answer ?? {}
|
|
98
98
|
if (!request.questions) return { action: (answer.optionIds ?? []).includes('accept') ? 'accept' : 'decline' }
|
|
99
99
|
const content = {}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// and an MCP server's form (mcpServer/elicitation/request), each as a request card; and the person's
|
|
4
4
|
// answer back in Codex's own response shape (an MCP server's form: codex-forms.mjs). Pure; codex-ask.mjs asks.
|
|
5
5
|
import { displayPath, short } from './claude-calls.mjs'
|
|
6
|
-
import { permissionOptions, requestId } from './protocol.mjs'
|
|
6
|
+
import { permissionOptions, requestId, unanswered } from './protocol.mjs'
|
|
7
7
|
|
|
8
8
|
const options = list => list.map((o, k) => ({ id: `o${k + 1}`, ...o }))
|
|
9
9
|
/** A question's answer: per question, else (the first one) the answer's own options and words. */
|
|
@@ -37,7 +37,7 @@ export function userInputRequest(params) {
|
|
|
37
37
|
|
|
38
38
|
/** The answers as Codex takes them, by its question ids; unanswered (timed out, secret) ones are empty. */
|
|
39
39
|
export function userInputResponse(params, request, resolved) {
|
|
40
|
-
const answer = resolved
|
|
40
|
+
const answer = unanswered(resolved) ? undefined : resolved?.answer
|
|
41
41
|
const asked = (params.questions ?? []).filter(q => !q.isSecret)
|
|
42
42
|
const answers = Object.fromEntries((params.questions ?? []).map(q => [q.id, { answers: [] }]))
|
|
43
43
|
asked.forEach((codexQ, n) => {
|
package/src/agents/protocol.mjs
CHANGED
|
@@ -58,6 +58,9 @@ export const PLAN_OPTIONS = [
|
|
|
58
58
|
{ id: 'revise', label: 'Revise', description: 'Change the plan first; say what to change' },
|
|
59
59
|
]
|
|
60
60
|
|
|
61
|
+
/** Nobody answered in time (or the job stopped first): not a refusal, so never reported as one. */
|
|
62
|
+
export const unanswered = resolved => resolved?.by === 'timeout'
|
|
63
|
+
|
|
61
64
|
/** Normalize the many ways an answer can arrive into option ids and text. */
|
|
62
65
|
export function normalizeAnswer(answer) {
|
|
63
66
|
const a = answer ?? {}
|
package/src/agents/settings.mjs
CHANGED
|
@@ -10,14 +10,19 @@ const MODES = new Set(['acceptEdits', 'default', 'plan', 'auto'])
|
|
|
10
10
|
/** The id an older Kira API sent for ask before edits. */
|
|
11
11
|
const OLD = { ask: 'default' }
|
|
12
12
|
|
|
13
|
+
/** The mode the person chose, by Claude Code's id; accept edits when it names none. */
|
|
14
|
+
export function modeOf(settings) {
|
|
15
|
+
const said = Object.hasOwn(OLD, settings?.mode ?? '') ? OLD[settings.mode] : settings?.mode
|
|
16
|
+
return MODES.has(said) ? said : 'acceptEdits'
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
/**
|
|
14
20
|
* The SDK's query options for these settings (left out when unset, so Claude Code's defaults stand).
|
|
15
21
|
* `kiraDecides`: Kira's rules check each call (checks.mjs); without them, edits are asked about.
|
|
16
22
|
*/
|
|
17
23
|
export function sdkOptionsFor(settings, kiraDecides = true) {
|
|
18
24
|
const s = settings ?? {}
|
|
19
|
-
const
|
|
20
|
-
const mode = MODES.has(said) ? said : 'acceptEdits'
|
|
25
|
+
const mode = modeOf(s)
|
|
21
26
|
return {
|
|
22
27
|
permissionMode: kiraDecides ? mode : mode === 'plan' ? 'plan' : 'default',
|
|
23
28
|
...(typeof s.model === 'string' && MODEL.test(s.model) ? { model: s.model } : {}),
|
package/src/job-tools.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// reported when done), stopping one, and steering one (words into its turn at work).
|
|
3
3
|
import { agentById } from './agents/index.mjs'
|
|
4
4
|
import { awaitAnswer, forgetJob } from './agents/answers.mjs'
|
|
5
|
+
import { autoIo } from './agents/auto.mjs'
|
|
5
6
|
import { resolveCwd, folderExists } from './claude-run.mjs'
|
|
6
7
|
import { readSession } from './claude-sessions.mjs'
|
|
7
8
|
import { changesSince, display, snapshot } from './git.mjs'
|
|
@@ -44,18 +45,19 @@ export const jobTools = {
|
|
|
44
45
|
const abort = new AbortController()
|
|
45
46
|
const live = { abort, steer: null }
|
|
46
47
|
let seq = 0
|
|
47
|
-
const
|
|
48
|
+
const settings = args.settings ?? null
|
|
49
|
+
// Auto mode answers every request itself: nothing waits on the person (auto.mjs).
|
|
50
|
+
const io = autoIo({
|
|
48
51
|
emit: event => sendLive({ type: 'job.event', jobId, seq: ++seq, at: new Date().toISOString(), event }),
|
|
49
52
|
ask: (request, signal) => awaitAnswer(jobId, request, signal),
|
|
50
53
|
check: call => checkCall(jobId, call, sendIfLive),
|
|
51
54
|
steerable: steer => (live.steer = steer),
|
|
52
|
-
}
|
|
55
|
+
}, settings)
|
|
53
56
|
running.set(jobId, live)
|
|
54
57
|
// Started is said at once. The git snapshot (what changed, reported at the end) is taken first but
|
|
55
58
|
// in the background: in a big repository it takes longer than the API waits for this answer.
|
|
56
59
|
const before = snapshot(dir).catch(() => null)
|
|
57
60
|
const persona = args.persona ?? null
|
|
58
|
-
const settings = args.settings ?? null
|
|
59
61
|
// What came with it from Kira, already copied here (the prompt names their paths): codex-input.mjs.
|
|
60
62
|
const attachments = Array.isArray(args.attachments) ? args.attachments : []
|
|
61
63
|
const job = { prompt, attachments, sessionId, cwd: dir, persona, settings, abort }
|