@linxin666/dsh-client-ui-task-board 0.4.5 → 0.4.7

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 (83) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +34 -18
  3. package/README.zh.md +34 -18
  4. package/lib/client.js +2804 -1024
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +2576 -416
  7. package/lib/types/client/CardDefaultPresetField.d.ts +35 -0
  8. package/lib/types/client/TaskBoardSettingsCard.d.ts +61 -0
  9. package/lib/types/client/board/ReasoningEffortField.d.ts +25 -0
  10. package/lib/types/client/board/model-options.d.ts +67 -0
  11. package/lib/types/client/board/preset-label.d.ts +21 -0
  12. package/lib/types/client/board/preset-roster.d.ts +45 -0
  13. package/lib/types/client/board/team-run-promotion.d.ts +26 -0
  14. package/lib/types/client/index.d.ts +4 -5
  15. package/lib/types/client/locales.d.ts +81 -0
  16. package/lib/types/client/plugin-card-seat.d.ts +5 -0
  17. package/lib/types/client/schedule-zone.d.ts +50 -0
  18. package/lib/types/core/card-defaults.d.ts +78 -0
  19. package/lib/types/core/controller.d.ts +41 -17
  20. package/lib/types/core/launch-width.d.ts +31 -0
  21. package/lib/types/core/poll-cadence.d.ts +31 -0
  22. package/lib/types/core/reasoning-effort.d.ts +57 -0
  23. package/lib/types/core/subtask.d.ts +30 -8
  24. package/lib/types/core/tasks.d.ts +163 -14
  25. package/lib/types/core/use-cases/task-create.d.ts +10 -5
  26. package/lib/types/core/use-cases/task-schedule.d.ts +68 -10
  27. package/lib/types/core/use-cases/task-update.d.ts +1 -1
  28. package/lib/types/core/verification-budget.d.ts +52 -0
  29. package/lib/types/core/verification.d.ts +376 -9
  30. package/lib/types/host/launch-admission.d.ts +72 -0
  31. package/lib/types/host/verification-gate.d.ts +24 -0
  32. package/lib/types/host/verification-runner.d.ts +38 -7
  33. package/lib/types/host-ledger.d.ts +91 -11
  34. package/lib/types/host-runner.d.ts +111 -13
  35. package/lib/types/host-service.d.ts +199 -8
  36. package/lib/types/index.d.ts +141 -0
  37. package/lib/types/protocol.d.ts +63 -12
  38. package/package.json +29 -33
  39. package/src/client/CardDefaultPresetField.tsx +101 -0
  40. package/src/client/TaskBoardSettingsCard.tsx +288 -17
  41. package/src/client/board/LinkSubtaskModal.tsx +8 -0
  42. package/src/client/board/NewTaskModal.tsx +237 -60
  43. package/src/client/board/ReasoningEffortField.tsx +50 -0
  44. package/src/client/board/TaskCard.tsx +3 -3
  45. package/src/client/board/TaskDetail.tsx +261 -24
  46. package/src/client/board/VerificationReport.tsx +132 -13
  47. package/src/client/board/model-options.ts +178 -0
  48. package/src/client/board/preset-label.ts +32 -0
  49. package/src/client/board/preset-roster.ts +58 -0
  50. package/src/client/board/status-key.ts +3 -0
  51. package/src/client/board/team-run-promotion.ts +39 -0
  52. package/src/client/index.ts +105 -108
  53. package/src/client/locales.ts +171 -6
  54. package/src/client/plugin-card-seat.ts +7 -0
  55. package/src/client/schedule-zone.ts +100 -2
  56. package/src/client/settings-entry-form.ts +0 -1
  57. package/src/core/card-defaults.ts +132 -0
  58. package/src/core/controller.ts +55 -16
  59. package/src/core/launch-width.ts +38 -0
  60. package/src/core/poll-cadence.ts +41 -0
  61. package/src/core/reasoning-effort.ts +92 -0
  62. package/src/core/store.ts +69 -15
  63. package/src/core/subtask.ts +48 -13
  64. package/src/core/tasks.ts +213 -39
  65. package/src/core/use-cases/task-create.ts +57 -25
  66. package/src/core/use-cases/task-schedule.ts +190 -25
  67. package/src/core/use-cases/task-update.ts +9 -4
  68. package/src/core/verification-budget.ts +115 -0
  69. package/src/core/verification.ts +785 -17
  70. package/src/host/agent-tools.ts +127 -29
  71. package/src/host/launch-admission.ts +140 -0
  72. package/src/host/verification-gate.ts +172 -16
  73. package/src/host/verification-runner.ts +207 -40
  74. package/src/host-ledger.ts +346 -48
  75. package/src/host-runner.ts +234 -35
  76. package/src/host-service.ts +539 -52
  77. package/src/index.ts +250 -7
  78. package/src/protocol.ts +160 -32
  79. package/lib/invariant.js +0 -6
  80. package/lib/types/client/board-card-seat.d.ts +0 -34
  81. package/lib/types/invariant.d.ts +0 -3
  82. package/src/client/board-card-seat.ts +0 -102
  83. package/src/invariant.ts +0 -4
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Admission control for the session-creating half of a run.
3
+ *
4
+ * One board action can open a whole subtree, and a team run hands every member
5
+ * of it to the Lead at once. Both paths used to fire every launch in the same
6
+ * tick, so the Host asked DSH for N sessions at once — the burst the repository's
7
+ * measured guidance caps at three heavy implementation subagents
8
+ * (docs/multi-agent-resources.md), with no mechanism enforcing it.
9
+ *
10
+ * The queue is SERVICE-wide, not per run group: the pressure it bounds is the
11
+ * machine's (the Host process's memory and the swap it drives), and two run
12
+ * groups opened from two tabs share one Host. Admission is round-robin across
13
+ * run groups — FIFO inside a group, so a group's first run (the Lead, which
14
+ * carries the run shape and the teammate namespace) always starts before its
15
+ * members — so a large subtree cannot starve a run opened after it, and no
16
+ * entry can be starved.
17
+ *
18
+ * The width is read through a live callback, so a settings edit takes effect on
19
+ * the next admission without remounting anything.
20
+ *
21
+ * @module dsh-task-board/host/launch-admission
22
+ */
23
+ /** One launch the queue has not started yet. */
24
+ export interface QueuedLaunch {
25
+ /** Run group this launch belongs to; the unit of round-robin fairness. */
26
+ readonly groupId: string;
27
+ /**
28
+ * Perform the launch. The slot is held until this promise settles, which
29
+ * makes "in flight" mean "still creating and prompting its session".
30
+ * A rejection is swallowed here so one broken launch can never wedge the
31
+ * slot; the caller owns reporting.
32
+ */
33
+ start(): Promise<void>;
34
+ /**
35
+ * Called by {@link LaunchAdmissionQueue.drain} for a launch that never
36
+ * started, so the caller can take its execution out of the running column.
37
+ */
38
+ abandon(): void;
39
+ }
40
+ export declare class LaunchAdmissionQueue {
41
+ private readonly width;
42
+ /** Waiting launches per run group, in arrival order. */
43
+ private readonly waiting;
44
+ /** Run groups in round-robin order. */
45
+ private rotation;
46
+ /** Slot holders of the launches in flight, one per admitted launch. */
47
+ private readonly inFlight;
48
+ /** Highest number of launches in flight at once; observability and tests. */
49
+ peakInFlight: number;
50
+ /**
51
+ * @param width - live admission width (already normalized by the caller).
52
+ */
53
+ constructor(width: () => number);
54
+ /** Launches the queue has not admitted yet. */
55
+ get pendingCount(): number;
56
+ /** Launches currently holding a slot. */
57
+ get inFlightCount(): number;
58
+ /** Admit one launch, immediately if a slot is free. */
59
+ enqueue(launch: QueuedLaunch): void;
60
+ /**
61
+ * Abandon every waiting launch: the queue is being torn down, so an entry
62
+ * that never started can never start. Launches already in flight are left
63
+ * alone — their session may already exist, and the callers that own them
64
+ * settle them through the normal path.
65
+ * @returns the abandoned launches, in abandonment order.
66
+ */
67
+ drain(): QueuedLaunch[];
68
+ private pump;
69
+ /** The next launch by round-robin across groups and FIFO inside one. */
70
+ private takeNext;
71
+ private admit;
72
+ }
@@ -18,7 +18,21 @@
18
18
  * - an anomaly (timeout, authentication, unparseable answer, an unusable judge
19
19
  * route) is recorded separately from a quality verdict and is never treated
20
20
  * as one;
21
+ * - exhausting the anomaly budget does NOT judge the card: an environment that
22
+ * cannot answer is not the work failing, so the cycle is held open with no
23
+ * judge call until a user clears the recorded anomalies (issue #1828);
24
+ * - an attempt the time budget ends before any verdict is recorded as its own
25
+ * stage and charged to no budget at all (issue #1828);
26
+ * - an INVALID acceptance (a veto whose required evidence is missing,
27
+ * unlocatable, or aimed at the empty-work baseline) is recorded in its own
28
+ * stage, spends its OWN bounded budget, and never consumes a quality attempt
29
+ * or fails the card; when that budget is spent the cycle is held for a human
30
+ * exactly like a spent anomaly budget;
21
31
  * - concurrent completion calls for one execution share one acceptance.
32
+
33
+ * The validity check itself is program-level (`assessAcceptanceValidity` in
34
+ * `core/verification.ts`), applied after the judge answers and BEFORE any
35
+ * quality verdict is written: a stronger prompt alone could not enforce it.
22
36
  */
23
37
  import type { PreToolDecision } from '@deepseek-ai/dsh-tools';
24
38
  import type { LlmRuntime } from '@deepseek-ai/dsh-llm';
@@ -51,6 +65,16 @@ export interface GoalVerificationGateDeps {
51
65
  * that serves none) simply means the judge sees the trajectory alone.
52
66
  */
53
67
  workspaceChanges?: () => WorkspaceChangeSource | undefined;
68
+ /**
69
+ * Ceiling of one judge request, read per acceptance (a row setting). Absent
70
+ * keeps the default, so a programmatic mount still judges.
71
+ */
72
+ callTimeoutMs?: () => number;
73
+ /**
74
+ * Total ceiling of one acceptance attempt, read per acceptance (a row
75
+ * setting). Absent keeps the default.
76
+ */
77
+ budgetMs?: () => number;
54
78
  logger: {
55
79
  warn(message: string, ...rest: unknown[]): void;
56
80
  };
@@ -15,18 +15,45 @@ export declare const VERIFICATION_MAX_TRACE_CHARS = 80000;
15
15
  export declare const VERIFICATION_MAX_ENTRY_CHARS = 4000;
16
16
  /** Largest per-finding text kept from one judge answer. */
17
17
  export declare const VERIFICATION_MAX_FINDING_CHARS = 400;
18
- /** Findings kept per judge answer and per acceptance. */
18
+ /** Largest verbatim citation kept from one finding. */
19
+ export declare const VERIFICATION_MAX_QUOTE_CHARS = 600;
20
+ /** Largest requirement/observation/gap statement kept from one finding. */
21
+ export declare const VERIFICATION_MAX_PROBLEM_CHARS = 400;
22
+ /** Structured findings kept per judge answer and per acceptance. */
19
23
  export declare const VERIFICATION_MAX_FINDINGS_PER_CALL = 3;
20
- export declare const VERIFICATION_MAX_FINDINGS = 6;
21
- /** One judge request's ceiling and its retry budget. */
22
- export declare const VERIFICATION_CALL_TIMEOUT_MS = 120000;
24
+ export declare const VERIFICATION_MAX_FINDINGS = 12;
25
+ /**
26
+ * Default ceiling of ONE judge request. The effective ceiling is a row setting
27
+ * (`goalVerificationCallTimeoutSeconds`), read live by the gate, because 120s was
28
+ * tight for a reasoning model reading an 80,000 character trace with a 16,384
29
+ * token output cap: every timeout it produced was charged as an acceptance
30
+ * anomaly against a budget that then failed the card.
31
+ */
32
+ export declare const VERIFICATION_CALL_TIMEOUT_MS = 150000;
23
33
  export declare const VERIFICATION_MAX_TOKENS = 16384;
24
34
  export declare const VERIFICATION_TEMPERATURE = 0.2;
25
35
  export declare const VERIFICATION_REQUEST_ATTEMPTS = 3;
26
36
  /** Redaction patterns, mirrored from the verifier's defaults. */
27
37
  export declare const VERIFICATION_REDACT_PATTERNS: readonly string[];
28
38
  /** Why one acceptance could not produce a verdict. */
29
- export type VerificationErrorKind = 'route-unavailable' | 'timeout' | 'auth' | 'parse' | 'request' | 'aborted';
39
+ export type VerificationErrorKind = 'route-unavailable' | 'timeout' | 'auth' | 'parse' | 'request' | 'aborted' | 'budget';
40
+ /**
41
+ * The time one acceptance attempt may still spend.
42
+ *
43
+ * `deadline` is the instant the whole attempt runs out; `callTimeoutMs` is the
44
+ * ceiling of a single judge call. The pair is what ties the retry budget to the
45
+ * acceptance budget: a call is not started unless its own ceiling still fits
46
+ * inside what is left, because starting one the outer budget will abort anyway
47
+ * only burns wall time and turns a slow machine into an anomaly.
48
+ */
49
+ export interface AcceptanceBudget {
50
+ /** Absolute instant (ms epoch) the acceptance attempt runs out at. */
51
+ deadline: number;
52
+ /** Ceiling of one judge call in milliseconds. */
53
+ callTimeoutMs: number;
54
+ /** Clock the caller runs on, injected so tests drive it deterministically. */
55
+ now: () => number;
56
+ }
30
57
  /** An acceptance anomaly: never a quality verdict, always recorded as one. */
31
58
  export declare class VerificationError extends Error {
32
59
  readonly kind: VerificationErrorKind;
@@ -89,8 +116,8 @@ export interface AcceptanceResult {
89
116
  * Run one acceptance: every criterion is judged twice, the odd round with the
90
117
  * A/B slots swapped, and the per-round scores are mapped back and averaged per
91
118
  * criterion.
92
- * @param input - the judge route, evidence, threshold and cancellation.
93
- * @returns the recorded attempt (quality verdict or anomaly).
119
+ * @param input - the judge route, evidence, threshold, time budget and cancellation.
120
+ * @returns the recorded attempt (quality verdict, anomaly, or budget stop).
94
121
  */
95
122
  export declare function runAcceptance(input: {
96
123
  llm: LlmRuntime;
@@ -99,6 +126,10 @@ export declare function runAcceptance(input: {
99
126
  threshold: number;
100
127
  index: number;
101
128
  signal: AbortSignal;
129
+ /** Instant (ms epoch) the attempt runs out at; defaults to now plus the default budget. */
130
+ deadline?: number;
131
+ /** Ceiling of one judge call; defaults to {@link VERIFICATION_CALL_TIMEOUT_MS}. */
132
+ callTimeoutMs?: number;
102
133
  /** Host-observed workspace changes, rendered as the prompt's reference context. */
103
134
  context?: string;
104
135
  /** How many changed files that context shows. */
@@ -1,6 +1,7 @@
1
- import { type ExecutionOutcome, type ExecutionRecord, type TaskRecord } from './core/tasks.ts';
1
+ import { type ExecutionOutcome, type ExecutionRecord, type ScheduleMode, type TaskRecord } from './core/tasks.ts';
2
+ import type { CardDefaults } from './core/card-defaults.ts';
2
3
  import { type TaskBoardAction, type TaskBoardSchedulerSnapshot } from './protocol.ts';
3
- import type { ExecutionVerification } from './core/verification.ts';
4
+ import { type ExecutionVerification } from './core/verification.ts';
4
5
  import { type TaskPermission } from './core/handover.ts';
5
6
  export interface LedgerState {
6
7
  revision: number;
@@ -35,7 +36,10 @@ export interface OpenExecutionReference {
35
36
  /** Minimal value copy used by the Host scheduler. */
36
37
  export interface DueScheduleReference {
37
38
  readonly taskId: string;
38
- readonly cron: string;
39
+ /** Whether this due rule is a recurring cron rule or a single planned instant. */
40
+ readonly mode: ScheduleMode;
41
+ /** Cron expression of a recurring rule; absent on a one-shot. */
42
+ readonly cron?: string;
39
43
  /** Zone this rule's wall clock is read in. */
40
44
  readonly timeZone: string;
41
45
  readonly nextRunAt: number;
@@ -44,6 +48,24 @@ export interface DueScheduleReference {
44
48
  export interface LedgerRuntimeView {
45
49
  readonly armedSchedules: number;
46
50
  readonly openExecutions: readonly OpenExecutionReference[];
51
+ /**
52
+ * Session ids of every open execution, including the ones
53
+ * {@link openExecutions} skips because their own outcome is already
54
+ * recorded. A caller that asks whether the board still observes any session
55
+ * must use this list: a deferred cascade parent is settled by its members,
56
+ * but it is still an open execution.
57
+ */
58
+ readonly openSessionIds: readonly string[];
59
+ /**
60
+ * Whether this document holds anything the session roster can still decide:
61
+ * an open execution that needs inspecting, or a card in the running column
62
+ * whose verdict may arrive from a settle this process never saw.
63
+ *
64
+ * A board with no running card, no open execution and no armed schedule has
65
+ * no use for the roster at all, which is what lets the Host poll stand down
66
+ * instead of re-reading every persisted session forever.
67
+ */
68
+ readonly needsSessionState: boolean;
47
69
  }
48
70
  /**
49
71
  * Best-effort single-letter process state ('R','S','D','Z',...) or undefined
@@ -104,9 +126,12 @@ export declare class HostTaskLedger {
104
126
  * instead of remounting the row.
105
127
  */
106
128
  private depthLimit;
129
+ /** Live reader of the deployment's new-card defaults (a volatile config read). */
130
+ private readonly cardDefaults;
107
131
  constructor(dir?: string, now?: () => number, options?: {
108
132
  sessionDefaultPermission?: TaskPermission | (() => TaskPermission);
109
133
  maxSubtaskDepth?: number;
134
+ cardDefaults?: () => CardDefaults;
110
135
  });
111
136
  /** Current subtask depth limit (1..3). */
112
137
  get maxSubtaskDepth(): number;
@@ -125,7 +150,7 @@ export declare class HostTaskLedger {
125
150
  };
126
151
  state(): LedgerState;
127
152
  /**
128
- * Runtime-only projection for the 5 s Host poll. It copies just primitive
153
+ * Runtime-only projection for the Host poll. It copies just primitive
129
154
  * identifiers and timestamps, never the complete task/execution history or
130
155
  * an authoritative mutable object from the ledger.
131
156
  */
@@ -152,9 +177,45 @@ export declare class HostTaskLedger {
152
177
  /**
153
178
  * Open the cascade one due schedule triggers: an empty array means nothing
154
179
  * ran (already running, or a participant whose elevated permission is still
155
- * unconfirmed), and the rule rolls to its next occurrence either way.
180
+ * unconfirmed, a one-shot whose instant was missed, or an exhausted budget),
181
+ * and the rule's own state says which.
182
+ *
183
+ * The run budget is enforced HERE, inside the single ledger writer: a rule
184
+ * whose counter already reached its cap is stopped instead of opened, so no
185
+ * restart, repeated timer fire or re-arm can overrun it.
186
+ * @param taskId - the task whose rule came due.
187
+ * @param triggeredAt - the instant the occurrence came due.
188
+ * @returns the executions this occurrence opened.
189
+ */
190
+ openScheduled(taskId: string, triggeredAt: number): OpenedRun[];
191
+ /**
192
+ * Record a due occurrence that opened no execution: roll a recurring rule to
193
+ * its next occurrence (budget untouched), or stop it when there is none. A
194
+ * one-shot has no second occurrence, so it always stops here with the reason.
195
+ */
196
+ private skipOccurrence;
197
+ /** Apply a schedule-occurrence transition and persist it atomically. */
198
+ private progressSchedule;
199
+ /**
200
+ * Refund the one scheduled run an occurrence consumed before its launch
201
+ * failed without creating a session (see
202
+ * {@link applyScheduleRefund}). Called by the service on the failed-launch
203
+ * path only; a manual run and a launch that did reach a session are never
204
+ * refunded. A no-op for a task with no rule, so a failed cascade member does
205
+ * not touch anything.
206
+ * @param taskId - the task whose occurrence is being refunded.
207
+ * @param now - clock instant (ms epoch).
208
+ */
209
+ refundScheduledOccurrence(taskId: string, now: number): void;
210
+ /**
211
+ * Boot / resume recovery: skip every occurrence that came due while the board
212
+ * was not running through it. A recurring rule rolls to its next future
213
+ * target; a one-shot whose instant has passed is stopped as missed, because
214
+ * there is no later occurrence left to run. Rendering the missed occurrence
215
+ * is deliberately not attempted: the ACL of a card that fired hours ago is
216
+ * stale, and the board's own recovery contract is "missed triggers are
217
+ * skipped, never replayed".
156
218
  */
157
- openScheduled(taskId: string, nextRunAt: number | undefined, triggeredAt: number): OpenedRun[];
158
219
  skipMissed(now: number): void;
159
220
  setScheduler(patch: Partial<TaskBoardSchedulerSnapshot>): void;
160
221
  /**
@@ -169,6 +230,14 @@ export declare class HostTaskLedger {
169
230
  getTask(id: string): TaskRecord | undefined;
170
231
  saveTaskRecord(task: TaskRecord): void;
171
232
  private apply;
233
+ /**
234
+ * Whether a creation-time schedule request is usable. A one-shot must carry
235
+ * a whole-millisecond instant in the future; a recurring rule must carry a
236
+ * valid expression with a reachable occurrence and, when capped, a positive
237
+ * whole run budget. An unusable zone is refused rather than reinterpreted as
238
+ * the Host zone.
239
+ */
240
+ private validScheduleRequest;
172
241
  /**
173
242
  * The binding that makes a run illegal, if any.
174
243
  *
@@ -194,15 +263,18 @@ export declare class HostTaskLedger {
194
263
  /** Whether a task or any of its subtasks still has an open execution. */
195
264
  private subtreeHasOpenExecution;
196
265
  /**
197
- * Open one execution per cascade participant (the requested task and its
198
- * on-board descendants, within the depth limit) under a single run group, and
199
- * commit the ledger once. The returned runs are ordered root-first, so the
200
- * Host launches the parent before its subtasks.
266
+ * Open one execution per cascade participant that still has work (the
267
+ * requested task and its on-board descendants, within the depth limit) under
268
+ * a single run group, and commit the ledger once. A descendant in 'done' is
269
+ * not a participant ({@link joinsCascadeRun}). The returned runs are ordered
270
+ * root-first, so the Host launches the parent before its subtasks.
201
271
  * @param root - the task the user ran.
202
272
  * @param now - clock instant (ms epoch).
203
273
  * @param initiator - the DSH session that asked for the run (audit only).
204
274
  * @param rerun - true to reset the root to 'todo' before starting it.
205
- * @param nextRunAt - when set, the root's schedule also rolls forward.
275
+ * @param progress - when set, this run was a due occurrence and the root's
276
+ * rule is advanced (counter, next target, stop/skip record) in the same
277
+ * atomic commit that opened the executions.
206
278
  */
207
279
  private startCascade;
208
280
  /**
@@ -248,6 +320,14 @@ export declare class HostTaskLedger {
248
320
  * @returns whether the document changed.
249
321
  */
250
322
  private closeTeamMembers;
323
+ /**
324
+ * Re-align every armed rule at load/import. A recurring rule is recomputed
325
+ * from the current instant; a one-shot is armed at its planned instant when
326
+ * that is still in the future, and stopped as missed once it has passed
327
+ * (there is no later occurrence to run). A recurring rule recomputed past an
328
+ * occurrence records the skip, so the board can show that a fire was passed
329
+ * over rather than silently rewinding.
330
+ */
251
331
  private repairSchedules;
252
332
  private reconcileInterruptedStarts;
253
333
  /**
@@ -1,7 +1,9 @@
1
- import type { TypertGateway } from '@deepseek-ai/dsh-api-gateway';
1
+ import { Context } from '@deepseek-ai/cordis';
2
+ import type { InvokeRemoteRequest, TypertGateway } from '@deepseek-ai/dsh-api-gateway';
2
3
  import type { SessionSummary } from '@deepseek-ai/dsh-api-session-controller/types';
3
4
  import type { CommandResult } from '@deepseek-ai/dsh-commands/types';
4
5
  import type { Workspace } from '@deepseek-ai/dsh-workspace/types';
6
+ import type { ModelCatalogView } from './core/verification.ts';
5
7
  import type { TaskRecord } from './core/tasks.ts';
6
8
  /**
7
9
  * Host services needed to validate a task's workspace before creating a
@@ -10,12 +12,47 @@ import type { TaskRecord } from './core/tasks.ts';
10
12
  export interface TaskBoardWorkspaceRegistry {
11
13
  list(): readonly Workspace[];
12
14
  }
15
+ /**
16
+ * The peer every gateway request names: derived from the public
17
+ * `InvokeRemoteRequest` type so this package needs no direct dependency on
18
+ * `@deepseek-ai/dsh-typert-protocol` for the shape.
19
+ */
20
+ export type GatewayPeer = InvokeRemoteRequest['peer'];
13
21
  interface GatewayRequest {
14
22
  namespace: string;
15
23
  method: string;
16
24
  args: Record<string, unknown>;
25
+ peer: GatewayPeer;
17
26
  signal?: AbortSignal;
18
27
  }
28
+ /**
29
+ * Mint the in-process owner peer this plugin's Host calls speak for.
30
+ *
31
+ * The 0.2.1 gateway made `peer` mandatory on every request, and the
32
+ * authorization layer reads only `peer.principal`: a principal of
33
+ * `{ kind: 'owner' }` is how host-local code states that the call IS the local
34
+ * user driving their own Host, so the gateway answers it directly instead of
35
+ * running a remote-access decision (only non-owner principals are judged).
36
+ * That is exactly the task board's position — it creates, renames, prompts,
37
+ * and reads sessions as the Host itself, which before 0.2.1 was the implicit
38
+ * privilege of every in-process caller. The board claims owner explicitly, and
39
+ * only ever inside its own Host process: no wire field can set a principal,
40
+ * and this plugin never forwards a browser-supplied one.
41
+ *
42
+ * `interactions` stays `'none'`: the board only invokes methods and opens the
43
+ * short `session/follow` stream. The gateway's interactions check gates
44
+ * exactly the forwarded-event endpoints (opening an event stream, answering a
45
+ * waterfall), which this caller never touches; an `'answer'` peer would
46
+ * additionally receive forwarded Host events, a capability the board neither
47
+ * needs nor wants.
48
+ *
49
+ * The peer's signal aborts once `ctx` disposes, so streams opened for it end
50
+ * with the plugin fiber, and `dispose()` is idempotent with racing calls
51
+ * sharing one completion. The id carries no credential — the protocol fixes
52
+ * `principal` as the only identity persisted in a session log — so a fresh
53
+ * unique id per mount is sufficient.
54
+ */
55
+ export declare function createOwnerPeer(ctx: Context): GatewayPeer;
19
56
  interface SessionGateway {
20
57
  invoke(request: GatewayRequest): Promise<unknown>;
21
58
  stream?(request: GatewayRequest): Promise<AsyncIterable<unknown>>;
@@ -78,15 +115,16 @@ export interface PromptContext {
78
115
  /** True when those members run as teammates inside this session (Team Lead). */
79
116
  team?: boolean;
80
117
  /**
81
- * Scheduled-run provenance: the instant the cron rule actually fired and the
82
- * IANA zone its wall clock was read in. Present only for a cron-triggered
83
- * run, so the model can resolve an unqualified date in the run's own terms
84
- * instead of guessing from the Host clock (issue #1722).
118
+ * Scheduled-run provenance: the instant the rule actually fired, its kind,
119
+ * and the IANA zone its wall clock was read in. Present only for a
120
+ * timer-triggered run, so the model can resolve an unqualified date in the
121
+ * run's own terms instead of guessing from the Host clock (issue #1722).
85
122
  */
86
123
  schedule?: {
87
124
  triggeredAt: number;
88
125
  timeZone: string;
89
- cron: string;
126
+ mode: 'cron' | 'once';
127
+ cron?: string;
90
128
  };
91
129
  }
92
130
  /**
@@ -110,15 +148,25 @@ export declare class HostExecutionRunner {
110
148
  private readonly gateway;
111
149
  private readonly commands?;
112
150
  private readonly workspaceRegistry?;
113
- /** Newest scanned event sequence per session with no matching execution end. */
151
+ /**
152
+ * Newest event sequence per session whose COMPLETE history walk (reaching
153
+ * the execution boundary) found no matching execution end. A later walk may
154
+ * trust everything at or below it and examine only newer records.
155
+ */
114
156
  private readonly scanMemos;
115
157
  private readonly unavailableAttempts;
116
158
  private readonly unavailableBackoffMs;
159
+ /**
160
+ * The peer every request speaks for. Production passes the peer bound to the
161
+ * plugin fiber; a bare runner (unit tests around fake gateways) gets one
162
+ * bound to a fresh root context instead.
163
+ */
164
+ private readonly ownerPeer;
117
165
  private unsupportedSessionListWarned;
118
166
  constructor(gateway: SessionGateway | TypertGateway, commands?: SessionCommandDispatcher | undefined, workspaceRegistry?: TaskBoardWorkspaceRegistry | undefined, unavailableRetry?: {
119
167
  attempts?: number;
120
168
  backoffMs?: number;
121
- });
169
+ }, ownerPeer?: GatewayPeer);
122
170
  private invoke;
123
171
  private stream;
124
172
  /**
@@ -144,6 +192,26 @@ export declare class HostExecutionRunner {
144
192
  promptContext?: PromptContext;
145
193
  onSession?: (sessionId: string) => void;
146
194
  onGoalArmed?: (armed: boolean) => void;
195
+ /**
196
+ * GLOBAL native /goal switch, resolved by the caller ONCE per execution and
197
+ * carried here so the decision is frozen for this launch.
198
+ *
199
+ * Frozen deliberately: a settings edit made while the run is in flight must
200
+ * not arm, disarm or otherwise change the goal/acceptance contract of a run
201
+ * already running. Absent means off — the default for every deployment that
202
+ * never touched the switch, including cards written before the option
203
+ * existed. A plain single turn is what an off switch produces; the task-level
204
+ * `goalRun` still applies on top of it (both must ASK for a goal).
205
+ */
206
+ goalEnabled?: boolean;
207
+ /**
208
+ * The host model catalog, read ONCE per execution by the caller. The card's
209
+ * pinned reasoning effort is sent only when this catalog declares it for the
210
+ * route the execution runs at, so one snapshot decides both the acceptance
211
+ * contract and the effort this run freezes. Absent means "no capability
212
+ * evidence": an effort is then not sent at all.
213
+ */
214
+ catalog?: ModelCatalogView;
147
215
  }): Promise<string>;
148
216
  /**
149
217
  * Fail closed when a reused session records a different Agent preset than
@@ -160,10 +228,30 @@ export declare class HostExecutionRunner {
160
228
  * @param pinned - the preset id the task pins, when it pins one.
161
229
  */
162
230
  private assertReusedPreset;
231
+ /**
232
+ * Split a `provider/model` pin. A pin without a provider keeps only the model
233
+ * id: the provider is the one the host catalog declares for it, resolved at
234
+ * launch because the session wire contract requires a provider.
235
+ */
236
+ private static pinnedRoute;
237
+ /**
238
+ * The route a card that pins no model actually runs at: the session's own
239
+ * recorded selection (the durable fold the GUI reads; `next` is the pending
240
+ * user choice, `lastUsed` what the last request consumed), or — for a session
241
+ * that has selected none yet, which is exactly a session that would start at
242
+ * the deployment default — the host default from the catalog.
243
+ *
244
+ * A route that cannot be READ is undefined and the effort is not sent:
245
+ * falling back to the host default there would silently switch the model of a
246
+ * session this run only meant to continue.
247
+ */
248
+ private sessionRoute;
163
249
  /**
164
250
  * Re-assert the pinned execution contract on a session and queue the task
165
251
  * prompt. Shared by the fresh-session and reuse paths so both apply exactly
166
- * the same permission/model pins before the prompt.
252
+ * the same permission/model/effort pins before the prompt. A session that
253
+ * refuses a selection carrying a catalog-confirmed effort throws here, so the
254
+ * prompt is never queued behind a run at the wrong level.
167
255
  */
168
256
  private pinAndPrompt;
169
257
  /**
@@ -182,11 +270,21 @@ export declare class HostExecutionRunner {
182
270
  * dsh's goal-round driver keeps starting continuation rounds until the agent
183
271
  * marks it complete (see {@link goalVerdict} for how the run settles).
184
272
  *
185
- * Opt-out: a task whose `goalRun` is an explicit false runs one plain turn.
273
+ * Two switches gate the arming, and BOTH must ask for it:
274
+ * - the GLOBAL native-/goal switch (`goalEnabled`, default OFF), frozen when
275
+ * this execution started; and
276
+ * - the task's own `goalRun` (absent means off, only an explicit true opts
277
+ * the card into a goal run).
278
+ *
186
279
  * A refusal (no command dispatcher, no `/goal` command in this cohort, an
187
- * objective the command rejects) is reported and the run continues as that
188
- * plain turn: the task was asked to run, and a missing goal mode must not
189
- * lose the work.
280
+ * objective the command rejects) is reported and the run continues as a plain
281
+ * turn: the task was asked to run, and a missing goal mode must not lose the
282
+ * work.
283
+ * @param sessionId - the session to arm in.
284
+ * @param task - the task being run.
285
+ * @param context - the run's prompt context.
286
+ * @param goalEnabled - the GLOBAL switch, frozen for this execution.
287
+ * @returns whether `/goal` was really armed.
190
288
  */
191
289
  private armGoal;
192
290
  /**