kira-glasses-bridge 2.14.0 → 2.15.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 +27 -1
- package/src/claude-designs.mjs +7 -5
- package/src/index.mjs +8 -1
package/package.json
CHANGED
package/src/agents/claude.mjs
CHANGED
|
@@ -191,11 +191,24 @@ export async function run({ prompt, sessionId, cwd, policy, persona, abort }, io
|
|
|
191
191
|
return applyAnswer(toolName, input, request, resolved.answer, suggestions)
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
// Background work (subagents, `run_in_background` shell commands, monitors)
|
|
195
|
+
// started by the model would outlive the turn: this is a one-shot run, so the
|
|
196
|
+
// moment the main thread stops the result is released, the process ends and
|
|
197
|
+
// that work is killed, never having reported. Claude's parting words ("the
|
|
198
|
+
// agents are still running; I'll compile the report when they finish") then
|
|
199
|
+
// became the job's final report, and Kira told the user it was still going.
|
|
200
|
+
// With this set, the tool has no background option: subagents run inline and
|
|
201
|
+
// their findings are in the answer.
|
|
202
|
+
const env = { ...process.env, CLAUDE_CODE_DISABLE_BACKGROUND_TASKS: '1' }
|
|
203
|
+
/** Background tasks the model still managed to start (an older CLI ignoring the flag): tool_use id -> description. */
|
|
204
|
+
const orphans = new Map()
|
|
205
|
+
|
|
194
206
|
try {
|
|
195
207
|
const q = query({
|
|
196
208
|
prompt,
|
|
197
209
|
options: {
|
|
198
210
|
cwd,
|
|
211
|
+
env,
|
|
199
212
|
...(sessionId ? { resume: sessionId } : {}),
|
|
200
213
|
permissionMode: 'default',
|
|
201
214
|
...(abort ? { abortController: abort } : {}),
|
|
@@ -211,18 +224,26 @@ export async function run({ prompt, sessionId, cwd, policy, persona, abort }, io
|
|
|
211
224
|
},
|
|
212
225
|
})
|
|
213
226
|
for await (const m of q) {
|
|
227
|
+
// Messages from inside a subagent carry the id of the Subtask call that started it.
|
|
228
|
+
const nested = !!m.parent_tool_use_id
|
|
214
229
|
if (m.type === 'system' && m.subtype === 'init') {
|
|
215
230
|
session = m.session_id ?? session
|
|
216
231
|
io.emit({ kind: 'session', sessionId: session, model: m.model ?? null })
|
|
232
|
+
} else if (m.type === 'system' && m.subtype === 'task_started') {
|
|
233
|
+
if (m.is_backgrounded && m.tool_use_id) orphans.set(m.tool_use_id, m.description ?? m.subagent_type ?? 'task')
|
|
234
|
+
} else if (m.type === 'system' && m.subtype === 'task_notification') {
|
|
235
|
+
if (m.tool_use_id) orphans.delete(m.tool_use_id)
|
|
217
236
|
} else if (m.type === 'assistant') {
|
|
218
237
|
for (const b of m.message?.content ?? []) {
|
|
219
|
-
|
|
238
|
+
// A subagent's narration is for its parent, not the user; its actions still show what is going on while the Subtask runs.
|
|
239
|
+
if (b.type === 'text' && b.text?.trim() && !nested) io.emit({ kind: 'message', text: short(b.text, TEXT_CAP) })
|
|
220
240
|
else if (b.type === 'tool_use') io.emit({ kind: 'action', id: b.id, tool: b.name, ...describeAction(b.name, b.input, cwd) })
|
|
221
241
|
}
|
|
222
242
|
} else if (m.type === 'user') {
|
|
223
243
|
for (const b of m.message?.content ?? []) {
|
|
224
244
|
if (b?.type === 'tool_result') {
|
|
225
245
|
const t = typeof b.content === 'string' ? b.content : Array.isArray(b.content) ? b.content.map(c => (c.type === 'text' ? c.text : '')).join('\n') : ''
|
|
246
|
+
if (/^Async agent launched successfully/.test(t.trim()) && !orphans.has(b.tool_use_id)) orphans.set(b.tool_use_id, 'subagent')
|
|
226
247
|
io.emit({ kind: 'action_result', id: b.tool_use_id, ok: !b.is_error, preview: short(t.trim(), 240) })
|
|
227
248
|
}
|
|
228
249
|
}
|
|
@@ -230,6 +251,11 @@ export async function run({ prompt, sessionId, cwd, policy, persona, abort }, io
|
|
|
230
251
|
session = m.session_id ?? session
|
|
231
252
|
if (m.subtype === 'success') text = String(m.result ?? '')
|
|
232
253
|
else error = `Claude stopped: ${m.subtype}${Array.isArray(m.errors) && m.errors.length ? ` (${m.errors.join('; ').slice(0, 300)})` : ''}`
|
|
254
|
+
// Work left running in the background dies with this process; say so rather than let "still running" stand as the outcome.
|
|
255
|
+
if (orphans.size) {
|
|
256
|
+
const note = `${orphans.size} background ${orphans.size === 1 ? 'task was' : 'tasks were'} still running when Claude ended its turn (${[...orphans.values()].slice(0, 5).join(', ')}); they were stopped with the session and their findings are not in this report. Send a follow-up to have that work done inline.`
|
|
257
|
+
text = text ? `${text}\n\n(${note})` : `(${note})`
|
|
258
|
+
}
|
|
233
259
|
const stats = { durationMs: m.duration_ms, turns: m.num_turns, costUsd: m.total_cost_usd }
|
|
234
260
|
io.emit({ kind: 'result', ok: !error, text: short(text, TEXT_CAP), error, sessionId: session, stats })
|
|
235
261
|
return { ok: !error, text, error, sessionId: session, stats }
|
package/src/claude-designs.mjs
CHANGED
|
@@ -155,14 +155,16 @@ async function designsOf(s) {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
/**
|
|
158
|
-
* Scan every session changed since `since` (ISO time; all of them when omitted)
|
|
159
|
-
*
|
|
160
|
-
*
|
|
158
|
+
* Scan every session changed since `since` (ISO time; all of them when omitted), or only the
|
|
159
|
+
* sessions named in `sessions` (a Claude job just finished in one). Returns the designs without
|
|
160
|
+
* their HTML, plus the HTML of those whose hash isn't in `known` (hashes the API already holds),
|
|
161
|
+
* so a rescan sends only what changed.
|
|
161
162
|
*/
|
|
162
|
-
export async function scanDesigns({ since, known = [] } = {}) {
|
|
163
|
+
export async function scanDesigns({ since, known = [], sessions: only } = {}) {
|
|
163
164
|
const after = since ? Date.parse(since) || 0 : 0
|
|
164
165
|
const have = new Set(known)
|
|
165
|
-
const
|
|
166
|
+
const pick = Array.isArray(only) && only.length ? new Set(only) : null
|
|
167
|
+
const sessions = (await sdkListSessions()).filter(s => (pick ? pick.has(s.sessionId) : s.lastModified > after))
|
|
166
168
|
const designs = []
|
|
167
169
|
let failed = 0
|
|
168
170
|
for (const s of sessions) {
|
package/src/index.mjs
CHANGED
|
@@ -45,6 +45,7 @@ const ANSWER_TIMEOUT_MS = 3 * 60 * 1000
|
|
|
45
45
|
* openTerminal({ cwd, command }) open_terminal in the app's own terminal (a Kira tab), not
|
|
46
46
|
* Terminal.app; resolves to what to report, or null to fall back
|
|
47
47
|
* terminals the app's shells, for remote devices (terminals.mjs)
|
|
48
|
+
* browser({ op, … }) Kira in the app's browser dock: open, read, click, type (browser-kira.mjs)
|
|
48
49
|
*/
|
|
49
50
|
let hooks = {}
|
|
50
51
|
let stopped = false
|
|
@@ -112,7 +113,7 @@ const tools = {
|
|
|
112
113
|
return listProjects(args.max)
|
|
113
114
|
},
|
|
114
115
|
async scan_claude_designs(args) {
|
|
115
|
-
return scanDesigns({ since: args.since, known: Array.isArray(args.known) ? args.known : [] })
|
|
116
|
+
return scanDesigns({ since: args.since, known: Array.isArray(args.known) ? args.known : [], sessions: args.sessions })
|
|
116
117
|
},
|
|
117
118
|
async list_claude_sessions(args) {
|
|
118
119
|
return listSessions(args.max)
|
|
@@ -198,6 +199,12 @@ const tools = {
|
|
|
198
199
|
/** This Mac's shells for a browser elsewhere (only inside the Mac app, only when switched on there). */
|
|
199
200
|
terminal: args => terminalCall(args),
|
|
200
201
|
|
|
202
|
+
/** Kira in the Mac app's browser dock: open a page, read it, click and type in it. */
|
|
203
|
+
async browser(args) {
|
|
204
|
+
if (!hooks.browser) return { error: 'the browser needs the Kira Mac app (this bridge has none)', reason: 'unavailable' }
|
|
205
|
+
return hooks.browser(args)
|
|
206
|
+
},
|
|
207
|
+
|
|
201
208
|
/** Open a terminal on this Mac in a folder and run a command there (used to hand a Claude session
|
|
202
209
|
* to the person): a Kira tab when the bridge runs inside the Mac app, else Terminal.app. */
|
|
203
210
|
async open_terminal(args) {
|