Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ All notable changes to this project are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.3] - 2026-10-26

### Added

- Measured the Meta prompt-cache window: same-key entries still hit after 12 minutes (2673 of
2800 tokens), a fresh key reports 0, and `prompt_cache_retention: 24h` made no difference in
that window — so there is no retention knob worth exposing.

### Changed

- **Delete on the Muse Code row now signs out.** The Models page's Delete only unsets the
user-layer route; the plugin treats that removal as a sign-out and deletes the
`llm-pi-ai/meta` OAuth record together with the mirrored `MUSE_CODE_SUB_TOKEN` reference,
instead of leaving a green dot behind for a row that no longer exists. The row is re-declared
(red dot) on the next refresh or restart.

## [0.2.2] - 2026-10-26

### Added
Expand Down
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cff-version: 1.2.0
message: "If you use this software, please cite it as below."
title: "dsh-muse-code"
version: 0.2.2
version: 0.2.3
date-released: "2026-10-26"
url: "https://github.com/TheStreamCode/dsh-muse-code"
repository-code: "https://github.com/TheStreamCode/dsh-muse-code"
Expand Down
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@ The Meta Model API caches the request prefix per `prompt_cache_key`, and a reque
key gets no reuse at all. DSH's pi-ai adapter sends the session id as the key for every call, so
turns in the same session reuse the prefix Meta already saw. Measured against the live API with
an identical 1.6k-token prefix: same key → 1521 of 1600 tokens cached on the second call;
different key or no key → 0.
different key or no key → 0. After a 12-minute pause the same-key entries still reported 2673 of
2800 cached tokens, with or without `prompt_cache_retention`, and a fresh key still reported 0 —
so the default retention is enough for normal turn pacing and there is no retention knob to tune.

The ratio you see in the usage panel therefore depends on how the session is used:

Expand Down Expand Up @@ -234,12 +236,13 @@ Both are DSH's generic provider-row actions and a plugin cannot hide them.
- **Edit** writes the route profile into your profile patch: display name, models, and the
`apiKeyEnv` reference. The plugin restores the reference on the next boot and refills an
emptied model list from the account catalog.
- **Delete** removes only that user-layer route: the provider leaves Settings → Models and the
model picker, and a hand-edited model list is lost; the credential record and its mirrored
reference stay. The plugin re-declares the route on the next boot and resyncs the catalog. To
actually remove Muse Code, uninstall the bundle and delete the credential record.

The supported off switch is the plugin itself:
- **Delete** removes the user-layer route and the plugin treats that removal as a sign-out: it
deletes the `llm-pi-ai/meta` OAuth record and clears the mirrored `MUSE_CODE_SUB_TOKEN`
reference. The provider leaves Settings → Models and the picker, and a hand-edited model list
is lost; the plugin re-declares the row on its next refresh (and on restart) with a red dot, so
sign in again to keep using Muse Code.
- To remove Muse Code for good, uninstall the bundle and delete the credential record; the
supported off switch is the plugin itself:

```bash
dsh plugin --profile desktop remove @thestreamcode/dsh-muse-code
Expand Down
10 changes: 10 additions & 0 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ type CredentialsFace = {
} | undefined>;
set?(reference: string, value: string): Promise<void>;
unset?(reference: string): Promise<void>;
deleteRecord?(key: string): Promise<void>;
modifyRecord?(key: string, update: (current: CredentialRecord | undefined) => Promise<CredentialRecord | undefined>): Promise<unknown>;
};
type WebRoute = {
Expand Down Expand Up @@ -275,6 +276,15 @@ export declare function prettifyModelName(id: string): string;
* @returns the chat model ids, or null on any failure.
*/
export declare function fetchLiveModelIds(accessKey: string, timeoutMs?: number): Promise<string[] | null>;
/**
* Sign out of Muse Code: drop the OAuth record and the route's mirrored
* credential reference. The Models page's **Delete** action only unsets the
* user-layer route, so the plugin treats that removal as a sign-out rather
* than leaving credentials behind for a row that no longer exists.
* @param credentials - the credential seam.
* @param logger - optional plugin logger.
*/
export declare function logoutMuseCode(credentials: CredentialsFace, logger?: Logger): Promise<void>;
/** Injected side effects for the credential bridge, overridable in tests. */
export type CredentialSyncOptions = {
logger?: Logger;
Expand Down
83 changes: 81 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ const KEY_LIFETIME_MS = 24 * 60 * 60 * 1000;
const REFRESH_WINDOW_MS = 6 * 60 * 60 * 1000;
/** How often the plugin re-checks the record while it runs. */
const REFRESH_CHECK_MS = 15 * 60 * 1000;
/** Settle time before treating a removed route as a user sign-out. */
const ROUTE_DELETE_CHECK_MS = 400;
/** Web route the Settings control calls to start the sign-in. */
export const LOGIN_ROUTE = "/muse-code/login";
/** Web route that reports whether the sign-in completed. */
Expand Down Expand Up @@ -146,20 +148,70 @@ export function apply(ctx) {
const settings = child.settings;
if (settings === undefined)
return () => { };
let sawRoute = false;
let checkTimer;
const routeIsDeclared = () => {
const row = settings.describe().find((candidate) => candidate.ns === SETTINGS_NS);
if (row === undefined)
return false;
const providers = row.value["providers"];
return isRecord(providers) && isRecord(providers[PROVIDER]);
};
const refresh = () => {
void (async () => {
await ensureMuseRoute(settings, ctx.logger);
await syncMuseModels(settings, ctx.logger, options);
if (routeIsDeclared())
sawRoute = true;
})().catch((error) => {
warn(ctx.logger, "dsh-muse-code: could not refresh the Muse Code settings", error);
});
};
// The Models page Delete only unsets the user-layer route; for a
// plugin-managed provider that reads as a sign-out, so drop the OAuth
// record and its mirrored reference instead of leaving a green dot for a
// row that no longer exists.
const scheduleDeleteCheck = () => {
if (checkTimer !== undefined)
clearTimeout(checkTimer);
checkTimer = setTimeout(() => {
checkTimer = undefined;
if (routeIsDeclared()) {
sawRoute = true;
return;
}
if (!sawRoute)
return;
sawRoute = false;
const credentials = credentialsFace;
if (credentials === undefined)
return;
void logoutMuseCode(credentials, ctx.logger).catch((error) => {
warn(ctx.logger, "dsh-muse-code: could not sign out after the route was deleted", error);
});
}, ROUTE_DELETE_CHECK_MS);
checkTimer.unref?.();
};
refresh();
const dispose = ctx.on?.("credentials/record-updated", (key) => {
const disposers = [];
const onRecord = ctx.on?.("credentials/record-updated", (key) => {
if (key === AUTH_KEY)
refresh();
});
return dispose ?? (() => { });
if (onRecord !== undefined)
disposers.push(onRecord);
const onDocument = ctx.on?.("settings/document-updated", (ns) => {
if (ns === SETTINGS_NS)
scheduleDeleteCheck();
});
if (onDocument !== undefined)
disposers.push(onDocument);
return () => {
if (checkTimer !== undefined)
clearTimeout(checkTimer);
for (const dispose of disposers)
dispose();
};
}, "dsh-muse-code: settings");
});
}
Expand Down Expand Up @@ -649,6 +701,33 @@ async function waitForCredentials(getter, attempts = 8, delayMs = 250) {
}
return undefined;
}
/**
* Sign out of Muse Code: drop the OAuth record and the route's mirrored
* credential reference. The Models page's **Delete** action only unsets the
* user-layer route, so the plugin treats that removal as a sign-out rather
* than leaving credentials behind for a row that no longer exists.
* @param credentials - the credential seam.
* @param logger - optional plugin logger.
*/
export async function logoutMuseCode(credentials, logger) {
let failed = false;
try {
await credentials.deleteRecord?.(AUTH_KEY);
}
catch (error) {
failed = true;
warn(logger, "dsh-muse-code: could not delete the Muse Code record", error);
}
try {
await credentials.unset?.(CREDENTIAL_REFERENCE);
}
catch (error) {
failed = true;
warn(logger, "dsh-muse-code: could not clear the Muse Code credential reference", error);
}
if (!failed)
logger?.info?.("dsh-muse-code: deleted the route in Settings > Models; signed out");
}
/**
* Keep the route's credential reference equal to the OAuth record's minted key
* and re-mint that key before it expires.
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@thestreamcode/dsh-muse-code",
"version": "0.2.2",
"version": "0.2.3",
"description": "Unofficial DeepSeek Harness integration for Meta's Muse Code subscription: declares the shipped Meta provider route and adds its sign-in command. Use at your own risk.",
"license": "MIT",
"author": {
Expand Down
72 changes: 70 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ const KEY_LIFETIME_MS = 24 * 60 * 60 * 1000
const REFRESH_WINDOW_MS = 6 * 60 * 60 * 1000
/** How often the plugin re-checks the record while it runs. */
const REFRESH_CHECK_MS = 15 * 60 * 1000
/** Settle time before treating a removed route as a user sign-out. */
const ROUTE_DELETE_CHECK_MS = 400
/** Web route the Settings control calls to start the sign-in. */
export const LOGIN_ROUTE = "/muse-code/login"
/** Web route that reports whether the sign-in completed. */
Expand Down Expand Up @@ -160,6 +162,7 @@ type CredentialsFace = {
resolve?(reference: string): Promise<{ value?: string; source?: string } | undefined>
set?(reference: string, value: string): Promise<void>
unset?(reference: string): Promise<void>
deleteRecord?(key: string): Promise<void>
modifyRecord?(
key: string,
update: (current: CredentialRecord | undefined) => Promise<CredentialRecord | undefined>,
Expand Down Expand Up @@ -305,19 +308,59 @@ export function apply(ctx: HostContext): void {
child.effect(() => {
const settings = child.settings
if (settings === undefined) return () => {}
let sawRoute = false
let checkTimer: ReturnType<typeof setTimeout> | undefined
const routeIsDeclared = (): boolean => {
const row = settings.describe().find((candidate) => candidate.ns === SETTINGS_NS)
if (row === undefined) return false
const providers = row.value["providers"]
return isRecord(providers) && isRecord(providers[PROVIDER])
}
const refresh = () => {
void (async () => {
await ensureMuseRoute(settings, ctx.logger)
await syncMuseModels(settings, ctx.logger, options)
if (routeIsDeclared()) sawRoute = true
})().catch((error: unknown) => {
warn(ctx.logger, "dsh-muse-code: could not refresh the Muse Code settings", error)
})
}
// The Models page Delete only unsets the user-layer route; for a
// plugin-managed provider that reads as a sign-out, so drop the OAuth
// record and its mirrored reference instead of leaving a green dot for a
// row that no longer exists.
const scheduleDeleteCheck = () => {
if (checkTimer !== undefined) clearTimeout(checkTimer)
checkTimer = setTimeout(() => {
checkTimer = undefined
if (routeIsDeclared()) {
sawRoute = true
return
}
if (!sawRoute) return
sawRoute = false
const credentials = credentialsFace
if (credentials === undefined) return
void logoutMuseCode(credentials, ctx.logger).catch((error: unknown) => {
warn(ctx.logger, "dsh-muse-code: could not sign out after the route was deleted", error)
})
}, ROUTE_DELETE_CHECK_MS)
checkTimer.unref?.()
}
refresh()
const dispose = ctx.on?.("credentials/record-updated", (key: string) => {
const disposers: Array<() => void> = []
const onRecord = ctx.on?.("credentials/record-updated", (key: string) => {
if (key === AUTH_KEY) refresh()
})
return dispose ?? (() => {})
if (onRecord !== undefined) disposers.push(onRecord)
const onDocument = ctx.on?.("settings/document-updated", (ns: string) => {
if (ns === SETTINGS_NS) scheduleDeleteCheck()
})
if (onDocument !== undefined) disposers.push(onDocument)
return () => {
if (checkTimer !== undefined) clearTimeout(checkTimer)
for (const dispose of disposers) dispose()
}
}, "dsh-muse-code: settings")
})
}
Expand Down Expand Up @@ -855,6 +898,31 @@ async function waitForCredentials(
return undefined
}

/**
* Sign out of Muse Code: drop the OAuth record and the route's mirrored
* credential reference. The Models page's **Delete** action only unsets the
* user-layer route, so the plugin treats that removal as a sign-out rather
* than leaving credentials behind for a row that no longer exists.
* @param credentials - the credential seam.
* @param logger - optional plugin logger.
*/
export async function logoutMuseCode(credentials: CredentialsFace, logger?: Logger): Promise<void> {
let failed = false
try {
await credentials.deleteRecord?.(AUTH_KEY)
} catch (error) {
failed = true
warn(logger, "dsh-muse-code: could not delete the Muse Code record", error)
}
try {
await credentials.unset?.(CREDENTIAL_REFERENCE)
} catch (error) {
failed = true
warn(logger, "dsh-muse-code: could not clear the Muse Code credential reference", error)
}
if (!failed) logger?.info?.("dsh-muse-code: deleted the route in Settings > Models; signed out")
}

/** Injected side effects for the credential bridge, overridable in tests. */
export type CredentialSyncOptions = {
logger?: Logger
Expand Down
Loading
Loading