dsh-plugin-effort-declare 0.1.2 → 0.1.4

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 (48) hide show
  1. package/CONTRIBUTING.en.md +20 -9
  2. package/CONTRIBUTING.md +24 -13
  3. package/INSTALL.en.md +9 -9
  4. package/INSTALL.md +16 -16
  5. package/README.en.md +25 -20
  6. package/README.md +28 -23
  7. package/lib/client.js +354 -82
  8. package/lib/client.js.map +1 -1
  9. package/lib/types/client/EffortDeclareSection.d.ts +14 -4
  10. package/lib/types/client/EffortDeclareSection.d.ts.map +1 -1
  11. package/lib/types/client/build-info.d.ts +2 -0
  12. package/lib/types/client/build-info.d.ts.map +1 -0
  13. package/lib/types/client/index.d.ts.map +1 -1
  14. package/lib/types/client/load-drafts.d.ts +36 -6
  15. package/lib/types/client/load-drafts.d.ts.map +1 -1
  16. package/lib/types/client/locales.d.ts +1 -1
  17. package/lib/types/client/locales.d.ts.map +1 -1
  18. package/lib/types/core/attribution.d.ts +13 -0
  19. package/lib/types/core/attribution.d.ts.map +1 -0
  20. package/lib/types/core/catalog.d.ts +8 -0
  21. package/lib/types/core/catalog.d.ts.map +1 -1
  22. package/lib/types/core/drafts.d.ts +8 -4
  23. package/lib/types/core/drafts.d.ts.map +1 -1
  24. package/lib/types/core/efforts.d.ts.map +1 -1
  25. package/lib/types/core/input.d.ts +15 -0
  26. package/lib/types/core/input.d.ts.map +1 -0
  27. package/lib/types/core/validate.d.ts +3 -0
  28. package/lib/types/core/validate.d.ts.map +1 -1
  29. package/package.json +2 -2
  30. package/src/README.en.md +2 -2
  31. package/src/README.md +2 -2
  32. package/src/client/EffortDeclareSection.tsx +161 -47
  33. package/src/client/README.en.md +7 -5
  34. package/src/client/README.md +7 -5
  35. package/src/client/build-info.ts +12 -0
  36. package/src/client/effort-declare.module.css +8 -0
  37. package/src/client/globals.d.ts +5 -0
  38. package/src/client/index.ts +13 -11
  39. package/src/client/load-drafts.ts +124 -8
  40. package/src/client/locales.ts +13 -4
  41. package/src/core/README.en.md +10 -8
  42. package/src/core/README.md +10 -8
  43. package/src/core/attribution.ts +24 -0
  44. package/src/core/catalog.ts +11 -0
  45. package/src/core/drafts.ts +40 -26
  46. package/src/core/efforts.ts +4 -1
  47. package/src/core/input.ts +49 -0
  48. package/src/core/validate.ts +8 -0
@@ -1,6 +1,12 @@
1
1
  /**
2
2
  * Load editable route drafts from llm.providers + the llm-pi-ai namespace.
3
3
  * Drafts come from the user layer; route protocol classification may use effective value.
4
+ *
5
+ * First paint uses `ensure()` (idle-only). Refresh never calls `ensure()`:
6
+ * wait until the mirror subscribe shows a namespace revision at least as new
7
+ * as the Host event, then `getSnapshot()`. Own mutate echoes are identified
8
+ * by revision (including older delayed echoes), not ignored as a generic
9
+ * document-updated.
4
10
  */
5
11
  import type { IApiClient } from '@deepseek-ai/dsh-api-remotes/client';
6
12
  import type { SettingsDescribeFace } from '@deepseek-ai/dsh-client-ui-settings/client';
@@ -12,12 +18,36 @@ export interface LoadDraftsResult {
12
18
  drafts: RouteDraft[];
13
19
  error?: string;
14
20
  }
21
+ export type LoadDraftsMode = 'ensure' | 'snapshot';
22
+ type MirrorSnapshot = ReturnType<SettingsDescribeFace['getSnapshot']>;
23
+ type MirrorDescribe = Pick<SettingsDescribeFace, 'getSnapshot' | 'subscribe'>;
24
+ /** Namespace revision on a describe snapshot, if that row exists. */
25
+ export declare function namespaceRevision(snapshot: MirrorSnapshot, ns: string): number | undefined;
15
26
  /**
16
- * First paint: `ensure()` (reads only from idle). Never treat ensure as refresh.
17
- * Callers that must not apply a stale settlement compare generation themselves.
18
- *
19
- * `formats` is the live schema union only. Empty means the dropdown has no
20
- * writable choices (stored values stay visible via `thinkingFormatChoices`).
27
+ * True when `incoming` is the Host echo of a mutate this page already folded,
28
+ * or an older revision the snapshot has already passed. `echoed` is undefined
29
+ * until the first successful write.
30
+ */
31
+ export declare function isOwnDocumentEcho(echoed: number | undefined, incoming: number): boolean;
32
+ /**
33
+ * After a preserve-dirty reload: conflicted cards get a conflict notice;
34
+ * live cards drop leftover conflict/error; saved notices stay; gone cards drop.
35
+ */
36
+ export declare function foldReloadNotices<T extends {
37
+ kind: string;
38
+ }>(current: Record<string, T>, args: {
39
+ conflicted: readonly string[];
40
+ conflictNotice: T;
41
+ liveProviders: readonly string[];
42
+ }): Record<string, T>;
43
+ /** Resolve when the mirror's namespace revision is at least `revision`, or abort. */
44
+ export declare function waitForNamespaceRevision(describe: MirrorDescribe, ns: string, revision: number, signal?: AbortSignal): Promise<'matched' | 'aborted'>;
45
+ /** Resolve when the namespace revision differs from `previous`, or abort. */
46
+ export declare function waitForNamespaceRevisionChange(describe: MirrorDescribe, ns: string, previous: number, signal?: AbortSignal): Promise<'changed' | 'aborted'>;
47
+ /**
48
+ * `ensure`: first paint / idle recovery (official ensure only reads from idle).
49
+ * `snapshot`: refresh after the mirror revision already moved — do not ensure.
21
50
  */
22
- export declare function loadDrafts(api: Pick<IApiClient, 'llm'>, describe: Pick<SettingsDescribeFace, 'ensure' | 'getSnapshot'>, schema: SchemaOps): Promise<LoadDraftsResult>;
51
+ export declare function loadDrafts(api: Pick<IApiClient, 'llm'>, describe: Pick<SettingsDescribeFace, 'ensure' | 'getSnapshot'>, schema: SchemaOps, mode?: LoadDraftsMode): Promise<LoadDraftsResult>;
52
+ export {};
23
53
  //# sourceMappingURL=load-drafts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"load-drafts.d.ts","sourceRoot":"","sources":["../../../src/client/load-drafts.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,UAAU,EAAyB,MAAM,qCAAqC,CAAA;AAC5F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAA;AAKtF,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAG9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAOhD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,MAAM,EAAE,UAAU,EAAE,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,EAC5B,QAAQ,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,GAAG,aAAa,CAAC,EAC9D,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,gBAAgB,CAAC,CA8C3B"}
1
+ {"version":3,"file":"load-drafts.d.ts","sourceRoot":"","sources":["../../../src/client/load-drafts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,UAAU,EAAyB,MAAM,qCAAqC,CAAA;AAC5F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAA;AAKtF,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAG9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAOhD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,MAAM,EAAE,UAAU,EAAE,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,UAAU,CAAA;AAElD,KAAK,cAAc,GAAG,UAAU,CAAC,oBAAoB,CAAC,aAAa,CAAC,CAAC,CAAA;AACrE,KAAK,cAAc,GAAG,IAAI,CAAC,oBAAoB,EAAE,aAAa,GAAG,WAAW,CAAC,CAAA;AAE7E,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE1F;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEvF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EAC1D,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,EAC1B,IAAI,EAAE;IACJ,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7B,cAAc,EAAE,CAAC,CAAA;IACjB,aAAa,EAAE,SAAS,MAAM,EAAE,CAAA;CACjC,GACA,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAcnB;AA4BD,qFAAqF;AACrF,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,cAAc,EACxB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAUhC;AAED,6EAA6E;AAC7E,wBAAsB,8BAA8B,CAClD,QAAQ,EAAE,cAAc,EACxB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAMhC;AAqDD;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,EAC5B,QAAQ,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,GAAG,aAAa,CAAC,EAC9D,MAAM,EAAE,SAAS,EACjB,IAAI,GAAE,cAAyB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAG3B"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Dictionary namespace for the effort-declare settings section.
3
3
  */
4
- export type EffortDeclareKey = 'nav' | 'title' | 'intro' | 'empty' | 'emptyHint' | 'loadError' | 'loading' | 'readOnly' | 'save' | 'saving' | 'saveBusy' | 'cancel' | 'saved' | 'conflict' | 'dirtyConflict' | 'presets' | 'presetDeepSeek' | 'presetOpenAI' | 'presetToggle' | 'presetToggleWarn' | 'model' | 'levels' | 'wire' | 'offMode' | 'offAbsent' | 'offEmpty' | 'offValue' | 'offValuePlaceholder' | 'clear' | 'advanced' | 'thinkingFormat' | 'thinkingFormatDefault' | 'supportsDeveloperRole' | 'supportsReasoningEffort' | 'developerTrueHint' | 'compatSummary' | 'errorEmpty' | 'errorOffOnly' | 'errorBadWire' | 'noModels' | 'reload';
4
+ export type EffortDeclareKey = 'nav' | 'title' | 'intro' | 'empty' | 'emptyHint' | 'loadError' | 'loading' | 'readOnly' | 'save' | 'saving' | 'saveBusy' | 'cancel' | 'saved' | 'conflict' | 'dirtyConflict' | 'presets' | 'presetDeepSeek' | 'presetOpenAI' | 'presetToggle' | 'presetToggleWarn' | 'model' | 'levels' | 'wire' | 'offMode' | 'offAbsent' | 'offEmpty' | 'offValue' | 'offValuePlaceholder' | 'clear' | 'advanced' | 'thinkingFormat' | 'thinkingFormatDefault' | 'supportsDeveloperRole' | 'supportsReasoningEffort' | 'developerTrueHint' | 'compatSummary' | 'errorEmpty' | 'errorOffOnly' | 'errorBadWire' | 'noModels' | 'reload' | 'imageInput' | 'imageInputHint' | 'errorBadInput';
5
5
  export declare const NS = "plugin-effort-declare";
6
6
  export declare const zh: Record<EffortDeclareKey, string>;
7
7
  export declare const en: Record<EffortDeclareKey, string>;
@@ -1 +1 @@
1
- {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,gBAAgB,GACxB,KAAK,GACL,OAAO,GACP,OAAO,GACP,OAAO,GACP,WAAW,GACX,WAAW,GACX,SAAS,GACT,UAAU,GACV,MAAM,GACN,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,OAAO,GACP,UAAU,GACV,eAAe,GACf,SAAS,GACT,gBAAgB,GAChB,cAAc,GACd,cAAc,GACd,kBAAkB,GAClB,OAAO,GACP,QAAQ,GACR,MAAM,GACN,SAAS,GACT,WAAW,GACX,UAAU,GACV,UAAU,GACV,qBAAqB,GACrB,OAAO,GACP,UAAU,GACV,gBAAgB,GAChB,uBAAuB,GACvB,uBAAuB,GACvB,yBAAyB,GACzB,mBAAmB,GACnB,eAAe,GACf,YAAY,GACZ,cAAc,GACd,cAAc,GACd,UAAU,GACV,QAAQ,CAAA;AAEZ,eAAO,MAAM,EAAE,0BAA0B,CAAA;AAEzC,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CA0C/C,CAAA;AAED,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CA0C/C,CAAA"}
1
+ {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,gBAAgB,GACxB,KAAK,GACL,OAAO,GACP,OAAO,GACP,OAAO,GACP,WAAW,GACX,WAAW,GACX,SAAS,GACT,UAAU,GACV,MAAM,GACN,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,OAAO,GACP,UAAU,GACV,eAAe,GACf,SAAS,GACT,gBAAgB,GAChB,cAAc,GACd,cAAc,GACd,kBAAkB,GAClB,OAAO,GACP,QAAQ,GACR,MAAM,GACN,SAAS,GACT,WAAW,GACX,UAAU,GACV,UAAU,GACV,qBAAqB,GACrB,OAAO,GACP,UAAU,GACV,gBAAgB,GAChB,uBAAuB,GACvB,uBAAuB,GACvB,yBAAyB,GACzB,mBAAmB,GACnB,eAAe,GACf,YAAY,GACZ,cAAc,GACd,cAAc,GACd,UAAU,GACV,QAAQ,GACR,YAAY,GACZ,gBAAgB,GAChB,eAAe,CAAA;AAEnB,eAAO,MAAM,EAAE,0BAA0B,CAAA;AAEzC,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CA6C/C,CAAA;AAED,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CA6C/C,CAAA"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Plugin footer attribution. Version and end-year are frozen into the client
3
+ * bundle at pack time; this module only formats the line.
4
+ */
5
+ /** First publication year (LICENSE). Not the user's wall clock. */
6
+ export declare const COPYRIGHT_FROM = 2026;
7
+ export declare const COPYRIGHT_HOLDER = "Stardust";
8
+ /**
9
+ * `0.1.2 © 2026 Stardust` or `0.1.2 © 2026–2027 Stardust`.
10
+ * Throws if version is empty or `to < from` — a bad stamp must not render.
11
+ */
12
+ export declare function formatAttribution(version: string, from: number, to: number): string;
13
+ //# sourceMappingURL=attribution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attribution.d.ts","sourceRoot":"","sources":["../../../src/core/attribution.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,mEAAmE;AACnE,eAAO,MAAM,cAAc,OAAO,CAAA;AAElC,eAAO,MAAM,gBAAgB,aAAa,CAAA;AAE1C;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CASnF"}
@@ -30,4 +30,12 @@ export declare const LLM_DEEPSEEK_NS = "llm-deepseek";
30
30
  export declare const DEEPSEEK_OFFICIAL = "deepseek-official";
31
31
  /** Any route key walks a dict schema to the same profile node. */
32
32
  export declare const SCHEMA_PROBE_ROUTE = "\0probe";
33
+ /** Request modalities llm-pi-ai accepts on `models[].input` / `defaultInput`. */
34
+ export declare const INPUT_MODALITIES: readonly ["text", "image"];
35
+ export type InputModality = (typeof INPUT_MODALITIES)[number];
36
+ /**
37
+ * Canonical write for a hand-declared vision model.
38
+ * Must include `text`; image-only is not a serviceable OpenAI-completions route.
39
+ */
40
+ export declare const IMAGE_CAPABLE_INPUT: readonly InputModality[];
33
41
  //# sourceMappingURL=catalog.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../../src/core/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,8DAA8D;AAC9D,eAAO,MAAM,eAAe,sEAQlB,CAAA;AAEV,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAE5D,yEAAyE;AACzE,eAAO,MAAM,2BAA2B,6DAEvC,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,yBAAyB,gJAW5B,CAAA;AAEV,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvE,sDAAsD;AACtD,eAAO,MAAM,kBAAkB,uBAAuB,CAAA;AAEtD,6CAA6C;AAC7C,eAAO,MAAM,YAAY,cAAc,CAAA;AAEvC,kDAAkD;AAClD,eAAO,MAAM,eAAe,iBAAiB,CAAA;AAE7C,+CAA+C;AAC/C,eAAO,MAAM,iBAAiB,sBAAsB,CAAA;AAEpD,kEAAkE;AAClE,eAAO,MAAM,kBAAkB,YAAgB,CAAA"}
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../../src/core/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,8DAA8D;AAC9D,eAAO,MAAM,eAAe,sEAQlB,CAAA;AAEV,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAE5D,yEAAyE;AACzE,eAAO,MAAM,2BAA2B,6DAEvC,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,yBAAyB,gJAW5B,CAAA;AAEV,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvE,sDAAsD;AACtD,eAAO,MAAM,kBAAkB,uBAAuB,CAAA;AAEtD,6CAA6C;AAC7C,eAAO,MAAM,YAAY,cAAc,CAAA;AAEvC,kDAAkD;AAClD,eAAO,MAAM,eAAe,iBAAiB,CAAA;AAE7C,+CAA+C;AAC/C,eAAO,MAAM,iBAAiB,sBAAsB,CAAA;AAEpD,kEAAkE;AAClE,eAAO,MAAM,kBAAkB,YAAgB,CAAA;AAE/C,iFAAiF;AACjF,eAAO,MAAM,gBAAgB,4BAA6B,CAAA;AAE1D,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE7D;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,aAAa,EAAsB,CAAA"}
@@ -34,9 +34,13 @@ export declare function applySaveSuccess(drafts: readonly RouteDraft[], savedPro
34
34
  user: unknown;
35
35
  revision: number;
36
36
  }): RouteDraft[];
37
+ /** Model-row fields this page edits; other keys follow the Models page. */
38
+ export declare const MODEL_OVERLAY_KEYS: readonly ["reasoningEfforts", "input"];
39
+ export type ModelOverlayKey = (typeof MODEL_OVERLAY_KEYS)[number];
37
40
  /**
38
41
  * Membership follows the latest user-layer models list (Models page add/delete).
39
- * Local unsaved `reasoningEfforts` (including a cleared key) overlay by id.
42
+ * Local unsaved overlay keys (including a cleared key) overlay by id; other
43
+ * fields on the row follow incoming.
40
44
  */
41
45
  export declare function mergeModelsById(args: {
42
46
  prevModels: readonly Record<string, unknown>[];
@@ -62,9 +66,9 @@ export declare function mergeCompat(args: {
62
66
  };
63
67
  /**
64
68
  * Apply a freshly loaded table. Membership and metadata follow incoming;
65
- * unsaved reasoningEfforts / dirty compat keys overlay by id. Conflict only
66
- * when a locally dirty field also changed in originals (revision-only bumps
67
- * and sibling-card saves do not warn).
69
+ * unsaved overlay keys (`reasoningEfforts`, `input`) / dirty compat keys
70
+ * overlay by id. Conflict only when a locally dirty field also changed in
71
+ * originals (revision-only bumps and sibling-card saves do not warn).
68
72
  */
69
73
  export declare function mergeLoadedDrafts(current: readonly RouteDraft[], incoming: readonly RouteDraft[], options: {
70
74
  preserveDirty: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"drafts.d.ts","sourceRoot":"","sources":["../../../src/core/drafts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"drafts.d.ts","sourceRoot":"","sources":["../../../src/core/drafts.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"efforts.d.ts","sourceRoot":"","sources":["../../../src/core/efforts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AAElE,uEAAuE;AACvE,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAA;AAE5E,mEAAmE;AACnE,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAA;AAElD,MAAM,MAAM,qBAAqB,GAAG,gBAAgB,GAAG,KAAK,GAAG,SAAS,CAAA;AAExE,kDAAkD;AAClD,wBAAgB,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAM/F;AAED,wEAAwE;AACxE,wBAAgB,QAAQ,CACtB,OAAO,EAAE,gBAAgB,EACzB,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,MAAM,GACZ,gBAAgB,CAMlB;AAED,uEAAuE;AACvE,wBAAgB,QAAQ,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC,GAAG,OAAO,CAE7G;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CACzB,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC,EACpC,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,MAAM,GACZ,gBAAgB,CAKlB;AAED,0DAA0D;AAC1D,wBAAgB,eAAe,CAC7B,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC,EACpC,IAAI,EAAE,MAAM,GACX,gBAAgB,CAGlB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAwBxG;AAED,sEAAsE;AACtE,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAI3F;AAED,2FAA2F;AAC3F,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,gBAAgB,GAAG,SAAS,CAKtF;AAED,wDAAwD;AACxD,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,OAAO,EAAE,gBAAgB,GAAG,SAAS,GACpC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQzB"}
1
+ {"version":3,"file":"efforts.d.ts","sourceRoot":"","sources":["../../../src/core/efforts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAA;AAElE,uEAAuE;AACvE,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAA;AAE5E,mEAAmE;AACnE,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAA;AAElD,MAAM,MAAM,qBAAqB,GAAG,gBAAgB,GAAG,KAAK,GAAG,SAAS,CAAA;AAExE,kDAAkD;AAClD,wBAAgB,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAM/F;AAED,wEAAwE;AACxE,wBAAgB,QAAQ,CACtB,OAAO,EAAE,gBAAgB,EACzB,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,MAAM,GACZ,gBAAgB,CASlB;AAED,uEAAuE;AACvE,wBAAgB,QAAQ,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC,GAAG,OAAO,CAE7G;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CACzB,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC,EACpC,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,MAAM,GACZ,gBAAgB,CAKlB;AAED,0DAA0D;AAC1D,wBAAgB,eAAe,CAC7B,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC,EACpC,IAAI,EAAE,MAAM,GACX,gBAAgB,CAGlB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAwBxG;AAED,sEAAsE;AACtE,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAI3F;AAED,2FAA2F;AAC3F,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,gBAAgB,GAAG,SAAS,CAKtF;AAED,wDAAwD;AACxD,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,OAAO,EAAE,gBAAgB,GAAG,SAAS,GACpC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQzB"}
@@ -0,0 +1,15 @@
1
+ /** Whether the stored `input` list includes image. Absence is false. */
2
+ export declare function readImageCapable(row: Record<string, unknown>): boolean;
3
+ /**
4
+ * Declare or drop image input on a spread row.
5
+ * Enabled writes `[text, image]`; disabled unsets the key (not `[]`, not `[text]`).
6
+ */
7
+ export declare function writeImageCapable(row: Record<string, unknown>, enabled: boolean): Record<string, unknown>;
8
+ /**
9
+ * Validate a stored `input` field. Returns an error code; never throws.
10
+ * Absence is valid. Empty list is rejected so a bad stamp cannot be re-saved.
11
+ */
12
+ export declare function validateInput(input: unknown): 'bad-input' | undefined;
13
+ /** Per-model client-side check used to show errors without throwing. */
14
+ export declare function modelInputError(row: Record<string, unknown>): 'bad-input' | undefined;
15
+ //# sourceMappingURL=input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../src/core/input.ts"],"names":[],"mappings":"AAOA,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAGtE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,OAAO,EAAE,OAAO,GACf,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKzB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,SAAS,CAWrE;AAED,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,GAAG,SAAS,CAGrF"}
@@ -1,3 +1,6 @@
1
+ export type ModelRowError = 'empty' | 'off-only' | 'bad-wire' | 'bad-input';
1
2
  /** Per-model client-side check used to show errors without throwing. */
2
3
  export declare function modelEffortError(row: Record<string, unknown>): 'empty' | 'off-only' | 'bad-wire' | undefined;
4
+ /** First blocking error on a model row (efforts, then input). */
5
+ export declare function modelRowError(row: Record<string, unknown>): ModelRowError | undefined;
3
6
  //# sourceMappingURL=validate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/core/validate.ts"],"names":[],"mappings":"AAEA,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAG5G"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/core/validate.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,WAAW,CAAA;AAE3E,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAG5G;AAED,iEAAiE;AACjE,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,aAAa,GAAG,SAAS,CAErF"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-plugin-effort-declare",
3
- "description": "Settings page to declare per-model reasoningEfforts and openai-completions compat on hand-declared llm-pi-ai routes. Writes the official namespace; does not intercept llm/stream or replace the Models page.",
4
- "version": "0.1.2",
3
+ "description": "Declare per-model reasoning efforts, image input, and openai-completions dialect on hand-declared llm-pi-ai routes. Writes the official namespace; does not intercept llm/stream or replace the Models page.",
4
+ "version": "0.1.4",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "repository": {
package/src/README.en.md CHANGED
@@ -16,8 +16,8 @@ Install: [INSTALL.en.md](../INSTALL.en.md). Usage: root [README.en.md](../README
16
16
  | Path | Role |
17
17
  | --- | --- |
18
18
  | [`index.ts`](./index.ts) | Host entry. Exports `name` and `apply`. v1 registers no host services or adapters; the bundle still needs this entry so `dsh plugin add` can load the package. |
19
- | [`core/`](./core/) | UI-free pure functions: efforts, presets, save ops, draft merge, route filtering. Shared by the client and tests; the v1 host entry does not import them (`tsconfig.host` still compiles `core` for types). |
20
- | [`client/`](./client/) | Browser half: locale, settings page, `settings.mutate` against `llm-pi-ai`. |
19
+ | [`core/`](./core/) | UI-free pure functions: reasoning efforts, image input, presets, save ops, draft merge, route filtering, footer attribution. Shared by the client and tests; the v1 host entry does not import them (`tsconfig.host` still compiles `core` for types). |
20
+ | [`client/`](./client/) | Browser half: locale, settings page, and official `settings.mutate` against `llm-pi-ai` for `reasoningEfforts` and `input`. |
21
21
  | [`css-modules.d.ts`](./css-modules.d.ts) | Types for `*.module.css` (class map plus `cssText` / `cssTagId`). |
22
22
 
23
23
  `tsconfig.host.json` compiles host + `core`; `tsconfig.client.json` compiles `client` + `core`. Build output lives in [`lib/`](../lib/).
package/src/README.md CHANGED
@@ -16,8 +16,8 @@
16
16
  | 路径 | 说明 |
17
17
  | --- | --- |
18
18
  | [`index.ts`](./index.ts) | Host 入口。导出 `name` 与 `apply`。v1 不注册 host 服务、不挂适配器;组合包仍需要这一入口才能被 `dsh plugin add` 加载。 |
19
- | [`core/`](./core/) | 与 UI 无关的纯函数:档位、预设、保存 ops、草稿合并、路由过滤。client 与单测共用;Host 入口 v1 不引用这些函数(`tsconfig.host` 仍编译 `core` 以产出类型)。 |
20
- | [`client/`](./client/) | 浏览器半区:locale、设置页、对 `llm-pi-ai` 的 `settings.mutate`。 |
19
+ | [`core/`](./core/) | 与 UI 无关的纯函数:推理档位、图片输入、预设、保存 ops、草稿合并、路由过滤、页脚归因格式。client 与单测共用;Host 入口 v1 不引用这些函数(`tsconfig.host` 仍编译 `core` 以产出类型)。 |
20
+ | [`client/`](./client/) | 浏览器半区:locale、设置页,经官方 `settings.mutate` 写入 `llm-pi-ai` 的 `reasoningEfforts` 与 `input`。 |
21
21
  | [`css-modules.d.ts`](./css-modules.d.ts) | `*.module.css` 模块类型(class map + `cssText` / `cssTagId`)。 |
22
22
 
23
23
  `tsconfig.host.json` 编译 host + `core`;`tsconfig.client.json` 编译 `client` + `core`。构建产物在 [`lib/`](../lib/)。
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Settings section: per-model reasoningEfforts + openai-completions compat
3
- * for hand-declared llm-pi-ai routes.
2
+ * Settings section: per-model reasoningEfforts, image input, and
3
+ * openai-completions compat for hand-declared llm-pi-ai routes.
4
4
  */
5
5
  import { useCallback, useEffect, useRef, useState, type ReactNode } from 'react'
6
6
  import type { IApiClient } from '@deepseek-ai/dsh-api-remotes/client'
@@ -29,19 +29,34 @@ import {
29
29
  import { applyPresetCompat, applyPresetEfforts, PRESETS, type PresetId } from '../core/presets.ts'
30
30
  import { buildSaveOps } from '../core/path-ops.ts'
31
31
  import { cloneModels, cloneObject } from '../core/paths.ts'
32
- import { modelEffortError } from '../core/validate.ts'
33
- import { loadDrafts } from './load-drafts.ts'
32
+ import { readImageCapable, writeImageCapable } from '../core/input.ts'
33
+ import { modelRowError } from '../core/validate.ts'
34
+ import { PLUGIN_FOOTER_TEXT } from './build-info.ts'
35
+ import {
36
+ foldReloadNotices,
37
+ isOwnDocumentEcho,
38
+ loadDrafts,
39
+ waitForNamespaceRevision,
40
+ waitForNamespaceRevisionChange,
41
+ type LoadDraftsMode,
42
+ } from './load-drafts.ts'
34
43
  import { validateSaveDraft, type SchemaOps } from './schema-ops.ts'
35
44
  import type { EffortDeclareKey } from './locales.ts'
36
45
  import css from './effort-declare.module.css'
37
46
 
38
- export type InvalidationSource = 'settings' | 'directory' | 'writable'
47
+ export type InvalidationSource = 'settings' | 'directory' | 'reset' | 'writable'
48
+
49
+ export type Invalidation =
50
+ | { source: 'writable' }
51
+ | { source: 'directory' }
52
+ | { source: 'reset' }
53
+ | { source: 'settings'; revision: number }
39
54
 
40
55
  export interface EffortDeclareSectionInjected {
41
56
  api: Pick<IApiClient, 'settings' | 'llm'>
42
57
  describe: SettingsDescribeFace
43
58
  schema: SchemaOps
44
- subscribeInvalidate: (listener: (source: InvalidationSource) => void) => () => void
59
+ subscribeInvalidate: (listener: (event: Invalidation) => void) => () => void
45
60
  }
46
61
 
47
62
  export interface EffortDeclareSectionProps extends Partial<EffortDeclareSectionInjected> {
@@ -61,10 +76,11 @@ function compatSummary(compat: Record<string, unknown>): string {
61
76
  return parts.join(' · ')
62
77
  }
63
78
 
64
- function errorText(code: ReturnType<typeof modelEffortError>, t: (key: EffortDeclareKey) => string): string | undefined {
79
+ function errorText(code: ReturnType<typeof modelRowError>, t: (key: EffortDeclareKey) => string): string | undefined {
65
80
  if (code === 'empty') return t('errorEmpty')
66
81
  if (code === 'off-only') return t('errorOffOnly')
67
82
  if (code === 'bad-wire') return t('errorBadWire')
83
+ if (code === 'bad-input') return t('errorBadInput')
68
84
  return undefined
69
85
  }
70
86
 
@@ -100,6 +116,16 @@ function ModelRowEditor(props: {
100
116
  {t('clear')}
101
117
  </button>
102
118
  </div>
119
+ <label className={css.check}>
120
+ <input
121
+ type="checkbox"
122
+ checked={readImageCapable(row)}
123
+ disabled={disabled}
124
+ onChange={(event) => { onChange(writeImageCapable(row, event.target.checked)) }}
125
+ />
126
+ {t('imageInput')}
127
+ </label>
128
+ {readImageCapable(row) ? <p className={css.notice}>{t('imageInputHint')}</p> : null}
103
129
  <span className={css.fieldLabel}>{t('levels')}</span>
104
130
  <div className={css.levels}>
105
131
  {THINKING_LEVELS_WITHOUT_OFF.map((level) => (
@@ -183,7 +209,7 @@ function RouteCard(props: {
183
209
  const summary = compatSummary(draft.compat)
184
210
  const sameWire = draft.compat.supportsReasoningEffort === false
185
211
  const clientError = draft.models
186
- .map(row => errorText(modelEffortError(row), t))
212
+ .map(row => errorText(modelRowError(row), t))
187
213
  .find(text => text !== undefined)
188
214
  const dirty = draftDirty(draft)
189
215
 
@@ -328,58 +354,133 @@ export function EffortDeclareSection(props: EffortDeclareSectionProps): ReactNod
328
354
  const [notices, setNotices] = useState<Record<string, CardNotice>>({})
329
355
  const generationRef = useRef(0)
330
356
  const draftsRef = useRef(drafts)
357
+ const echoedRevisionRef = useRef<number | undefined>(undefined)
358
+ const pendingRevisionRef = useRef<number | undefined>(undefined)
359
+ const busyRouteRef = useRef<string | null>(null)
360
+ const abortRef = useRef<AbortController | null>(null)
331
361
  draftsRef.current = drafts
332
362
 
333
- const reload = useCallback((preserveDirty: boolean): void => {
363
+ const applyDrafts = (next: RouteDraft[] | ((current: RouteDraft[]) => RouteDraft[])): void => {
364
+ const resolved = typeof next === 'function' ? next(draftsRef.current) : next
365
+ draftsRef.current = resolved
366
+ setDrafts(resolved)
367
+ }
368
+
369
+ const snapshotMode = (): LoadDraftsMode => (
370
+ describe === undefined || describe.getSnapshot().status === 'idle' ? 'ensure' : 'snapshot'
371
+ )
372
+
373
+ const beginGeneration = (): { generation: number; signal: AbortSignal } => {
374
+ abortRef.current?.abort()
375
+ const abort = new AbortController()
376
+ abortRef.current = abort
377
+ return { generation: nextGeneration(generationRef), signal: abort.signal }
378
+ }
379
+
380
+ const failGeneration = (generation: number, failure: unknown): void => {
381
+ if (!generationIsCurrent(generationRef, generation)) return
382
+ setStatus('error')
383
+ setError(failure instanceof Error ? failure.message : t('loadError'))
384
+ }
385
+
386
+ const settleReload = (
387
+ generation: number,
388
+ preserveDirty: boolean,
389
+ result: Awaited<ReturnType<typeof loadDrafts>>,
390
+ ): void => {
391
+ if (!generationIsCurrent(generationRef, generation)) return
392
+ setWritable(result.writable)
393
+ setFormats(result.formats)
394
+ if (result.error !== undefined) {
395
+ setStatus('error')
396
+ setError(result.error)
397
+ return
398
+ }
399
+ const merged = mergeLoadedDrafts(draftsRef.current, result.drafts, { preserveDirty })
400
+ applyDrafts(merged.drafts)
401
+ setNotices(current => foldReloadNotices(current, {
402
+ conflicted: merged.conflicted,
403
+ conflictNotice: { kind: 'conflict', text: t('dirtyConflict') },
404
+ liveProviders: merged.drafts.map(draft => draft.provider),
405
+ }))
406
+ setStatus('ready')
407
+ }
408
+
409
+ const loadSnapshotThenSettle = async (generation: number, preserveDirty: boolean): Promise<void> => {
410
+ if (api === undefined || describe === undefined || schema === undefined) return
411
+ if (!generationIsCurrent(generationRef, generation)) return
412
+ try {
413
+ const result = await loadDrafts(api, describe, schema, 'snapshot')
414
+ settleReload(generation, preserveDirty, result)
415
+ } catch (failure) {
416
+ failGeneration(generation, failure)
417
+ }
418
+ }
419
+
420
+ const reload = useCallback((preserveDirty: boolean, mode: LoadDraftsMode = 'ensure'): void => {
334
421
  if (api === undefined || describe === undefined || schema === undefined) {
335
422
  setStatus('error')
336
423
  setError(t('loadError'))
337
424
  return
338
425
  }
339
- const generation = nextGeneration(generationRef)
426
+ const { generation } = beginGeneration()
340
427
  if (draftsRef.current.length === 0) setStatus('loading')
341
428
  setError('')
342
- void loadDrafts(api, describe, schema).then((result) => {
343
- if (!generationIsCurrent(generationRef, generation)) return
344
- setWritable(result.writable)
345
- setFormats(result.formats)
346
- if (result.error !== undefined) {
347
- setStatus('error')
348
- setError(result.error)
349
- return
350
- }
351
- const merged = mergeLoadedDrafts(draftsRef.current, result.drafts, { preserveDirty })
352
- setDrafts(merged.drafts)
353
- if (merged.conflicted.length > 0) {
354
- setNotices(current => {
355
- const next = { ...current }
356
- for (const provider of merged.conflicted) {
357
- next[provider] = { kind: 'conflict', text: t('dirtyConflict') }
358
- }
359
- return next
360
- })
361
- }
362
- setStatus('ready')
429
+ void loadDrafts(api, describe, schema, mode).then((result) => {
430
+ settleReload(generation, preserveDirty, result)
363
431
  }, (failure: unknown) => {
364
- if (!generationIsCurrent(generationRef, generation)) return
365
- setStatus('error')
366
- setError(failure instanceof Error ? failure.message : t('loadError'))
432
+ failGeneration(generation, failure)
433
+ })
434
+ }, [api, describe, schema, t])
435
+
436
+ const refreshAtRevision = useCallback((revision: number, preserveDirty: boolean): void => {
437
+ if (api === undefined || describe === undefined || schema === undefined) return
438
+ const { generation, signal } = beginGeneration()
439
+ if (draftsRef.current.length === 0) setStatus('loading')
440
+ setError('')
441
+ void waitForNamespaceRevision(describe, LLM_PI_AI_NS, revision, signal).then((outcome) => {
442
+ if (outcome === 'aborted' || !generationIsCurrent(generationRef, generation)) return
443
+ return loadSnapshotThenSettle(generation, preserveDirty)
444
+ }, (failure: unknown) => {
445
+ failGeneration(generation, failure)
367
446
  })
368
447
  }, [api, describe, schema, t])
369
448
 
370
- useEffect(() => { reload(false) }, [reload])
449
+ const flushPendingSettings = (refresh: (revision: number, preserveDirty: boolean) => void): void => {
450
+ const pending = pendingRevisionRef.current
451
+ pendingRevisionRef.current = undefined
452
+ if (pending === undefined) return
453
+ if (isOwnDocumentEcho(echoedRevisionRef.current, pending)) return
454
+ refresh(pending, true)
455
+ }
456
+
457
+ useEffect(() => { reload(false, 'ensure') }, [reload])
458
+ useEffect(() => () => {
459
+ abortRef.current?.abort()
460
+ nextGeneration(generationRef)
461
+ }, [])
371
462
 
372
463
  useEffect(() => {
373
464
  if (props.subscribeInvalidate === undefined) return undefined
374
- return props.subscribeInvalidate((source) => {
375
- if (source === 'writable') {
465
+ return props.subscribeInvalidate((event) => {
466
+ if (event.source === 'writable') {
376
467
  const view = describe?.getSnapshot().view
377
468
  if (view !== undefined) setWritable(view.writable)
378
469
  return
379
470
  }
380
- if (source === 'settings' || source === 'directory') reload(true)
471
+ if (event.source === 'settings') {
472
+ if (busyRouteRef.current !== null) {
473
+ pendingRevisionRef.current = event.revision
474
+ return
475
+ }
476
+ if (isOwnDocumentEcho(echoedRevisionRef.current, event.revision)) return
477
+ refreshAtRevision(event.revision, true)
478
+ return
479
+ }
480
+ if (event.source === 'directory') reload(true, snapshotMode())
481
+ if (event.source === 'reset') reload(true, 'ensure')
381
482
  })
382
- }, [describe, props.subscribeInvalidate, reload])
483
+ }, [describe, props.subscribeInvalidate, refreshAtRevision, reload])
383
484
 
384
485
  const patchNotice = (provider: string, notice: CardNotice | undefined): void => {
385
486
  setNotices(current => {
@@ -392,17 +493,18 @@ export function EffortDeclareSection(props: EffortDeclareSectionProps): ReactNod
392
493
 
393
494
  const save = async (draft: RouteDraft): Promise<void> => {
394
495
  if (api === undefined || describe === undefined || schema === undefined) return
395
- if (status === 'loading' || busyRoute !== null) {
496
+ if (status === 'loading' || busyRouteRef.current !== null) {
396
497
  patchNotice(draft.provider, { kind: 'error', text: t('saveBusy') })
397
498
  return
398
499
  }
399
500
  const blocking = draft.models
400
- .map(row => errorText(modelEffortError(row), t))
501
+ .map(row => errorText(modelRowError(row), t))
401
502
  .find(text => text !== undefined)
402
503
  if (blocking !== undefined) {
403
504
  patchNotice(draft.provider, { kind: 'error', text: blocking })
404
505
  return
405
506
  }
507
+ busyRouteRef.current = draft.provider
406
508
  setBusyRoute(draft.provider)
407
509
  patchNotice(draft.provider, undefined)
408
510
  try {
@@ -414,7 +516,7 @@ export function EffortDeclareSection(props: EffortDeclareSectionProps): ReactNod
414
516
  afterCompat: draft.compat,
415
517
  })
416
518
  if (ops.length === 0) {
417
- setDrafts(current => current.map(row => row.provider === draft.provider ? alignDraft(row) : row))
519
+ applyDrafts(current => current.map(row => row.provider === draft.provider ? alignDraft(row) : row))
418
520
  return
419
521
  }
420
522
  const willWriteCompat = ops.some(op => (
@@ -454,12 +556,21 @@ export function EffortDeclareSection(props: EffortDeclareSectionProps): ReactNod
454
556
  kind: conflict ? 'conflict' : 'error',
455
557
  text: conflict ? t('conflict') : response.result.error.message,
456
558
  })
457
- if (conflict) reload(true)
559
+ if (conflict) {
560
+ const { generation, signal } = beginGeneration()
561
+ void waitForNamespaceRevisionChange(describe, LLM_PI_AI_NS, draft.revision, signal).then((outcome) => {
562
+ if (outcome === 'aborted' || !generationIsCurrent(generationRef, generation)) return
563
+ return loadSnapshotThenSettle(generation, true)
564
+ }, (failure: unknown) => {
565
+ failGeneration(generation, failure)
566
+ })
567
+ }
458
568
  return
459
569
  }
460
570
  const view = response.result.value
571
+ echoedRevisionRef.current = view.revision
461
572
  describe.acceptView(view)
462
- setDrafts(current => applySaveSuccess(current, draft.provider, {
573
+ applyDrafts(applySaveSuccess(draftsRef.current, draft.provider, {
463
574
  user: view.user ?? {},
464
575
  revision: view.revision,
465
576
  }))
@@ -470,7 +581,9 @@ export function EffortDeclareSection(props: EffortDeclareSectionProps): ReactNod
470
581
  text: failure instanceof Error ? failure.message : t('loadError'),
471
582
  })
472
583
  } finally {
584
+ busyRouteRef.current = null
473
585
  setBusyRoute(null)
586
+ flushPendingSettings(refreshAtRevision)
474
587
  }
475
588
  }
476
589
 
@@ -491,7 +604,7 @@ export function EffortDeclareSection(props: EffortDeclareSectionProps): ReactNod
491
604
  {status === 'error' ? <p className={css.error}>{error}</p> : null}
492
605
  {showReload
493
606
  ? (
494
- <button type="button" className={css.secondaryButton} onClick={() => { reload(true) }}>{t('reload')}</button>
607
+ <button type="button" className={css.secondaryButton} onClick={() => { reload(true, snapshotMode()) }}>{t('reload')}</button>
495
608
  )
496
609
  : null}
497
610
  {showEmpty
@@ -517,12 +630,12 @@ export function EffortDeclareSection(props: EffortDeclareSectionProps): ReactNod
517
630
  t={t}
518
631
  onChange={(next) => {
519
632
  patchNotice(next.provider, undefined)
520
- setDrafts(current => current.map(row => row.provider === next.provider ? next : row))
633
+ applyDrafts(current => current.map(row => row.provider === next.provider ? next : row))
521
634
  }}
522
635
  onSave={(next) => { void save(next) }}
523
636
  onCancel={(next) => {
524
637
  patchNotice(next.provider, undefined)
525
- setDrafts(current => current.map(row => row.provider === next.provider
638
+ applyDrafts(current => current.map(row => row.provider === next.provider
526
639
  ? {
527
640
  ...row,
528
641
  models: cloneModels(row.originalModels),
@@ -535,6 +648,7 @@ export function EffortDeclareSection(props: EffortDeclareSectionProps): ReactNod
535
648
  </ul>
536
649
  )
537
650
  : null}
651
+ <p className={css.footer}>{PLUGIN_FOOTER_TEXT}</p>
538
652
  </div>
539
653
  )
540
654
  }
@@ -7,7 +7,7 @@
7
7
  </samp>
8
8
  </p>
9
9
 
10
- Browser half: registers **Reasoning efforts** in the Web UI settings panel and writes `llm-pi-ai` through the official settings RPC.
10
+ Browser half: registers **Reasoning efforts** in the Web UI settings panel and writes per-model `reasoningEfforts` and `input` on `llm-pi-ai` through the official settings RPC.
11
11
 
12
12
  Wiring failures in `apply` throw, same as the official Models page. Do not swallow `apply` with a blanket `try/catch`. Locale, CSS, and event subscriptions are cleaned up through `ctx.effect` on unload.
13
13
 
@@ -17,10 +17,12 @@ Cross-plugin work uses Cordis services only (`connection`, `settingsScope`, `set
17
17
 
18
18
  | File | Role |
19
19
  | --- | --- |
20
- | [`index.ts`](./index.ts) | Registers zh/en copy, CSS (`ctx.effect` insert/remove), and `settings.section` (id `effort-declare`, order 12). `describe.subscribe` only syncs `writable` (it fires for every namespace, including this page’s own `acceptView`). Full reloads come from `settings/document-updated` (`llm-pi-ai` only), `llm/adapters-updated`, and `connection/reset`. `locale: NS` lets the framework inject `t`; `inject` only returns `api` / `describe` / `schema` / `subscribeInvalidate`. |
21
- | [`EffortDeclareSection.tsx`](./EffortDeclareSection.tsx) | Route cards, presets, Off tri-state, advanced protocol switches, save and cancel. Save is exclusive for the whole `llm-pi-ai` namespace; `busy` is “Saving…” on the in-flight card, `saveLocked` disables the others. A successful write folds `acceptView` + `applySaveSuccess` and does not full-reload. |
22
- | [`load-drafts.ts`](./load-drafts.ts) | Builds drafts from `llm.providers` + the settings mirror; drafts come from `user`, protocol classification may use `value`. `thinkingFormat` choices come only from the live schema union. |
23
- | [`locales.ts`](./locales.ts) | Copy namespace `plugin-effort-declare`. |
20
+ | [`index.ts`](./index.ts) | Registers zh/en copy, CSS (`ctx.effect` insert/remove), and `settings.section` (id `effort-declare`, order 12). `describe.subscribe` only syncs `writable` (it fires for every namespace, including this page’s own `acceptView`). `settings/document-updated` (`llm-pi-ai` only) carries the Host revision; the section skips its own mutate echo, waits until the mirror revision has **caught up**, then `getSnapshot()` — it does not use `ensure()` as refresh. Provider directory changes come from `llm/adapters-updated`; `connection/reset` uses `ensure()` so an in-flight official `load()` is awaited. `locale: NS` lets the framework inject `t`; `inject` only returns `api` / `describe` / `schema` / `subscribeInvalidate`. |
21
+ | [`EffortDeclareSection.tsx`](./EffortDeclareSection.tsx) | Route cards, presets, Off tri-state, per-model **Accepts image input**, advanced protocol switches, save and cancel. Save is exclusive for the whole `llm-pi-ai` namespace (ref-guarded against double-click); `busy` is “Saving…” on the in-flight card, `saveLocked` disables the others. Save is blocked by `modelRowError` for empty efforts, Off-only, and illegal `input`. A successful write syncs `draftsRef` immediately and folds `acceptView` + `applySaveSuccess`. External `document-updated` events that arrive during save are replayed afterwards. A reload with no conflict clears that card’s conflict/error notice. Footer shows the version and copyright years frozen at pack time. |
22
+ | [`build-info.ts`](./build-info.ts) | Reads `package.json` version and UTC pack year injected by tsdown `define`, and formats the footer line. |
23
+ | [`globals.d.ts`](./globals.d.ts) | Types for `__PLUGIN_VERSION__` and `__COPYRIGHT_TO__`. |
24
+ | [`load-drafts.ts`](./load-drafts.ts) | Builds drafts from `llm.providers` + the settings mirror; drafts come from `user`, protocol classification may use `value`. `thinkingFormat` choices come only from the live schema union. First paint uses `ensure()`; refresh reads `getSnapshot()` only, with helpers that wait for the namespace revision. |
25
+ | [`locales.ts`](./locales.ts) | Copy namespace `plugin-effort-declare` (efforts, image input, and validation errors). |
24
26
  | [`effort-declare.module.css`](./effort-declare.module.css) | `--dsw-alias-*` tokens only, so dark theme stays correct. |
25
27
  | [`schema-ops.ts`](./schema-ops.ts) | Binds `settingsSchema` as plain callbacks (including `validate`) so the service identity is not passed into React. |
26
28