@linxin666/dsh-client-ui-task-board 0.3.23 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/icon.svg +3 -0
  2. package/lib/client.js +351 -85
  3. package/lib/client.js.map +1 -1
  4. package/lib/index.js +174 -916
  5. package/lib/types/client/PluginSettingsCard.d.ts +4 -0
  6. package/lib/types/client/TaskBoardSettingsCard.d.ts +5 -5
  7. package/lib/types/client/board/TaskForm.d.ts +6 -1
  8. package/lib/types/client/board/parse-model-pref.d.ts +4 -0
  9. package/lib/types/client/index.d.ts +43 -10
  10. package/lib/types/client/locales.d.ts +1 -0
  11. package/lib/types/client/main-session.d.ts +32 -0
  12. package/lib/types/client/plugin-card-seat.d.ts +103 -0
  13. package/lib/types/client/settings-form.d.ts +17 -17
  14. package/lib/types/core/controller.d.ts +10 -8
  15. package/lib/types/core/session-reuse.d.ts +4 -2
  16. package/lib/types/host-ledger.d.ts +15 -0
  17. package/lib/types/http.d.ts +18 -1
  18. package/lib/types/index.d.ts +72 -17
  19. package/package.json +30 -24
  20. package/src/client/PluginSettingsCard.tsx +7 -2
  21. package/src/client/TaskBoardSettingsCard.tsx +7 -5
  22. package/src/client/board/NewTaskModal.tsx +33 -7
  23. package/src/client/board/TaskForm.tsx +13 -0
  24. package/src/client/board/parse-model-pref.ts +35 -0
  25. package/src/client/board.module.css +31 -12
  26. package/src/client/index.ts +121 -38
  27. package/src/client/locales.ts +2 -0
  28. package/src/client/main-session.ts +43 -0
  29. package/src/client/plugin-card-seat.ts +192 -0
  30. package/src/client/settings-form.ts +34 -31
  31. package/src/client/sidebar-entry-core.ts +3 -3
  32. package/src/client/sidebar-entry.ts +2 -2
  33. package/src/core/controller.ts +12 -8
  34. package/src/core/session-reuse.ts +14 -5
  35. package/src/host-ledger.ts +59 -11
  36. package/src/http.ts +23 -1
  37. package/src/index.ts +97 -52
@@ -47,6 +47,10 @@ export interface PluginSettingsCardProps<TKey extends string = string> {
47
47
  * edits, so the footer would sit there permanently disabled.
48
48
  */
49
49
  hideFooter?: boolean;
50
+ /** Render children even when the settings namespace is not exposed by the host. */
51
+ renderChildrenWhenNotExposed?: boolean;
52
+ /** Hide the 'not exposed' warning banner when the settings namespace is not exposed. */
53
+ hideNotExposedNotice?: boolean;
50
54
  /** The plugin's controls. */
51
55
  children: ReactNode;
52
56
  }
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
7
7
  import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
8
- import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
8
+ import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
9
9
  import { type CardActions, type CardShell, type FieldState as CardFieldState } from './settings-form.ts';
10
10
  /** The task-board fields this card edits (the namespace's full schema). */
11
11
  export interface TaskBoardSettings {
@@ -32,12 +32,12 @@ export interface TaskBoardSettingsCardFace extends CardActions {
32
32
  taskBoardSettingsCard: SnapshotStore<TaskBoardSettingsCardState>;
33
33
  };
34
34
  }
35
- /** Bridges the `task-board` scope onto the card's staged form. */
35
+ /** Bridges the `task-board` settings form onto the card's staged form. */
36
36
  export declare class TaskBoardSettingsCardController {
37
37
  private readonly form;
38
38
  private readonly store;
39
- /** @param scope - the bound settings scope for the `task-board` namespace. */
40
- constructor(scope: SettingsScope<TaskBoardSettings>);
39
+ /** @param scope - the bound configuration form for the `task-board` namespace. */
40
+ constructor(scope: ConfigForm<TaskBoardSettings>);
41
41
  private projection;
42
42
  /**
43
43
  * Build the face the card's slot registration injects.
@@ -45,7 +45,7 @@ export declare class TaskBoardSettingsCardController {
45
45
  */
46
46
  inject(): TaskBoardSettingsCardFace;
47
47
  /**
48
- * Release the card's scope subscription and bound stores; the slot
48
+ * Release the card's form subscription and bound stores; the slot
49
49
  * disposer calls this on teardown.
50
50
  */
51
51
  dispose(): void;
@@ -7,7 +7,7 @@
7
7
  import type { ReactNode } from 'react';
8
8
  import { type TaskTag } from '../../core/tasks.ts';
9
9
  /** Modal overlay: closes on backdrop press, submits through the form. */
10
- export declare function ModalShell({ ariaLabel, title, error, pending, submitLabel, onSubmit, onClose, children, }: {
10
+ export declare function ModalShell({ ariaLabel, title, error, pending, submitLabel, onSubmit, onClose, secondaryAction, children, }: {
11
11
  ariaLabel: string;
12
12
  title: string;
13
13
  error: string | undefined;
@@ -15,6 +15,11 @@ export declare function ModalShell({ ariaLabel, title, error, pending, submitLab
15
15
  submitLabel: string;
16
16
  onSubmit: () => void;
17
17
  onClose: () => void;
18
+ /** Optional second action beside the primary submit (e.g. create and run). */
19
+ secondaryAction?: {
20
+ label: string;
21
+ onSubmit: () => void;
22
+ };
18
23
  children: ReactNode;
19
24
  }): import("react").JSX.Element;
20
25
  /** Title + description + prompt fields shared by the new and edit task forms. */
@@ -0,0 +1,4 @@
1
+ /** The remembered parse model; '' means the Host default (or no preference). */
2
+ export declare function readParseModelPreference(storage?: Pick<Storage, 'getItem'> | undefined): string;
3
+ /** Remember the picked parse model; '' records the Host-default choice. */
4
+ export declare function writeParseModelPreference(model: string, storage?: Pick<Storage, 'setItem'> | undefined): void;
@@ -9,8 +9,30 @@
9
9
  * plugin must not take the GUI down.
10
10
  */
11
11
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
12
- import type { SettingsScope, SettingsScopeSpec } from '@deepseek-ai/dsh-client-ui-settings/client';
12
+ import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
13
+ import { type TaskBoardSettings } from './TaskBoardSettingsCard.tsx';
13
14
  import { type TaskBoardKey } from './locales.ts';
15
+ /** Domain-owned description of one settings namespace a family card binds. */
16
+ export interface SettingsFormSpec<T> {
17
+ /** Settings namespace the card edits. */
18
+ namespace: string;
19
+ /**
20
+ * Narrow one wire section; undefined keeps the last accepted value. The
21
+ * shared form already resolves the namespace's own serialized wire schema,
22
+ * so a decoder exists only to narrow beyond that schema.
23
+ */
24
+ decode?: (section: unknown) => T | undefined;
25
+ }
26
+ /**
27
+ * The family settings binder published by dsh-web-settings. Its `bind` resolves
28
+ * a family namespace to the profile entry id that owns it and hands back the
29
+ * shared configuration form, so it is the only seat that can reach this card's
30
+ * form on a Host whose row id is not the namespace.
31
+ */
32
+ export interface SettingsFormBinder {
33
+ /** Bind one family settings namespace. */
34
+ bind<T>(spec: SettingsFormSpec<T>): ConfigForm<T>;
35
+ }
14
36
  declare module '@deepseek-ai/dsh-client-ui-slots' {
15
37
  interface LocaleNamespaceMap {
16
38
  /** Task-board surface copy. */
@@ -19,9 +41,9 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
19
41
  interface SlotMap {
20
42
  /**
21
43
  * The child slot the Web UI plugin group declares; this card registers
22
- * into the group instead of the top-level `settings.plugin.item` list.
23
- * Spelled here with the same shape so this package can register without
24
- * depending on the sibling UI package.
44
+ * into the group's list seat rather than the official
45
+ * bundle-configuration seat. Spelled here with the same shape so this
46
+ * package can register without depending on the sibling UI package.
25
47
  */
26
48
  'web-ui.plugin.item': {
27
49
  kind: 'list';
@@ -38,13 +60,11 @@ export interface SettingsPluginItemOwnerProps {
38
60
  declare module '@deepseek-ai/cordis' {
39
61
  interface Context {
40
62
  /**
41
- * Optional rc.6 compatibility binder provided by dsh-web-settings;
42
- * absent when that group plugin is not installed, so callers fall back to
43
- * the official settings scope.
63
+ * Optional family settings binder provided by dsh-web-settings; absent
64
+ * when that group plugin is not installed, so callers fall back to the
65
+ * shared configuration forms service.
44
66
  */
45
- webUiSettings?: {
46
- bind<S>(spec: SettingsScopeSpec<S>): SettingsScope<S>;
47
- };
67
+ webUiSettings?: SettingsFormBinder;
48
68
  }
49
69
  }
50
70
  /**
@@ -61,3 +81,16 @@ export declare const inject: string[];
61
81
  * @param ctx - client root context (services: sessions, workspaces).
62
82
  */
63
83
  export declare function apply(ctx: ClientContext): void;
84
+ /**
85
+ * Bind the settings form this card stages over.
86
+ *
87
+ * The family binder (`ctx.get('webUiSettings')`, published by dsh-web-settings)
88
+ * comes first: it is what traces this package's family namespace onto the
89
+ * profile entry id the Host serves the form under, and it keeps the loopback
90
+ * bridge as its own fallback. A page without that group falls back to the
91
+ * shared configuration forms service bound directly at one of this package's
92
+ * own profile entry ids.
93
+ * @param ctx - client root context.
94
+ * @returns the form the settings card reads and writes.
95
+ */
96
+ export declare function bindSettingsForm(ctx: ClientContext): ConfigForm<TaskBoardSettings>;
@@ -61,6 +61,7 @@ export declare const zh: {
61
61
  'new.prompt': string;
62
62
  'new.promptPlaceholder': string;
63
63
  'new.submit': string;
64
+ 'new.createAndRun': string;
64
65
  'new.cancel': string;
65
66
  'new.required': string;
66
67
  'new.freeze': string;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Main-view Session derivation for the multi-instance Client Session model.
3
+ *
4
+ * The Client Session Controller carries no global "current" selection: view
5
+ * selection belongs to the workspace UI, which owns the main-area reference and
6
+ * publishes it through the catalog's per-source ownership counts
7
+ * (`SessionSummary.retainedBy.mainView`). A plugin that needs the Session the
8
+ * main view shows derives it from that marker instead of the removed
9
+ * `SessionListState.current` field.
10
+ *
11
+ * The marker describes view ownership, not authority. It is the right input for
12
+ * presentation and for deciding which view a navigation targets; it is not a
13
+ * business fallback, because source counts never imply visibility or success.
14
+ * @module dsh-web/shared/main-session
15
+ */
16
+ /** The catalog row subset this helper reads (a `SessionSummary` projection). */
17
+ export interface MainViewOwnedRow<Id extends string = string> {
18
+ /** Catalog row identity. */
19
+ readonly id: Id;
20
+ /** Per-source local ownership counts; absent rows are never owned. */
21
+ readonly retainedBy?: Readonly<Partial<Record<string, number>>> | undefined;
22
+ }
23
+ /**
24
+ * Resolve the Session the main view currently shows.
25
+ *
26
+ * Reads the catalog's rows rather than a per-id retain-info source: ownership
27
+ * counts ride the list snapshot, so this neither allocates observers nor opens
28
+ * history, and a subscription to the list still fires when the selection moves.
29
+ * @param byId - the session catalog's rows (`SessionListState.byId`), when available.
30
+ * @returns the main-view session id, or undefined when the main view shows none.
31
+ */
32
+ export declare function mainViewSessionId<Id extends string>(byId: Readonly<Record<Id, MainViewOwnedRow<Id> | undefined>> | undefined | null): Id | undefined;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Family plugin-card seat.
3
+ *
4
+ * A family plugin contributes its settings card to whichever plugin-card seat
5
+ * the running host actually renders:
6
+ *
7
+ * - `web-ui.plugin.item` — the list seat declared by the dsh-web-settings
8
+ * group section (this family's own first-level "Web UI plugins" section);
9
+ * - `plugins.bundle.config` — the official keyed seat of the harness's plugin
10
+ * manager page, keyed by the bundle's package name and rendered on that
11
+ * bundle's page. alpha.2 removed the `settings.plugin.item` keyed seat of the
12
+ * `ui-settings-plugins` tab that this helper used before, so a card keyed by
13
+ * its settings namespace has no seat to land in any more.
14
+ *
15
+ * SEAT SELECTION IS NOT A DECLARATION PROBE. The official plugin surface
16
+ * belongs to the harness bundle and declares its seats before any external
17
+ * plugin's `apply()` runs, so "is the official seat declared?" answers yes even
18
+ * in the deployment whose whole point is the family group. Choosing on that
19
+ * probe sends every family card to the official page and leaves the group's own
20
+ * section permanently empty — the family of reports where the section renders
21
+ * its heading and zero cards.
22
+ *
23
+ * The signal that actually distinguishes the two deployments is whether
24
+ * dsh-web-settings is loaded: it is the package that owns the group section and
25
+ * it publishes the `webUiSettings` service during `apply()`, which every
26
+ * family plugin already reads for its settings form. Group loaded -> the family
27
+ * seat; group absent -> the official seat.
28
+ *
29
+ * The decision is re-evaluated on every `slots/changed` because the group may
30
+ * apply after this plugin (the family aggregate orders it first, a profile that
31
+ * installs the group separately need not): the initial contribution goes to the
32
+ * official seat, then moves to the family seat the moment the group's section
33
+ * registers. The entry is disposed before the replacement is registered, so a
34
+ * card is never in two seats at once.
35
+ *
36
+ * The shared tree has no client-SDK dependency, so this module reads its
37
+ * context through the structural shape below; callers pass the plugin's own
38
+ * `ctx`.
39
+ */
40
+ /** The family list seat key. */
41
+ export declare const FAMILY_PLUGIN_CARD_SEAT = "web-ui.plugin.item";
42
+ /** The official keyed plugin-card seat key (the alpha.2 bundle-configuration seat). */
43
+ export declare const OFFICIAL_PLUGIN_CARD_SEAT = "plugins.bundle.config";
44
+ /** The service dsh-web-settings publishes while it is loaded. */
45
+ export declare const FAMILY_GROUP_SERVICE = "webUiSettings";
46
+ /** The slot-registry member this helper uses (structurally satisfied by ctx.slots). */
47
+ export interface PluginCardSlots {
48
+ /** Contribution of one card entry. */
49
+ register(options: never, component: never): unknown;
50
+ }
51
+ /** The slice of the client context a card contribution needs. */
52
+ export interface PluginCardContext {
53
+ slots: PluginCardSlots;
54
+ /** Service lookup; absent on a context double that only models the slots. */
55
+ get?(name: string): unknown;
56
+ /** Event subscription seat; absent on an event-less test double. */
57
+ on?(event: string, listener: (...args: never[]) => void): unknown;
58
+ }
59
+ /** Owner share of a family list-seat card (that seat supplies nothing). */
60
+ export interface SettingsPluginItemOwnerProps {
61
+ /** Marker field: card owner props are intentionally empty. */
62
+ children?: never;
63
+ }
64
+ /** One family plugin's card contribution. */
65
+ export interface PluginCardSeat {
66
+ /**
67
+ * Npm package name of the bundle this card configures; the official keyed
68
+ * seat dispatches on it. Every family package ships its own bundle patch, so
69
+ * a standalone install lists that package as the bundle whose page renders
70
+ * this card.
71
+ */
72
+ bundle: string;
73
+ /** Family list-seat entry id. */
74
+ id: string;
75
+ /** Family list-seat sort order. */
76
+ order?: number;
77
+ /** Family list-seat display label; the official keyed seat carries none. */
78
+ label?: () => string;
79
+ /** Locale namespace the card renders with. */
80
+ locale: string;
81
+ /** Business-face factory of the registration. */
82
+ inject?: () => object;
83
+ /**
84
+ * The card component. Its props type is the seat's composed shape, which is
85
+ * chosen at runtime, so the helper takes it erased; the registrant keeps its
86
+ * own precise typing at the call site.
87
+ */
88
+ component: unknown;
89
+ }
90
+ /**
91
+ * Whether the family group (dsh-web-settings) is loaded in this page. The
92
+ * service is the group package's own contract, so the probe cannot be fooled
93
+ * by a harness release that starts declaring the official seat differently.
94
+ */
95
+ export declare function familyGroupLoaded(ctx: PluginCardContext): boolean;
96
+ /**
97
+ * Contribute one family plugin card to the seat this host renders, following
98
+ * the group if it loads later. The entry is disposed and re-registered on a
99
+ * seat change, never duplicated.
100
+ * @param ctx - client context (its slot registry decides the seat).
101
+ * @param seat - the card contribution.
102
+ */
103
+ export declare function installPluginCard(ctx: PluginCardContext, seat: PluginCardSeat): void;
@@ -6,7 +6,7 @@
6
6
  * each plugin's client bundle; mirrors the official ui-plugin-config
7
7
  * card-store pattern.
8
8
  */
9
- import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
9
+ import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
10
10
  import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
11
11
  /** The write one field's staged text performs when the card is saved. */
12
12
  export type FieldWrite = {
@@ -86,7 +86,7 @@ export interface CardActions {
86
86
  /** Drop every staged edit. */
87
87
  discard: () => void;
88
88
  }
89
- /** One durable write inside the save's atomic scope mutation. */
89
+ /** One durable write inside the save's atomic form mutation. */
90
90
  export interface BatchedWrite {
91
91
  /** Field this entry writes. */
92
92
  field: string;
@@ -130,20 +130,20 @@ export declare class CardForm<T> {
130
130
  private readonly specs;
131
131
  private readonly staged;
132
132
  private readonly listeners;
133
- /** The scope subscription installed in the constructor; released by dispose(). */
134
- private readonly disposeScope;
133
+ /** The form subscription installed in the constructor; released by dispose(). */
134
+ private readonly disposeForm;
135
135
  private disposed;
136
136
  private saving;
137
137
  private failed;
138
138
  private failedReason;
139
- /** @param scope - the bound settings scope for this card's namespace. */
140
- constructor(scope: SettingsScope<T>, specs: FieldSpec[]);
139
+ /** @param scope - the bound configuration form for this card's namespace. */
140
+ constructor(scope: ConfigForm<T>, specs: FieldSpec[]);
141
141
  /**
142
- * Release the scope subscription and every bound store listener. The card
142
+ * Release the form subscription and every bound store listener. The card
143
143
  * must call this on teardown; later calls are no-ops.
144
144
  */
145
145
  dispose(): void;
146
- /** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
146
+ /** Publish a projection of this form, rebuilt whenever the form or a draft changes. */
147
147
  bind<S>(project: () => S): SnapshotStore<S>;
148
148
  /** Read the card-level state: what the Host serves, and what a save would do. */
149
149
  shell(): CardShell;
@@ -152,19 +152,19 @@ export declare class CardForm<T> {
152
152
  /** The actions the card's slot registration injects. */
153
153
  actions(): CardActions;
154
154
  /**
155
- * Write every staged edit in one atomic scope mutation, then re-seed from
155
+ * Write every staged edit in one atomic form mutation, then re-seed from
156
156
  * what the Host accepted.
157
157
  *
158
158
  * The whole batch rides one mutate, so cross-field validate hooks
159
159
  * (baseURL+model) judge it as a unit: the Host either applies every write
160
- * or refuses the batch. The 0.1.2 scope contract never rejects a refused
161
- * mutation — the scope recovers with a fresh Host view and resolves — so
162
- * resolution alone proves nothing: the outcome is judged by reading the
163
- * settled snapshot back, one planned write at a time, and one missed write
164
- * fails the whole save. A scope that still rejects on refusal (the dsh-web
165
- * bridge scope) reports through the same failure path with its rejection
166
- * message. A save that did not land keeps its drafts, so the user can
167
- * correct them instead of retyping.
160
+ * or refuses the batch. The form contract answers a refusal or a skipped
161
+ * write with `false` (it recovers with a fresh Host view instead of
162
+ * throwing), so the outcome is judged twice: the answer itself, and then the
163
+ * settled snapshot read back one planned write at a time. One missed write
164
+ * fails the whole save. A transport that rejects instead (the dsh-web bridge
165
+ * controller on a dead connection) reports through the same failure path
166
+ * with its rejection message. A save that did not land keeps its drafts, so
167
+ * the user can correct them instead of retyping.
168
168
  * @returns settlement after the mutation and the read-back.
169
169
  */
170
170
  save(): Promise<void>;
@@ -30,16 +30,18 @@ export interface TaskBoardTransport {
30
30
  */
31
31
  parseDraft?(request: TaskBoardParseRequest, signal?: AbortSignal): Promise<TaskBoardParseDraft>;
32
32
  }
33
- /** The sessions face the controller needs for navigation awareness. */
33
+ /**
34
+ * The sessions face the controller needs for navigation awareness. The Client
35
+ * Session Controller carries no global selection since 0.1.6-alpha.2, so the
36
+ * wiring resolves the main-view Session once and exposes it as current().
37
+ */
34
38
  export interface SessionsControllerFace {
35
- list: {
36
- getSnapshot(): {
37
- current: string | undefined;
38
- };
39
- subscribe(fn: () => void): () => void;
40
- };
41
- /** Select a session as current (navigates the conversation view). */
39
+ /** The Session the main view currently shows, when any. */
40
+ current(): string | undefined;
41
+ /** Navigate the main view to a Session. */
42
42
  open(id: string): void;
43
+ /** Subscribe to catalog changes; selection ownership rides the same snapshot. */
44
+ subscribe(fn: () => void): () => void;
43
45
  }
44
46
  /** Controller dependencies (all swappable in tests). */
45
47
  export interface ControllerDeps {
@@ -8,8 +8,10 @@ import type { TaskRecord } from './tasks.ts';
8
8
  * Pick the session a new execution of this task may continue in, or undefined
9
9
  * to mint a fresh conversation. Reuse requires ALL of:
10
10
  * - the task opted in (`reuseSession === true`);
11
- * - the task's newest execution carries a session id and has settled (an open
12
- * execution would have made the ledger refuse the run in the first place);
11
+ * - the task's newest SETTLED execution carries a session id (issue #1587: the
12
+ * launch path calls this after `startExecution` appended this run's own open
13
+ * record, so reading the array tail always found an unsettled row and reuse
14
+ * never happened);
13
15
  * - the roster is known and that session is present and idle.
14
16
  *
15
17
  * An unknown roster (session/list unavailable) never reuses: minting a fresh
@@ -37,6 +37,21 @@ export interface LedgerRuntimeView {
37
37
  */
38
38
  export declare function processState(pid: number): string | undefined;
39
39
  export declare function processIsAlive(pid: number): boolean;
40
+ /** Runs one PowerShell script and returns its trimmed stdout. */
41
+ export type PowerShellProbe = (script: string, timeoutMs: number) => string | undefined;
42
+ /**
43
+ * Windows start time (Unix epoch ms) of a live process.
44
+ *
45
+ * `Get-Process` is the precise first choice, but an unprivileged caller cannot
46
+ * read `.StartTime` for a protected process (System, svchost): the property is
47
+ * empty, so the probe returns nothing. Without a fallback, a crash leftover
48
+ * lock whose PID was reused by such a process could never be proven stale and
49
+ * blocked every startup until the lock was deleted by hand (issue #1629).
50
+ * Win32_Process through CIM reports the same CreationDate for those processes
51
+ * at the same millisecond precision, so it is the second identity source. The
52
+ * probe is injectable so the fallback chain is testable off Windows.
53
+ */
54
+ export declare function win32StartTimeMs(pid: number, probe?: PowerShellProbe): number | undefined;
40
55
  export declare class HostTaskLedger {
41
56
  private readonly now;
42
57
  private document;
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * Shared JSON body/response helpers for the host route families: one strict
3
3
  * bounded body reader, one lenient bounded body reader, one JSON object
4
- * narrow, and one JSON writer. Previously these were copy-pasted across the
4
+ * narrow, one JSON writer, and the outbound request-init helper that keeps a
5
+ * host fetch body decodable (withIdentityEncoding). Previously the readers
6
+ * were copy-pasted across the
5
7
  * package route files (routes.ts, update-routes.ts, mobile-api.ts, and each
6
8
  * family's route module) with drifting contracts: body caps ranging 4 KiB to
7
9
  * 1 MiB and four distinct overflow behaviors (reject, undefined, null, throw).
@@ -34,6 +36,21 @@ export declare function readJsonBody(req: IncomingMessage, opts?: {
34
36
  }): Promise<unknown | null>;
35
37
  /** Narrow a value to a JSON object, or undefined when it is not one. */
36
38
  export declare function asJsonObject(value: unknown): Record<string, unknown> | undefined;
39
+ /**
40
+ * Request init that asks a remote origin for an uncompressed body.
41
+ *
42
+ * The DSH host boots `@deepseek-ai/dsh-http-proxy`, whose top-level import of
43
+ * the npm `undici` copy replaces the legacy `undici.globalDispatcher.1` slot
44
+ * Node's built-in `fetch()` reads. That cross-major wrapper drops the
45
+ * `content-encoding` header and automatic decompression, so a plain fetch
46
+ * resolves to raw gzip/brotli/zstd bytes — compressed noise no JSON or text
47
+ * consumer can read. Every host fetch that parses a remote body must request
48
+ * identity encoding. Caller headers survive; `accept-encoding` is forced to
49
+ * `identity` because a caller value has no decoder behind it in this host.
50
+ * @param init - the caller's request init (signal, headers, method, ...).
51
+ * @returns a copy with `accept-encoding: identity` merged in.
52
+ */
53
+ export declare function withIdentityEncoding(init?: RequestInit): RequestInit;
37
54
  /**
38
55
  * Write one JSON response. Default headers are the family defaults
39
56
  * (content-type and referrer-policy); caller headers are appended or
@@ -5,9 +5,8 @@
5
5
  * execution reconciliation, and optional idle-sleep inhibitor. The browser is
6
6
  * a same-origin asynchronous view over that service.
7
7
  */
8
- import type { Context } from '@deepseek-ai/cordis';
9
- import type { SettingsNamespace } from '@deepseek-ai/dsh-settings';
10
- import z from 'schemastery';
8
+ import type { Context, Volatile } from '@deepseek-ai/cordis';
9
+ import z from '@deepseek-ai/schemastery';
11
10
  import type { LlmRuntime } from '@deepseek-ai/dsh-llm';
12
11
  import { type TaskPermission } from './core/tasks.ts';
13
12
  /** Default environment variable holding the authenticated proxy token. */
@@ -16,24 +15,30 @@ export declare const inject: string[];
16
15
  /** Model-facing announcement: plugin presence, capabilities, and limits. */
17
16
  export declare const TASK_BOARD_GUIDANCE = "\u672C\u673A\u5DF2\u5B89\u88C5 dsh-task-board \u63D2\u4EF6\uFF08DSH Web GUI \u7684\u4EFB\u52A1\u770B\u677F\uFF09\uFF1A\u4FA7\u8FB9\u680F\u300C\u4EFB\u52A1\u770B\u677F\u300D\u5165\u53E3\uFF1B\u5728 dsh-web \u63D2\u4EF6\u5168\u5BB6\u6876\u4ED3\u5E93\uFF08packages/dsh-task-board\uFF09\u7EDF\u4E00\u7EF4\u62A4\uFF0C\u7ECF\u805A\u5408\u5305 web-ui-all \u4E00\u952E\u5B89\u88C5\u3002\u80FD\u529B\uFF1A\u591A\u5217\u770B\u677F\u7BA1\u7406\u4EFB\u52A1\uFF1BHost \u6743\u5A01\u8D26\u672C\uFF1B\u5173\u95ED\u6D4F\u89C8\u5668\u540E\u4ECD\u7531 Host \u6267\u884C\u548C\u7ED3\u7B97\uFF1B\u4EFB\u52A1\u53EF\u9489\u4F4F\u5DE5\u4F5C\u533A\u3001agent \u9884\u8BBE\u548C\u6743\u9650\uFF1B\u652F\u6301 Host \u672C\u5730\u65F6\u533A\u7684 5 \u6BB5 cron\uFF0C\u9519\u8FC7\u7684\u89E6\u53D1\u70B9\u4E0D\u8865\u8DD1\uFF1B\u53EF\u9009\u4E14\u9ED8\u8BA4\u5173\u95ED\u7684\u7A7A\u95F2\u7CFB\u7EDF\u7761\u7720\u4FDD\u62A4\u5141\u8BB8\u5C4F\u5E55\u7184\u706D\uFF0C\u4F46\u4E0D\u627F\u8BFA\u62E6\u622A\u5408\u76D6\u3001\u624B\u52A8\u7761\u7720\u3001\u4F11\u7720\u3001\u5173\u673A\u6216\u5524\u9192\u5DF2\u7761\u7720\u673A\u5668\u3002\u6267\u884C\u6D88\u8017 API \u989D\u5EA6\u3002\u7528\u6237\u63D0\u5230\u300C\u4EFB\u52A1\u770B\u677F / \u770B\u677F / \u5B9A\u65F6\u4EFB\u52A1\u300D\u65F6\u5373\u6307\u672C\u63D2\u4EF6\uFF0C\u8BF7\u636E\u6B64\u534F\u4F5C\u3002\u82E5\u4F60\u540C\u65F6\u7528 todo_write \u7EF4\u62A4\u4F1A\u8BDD\u9876\u90E8\u7684\u53EF\u89C1\u8BA1\u5212\u5217\u8868\uFF0C\u6700\u7EC8\u56DE\u590D\u524D\u5FC5\u987B\u518D\u6B21\u8C03\u7528 todo_write \u6536\u5C3E\uFF1A\u6CA1\u6709\u5269\u4F59\u5DE5\u4F5C\u65F6\u4E0D\u8981\u4FDD\u7559 in_progress\uFF0C\u5DF2\u5B8C\u6210\u7684\u6700\u540E\u4E00\u6B65\u8981\u6807\u4E3A completed\u3002";
18
17
  /**
19
- * Settings namespace of the board's announcement capability — the section the
20
- * web settings surface edits. Spelled here rather than imported: the browser
21
- * half spells the same value and must not depend on a Host package.
18
+ * Plugin config, validated by the same-named schemastery schema.
19
+ *
20
+ * This schema IS the board's settings page: the 0.1.7 Host serves one
21
+ * configuration form per profile entry from the entry's own Config, so the
22
+ * three fields the browser card edits are marked volatile — the Loader commits
23
+ * an edit into the running fiber's references without remounting the row (no
24
+ * settings document of the plugin's own exists any more).
22
25
  */
23
- export declare const TASK_BOARD_SETTINGS_NAMESPACE: SettingsNamespace;
24
- /** Plugin config, validated by the same-named schemastery schema. */
25
26
  export interface Config {
26
27
  /**
27
28
  * When true (default), a system-prompt section announces the board to every
28
29
  * agent. Set false to keep the board silent in prompts; agents then learn
29
30
  * about it only when the user mentions it.
30
31
  */
31
- announceToAgent?: boolean;
32
+ announceToAgent?: Volatile<boolean>;
32
33
  /** Master switch for the plugin (browser half + host announcement). */
33
- enabled?: boolean;
34
+ enabled?: Volatile<boolean>;
34
35
  /** Prevent idle system sleep while sessions run or schedules are armed. */
35
- preventIdleSleep?: boolean;
36
- /** Canonical reverse-proxy Host authorities admitted with a server-side token. */
36
+ preventIdleSleep?: Volatile<boolean>;
37
+ /**
38
+ * Canonical reverse-proxy Host authorities admitted with a server-side
39
+ * token. Deployment-level, so it stays an ordinary field: editing it reloads
40
+ * the row, which re-registers the routes with the new proxy access.
41
+ */
37
42
  trustedProxyHosts?: string[];
38
43
  /** Environment variable whose value the authenticated proxy injects upstream. */
39
44
  proxyTokenEnv?: string;
@@ -44,7 +49,52 @@ export interface Config {
44
49
  */
45
50
  sessionDefaultPermission?: TaskPermission;
46
51
  }
47
- export declare const Config: z<Config>;
52
+ /**
53
+ * The schema is left to inference rather than annotated with `z<Config>`: a
54
+ * volatile field's parsed output is a `Volatile` reference while its accepted
55
+ * input stays the plain value, so the two sides no longer share one shape and
56
+ * the annotation would reject the schema the Host must be given.
57
+ */
58
+ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
59
+ announceToAgent: z<boolean, boolean, "volatile-defined">;
60
+ enabled: z<boolean, boolean, "volatile-defined">;
61
+ preventIdleSleep: z<boolean, boolean, "volatile-defined">;
62
+ trustedProxyHosts: z<string[], string[], "defined">;
63
+ proxyTokenEnv: z<string, string, "defined">;
64
+ sessionDefaultPermission: z<"read-only" | "workspace-write" | "danger-full-access", "read-only" | "workspace-write" | "danger-full-access", "defined">;
65
+ }>>, Schemastery.ObjectT<NoInfer<{
66
+ announceToAgent: z<boolean, boolean, "volatile-defined">;
67
+ enabled: z<boolean, boolean, "volatile-defined">;
68
+ preventIdleSleep: z<boolean, boolean, "volatile-defined">;
69
+ trustedProxyHosts: z<string[], string[], "defined">;
70
+ proxyTokenEnv: z<string, string, "defined">;
71
+ sessionDefaultPermission: z<"read-only" | "workspace-write" | "danger-full-access", "read-only" | "workspace-write" | "danger-full-access", "defined">;
72
+ }>>, "plain">;
73
+ declare module '@deepseek-ai/cordis' {
74
+ interface Events {
75
+ /**
76
+ * Volatile config values were committed into the running fiber without a
77
+ * remount; dispatched to the owning fiber only. Spelled here because the
78
+ * Loader package is not a dependency of this plugin, with the Loader's own
79
+ * shape so the two declarations merge when a Host program carries both.
80
+ * @param paths - changed config paths as key arrays; every value is committed before dispatch.
81
+ * @mode emit
82
+ */
83
+ 'loader/volatile-update'(paths: readonly (readonly string[])[]): void;
84
+ }
85
+ }
86
+ /**
87
+ * Read one config field's current value.
88
+ *
89
+ * The Loader hands schema-volatile fields as stable references it commits in
90
+ * place, so a live value must be read at use time rather than captured when
91
+ * the plugin activates; a plain value (a programmatic mount, or a field the
92
+ * schema does not mark volatile) is returned as it stands.
93
+ * @param field - the config field as the Loader handed it.
94
+ * @param fallback - value to use when the field is absent.
95
+ * @returns the effective field value.
96
+ */
97
+ export declare function readConfigField<T>(field: Volatile<T> | T | undefined, fallback: T): T;
48
98
  /** Resolve proxy access without ever placing the token value in plugin config. */
49
99
  export declare function resolveProxyAccess(config: Config | undefined, env?: NodeJS.ProcessEnv): {
50
100
  trustedProxyHosts: string[];
@@ -59,10 +109,15 @@ export declare function resolveProxyAccess(config: Config | undefined, env?: Nod
59
109
  */
60
110
  export declare function resolveLlmRuntime(ctx: Context): LlmRuntime | undefined;
61
111
  /**
62
- * Register the board's announcement section, gated on the composition entry's
63
- * `announceToAgent` (and the live settings value once the web settings
64
- * surface is served). The section is re-registered whenever the source
65
- * changes, so a settings edit takes effect without a restart.
112
+ * Activate the board's host half: ledger, routes, cron scheduler, power
113
+ * inhibitor, and the model-facing announcement.
114
+ *
115
+ * The effective settings are the config the Host hands this row. The three
116
+ * fields the browser card edits are schema-volatile, so the Loader commits an
117
+ * edit into the running fiber's references instead of remounting the row —
118
+ * `sync` therefore reads them at use time and follows
119
+ * `loader/volatile-update`, the event the Loader emits once it has committed
120
+ * them. Every other field reloads the row, which re-runs this activation.
66
121
  * @param ctx - the plugin context (systemPrompt injected).
67
122
  * @param config - resolved plugin config (schema defaults applied by the loader).
68
123
  */