orynacode-ai 1.16.19 → 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.19",
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,136 +1,22 @@
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
 
6
5
  export function getOrynaGateId(dir: string): string {
7
6
  try {
8
7
  const config = JSON.parse(readFileSync(path.join(dir, ".orynagate"), "utf-8"))
8
+ if (config.ticket) return config.ticket
9
9
  if (config.id && typeof config.id === "string") return config.id
10
10
  } catch {}
11
11
  return path.basename(dir)
12
12
  }
13
13
 
14
- export function setReady(ready: boolean) {
15
- const s = agentStatus()
16
- setAgentStatus({ ...s, ready })
17
- }
18
-
19
- const REPLY_FILE = `/tmp/oryna-reply-${process.pid}`
20
- let ws: WebSocket | null = null
21
- let reconnectTimer: ReturnType<typeof setTimeout> | null = null
22
- let heartbeatTimer: ReturnType<typeof setInterval> | null = null
23
- let replyWatchTimer: ReturnType<typeof setInterval> | null = null
24
- let stopped = false
25
- let connecting = false
26
- let onMessage: ((content: string, from: string) => Promise<void>) | null = null
27
-
28
- export function setMessageHandler(fn: (content: string, from: string) => Promise<void>) {
29
- onMessage = fn
30
- }
31
-
32
- function readAuthUrl(): string | undefined {
14
+ export function getOrynaGateKey(dir: string): string {
15
+ const user = os.userInfo().username || "user"
33
16
  try {
34
- const dataDir = process.env.XDG_DATA_HOME || path.join(os.homedir(), ".local", "share")
35
- const authPath = path.join(dataDir, "orynacode", "auth.json")
36
- const auth = JSON.parse(readFileSync(authPath, "utf-8"))
37
- const local = auth?.["orynagate"]
38
- if (local?.metadata?.url) return local.metadata.url
17
+ const config = JSON.parse(readFileSync(path.join(dir, ".orynagate"), "utf-8"))
18
+ if (config.ticket) return `sk-ticket-${user}-${config.ticket}`
19
+ if (config.id && typeof config.id === "string") return `sk-local-${user}-${config.id}`
39
20
  } catch {}
40
- return undefined
41
- }
42
-
43
- function startReplyWatch() {
44
- if (replyWatchTimer) return
45
- let offset = 0
46
- replyWatchTimer = setInterval(() => {
47
- try {
48
- const raw = readFileSync(REPLY_FILE, "utf-8")
49
- const lines = raw.split("\n")
50
- let sent = 0
51
- for (let i = offset; i < lines.length; i++) {
52
- const line = lines[i].trim()
53
- if (!line) continue
54
- if (ws?.readyState === WebSocket.OPEN) {
55
- ws.send(line)
56
- sent++
57
- }
58
- }
59
- offset += sent
60
- } catch {}
61
- }, 300)
62
- }
63
-
64
- export function start() {
65
- stopped = false
66
- const url = process.env.ORYNA_GATE_URL || readAuthUrl()
67
- if (!url) return
68
-
69
- const host = new URL(url).host
70
- const user = os.userInfo().username || "user"
71
- const token = `sk-local-${user}-${path.basename(process.env.ORYNA_GATE_WORKSPACE || process.cwd())}`
72
- const wsUrl = `ws://${host}/ws?token=${token}&name=orynacode`
73
-
74
- const connect = () => {
75
- if (connecting) return
76
- connecting = true
77
- const socket = new WebSocket(wsUrl)
78
- ws = socket
79
-
80
- socket.addEventListener("open", () => {
81
- startReplyWatch()
82
- const s = agentStatus()
83
- setAgentStatus({ connected: true, processing: false, ready: s.ready, url: host })
84
- heartbeatTimer = setInterval(() => {
85
- if (socket.readyState === WebSocket.OPEN) socket.send("__PING__")
86
- }, 30000)
87
- })
88
-
89
- socket.addEventListener("message", async (event) => {
90
- try {
91
- const msg = JSON.parse(event.data.toString())
92
- if (!msg.success) return
93
- if (msg.recv === "hello") return
94
- if (msg.recv === "error") return
95
- if (msg.recv !== "message") return
96
-
97
- const content = msg.data?.content
98
- const from = msg.data?.from || "unknown"
99
- if (!content) return
100
-
101
- setTimeout(() => {
102
- const s = agentStatus()
103
- setAgentStatus({ connected: true, processing: true, ready: s.ready, url: host })
104
- }, 0)
105
- if (onMessage) await onMessage(content, from)
106
- setTimeout(() => {
107
- const s = agentStatus()
108
- setAgentStatus({ connected: true, processing: false, ready: s.ready, url: host })
109
- }, 0)
110
- } catch {}
111
- })
112
-
113
- socket.addEventListener("close", () => {
114
- connecting = false
115
- const s = agentStatus()
116
- setAgentStatus({ connected: false, processing: false, ready: s.ready, url: host })
117
- if (!stopped) reconnectTimer = setTimeout(connect, 3000)
118
- })
119
-
120
- socket.addEventListener("error", () => {
121
- socket.close()
122
- })
123
- }
124
-
125
- connect()
126
- }
127
-
128
- export function stop() {
129
- connecting = false
130
- stopped = true
131
- if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null }
132
- if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null }
133
- if (replyWatchTimer) { clearInterval(replyWatchTimer); replyWatchTimer = null }
134
- if (ws) { ws.close(); ws = null }
135
- setAgentStatus({ connected: false, processing: false, ready: false, url: "" })
21
+ return `sk-local-${user}-${path.basename(dir)}`
136
22
  }
@@ -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",
@@ -7,7 +7,7 @@ import { NoSuchModelError, type Provider as SDK } from "ai"
7
7
  import { Log } from "@opencode-ai/core/util/log"
8
8
  import { Npm } from "@opencode-ai/core/npm"
9
9
  import { Hash } from "@opencode-ai/core/util/hash"
10
- import { getOrynaGateId } from "../oryna/agent"
10
+ import { getOrynaGateKey } from "../oryna/agent"
11
11
  import { Plugin } from "../plugin"
12
12
  import { serviceUse } from "@opencode-ai/core/effect/service-use"
13
13
  import { type LanguageModelV3 } from "@ai-sdk/provider"
@@ -1688,9 +1688,7 @@ export const layer = Layer.effect(
1688
1688
  if (baseURL !== undefined) options["baseURL"] = baseURL
1689
1689
  if (options["apiKey"] === undefined && provider.key) options["apiKey"] = provider.key
1690
1690
  if (model.providerID === "orynagate") {
1691
- const id = getOrynaGateId(process.cwd())
1692
- const user = os.userInfo().username || "user"
1693
- options["apiKey"] = `sk-local-${user}-${id}`
1691
+ options["apiKey"] = getOrynaGateKey(process.env.ORYNA_GATE_WORKSPACE || process.cwd())
1694
1692
  }
1695
1693
  if (model.headers)
1696
1694
  options["headers"] = {
@@ -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"))