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.
- package/package.json +1 -1
- package/runtime/lib/bin.js +865 -242
- package/runtime/lib/index.js +867 -242
- package/runtime/lib/types/buildInfo.generated.d.ts +4 -0
- package/runtime/lib/types/tui/acrylMark.d.ts +14 -0
- package/runtime/lib/types/tui/actions.d.ts +9 -6
- package/runtime/lib/types/tui/auth-guidance.d.ts +0 -2
- package/runtime/lib/types/tui/listWindow.d.ts +24 -0
- package/runtime/lib/types/tui/login/LoginOverlay.d.ts +52 -11
- package/runtime/lib/types/tui/login/types.d.ts +6 -1
- package/runtime/lib/types/tui/modelProfile/ModelProfileOverlay.d.ts +41 -3
- package/runtime/lib/types/tui/modelProfile/types.d.ts +24 -0
- package/runtime/lib/types/tui/store.d.ts +10 -0
- package/runtime/lib/types/yly/yly-pet.d.ts +1 -1
- package/runtime/lib/types/yly/yly-programs.d.ts +1 -1
- package/runtime/node_modules/.modules.yaml +2 -2
- package/runtime/node_modules/.package-map.json +1 -1
- package/runtime/node_modules/@earendil-works/pi-ai/dist/auth/oauth/oauth-page.js +1 -1
- package/runtime/node_modules/acryl-control/lib/index.js +66 -1
- package/runtime/node_modules/acryl-control/lib/types/authorization/service.d.ts +23 -0
- package/runtime/node_modules/acryl-control/lib/types/authorization/types.d.ts +46 -0
- package/runtime/node_modules/acryl-control/lib/types/credential/projection.d.ts +63 -0
- package/runtime/node_modules/acryl-control/lib/types/credential/types.d.ts +38 -0
- package/runtime/node_modules/acryl-control/lib/types/index.d.ts +4 -0
- package/runtime/node_modules/acryl-control/package.json +1 -1
- package/runtime/node_modules/acryl-harness-runtime/lib/index.mjs +129 -2
- package/runtime/node_modules/acryl-harness-runtime/lib/types/acryl-home.d.ts +35 -0
- package/runtime/node_modules/acryl-harness-runtime/lib/types/index.d.ts +2 -0
- package/runtime/node_modules/acryl-harness-runtime/lib/types/session-bridge.d.ts +13 -0
- package/runtime/node_modules/acryl-harness-runtime/lib/types/session-log-exporter.d.ts +31 -0
- package/runtime/node_modules/acryl-harness-runtime/package.json +1 -1
- package/runtime/node_modules/acryl-harness-runtime/src/acryl-home.ts +50 -0
- package/runtime/node_modules/acryl-harness-runtime/src/index.ts +16 -0
- package/runtime/node_modules/acryl-harness-runtime/src/session-bridge.ts +24 -1
- package/runtime/node_modules/acryl-harness-runtime/src/session-log-exporter.ts +69 -0
- package/runtime/package.json +4 -2
- 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"><
|
|
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("&", "&")
|
|
@@ -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,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;
|