orynacode-ai 1.16.20 → 1.16.21

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
- "version": "1.16.20",
3
+ "version": "1.16.21",
4
4
  "name": "orynacode-ai",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -217,9 +217,11 @@ export const TuiThreadCommand = cmd({
217
217
  return
218
218
  }
219
219
 
220
- setTimeout(() => {
221
- client.call("checkUpgrade", { directory: cwd }).catch(() => {})
222
- }, 1000).unref?.()
220
+ if (process.env.OPENCODE_CLIENT !== "desktop") {
221
+ setTimeout(() => {
222
+ client.call("checkUpgrade", { directory: cwd }).catch(() => {})
223
+ }, 1000).unref?.()
224
+ }
223
225
 
224
226
  try {
225
227
  const { Effect } = await import("effect")
@@ -7,6 +7,6 @@ export type AgentState = {
7
7
  url: string
8
8
  }
9
9
 
10
- export const [agentStatus, setAgentStatus] = createRoot(() =>
11
- createSignal<AgentState>({ connected: false, processing: false, ready: false, url: "" }),
10
+ export const agentStatus = createRoot(() =>
11
+ createSignal<AgentState>({ connected: false, processing: false, ready: false, url: "" })[0],
12
12
  )
@@ -1,5 +1,4 @@
1
1
  import { readFileSync } from "fs"
2
- import { setAgentStatus, agentStatus } from "./agent-signal"
3
2
  import os from "os"
4
3
  import path from "path"
5
4
 
@@ -21,126 +20,3 @@ export function getOrynaGateKey(dir: string): string {
21
20
  } catch {}
22
21
  return `sk-local-${user}-${path.basename(dir)}`
23
22
  }
24
-
25
- export function setReady(ready: boolean) {
26
- const s = agentStatus()
27
- setAgentStatus({ ...s, ready })
28
- }
29
-
30
- const REPLY_FILE = `/tmp/oryna-reply-${process.pid}`
31
- let ws: WebSocket | null = null
32
- let reconnectTimer: ReturnType<typeof setTimeout> | null = null
33
- let heartbeatTimer: ReturnType<typeof setInterval> | null = null
34
- let replyWatchTimer: ReturnType<typeof setInterval> | null = null
35
- let stopped = false
36
- let connecting = false
37
- let onMessage: ((content: string, from: string) => Promise<void>) | null = null
38
-
39
- export function setMessageHandler(fn: (content: string, from: string) => Promise<void>) {
40
- onMessage = fn
41
- }
42
-
43
- function readAuthUrl(): string | undefined {
44
- try {
45
- const dataDir = process.env.XDG_DATA_HOME || path.join(os.homedir(), ".local", "share")
46
- const authPath = path.join(dataDir, "orynacode", "auth.json")
47
- const auth = JSON.parse(readFileSync(authPath, "utf-8"))
48
- const local = auth?.["orynagate"]
49
- if (local?.metadata?.url) return local.metadata.url
50
- } catch {}
51
- return undefined
52
- }
53
-
54
- function startReplyWatch() {
55
- if (replyWatchTimer) return
56
- let offset = 0
57
- replyWatchTimer = setInterval(() => {
58
- try {
59
- const raw = readFileSync(REPLY_FILE, "utf-8")
60
- const lines = raw.split("\n")
61
- let sent = 0
62
- for (let i = offset; i < lines.length; i++) {
63
- const line = lines[i].trim()
64
- if (!line) continue
65
- if (ws?.readyState === WebSocket.OPEN) {
66
- ws.send(line)
67
- sent++
68
- }
69
- }
70
- offset += sent
71
- } catch {}
72
- }, 300)
73
- }
74
-
75
- export function start() {
76
- stopped = false
77
- const url = process.env.ORYNA_GATE_URL || readAuthUrl()
78
- if (!url) return
79
-
80
- const host = new URL(url).host
81
- const token = getOrynaGateKey(process.env.ORYNA_GATE_WORKSPACE || process.cwd())
82
- const wsUrl = `ws://${host}/ws?token=${token}&name=orynacode`
83
-
84
- const connect = () => {
85
- if (connecting) return
86
- connecting = true
87
- const socket = new WebSocket(wsUrl)
88
- ws = socket
89
-
90
- socket.addEventListener("open", () => {
91
- startReplyWatch()
92
- const s = agentStatus()
93
- setAgentStatus({ connected: true, processing: false, ready: s.ready, url: host })
94
- heartbeatTimer = setInterval(() => {
95
- if (socket.readyState === WebSocket.OPEN) socket.send("__PING__")
96
- }, 30000)
97
- })
98
-
99
- socket.addEventListener("message", async (event) => {
100
- try {
101
- const msg = JSON.parse(event.data.toString())
102
- if (!msg.success) return
103
- if (msg.recv === "hello") return
104
- if (msg.recv === "error") return
105
- if (msg.recv !== "message") return
106
-
107
- const content = msg.data?.content
108
- const from = msg.data?.from || "unknown"
109
- if (!content) return
110
-
111
- setTimeout(() => {
112
- const s = agentStatus()
113
- setAgentStatus({ connected: true, processing: true, ready: s.ready, url: host })
114
- }, 0)
115
- if (onMessage) await onMessage(content, from)
116
- setTimeout(() => {
117
- const s = agentStatus()
118
- setAgentStatus({ connected: true, processing: false, ready: s.ready, url: host })
119
- }, 0)
120
- } catch {}
121
- })
122
-
123
- socket.addEventListener("close", () => {
124
- connecting = false
125
- const s = agentStatus()
126
- setAgentStatus({ connected: false, processing: false, ready: s.ready, url: host })
127
- if (!stopped) reconnectTimer = setTimeout(connect, 3000)
128
- })
129
-
130
- socket.addEventListener("error", () => {
131
- socket.close()
132
- })
133
- }
134
-
135
- connect()
136
- }
137
-
138
- export function stop() {
139
- connecting = false
140
- stopped = true
141
- if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null }
142
- if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null }
143
- if (replyWatchTimer) { clearInterval(replyWatchTimer); replyWatchTimer = null }
144
- if (ws) { ws.close(); ws = null }
145
- setAgentStatus({ connected: false, processing: false, ready: false, url: "" })
146
- }
@@ -1,12 +1,13 @@
1
- import { appendFileSync } from "fs"
1
+ const REPLY_URL = process.env.ORYNA_GATE_REPLY_URL + "/reply"
2
2
 
3
- const FILE = `/tmp/oryna-reply-${process.pid}`
3
+ export async function sendReply(content: string, to?: string) {
4
+ if (!content) return
4
5
 
5
- export function sendReply(content: string, to?: string) {
6
- const args: Record<string, string> = { content }
7
- if (to) args.to = to
8
- appendFileSync(
9
- FILE,
10
- JSON.stringify({ cmd: "reply", args: JSON.stringify(args) }) + "\n",
11
- )
6
+ if (REPLY_URL) {
7
+ fetch(REPLY_URL, {
8
+ method: "POST",
9
+ headers: { "Content-Type": "application/json" },
10
+ body: JSON.stringify({ content, to: to ?? "" }),
11
+ }).catch(() => {})
12
+ }
12
13
  }
@@ -291,34 +291,38 @@ export async function OrynaAuthPlugin(input: PluginInput): Promise<Hooks> {
291
291
  return jwt ? { apiKey: jwt } : {}
292
292
  },
293
293
  methods: [
294
- {
295
- type: "oauth",
296
- label: "Login with Oryna AI (Browser)",
297
- authorize: async () => {
298
- try {
299
- const tokenPromise = reAuthOryna()
300
- return {
301
- url: ORYNA_LOGIN_URL,
302
- instructions: "Complete login in your browser. This window will close automatically when done.",
303
- method: "auto" as const,
304
- callback: async () => {
294
+ ...(process.env.OPENCODE_CLIENT !== "desktop"
295
+ ? [
296
+ {
297
+ type: "oauth",
298
+ label: "Login with Oryna AI (Browser)",
299
+ authorize: async () => {
305
300
  try {
306
- return { type: "success" as const, key: await tokenPromise }
301
+ const tokenPromise = reAuthOryna()
302
+ return {
303
+ url: ORYNA_LOGIN_URL,
304
+ instructions: "Complete login in your browser. This window will close automatically when done.",
305
+ method: "auto" as const,
306
+ callback: async () => {
307
+ try {
308
+ return { type: "success" as const, key: await tokenPromise }
309
+ } catch {
310
+ return { type: "failed" as const }
311
+ }
312
+ },
313
+ }
307
314
  } catch {
308
- return { type: "failed" as const }
315
+ return {
316
+ url: ORYNA_LOGIN_URL,
317
+ instructions: "Failed to start login server. Please use API key instead.",
318
+ method: "code" as const,
319
+ callback: async () => ({ type: "failed" as const }),
320
+ }
309
321
  }
310
322
  },
311
- }
312
- } catch {
313
- return {
314
- url: ORYNA_LOGIN_URL,
315
- instructions: "Failed to start login server. Please use API key instead.",
316
- method: "code" as const,
317
- callback: async () => ({ type: "failed" as const }),
318
- }
319
- }
320
- },
321
- },
323
+ } as const,
324
+ ]
325
+ : []),
322
326
  {
323
327
  type: "api",
324
328
  label: "API key",
@@ -88,6 +88,7 @@ export const SessionPaths = {
88
88
  remove: `${root}/:sessionID`,
89
89
  update: `${root}/:sessionID`,
90
90
  fork: `${root}/:sessionID/fork`,
91
+ focus: `${root}/:sessionID/focus`,
91
92
  abort: `${root}/:sessionID/abort`,
92
93
  share: `${root}/:sessionID/share`,
93
94
  init: `${root}/:sessionID/init`,
@@ -250,6 +251,18 @@ export const SessionApi = HttpApi.make("session")
250
251
  description: "Create a new session by forking an existing session at a specific message point.",
251
252
  }),
252
253
  ),
254
+ HttpApiEndpoint.post("focus", SessionPaths.focus, {
255
+ params: { sessionID: SessionID },
256
+ query: WorkspaceRoutingQuery,
257
+ success: described(Schema.Boolean, "Session focused"),
258
+ error: [HttpApiError.BadRequest, ApiNotFoundError],
259
+ }).annotateMerge(
260
+ OpenApi.annotations({
261
+ identifier: "session.focus",
262
+ summary: "Focus session",
263
+ description: "Navigate the TUI to the specified session via SSE event.",
264
+ }),
265
+ ),
253
266
  HttpApiEndpoint.post("abort", SessionPaths.abort, {
254
267
  params: { sessionID: SessionID },
255
268
  query: WorkspaceRoutingQuery,
@@ -1,5 +1,6 @@
1
1
  import { PermissionV1 } from "@opencode-ai/core/v1/permission"
2
2
  import { Agent } from "@/agent/agent"
3
+ import { GlobalBus } from "@/bus/global"
3
4
  import { SessionV1 } from "@opencode-ai/core/v1/session"
4
5
  import { EventV2Bridge } from "@/event-v2-bridge"
5
6
  import { Command } from "@/command"
@@ -410,6 +411,22 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
410
411
  return yield* session.updatePart(payload)
411
412
  })
412
413
 
414
+ const focus = Effect.fn("SessionHttpApi.focus")(function* (ctx: {
415
+ params: { sessionID: SessionID }
416
+ }) {
417
+ const info = yield* requireSession(ctx.params.sessionID)
418
+ yield* Effect.sync(() => {
419
+ GlobalBus.emit("event", {
420
+ directory: info.directory,
421
+ payload: {
422
+ type: "tui.session.select",
423
+ properties: { sessionID: ctx.params.sessionID },
424
+ },
425
+ })
426
+ })
427
+ return true
428
+ })
429
+
413
430
  return handlers
414
431
  .handle("list", list)
415
432
  .handle("status", status)
@@ -423,6 +440,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
423
440
  .handle("remove", remove)
424
441
  .handle("update", update)
425
442
  .handleRaw("fork", forkRaw)
443
+ .handle("focus", focus)
426
444
  .handle("abort", abort)
427
445
  .handle("init", init)
428
446
  .handle("share", share)
@@ -917,7 +917,7 @@ export const layer = Layer.effect(
917
917
 
918
918
  const msgs = yield* session.messages({ sessionID: ctx.sessionID }).pipe(Effect.orDie)
919
919
  const lastUser = msgs.findLast((m) => m.info.role === "user")
920
- const collabMatch = (lastUser?.info as any)?.system?.match(/orynagate:from=(\S+)/)
920
+ const collabMatch = (lastUser?.info as any)?.system?.match(/collab:from=(\S+)/)
921
921
  if (collabMatch) {
922
922
  const userParts = lastUser?.parts ?? []
923
923
  const alreadyReplied = userParts.some((p) => p.type === "text" && (p as any).text?.includes("✓ Replied"))