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
@@ -1,4 +1,4 @@
1
- const LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" aria-hidden="true"><path fill="#fff" fill-rule="evenodd" d="M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z"/><path fill="#fff" d="M517.36 400 H634.72 V634.72 H517.36 Z"/></svg>`;
1
+ const LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" aria-hidden="true"><g fill="#fff"><rect x="76.0" y="418.0" width="10.5" height="10.5"/><rect x="88.0" y="394.0" width="10.5" height="10.5"/><rect x="88.0" y="406.0" width="10.5" height="10.5"/><rect x="88.0" y="418.0" width="10.5" height="10.5"/><rect x="100.0" y="382.0" width="10.5" height="10.5"/><rect x="100.0" y="394.0" width="10.5" height="10.5"/><rect x="112.0" y="370.0" width="10.5" height="10.5"/><rect x="112.0" y="382.0" width="10.5" height="10.5"/><rect x="124.0" y="358.0" width="10.5" height="10.5"/><rect x="124.0" y="370.0" width="10.5" height="10.5"/><rect x="136.0" y="346.0" width="10.5" height="10.5"/><rect x="136.0" y="358.0" width="10.5" height="10.5"/><rect x="148.0" y="334.0" width="10.5" height="10.5"/><rect x="148.0" y="346.0" width="10.5" height="10.5"/><rect x="160.0" y="334.0" width="10.5" height="10.5"/><rect x="160.0" y="346.0" width="10.5" height="10.5"/><rect x="160.0" y="358.0" width="10.5" height="10.5"/><rect x="172.0" y="358.0" width="10.5" height="10.5"/><rect x="184.0" y="370.0" width="10.5" height="10.5"/><rect x="196.0" y="370.0" width="10.5" height="10.5"/><rect x="196.0" y="382.0" width="10.5" height="10.5"/><rect x="196.0" y="394.0" width="10.5" height="10.5"/><rect x="208.0" y="370.0" width="10.5" height="10.5"/><rect x="208.0" y="406.0" width="10.5" height="10.5"/><rect x="220.0" y="370.0" width="10.5" height="10.5"/><rect x="220.0" y="418.0" width="10.5" height="10.5"/><rect x="232.0" y="370.0" width="10.5" height="10.5"/><rect x="244.0" y="370.0" width="10.5" height="10.5"/><rect x="256.0" y="358.0" width="10.5" height="10.5"/><rect x="256.0" y="370.0" width="10.5" height="10.5"/><rect x="256.0" y="382.0" width="10.5" height="10.5"/><rect x="268.0" y="346.0" width="10.5" height="10.5"/><rect x="268.0" y="382.0" width="10.5" height="10.5"/><rect x="268.0" y="394.0" width="10.5" height="10.5"/><rect x="280.0" y="334.0" width="10.5" height="10.5"/><rect x="280.0" y="394.0" width="10.5" height="10.5"/><rect x="280.0" y="406.0" width="10.5" height="10.5"/><rect x="280.0" y="418.0" width="10.5" height="10.5"/><rect x="292.0" y="310.0" width="10.5" height="10.5"/><rect x="292.0" y="418.0" width="10.5" height="10.5"/><rect x="292.0" y="430.0" width="10.5" height="10.5"/><rect x="304.0" y="298.0" width="10.5" height="10.5"/><rect x="304.0" y="430.0" width="10.5" height="10.5"/><rect x="304.0" y="442.0" width="10.5" height="10.5"/><rect x="316.0" y="286.0" width="10.5" height="10.5"/><rect x="316.0" y="442.0" width="10.5" height="10.5"/><rect x="328.0" y="274.0" width="10.5" height="10.5"/><rect x="328.0" y="442.0" width="10.5" height="10.5"/><rect x="340.0" y="274.0" width="10.5" height="10.5"/><rect x="340.0" y="442.0" width="10.5" height="10.5"/><rect x="352.0" y="274.0" width="10.5" height="10.5"/><rect x="352.0" y="310.0" width="10.5" height="10.5"/><rect x="352.0" y="442.0" width="10.5" height="10.5"/><rect x="364.0" y="274.0" width="10.5" height="10.5"/><rect x="364.0" y="310.0" width="10.5" height="10.5"/><rect x="364.0" y="442.0" width="10.5" height="10.5"/><rect x="376.0" y="274.0" width="10.5" height="10.5"/><rect x="376.0" y="310.0" width="10.5" height="10.5"/><rect x="376.0" y="442.0" width="10.5" height="10.5"/><rect x="388.0" y="274.0" width="10.5" height="10.5"/><rect x="388.0" y="310.0" width="10.5" height="10.5"/><rect x="388.0" y="358.0" width="10.5" height="10.5"/><rect x="388.0" y="442.0" width="10.5" height="10.5"/><rect x="400.0" y="274.0" width="10.5" height="10.5"/><rect x="400.0" y="310.0" width="10.5" height="10.5"/><rect x="400.0" y="358.0" width="10.5" height="10.5"/><rect x="400.0" y="370.0" width="10.5" height="10.5"/><rect x="400.0" y="442.0" width="10.5" height="10.5"/><rect x="412.0" y="274.0" width="10.5" height="10.5"/><rect x="412.0" y="310.0" width="10.5" height="10.5"/><rect x="412.0" y="358.0" width="10.5" height="10.5"/><rect x="412.0" y="382.0" width="10.5" height="10.5"/><rect x="412.0" y="394.0" width="10.5" height="10.5"/><rect x="412.0" y="442.0" width="10.5" height="10.5"/><rect x="424.0" y="310.0" width="10.5" height="10.5"/><rect x="424.0" y="358.0" width="10.5" height="10.5"/><rect x="424.0" y="406.0" width="10.5" height="10.5"/><rect x="424.0" y="442.0" width="10.5" height="10.5"/><rect x="436.0" y="310.0" width="10.5" height="10.5"/><rect x="436.0" y="358.0" width="10.5" height="10.5"/><rect x="436.0" y="406.0" width="10.5" height="10.5"/><rect x="448.0" y="310.0" width="10.5" height="10.5"/><rect x="448.0" y="322.0" width="10.5" height="10.5"/><rect x="448.0" y="346.0" width="10.5" height="10.5"/><rect x="448.0" y="358.0" width="10.5" height="10.5"/><rect x="448.0" y="430.0" width="10.5" height="10.5"/><rect x="460.0" y="334.0" width="10.5" height="10.5"/><rect x="460.0" y="442.0" width="10.5" height="10.5"/><rect x="472.0" y="442.0" width="10.5" height="10.5"/><rect x="484.0" y="454.0" width="10.5" height="10.5"/><rect x="484.0" y="466.0" width="10.5" height="10.5"/><rect x="496.0" y="478.0" width="10.5" height="10.5"/><rect x="508.0" y="478.0" width="10.5" height="10.5"/><rect x="508.0" y="490.0" width="10.5" height="10.5"/><rect x="508.0" y="502.0" width="10.5" height="10.5"/><rect x="508.0" y="514.0" width="10.5" height="10.5"/><rect x="520.0" y="478.0" width="10.5" height="10.5"/><rect x="532.0" y="454.0" width="10.5" height="10.5"/><rect x="532.0" y="466.0" width="10.5" height="10.5"/><rect x="544.0" y="442.0" width="10.5" height="10.5"/><rect x="556.0" y="442.0" width="10.5" height="10.5"/><rect x="568.0" y="430.0" width="10.5" height="10.5"/><rect x="580.0" y="418.0" width="10.5" height="10.5"/><rect x="580.0" y="430.0" width="10.5" height="10.5"/><rect x="592.0" y="406.0" width="10.5" height="10.5"/><rect x="592.0" y="418.0" width="10.5" height="10.5"/><rect x="592.0" y="430.0" width="10.5" height="10.5"/><rect x="604.0" y="394.0" width="10.5" height="10.5"/><rect x="604.0" y="406.0" width="10.5" height="10.5"/><rect x="604.0" y="430.0" width="10.5" height="10.5"/><rect x="616.0" y="382.0" width="10.5" height="10.5"/><rect x="616.0" y="430.0" width="10.5" height="10.5"/><rect x="628.0" y="370.0" width="10.5" height="10.5"/><rect x="628.0" y="430.0" width="10.5" height="10.5"/><rect x="640.0" y="430.0" width="10.5" height="10.5"/><rect x="652.0" y="430.0" width="10.5" height="10.5"/><rect x="664.0" y="430.0" width="10.5" height="10.5"/><rect x="676.0" y="430.0" width="10.5" height="10.5"/><rect x="688.0" y="430.0" width="10.5" height="10.5"/><rect x="700.0" y="430.0" width="10.5" height="10.5"/><rect x="712.0" y="430.0" width="10.5" height="10.5"/></g></svg>`;
2
2
  function escapeHtml(value) {
3
3
  return value
4
4
  .replaceAll("&", "&amp;")
@@ -348,6 +348,71 @@ function parseAcrylSessionSnapshot(value) {
348
348
  });
349
349
  }
350
350
  //#endregion
351
+ //#region src/credential/projection.ts
352
+ /** Read a nested value out of an untyped resolved/raw settings section. */
353
+ function getAtPath(value, path) {
354
+ let current = value;
355
+ for (const key of path) {
356
+ if (current === null || typeof current !== "object") return void 0;
357
+ current = current[key];
358
+ }
359
+ return current;
360
+ }
361
+ /** Compute every provider route's joined credential/settings/live-registration row. */
362
+ async function computeCredentialProjection(services, options) {
363
+ const scope = options.loginRecordScope ?? "llm-pi-ai";
364
+ const configurable = services.llm.listConfigurableProviders();
365
+ const live = new Set(services.llm.listProviders().map((provider) => provider.id));
366
+ const descriptors = services.settings.describe({ redactSecrets: true });
367
+ const byNs = new Map(descriptors.map((descriptor) => [descriptor.ns, descriptor]));
368
+ const rows = [];
369
+ for (const entry of configurable) {
370
+ const descriptor = byNs.get(entry.settingsNs);
371
+ const value = descriptor === void 0 ? void 0 : getAtPath(descriptor.value, entry.settingsPath);
372
+ const userValue = descriptor === void 0 ? void 0 : getAtPath(descriptor.user, entry.settingsPath);
373
+ const apiKeyRef = value?.apiKeyEnv ?? options.deriveApiKeyRef(entry.provider);
374
+ const info = await services.credentials.describe(apiKeyRef);
375
+ const loginRecord = await services.credentials.readRecord?.(`${scope}/${entry.provider}`);
376
+ const authMethod = loginRecord !== void 0 ? loginRecord.kind === "grant" ? "oauth" : "api-key" : info.configured ? "api-key" : void 0;
377
+ const isLive = live.has(entry.provider);
378
+ const models = isLive ? await services.llm.listModels(entry.provider).catch(() => value?.models ?? []) : value?.models ?? [];
379
+ rows.push({
380
+ route: entry.provider,
381
+ displayName: value?.displayName ?? entry.displayName,
382
+ settingsNs: entry.settingsNs,
383
+ settingsPath: entry.settingsPath,
384
+ hasSettingsProfile: userValue !== void 0,
385
+ hasCredential: info.configured || loginRecord !== void 0,
386
+ isLive,
387
+ authMethod,
388
+ api: value?.api,
389
+ baseURL: value?.baseURL,
390
+ apiKeyRef,
391
+ models,
392
+ revision: descriptor?.revision
393
+ });
394
+ }
395
+ return rows;
396
+ }
397
+ //#endregion
398
+ //#region src/authorization/service.ts
399
+ var AuthorizationService = class {
400
+ deps;
401
+ constructor(deps) {
402
+ this.deps = deps;
403
+ }
404
+ async begin(request) {
405
+ const outcome = await this.deps.authorization.begin(request);
406
+ if (outcome.status === "authorized") {
407
+ const slash = request.key.indexOf("/");
408
+ const providerId = slash === -1 ? request.key : request.key.slice(slash + 1);
409
+ if (this.deps.routeActivation !== void 0) await this.deps.routeActivation.ensureRouteActivated(providerId, request.method);
410
+ this.deps.onCredentialChanged(request.key);
411
+ }
412
+ return outcome;
413
+ }
414
+ };
415
+ //#endregion
351
416
  //#region src/lifecycle/controller.ts
352
417
  var PluginLifecycleError = class extends Error {
353
418
  code;
@@ -530,6 +595,6 @@ function nonEmptyCommandId(value) {
530
595
  return value;
531
596
  }
532
597
  //#endregion
533
- export { AcrAgentControlError, AcrAgentControlService, AcrPluginLifecycleController, AcrPluginLifecycleService, AcrRuntimeArchitectureService, Context, PROVIDER_CAPABILITIES, PluginLifecycleError, Service, acpProvider, claudeProvider, codexProvider, createAcrylSessionClient, createProviderPlugin, dshNativeProvider, parseAcrylSessionSnapshot, projectRuntimeArchitecture };
598
+ export { AcrAgentControlError, AcrAgentControlService, AcrPluginLifecycleController, AcrPluginLifecycleService, AcrRuntimeArchitectureService, AuthorizationService, Context, PROVIDER_CAPABILITIES, PluginLifecycleError, Service, acpProvider, claudeProvider, codexProvider, computeCredentialProjection, createAcrylSessionClient, createProviderPlugin, dshNativeProvider, parseAcrylSessionSnapshot, projectRuntimeArchitecture };
534
599
 
535
600
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `AuthorizationService`: the `beginAuthorization` use-case, owned outside
3
+ * the terminal surface (finding R1). Forwards to the underlying
4
+ * `AuthorizationPort`, activates the route on success (no display-name
5
+ * encoding — the `-oauth` suffix hack is deleted, not moved; see finding R2),
6
+ * and emits `credential.changed` so subscribers can invalidate a targeted
7
+ * projection instead of a shotgun dual reload (finding R4).
8
+ *
9
+ * Framework-free: every dependency is a plain port, so this is directly
10
+ * unit-testable with stubs — no Cordis boot required.
11
+ */
12
+ import type { AuthorizationBeginRequest, AuthorizationOutcome, AuthorizationPort, CredentialChangedListener, RouteActivationPort } from './types.ts';
13
+ export interface AuthorizationServiceDeps {
14
+ readonly authorization: AuthorizationPort;
15
+ /** Optional: absent profiles degrade to "not available" (matches the surface's own optional-service pattern). */
16
+ readonly routeActivation?: RouteActivationPort;
17
+ readonly onCredentialChanged: CredentialChangedListener;
18
+ }
19
+ export declare class AuthorizationService {
20
+ private readonly deps;
21
+ constructor(deps: AuthorizationServiceDeps);
22
+ begin(request: AuthorizationBeginRequest): Promise<AuthorizationOutcome>;
23
+ }
@@ -0,0 +1,46 @@
1
+ /** Typed authorization use-case contract: `/login`'s `beginAuthorization` action. */
2
+ /** A question the running flow needs answered before it can continue (opaque pass-through). */
3
+ export interface AuthorizationInteraction {
4
+ notify(notice: {
5
+ readonly message: string;
6
+ readonly url?: string;
7
+ readonly code?: string;
8
+ }): void;
9
+ prompt(prompt: {
10
+ readonly kind: 'text' | 'secret' | 'select';
11
+ readonly message: string;
12
+ readonly options?: readonly {
13
+ readonly id: string;
14
+ readonly label: string;
15
+ readonly description?: string;
16
+ }[];
17
+ readonly placeholder?: string;
18
+ readonly signal?: AbortSignal;
19
+ }): Promise<string>;
20
+ }
21
+ export interface AuthorizationBeginRequest {
22
+ /** The credential record key this flow writes (`<scope>/<id>`). */
23
+ readonly key: string;
24
+ readonly method: string | undefined;
25
+ readonly interaction: AuthorizationInteraction;
26
+ }
27
+ export interface AuthorizationOutcome {
28
+ readonly status: string;
29
+ readonly [extra: string]: unknown;
30
+ }
31
+ /** The underlying pi-ai (or equivalent) authorization flow runner. */
32
+ export interface AuthorizationPort {
33
+ begin(request: AuthorizationBeginRequest): Promise<AuthorizationOutcome>;
34
+ }
35
+ /**
36
+ * Make a route usable from `/model` after a successful sign-in: `dsh-llm-pi-ai`
37
+ * only registers a route as live once its settings section names it (even an
38
+ * empty profile — "use the installed catalog's defaults" — counts), so a
39
+ * signed-in-but-never-configured route stays credentialed but absent from
40
+ * `ctx.llm`'s live directory otherwise. A no-op when the route already has a
41
+ * settings profile, so it never clobbers one the user already configured.
42
+ */
43
+ export interface RouteActivationPort {
44
+ ensureRouteActivated(providerId: string, method: string | undefined): Promise<void>;
45
+ }
46
+ export type CredentialChangedListener = (key: string) => void;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * `CredentialProjection`: the one join of `ctx.llm` + `ctx.settings` +
3
+ * `ctx.credentials` into provider rows. Replaces the surface's own
4
+ * `computeProviderRows`/`loadAuthorizationFlows` duplicate joins (finding R1)
5
+ * so `/login` and `/model` read the same projection instead of two
6
+ * independently-fetched pictures of the same state.
7
+ *
8
+ * Framework-free: takes plain service ports (not a Cordis `Context`), so it
9
+ * is directly unit-testable with stubs — no Cordis boot required. The
10
+ * surface wires these ports from `host.ctx.get(...)`.
11
+ */
12
+ import type { CredentialModelEntry, CredentialProjectionRow } from './types.ts';
13
+ export interface CredentialProjectionConfigurableProvider {
14
+ readonly provider: string;
15
+ readonly displayName: string;
16
+ readonly settingsNs: string;
17
+ readonly settingsPath: readonly string[];
18
+ }
19
+ export interface CredentialProjectionLlmPort {
20
+ listConfigurableProviders(): readonly CredentialProjectionConfigurableProvider[];
21
+ listProviders(): readonly {
22
+ readonly id: string;
23
+ }[];
24
+ listModels(provider: string): Promise<readonly CredentialModelEntry[]>;
25
+ }
26
+ export interface CredentialProjectionSettingsDescriptor {
27
+ readonly ns: string;
28
+ readonly value: unknown;
29
+ readonly user?: unknown;
30
+ readonly revision?: number;
31
+ }
32
+ export interface CredentialProjectionSettingsPort {
33
+ describe(options: {
34
+ readonly redactSecrets: boolean;
35
+ }): readonly CredentialProjectionSettingsDescriptor[];
36
+ }
37
+ export interface CredentialProjectionRecord {
38
+ readonly kind: string;
39
+ }
40
+ export interface CredentialProjectionCredentialsPort {
41
+ describe(ref: string): Promise<{
42
+ readonly configured: boolean;
43
+ }>;
44
+ readRecord?(key: string): Promise<CredentialProjectionRecord | undefined>;
45
+ }
46
+ export interface CredentialProjectionServices {
47
+ readonly llm: CredentialProjectionLlmPort;
48
+ readonly settings: CredentialProjectionSettingsPort;
49
+ readonly credentials: CredentialProjectionCredentialsPort;
50
+ }
51
+ export interface CredentialProjectionOptions {
52
+ /** Derive the settings-fallback `apiKeyEnv` reference for a route with no stored profile. */
53
+ readonly deriveApiKeyRef: (route: string) => string;
54
+ /**
55
+ * The scope a `/login` sign-in's own credential record lives under
56
+ * (`dsh-llm-pi-ai`'s `credentialKey(scope, providerId)`), separate from the
57
+ * `apiKeyEnv` reference a manually configured provider's settings profile
58
+ * points at. Defaults to `'llm-pi-ai'`.
59
+ */
60
+ readonly loginRecordScope?: string;
61
+ }
62
+ /** Compute every provider route's joined credential/settings/live-registration row. */
63
+ export declare function computeCredentialProjection(services: CredentialProjectionServices, options: CredentialProjectionOptions): Promise<readonly CredentialProjectionRow[]>;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Typed read-model for a provider route's credential/authorization state.
3
+ * `/login` and `/model` (in `acryl-cli`) both consume the *same* projection —
4
+ * this is the one place `hasCredential`/`hasSettingsProfile`/`isLive`/
5
+ * `authMethod` are defined, so no field means two different things across
6
+ * the two overlays (see `specs/001-acryl-refactor-improvements-and-tech-debt`,
7
+ * finding R3).
8
+ */
9
+ /** How a stored credential was obtained. */
10
+ export type AuthMethod = 'oauth' | 'api-key';
11
+ /** One model entry within a provider's catalog (subset shared with the surface's own `ModelEntry`). */
12
+ export interface CredentialModelEntry {
13
+ readonly id: string;
14
+ readonly name?: string;
15
+ readonly contextWindow?: number;
16
+ readonly maxTokens?: number;
17
+ }
18
+ /** One provider route, joined from `ctx.llm` + `ctx.settings` + `ctx.credentials`. */
19
+ export interface CredentialProjectionRow {
20
+ readonly route: string;
21
+ readonly displayName: string;
22
+ readonly settingsNs: string;
23
+ readonly settingsPath: readonly string[];
24
+ /** Whether the user's settings document has an override for this route (a `/model` save, not a `/login` sign-in). */
25
+ readonly hasSettingsProfile: boolean;
26
+ /** Whether a credential — an `apiKeyEnv` secret or a `/login` record — is stored for this route. */
27
+ readonly hasCredential: boolean;
28
+ /** Whether the route is currently registered/live in `ctx.llm`. */
29
+ readonly isLive: boolean;
30
+ /** How the credential behind `hasCredential` was obtained, or `undefined` when none is configured. */
31
+ readonly authMethod: AuthMethod | undefined;
32
+ readonly api: string | undefined;
33
+ readonly baseURL: string | undefined;
34
+ readonly apiKeyRef: string;
35
+ readonly models: readonly CredentialModelEntry[];
36
+ /** Settings revision this row was read at; replay as `expectedRevision` on write. */
37
+ readonly revision: number | undefined;
38
+ }
@@ -10,6 +10,10 @@ export * from './architecture/projection.ts';
10
10
  export * from './architecture/provider.ts';
11
11
  export * from './contracts/operations.ts';
12
12
  export * from './contracts/session.ts';
13
+ export * from './credential/types.ts';
14
+ export * from './credential/projection.ts';
15
+ export * from './authorization/types.ts';
16
+ export * from './authorization/service.ts';
13
17
  export * from './events.ts';
14
18
  export * from './lifecycle/controller.ts';
15
19
  export * from './lifecycle/provider.ts';
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acryl-control",
3
- "version": "0.1.31",
3
+ "version": "0.1.36",
4
4
  "private": false,
5
5
  "description": "Host-neutral Cordis control plane for ACRYL",
6
6
  "license": "MIT",
@@ -1,9 +1,12 @@
1
1
  import { createRequire } from "node:module";
2
- import { existsSync, writeFileSync } from "node:fs";
2
+ import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
+ import { installModelSelection } from "@deepseek-ai/dsh-agent";
5
6
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
6
7
  import { SessionId } from "@deepseek-ai/dsh-session";
8
+ import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
9
+ import { homedir } from "node:os";
7
10
  import { provideCmdline } from "@deepseek-ai/dsh-cmdline";
8
11
  import { DEFAULT_PROFILE_BUNDLES, boot, composeEntries, healProfilesModuleFallback, initProfile, loadProfile, resolveProfileDir } from "@deepseek-ai/dsh-app-boot";
9
12
  import { defineTool } from "@deepseek-ai/dsh-tools";
@@ -114,6 +117,8 @@ function status(agent) {
114
117
  */
115
118
  function createAcrylSessionBridge(ctx, options) {
116
119
  const handles = /* @__PURE__ */ new Map();
120
+ const modelSelections = /* @__PURE__ */ new Map();
121
+ const modelSelectionDisposers = /* @__PURE__ */ new Map();
117
122
  const subscribers = /* @__PURE__ */ new Map();
118
123
  const eventListeners = /* @__PURE__ */ new Map();
119
124
  let disposed = false;
@@ -175,6 +180,12 @@ function createAcrylSessionBridge(ctx, options) {
175
180
  }
176
181
  });
177
182
  handles.set(handle.agent.id, handle);
183
+ const ref = {
184
+ current: void 0,
185
+ assembled: void 0
186
+ };
187
+ modelSelectionDisposers.set(handle.agent.id, installModelSelection(handle.agent.ctx, ref));
188
+ modelSelections.set(handle.agent.id, ref);
178
189
  return handle.agent.id;
179
190
  },
180
191
  snapshot,
@@ -234,6 +245,15 @@ function createAcrylSessionBridge(ctx, options) {
234
245
  }));
235
246
  await accepted;
236
247
  },
248
+ async selectModel(input) {
249
+ agentFor(input.sessionId);
250
+ const ref = modelSelections.get(input.sessionId);
251
+ if (ref === void 0) throw new Error(`ACRYL session ${input.sessionId} has no installed model selection`);
252
+ ref.current = {
253
+ provider: input.provider,
254
+ model: input.model
255
+ };
256
+ },
237
257
  async cancel(sessionId) {
238
258
  agentFor(sessionId).cancel({ kind: "user" });
239
259
  },
@@ -243,6 +263,9 @@ function createAcrylSessionBridge(ctx, options) {
243
263
  offSessionEvent();
244
264
  subscribers.clear();
245
265
  eventListeners.clear();
266
+ for (const dispose of modelSelectionDisposers.values()) dispose();
267
+ modelSelectionDisposers.clear();
268
+ modelSelections.clear();
246
269
  const activeHandles = [...handles.values()];
247
270
  handles.clear();
248
271
  const sessions = ctx.get("sessions");
@@ -259,6 +282,106 @@ function createAcrylSessionBridge(ctx, options) {
259
282
  });
260
283
  }
261
284
  //#endregion
285
+ //#region src/session-log-exporter.ts
286
+ /**
287
+ * A durable, cross-surface debug log for ACRYL sessions.
288
+ *
289
+ * Cordis's own `ctx.logger` already carries every structured log record any
290
+ * plugin emits, but nothing in this stack persisted it anywhere — a failure
291
+ * like a vendored provider's OAuth error only ever reached the terminal
292
+ * transcript, gone the moment the pane scrolled or the process exited. This
293
+ * registers one JSONL file exporter on `ctx.logger` per surface (CLI/TUI,
294
+ * web, and — once wired there too — the desktop GUI), so `error`/`warn`
295
+ * records (and everything, when `ACRYL_LOG_LEVEL=debug`) survive the session
296
+ * and can be read back after the fact instead of re-derived from a
297
+ * screenshot.
298
+ *
299
+ * @module acryl-harness-runtime/session-log-exporter
300
+ */
301
+ const LOGGER_LEVEL_WARN = 2;
302
+ const LOGGER_LEVEL_DEBUG = 3;
303
+ /**
304
+ * Register the file exporter for this process's lifetime. `ctx.logger.exporter()`
305
+ * already ties its own disposal to the fiber that registers it (see
306
+ * `LoggerService.exporter` in `@deepseek-ai/cordis`), so this needs no extra
307
+ * `ctx.effect()` wrapper of its own.
308
+ * @param ctx - the plugin context whose `ctx.logger` gains the exporter.
309
+ * @param options - which surface/home this log file belongs to.
310
+ */
311
+ function installSessionLogExporter(ctx, options) {
312
+ const logDir = join(resolveDshHome(options.dshHome), "logs");
313
+ try {
314
+ mkdirSync(logDir, {
315
+ recursive: true,
316
+ mode: 448
317
+ });
318
+ } catch {
319
+ return;
320
+ }
321
+ const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
322
+ const logFile = join(logDir, `acryl-${options.surface}-${day}.jsonl`);
323
+ const threshold = process.env.ACRYL_LOG_LEVEL === "debug" ? LOGGER_LEVEL_DEBUG : LOGGER_LEVEL_WARN;
324
+ ctx.logger.exporter({ export(message) {
325
+ if (message.level > threshold) return;
326
+ try {
327
+ appendFileSync(logFile, `${JSON.stringify({
328
+ ts: new Date(message.ts).toISOString(),
329
+ type: message.type,
330
+ name: message.name,
331
+ args: message.args.map((arg) => arg instanceof Error ? {
332
+ message: arg.message,
333
+ stack: arg.stack,
334
+ name: arg.name
335
+ } : arg)
336
+ })}\n`, { mode: 384 });
337
+ } catch {}
338
+ } });
339
+ }
340
+ //#endregion
341
+ //#region src/acryl-home.ts
342
+ /**
343
+ * ACRYL's own data root, nested one level above the DSH engine home it
344
+ * composes.
345
+ *
346
+ * The stock DSH Desktop app (from dshdesktop.com) uses plain `~/.dsh`, and so
347
+ * did every ACRYL surface until now — sharing that root made the two
348
+ * genuinely different products silently share credentials/settings/sessions,
349
+ * which is both confusing and useless for comparing ACRYL against a stock
350
+ * DSH Desktop install side by side. ACRYL now owns `~/.acryl` and nests each
351
+ * engine's artifacts under it by engine name:
352
+ *
353
+ * ```txt
354
+ * ~/.acryl/ ACRYL's own root — everything not engine-specific
355
+ * ~/.acryl/.dsh/ the DSH engine home (what DSH_HOME resolves to)
356
+ * ~/.acryl/.pi/ reserved for a future pi.dev engine
357
+ * ```
358
+ *
359
+ * `$DSH_HOME`, if a caller has already set it explicitly, still wins — this
360
+ * only changes the *default* the harness's own `resolveDshHome()` falls
361
+ * back to when nothing overrides it.
362
+ *
363
+ * @module acryl-harness-runtime/acryl-home
364
+ */
365
+ /** ACRYL's own root directory name under the OS home. */
366
+ const ACRYL_HOME_DIR_NAME = ".acryl";
367
+ /** The DSH engine's directory name, nested under ACRYL's root. */
368
+ const ACRYL_DSH_ENGINE_DIR_NAME = ".dsh";
369
+ /** ACRYL's own root — `~/.acryl` unless `$ACRYL_HOME` overrides it. */
370
+ function resolveAcrylHome(env = process.env) {
371
+ const overridden = env.ACRYL_HOME?.trim();
372
+ return overridden && overridden !== "" ? overridden : join(homedir(), ACRYL_HOME_DIR_NAME);
373
+ }
374
+ /**
375
+ * The DSH engine home ACRYL boots against: `$DSH_HOME` if a caller already
376
+ * set it (highest precedence, same as `resolveDshHome()` itself), otherwise
377
+ * `<acrylHome>/.dsh` instead of the harness's own bare `~/.dsh` default.
378
+ */
379
+ function resolveAcrylDshHome(env = process.env) {
380
+ const overridden = env.DSH_HOME?.trim();
381
+ if (overridden && overridden !== "") return resolveDshHome(void 0, env);
382
+ return resolveDshHome(join(resolveAcrylHome(env), ACRYL_DSH_ENGINE_DIR_NAME), env);
383
+ }
384
+ //#endregion
262
385
  //#region src/plugin-acryl-workspace-status.ts
263
386
  /** Canonical tool name exposed to the model. */
264
387
  const TOOL_NAME = "acryl_workspace_status";
@@ -330,6 +453,7 @@ const profileRoot = "[]\n";
330
453
  /** Boot one normal pinned-Harness ACRYL profile in a single Cordis root. */
331
454
  async function bootAcrylHarnessProfile(options) {
332
455
  if (options.profile.trim() === "") throw new Error("ACRYL Harness profile must not be empty");
456
+ process.env.DSH_HOME = resolveAcrylDshHome();
333
457
  initProfile(resolveProfileDir(options.profile), DEFAULT_PROFILE_BUNDLES);
334
458
  healProfilesModuleFallback(dshInstallAnchor);
335
459
  const profile = loadProfile("acryl", options.profile, dshInstallAnchor);
@@ -343,6 +467,7 @@ async function bootAcrylHarnessProfile(options) {
343
467
  if (composeEntries([patches]).find((entry) => entry.id === "hmr")?.disabled !== true && !process.execArgv.includes("--expose-internals")) throw new Error("ACRYL profile enables Cordis HMR and must be launched with Node --expose-internals");
344
468
  const ctx = await boot("acryl", rootConfig, patches, options.prepare);
345
469
  if (ctx.tools) installAcrylWorkspaceStatusTool(ctx);
470
+ installSessionLogExporter(ctx, { surface: "tui" });
346
471
  let disposed = false;
347
472
  return Object.freeze({
348
473
  ctx,
@@ -363,6 +488,7 @@ async function bootAcrylHarnessProfile(options) {
363
488
  */
364
489
  async function bootAcrylWebProfile(options = {}) {
365
490
  const profileName = "web";
491
+ process.env.DSH_HOME = resolveAcrylDshHome();
366
492
  initProfile(resolveProfileDir(profileName), DEFAULT_PROFILE_BUNDLES);
367
493
  healProfilesModuleFallback(dshInstallAnchor);
368
494
  const profile = loadProfile("web", profileName, dshInstallAnchor);
@@ -384,6 +510,7 @@ async function bootAcrylWebProfile(options = {}) {
384
510
  return options.prepare?.(hostCtx);
385
511
  });
386
512
  if (ctx.tools) installAcrylWorkspaceStatusTool(ctx);
513
+ installSessionLogExporter(ctx, { surface: "web" });
387
514
  const startup = ctx.get("webStartup");
388
515
  const url = `http://${startup?.host ?? "127.0.0.1"}:${startup?.port ?? 3080}`;
389
516
  let disposed = false;
@@ -399,6 +526,6 @@ async function bootAcrylWebProfile(options = {}) {
399
526
  });
400
527
  }
401
528
  //#endregion
402
- export { ACRYL_CODING_CAPABILITIES, bootAcrylHarnessProfile, bootAcrylWebProfile, createAcrylCodingCapabilityPatches, createAcrylSessionBridge };
529
+ export { ACRYL_CODING_CAPABILITIES, ACRYL_DSH_ENGINE_DIR_NAME, ACRYL_HOME_DIR_NAME, bootAcrylHarnessProfile, bootAcrylWebProfile, createAcrylCodingCapabilityPatches, createAcrylSessionBridge, installSessionLogExporter, resolveAcrylDshHome, resolveAcrylHome };
403
530
 
404
531
  //# sourceMappingURL=index.mjs.map
@@ -0,0 +1,35 @@
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
+ /** ACRYL's own root directory name under the OS home. */
25
+ export declare const ACRYL_HOME_DIR_NAME = ".acryl";
26
+ /** The DSH engine's directory name, nested under ACRYL's root. */
27
+ export declare const ACRYL_DSH_ENGINE_DIR_NAME = ".dsh";
28
+ /** ACRYL's own root — `~/.acryl` unless `$ACRYL_HOME` overrides it. */
29
+ export declare function resolveAcrylHome(env?: Record<string, string | undefined>): string;
30
+ /**
31
+ * The DSH engine home ACRYL boots against: `$DSH_HOME` if a caller already
32
+ * set it (highest precedence, same as `resolveDshHome()` itself), otherwise
33
+ * `<acrylHome>/.dsh` instead of the harness's own bare `~/.dsh` default.
34
+ */
35
+ export declare function resolveAcrylDshHome(env?: Record<string, string | undefined>): string;
@@ -1,6 +1,8 @@
1
1
  export { ACRYL_CODING_CAPABILITIES, createAcrylCodingCapabilityPatches, type AcrylCodingCapability, type AcrylSurface, } from './coding-capabilities.ts';
2
2
  export type { DurableSessionMessage, DurableSessionMessagePort, DurableSessionMessageReceipt, } from './durable-message.ts';
3
3
  export { createAcrylSessionBridge, type AcrylSessionBridge, type AcrylSessionBridgeOptions, } from './session-bridge.ts';
4
+ export { installSessionLogExporter, type InstallSessionLogExporterOptions, } from './session-log-exporter.ts';
5
+ export { ACRYL_DSH_ENGINE_DIR_NAME, ACRYL_HOME_DIR_NAME, resolveAcrylDshHome, resolveAcrylHome, } from './acryl-home.ts';
4
6
  import type { Context } from '@deepseek-ai/cordis';
5
7
  export interface BootAcrylHarnessProfileOptions {
6
8
  readonly profile: string;
@@ -22,6 +22,19 @@ export interface AcrylSessionBridge {
22
22
  readonly sessionId: string;
23
23
  readonly text: string;
24
24
  }): Promise<void>;
25
+ /**
26
+ * Switch an already-open session's live model. `agentOptions.provider`/`model`
27
+ * are a one-time construction input to `ctx.agents.create` — not a live
28
+ * setting — so this is the only thing that changes what a running session
29
+ * sends its next request to (`ModelSelectionRef` installed on the agent's
30
+ * own scoped context via `installModelSelection`, per-step prompt assembly
31
+ * reads it fresh).
32
+ */
33
+ selectModel(input: {
34
+ readonly sessionId: string;
35
+ readonly provider: string;
36
+ readonly model: string;
37
+ }): Promise<void>;
25
38
  cancel(sessionId: string): Promise<void>;
26
39
  dispose(): Promise<void>;
27
40
  }
@@ -0,0 +1,31 @@
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
+ import type { Context } from '@deepseek-ai/cordis';
17
+ export interface InstallSessionLogExporterOptions {
18
+ /** Which ACRYL surface this process is (`tui`, `web`, `desktop`) — becomes part of the log filename. */
19
+ readonly surface: string;
20
+ /** Harness home override; defaults to `$DSH_HOME`/`~/.dsh` exactly like credentials/settings resolve it. */
21
+ readonly dshHome?: string;
22
+ }
23
+ /**
24
+ * Register the file exporter for this process's lifetime. `ctx.logger.exporter()`
25
+ * already ties its own disposal to the fiber that registers it (see
26
+ * `LoggerService.exporter` in `@deepseek-ai/cordis`), so this needs no extra
27
+ * `ctx.effect()` wrapper of its own.
28
+ * @param ctx - the plugin context whose `ctx.logger` gains the exporter.
29
+ * @param options - which surface/home this log file belongs to.
30
+ */
31
+ export declare function installSessionLogExporter(ctx: Context, options: InstallSessionLogExporterOptions): void;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acryl-harness-runtime",
3
- "version": "0.1.31",
3
+ "version": "0.1.36",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",