acryl-cli-linux-x64 0.1.31 → 0.1.36

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.
Files changed (37) hide show
  1. package/package.json +1 -1
  2. package/runtime/lib/bin.js +865 -242
  3. package/runtime/lib/index.js +867 -242
  4. package/runtime/lib/types/buildInfo.generated.d.ts +4 -0
  5. package/runtime/lib/types/tui/acrylMark.d.ts +14 -0
  6. package/runtime/lib/types/tui/actions.d.ts +9 -6
  7. package/runtime/lib/types/tui/auth-guidance.d.ts +0 -2
  8. package/runtime/lib/types/tui/listWindow.d.ts +24 -0
  9. package/runtime/lib/types/tui/login/LoginOverlay.d.ts +52 -11
  10. package/runtime/lib/types/tui/login/types.d.ts +6 -1
  11. package/runtime/lib/types/tui/modelProfile/ModelProfileOverlay.d.ts +41 -3
  12. package/runtime/lib/types/tui/modelProfile/types.d.ts +24 -0
  13. package/runtime/lib/types/tui/store.d.ts +10 -0
  14. package/runtime/lib/types/yly/yly-pet.d.ts +1 -1
  15. package/runtime/lib/types/yly/yly-programs.d.ts +1 -1
  16. package/runtime/node_modules/.modules.yaml +2 -2
  17. package/runtime/node_modules/.package-map.json +1 -1
  18. package/runtime/node_modules/@earendil-works/pi-ai/dist/auth/oauth/oauth-page.js +1 -1
  19. package/runtime/node_modules/acryl-control/lib/index.js +66 -1
  20. package/runtime/node_modules/acryl-control/lib/types/authorization/service.d.ts +23 -0
  21. package/runtime/node_modules/acryl-control/lib/types/authorization/types.d.ts +46 -0
  22. package/runtime/node_modules/acryl-control/lib/types/credential/projection.d.ts +63 -0
  23. package/runtime/node_modules/acryl-control/lib/types/credential/types.d.ts +38 -0
  24. package/runtime/node_modules/acryl-control/lib/types/index.d.ts +4 -0
  25. package/runtime/node_modules/acryl-control/package.json +1 -1
  26. package/runtime/node_modules/acryl-harness-runtime/lib/index.mjs +129 -2
  27. package/runtime/node_modules/acryl-harness-runtime/lib/types/acryl-home.d.ts +35 -0
  28. package/runtime/node_modules/acryl-harness-runtime/lib/types/index.d.ts +2 -0
  29. package/runtime/node_modules/acryl-harness-runtime/lib/types/session-bridge.d.ts +13 -0
  30. package/runtime/node_modules/acryl-harness-runtime/lib/types/session-log-exporter.d.ts +31 -0
  31. package/runtime/node_modules/acryl-harness-runtime/package.json +1 -1
  32. package/runtime/node_modules/acryl-harness-runtime/src/acryl-home.ts +50 -0
  33. package/runtime/node_modules/acryl-harness-runtime/src/index.ts +16 -0
  34. package/runtime/node_modules/acryl-harness-runtime/src/session-bridge.ts +24 -1
  35. package/runtime/node_modules/acryl-harness-runtime/src/session-log-exporter.ts +69 -0
  36. package/runtime/package.json +4 -2
  37. package/runtime/receipt.json +2 -2
@@ -0,0 +1,50 @@
1
+ /**
2
+ * ACRYL's own data root, nested one level above the DSH engine home it
3
+ * composes.
4
+ *
5
+ * The stock DSH Desktop app (from dshdesktop.com) uses plain `~/.dsh`, and so
6
+ * did every ACRYL surface until now — sharing that root made the two
7
+ * genuinely different products silently share credentials/settings/sessions,
8
+ * which is both confusing and useless for comparing ACRYL against a stock
9
+ * DSH Desktop install side by side. ACRYL now owns `~/.acryl` and nests each
10
+ * engine's artifacts under it by engine name:
11
+ *
12
+ * ```txt
13
+ * ~/.acryl/ ACRYL's own root — everything not engine-specific
14
+ * ~/.acryl/.dsh/ the DSH engine home (what DSH_HOME resolves to)
15
+ * ~/.acryl/.pi/ reserved for a future pi.dev engine
16
+ * ```
17
+ *
18
+ * `$DSH_HOME`, if a caller has already set it explicitly, still wins — this
19
+ * only changes the *default* the harness's own `resolveDshHome()` falls
20
+ * back to when nothing overrides it.
21
+ *
22
+ * @module acryl-harness-runtime/acryl-home
23
+ */
24
+
25
+ import { homedir } from 'node:os'
26
+ import { join } from 'node:path'
27
+ import { resolveDshHome } from '@deepseek-ai/dsh-home-paths'
28
+
29
+ /** ACRYL's own root directory name under the OS home. */
30
+ export const ACRYL_HOME_DIR_NAME = '.acryl'
31
+
32
+ /** The DSH engine's directory name, nested under ACRYL's root. */
33
+ export const ACRYL_DSH_ENGINE_DIR_NAME = '.dsh'
34
+
35
+ /** ACRYL's own root — `~/.acryl` unless `$ACRYL_HOME` overrides it. */
36
+ export function resolveAcrylHome(env: Record<string, string | undefined> = process.env): string {
37
+ const overridden = env.ACRYL_HOME?.trim()
38
+ return overridden && overridden !== '' ? overridden : join(homedir(), ACRYL_HOME_DIR_NAME)
39
+ }
40
+
41
+ /**
42
+ * The DSH engine home ACRYL boots against: `$DSH_HOME` if a caller already
43
+ * set it (highest precedence, same as `resolveDshHome()` itself), otherwise
44
+ * `<acrylHome>/.dsh` instead of the harness's own bare `~/.dsh` default.
45
+ */
46
+ export function resolveAcrylDshHome(env: Record<string, string | undefined> = process.env): string {
47
+ const overridden = env.DSH_HOME?.trim()
48
+ if (overridden && overridden !== '') return resolveDshHome(undefined, env)
49
+ return resolveDshHome(join(resolveAcrylHome(env), ACRYL_DSH_ENGINE_DIR_NAME), env)
50
+ }
@@ -14,6 +14,16 @@ export {
14
14
  type AcrylSessionBridge,
15
15
  type AcrylSessionBridgeOptions,
16
16
  } from './session-bridge.ts'
17
+ export {
18
+ installSessionLogExporter,
19
+ type InstallSessionLogExporterOptions,
20
+ } from './session-log-exporter.ts'
21
+ export {
22
+ ACRYL_DSH_ENGINE_DIR_NAME,
23
+ ACRYL_HOME_DIR_NAME,
24
+ resolveAcrylDshHome,
25
+ resolveAcrylHome,
26
+ } from './acryl-home.ts'
17
27
 
18
28
  import { writeFileSync } from 'node:fs'
19
29
  import { createRequire } from 'node:module'
@@ -30,8 +40,10 @@ import {
30
40
  resolveProfileDir,
31
41
  } from '@deepseek-ai/dsh-app-boot'
32
42
 
43
+ import { resolveAcrylDshHome } from './acryl-home.ts'
33
44
  import { createAcrylCodingCapabilityPatches } from './coding-capabilities.ts'
34
45
  import { installAcrylWorkspaceStatusTool } from './plugin-acryl-workspace-status.ts'
46
+ import { installSessionLogExporter } from './session-log-exporter.ts'
35
47
 
36
48
  const require = createRequire(import.meta.url)
37
49
  const dshInstallAnchor = require.resolve('@deepseek-ai/dsh/package.json')
@@ -53,6 +65,7 @@ export async function bootAcrylHarnessProfile(
53
65
  options: BootAcrylHarnessProfileOptions,
54
66
  ): Promise<AcrylHarnessRuntime> {
55
67
  if (options.profile.trim() === '') throw new Error('ACRYL Harness profile must not be empty')
68
+ process.env.DSH_HOME = resolveAcrylDshHome()
56
69
  const profileDirectory = resolveProfileDir(options.profile)
57
70
  initProfile(profileDirectory, DEFAULT_PROFILE_BUNDLES)
58
71
  healProfilesModuleFallback(dshInstallAnchor)
@@ -72,6 +85,7 @@ export async function bootAcrylHarnessProfile(
72
85
  }
73
86
  const ctx = await boot('acryl', rootConfig, patches, options.prepare)
74
87
  if ((ctx as { tools?: unknown }).tools) installAcrylWorkspaceStatusTool(ctx)
88
+ installSessionLogExporter(ctx, { surface: 'tui' })
75
89
  let disposed = false
76
90
  return Object.freeze({
77
91
  ctx,
@@ -110,6 +124,7 @@ export async function bootAcrylWebProfile(
110
124
  options: BootAcrylWebProfileOptions = {},
111
125
  ): Promise<AcrylWebRuntime> {
112
126
  const profileName = 'web'
127
+ process.env.DSH_HOME = resolveAcrylDshHome()
113
128
  const profileDirectory = resolveProfileDir(profileName)
114
129
  initProfile(profileDirectory, DEFAULT_PROFILE_BUNDLES)
115
130
  healProfilesModuleFallback(dshInstallAnchor)
@@ -127,6 +142,7 @@ export async function bootAcrylWebProfile(
127
142
  return options.prepare?.(hostCtx)
128
143
  })
129
144
  if ((ctx as { tools?: unknown }).tools) installAcrylWorkspaceStatusTool(ctx)
145
+ installSessionLogExporter(ctx, { surface: 'web' })
130
146
  const startup = ctx.get('webStartup') as { host?: string; port?: number } | undefined
131
147
  const host = startup?.host ?? '127.0.0.1'
132
148
  const port = startup?.port ?? 3080
@@ -1,5 +1,5 @@
1
1
  import type { Context } from '@deepseek-ai/cordis'
2
- import type { Agent, AgentHandle } from '@deepseek-ai/dsh-agent'
2
+ import { installModelSelection, type Agent, type AgentHandle, type ModelSelectionRef } from '@deepseek-ai/dsh-agent'
3
3
  import { createUserMessage } from '@deepseek-ai/dsh-llm'
4
4
  import { SessionId, type SessionEvent } from '@deepseek-ai/dsh-session'
5
5
  import type {} from '@deepseek-ai/dsh-agent-default-model'
@@ -39,6 +39,15 @@ export interface AcrylSessionBridge {
39
39
  listener: (event: SessionEvent) => void,
40
40
  ): Promise<AcrylSessionEventSubscription>
41
41
  submitPrompt(input: { readonly sessionId: string; readonly text: string }): Promise<void>
42
+ /**
43
+ * Switch an already-open session's live model. `agentOptions.provider`/`model`
44
+ * are a one-time construction input to `ctx.agents.create` — not a live
45
+ * setting — so this is the only thing that changes what a running session
46
+ * sends its next request to (`ModelSelectionRef` installed on the agent's
47
+ * own scoped context via `installModelSelection`, per-step prompt assembly
48
+ * reads it fresh).
49
+ */
50
+ selectModel(input: { readonly sessionId: string; readonly provider: string; readonly model: string }): Promise<void>
42
51
  cancel(sessionId: string): Promise<void>
43
52
  dispose(): Promise<void>
44
53
  }
@@ -101,6 +110,8 @@ export function createAcrylSessionBridge(
101
110
  options: AcrylSessionBridgeOptions,
102
111
  ): AcrylSessionBridge {
103
112
  const handles = new Map<string, AgentHandle>()
113
+ const modelSelections = new Map<string, ModelSelectionRef>()
114
+ const modelSelectionDisposers = new Map<string, () => void>()
104
115
  const subscribers = new Map<string, Set<(snapshot: AcrylSessionSnapshot) => void>>()
105
116
  const eventListeners = new Map<string, Set<(event: SessionEvent) => void>>()
106
117
  let disposed = false
@@ -170,6 +181,9 @@ export function createAcrylSessionBridge(
170
181
  agentOptions: { provider: selection.provider, model: selection.model },
171
182
  })
172
183
  handles.set(handle.agent.id, handle)
184
+ const ref: ModelSelectionRef = { current: undefined, assembled: undefined }
185
+ modelSelectionDisposers.set(handle.agent.id, installModelSelection(handle.agent.ctx, ref))
186
+ modelSelections.set(handle.agent.id, ref)
173
187
  return handle.agent.id
174
188
  },
175
189
  snapshot,
@@ -235,6 +249,12 @@ export function createAcrylSessionBridge(
235
249
  }))
236
250
  await accepted
237
251
  },
252
+ async selectModel(input: { readonly sessionId: string; readonly provider: string; readonly model: string }): Promise<void> {
253
+ agentFor(input.sessionId)
254
+ const ref = modelSelections.get(input.sessionId)
255
+ if (ref === undefined) throw new Error(`ACRYL session ${input.sessionId} has no installed model selection`)
256
+ ref.current = { provider: input.provider, model: input.model }
257
+ },
238
258
  async cancel(sessionId: string): Promise<void> {
239
259
  agentFor(sessionId).cancel({ kind: 'user' })
240
260
  },
@@ -244,6 +264,9 @@ export function createAcrylSessionBridge(
244
264
  offSessionEvent()
245
265
  subscribers.clear()
246
266
  eventListeners.clear()
267
+ for (const dispose of modelSelectionDisposers.values()) dispose()
268
+ modelSelectionDisposers.clear()
269
+ modelSelections.clear()
247
270
  // Durable continuity: idle the turn, checkpoint the session log, then
248
271
  // release the native handle. Mirror of Tomo's shutdown sequence, owned
249
272
  // here so every surface gets the same durability guarantee.
@@ -0,0 +1,69 @@
1
+ /**
2
+ * A durable, cross-surface debug log for ACRYL sessions.
3
+ *
4
+ * Cordis's own `ctx.logger` already carries every structured log record any
5
+ * plugin emits, but nothing in this stack persisted it anywhere — a failure
6
+ * like a vendored provider's OAuth error only ever reached the terminal
7
+ * transcript, gone the moment the pane scrolled or the process exited. This
8
+ * registers one JSONL file exporter on `ctx.logger` per surface (CLI/TUI,
9
+ * web, and — once wired there too — the desktop GUI), so `error`/`warn`
10
+ * records (and everything, when `ACRYL_LOG_LEVEL=debug`) survive the session
11
+ * and can be read back after the fact instead of re-derived from a
12
+ * screenshot.
13
+ *
14
+ * @module acryl-harness-runtime/session-log-exporter
15
+ */
16
+
17
+ import { appendFileSync, mkdirSync } from 'node:fs'
18
+ import { join } from 'node:path'
19
+ import type { Context } from '@deepseek-ai/cordis'
20
+ import { resolveDshHome } from '@deepseek-ai/dsh-home-paths'
21
+
22
+ export interface InstallSessionLogExporterOptions {
23
+ /** Which ACRYL surface this process is (`tui`, `web`, `desktop`) — becomes part of the log filename. */
24
+ readonly surface: string
25
+ /** Harness home override; defaults to `$DSH_HOME`/`~/.dsh` exactly like credentials/settings resolve it. */
26
+ readonly dshHome?: string
27
+ }
28
+
29
+ const LOGGER_LEVEL_WARN = 2
30
+ const LOGGER_LEVEL_DEBUG = 3
31
+
32
+ /**
33
+ * Register the file exporter for this process's lifetime. `ctx.logger.exporter()`
34
+ * already ties its own disposal to the fiber that registers it (see
35
+ * `LoggerService.exporter` in `@deepseek-ai/cordis`), so this needs no extra
36
+ * `ctx.effect()` wrapper of its own.
37
+ * @param ctx - the plugin context whose `ctx.logger` gains the exporter.
38
+ * @param options - which surface/home this log file belongs to.
39
+ */
40
+ export function installSessionLogExporter(ctx: Context, options: InstallSessionLogExporterOptions): void {
41
+ const dshHome = resolveDshHome(options.dshHome)
42
+ const logDir = join(dshHome, 'logs')
43
+ try {
44
+ mkdirSync(logDir, { recursive: true, mode: 0o700 })
45
+ } catch {
46
+ return // best-effort diagnostics only; a session must never fail to start over this
47
+ }
48
+ const day = new Date().toISOString().slice(0, 10)
49
+ const logFile = join(logDir, `acryl-${options.surface}-${day}.jsonl`)
50
+ const threshold = process.env.ACRYL_LOG_LEVEL === 'debug' ? LOGGER_LEVEL_DEBUG : LOGGER_LEVEL_WARN
51
+
52
+ ctx.logger.exporter({
53
+ export(message) {
54
+ if (message.level > threshold) return
55
+ try {
56
+ appendFileSync(logFile, `${JSON.stringify({
57
+ ts: new Date(message.ts).toISOString(),
58
+ type: message.type,
59
+ name: message.name,
60
+ args: message.args.map(arg => (arg instanceof Error
61
+ ? { message: arg.message, stack: arg.stack, name: arg.name }
62
+ : arg)),
63
+ })}\n`, { mode: 0o600 })
64
+ } catch {
65
+ // best-effort: a write failure here must never take a session down
66
+ }
67
+ },
68
+ })
69
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
- "name": "acryl-tui",
3
- "version": "0.1.31",
2
+ "name": "acryl-cli",
3
+ "version": "0.1.36",
4
4
  "private": true,
5
5
  "description": "Canonical ACRYL pi-tui coding-agent terminal client",
6
6
  "license": "MIT",
@@ -25,7 +25,9 @@
25
25
  "node": ">=22"
26
26
  },
27
27
  "scripts": {
28
+ "prebuild": "node scripts/generate-build-info.mjs",
28
29
  "build": "tsdown && tsc -p tsconfig.json --emitDeclarationOnly",
30
+ "pretypecheck": "node scripts/generate-build-info.mjs",
29
31
  "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.tests.json --noEmit",
30
32
  "test": "vitest run",
31
33
  "tui": "node lib/bin.js tui",
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "surface": "cli",
4
4
  "target": "linux-x64",
5
- "version": "0.1.31",
5
+ "version": "0.1.36",
6
6
  "packageName": "acryl-cli-linux-x64",
7
- "payloadSha256": "28662eabe7bc951c7b2dcc1f038eaffedaa340ee1a8ff8ad721fea5f95d501c9"
7
+ "payloadSha256": "99de89fd03be2f9ae485b5d15b52063a909928eaff7c9330f178d98e55a22a32"
8
8
  }