dsh-lowtide 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -0
- package/cordis.patch.yml +39 -0
- package/lib/client.js +4793 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +7423 -0
- package/lib/types/client/api.d.ts +169 -0
- package/lib/types/client/components/CandidatesView.d.ts +8 -0
- package/lib/types/client/components/ConfirmGate.d.ts +6 -0
- package/lib/types/client/components/DroppedRow.d.ts +6 -0
- package/lib/types/client/components/InterceptCard.d.ts +17 -0
- package/lib/types/client/components/MorningReport.d.ts +6 -0
- package/lib/types/client/components/NewTaskModal.d.ts +9 -0
- package/lib/types/client/components/PriceBand.d.ts +17 -0
- package/lib/types/client/components/PricePill.d.ts +8 -0
- package/lib/types/client/components/QueueDock.d.ts +8 -0
- package/lib/types/client/components/TaskDetail.d.ts +7 -0
- package/lib/types/client/components/TaskForm.d.ts +85 -0
- package/lib/types/client/components/TaskRow.d.ts +7 -0
- package/lib/types/client/components/WindowEditorModal.d.ts +6 -0
- package/lib/types/client/components/atoms.d.ts +25 -0
- package/lib/types/client/hooks/useInterceptDraft.d.ts +13 -0
- package/lib/types/client/i18n.d.ts +348 -0
- package/lib/types/client/index.d.ts +15 -0
- package/lib/types/client/lib/taskMeta.d.ts +10 -0
- package/lib/types/client/settings.d.ts +5 -0
- package/lib/types/client/store.d.ts +188 -0
- package/lib/types/src/api-trust.d.ts +18 -0
- package/lib/types/src/git.d.ts +16 -0
- package/lib/types/src/index.d.ts +10 -0
- package/lib/types/src/intake.d.ts +54 -0
- package/lib/types/src/models.d.ts +38 -0
- package/lib/types/src/routes.d.ts +15 -0
- package/lib/types/src/runner.d.ts +89 -0
- package/lib/types/src/scheduler.d.ts +37 -0
- package/lib/types/src/session-picker.d.ts +14 -0
- package/lib/types/src/state-machine.d.ts +13 -0
- package/lib/types/src/store.d.ts +116 -0
- package/lib/types/src/strategies/review-dimensions.d.ts +14 -0
- package/lib/types/src/strategies/smart-iterative.d.ts +61 -0
- package/lib/types/src/turns.d.ts +97 -0
- package/package.json +105 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Available-model enumeration (functional round): every model this machine's
|
|
3
|
+
* dsh has configured. Uses the llm service's canonical catalog API
|
|
4
|
+
* (`ctx.llm.listProviders` + `ctx.llm.listModels`) instead of hard-coding the
|
|
5
|
+
* deepseek pair or reading one settings namespace, so ANY provider the user
|
|
6
|
+
* has connected — deepseek official, llm-pi-ai gateways, custom
|
|
7
|
+
* openai-compatible endpoints, … — is listed with exactly the models its
|
|
8
|
+
* adapter actually serves (configured catalog or adapter defaults).
|
|
9
|
+
*/
|
|
10
|
+
import { type PriceTier } from 'lowtide-core';
|
|
11
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
12
|
+
export interface AvailableModel {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
/** Whether this model has an official/override price entry (deepseek pair). */
|
|
16
|
+
priceKnown: boolean;
|
|
17
|
+
/** Adapter-declared input modalities (e.g. ["text", "image"]) — optional. */
|
|
18
|
+
inputModalities?: string[];
|
|
19
|
+
/** Supported reasoning effort ids (e.g. ["off","low","high","max"]). */
|
|
20
|
+
reasoningEfforts?: string[];
|
|
21
|
+
/** The model's default reasoning effort id, when the adapter declares one. */
|
|
22
|
+
defaultReasoningEffort?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface AvailableProvider {
|
|
25
|
+
provider: string;
|
|
26
|
+
displayName: string;
|
|
27
|
+
models: AvailableModel[];
|
|
28
|
+
}
|
|
29
|
+
/** Enumerate all configured models on this machine (deepseek first). A
|
|
30
|
+
* provider whose catalog cannot be read (missing credential, no models) is
|
|
31
|
+
* skipped rather than failing the whole listing. Reasoning metadata comes
|
|
32
|
+
* from the adapter's exact-model resolution so every model — including
|
|
33
|
+
* user-added providers — reports its own supported effort set. */
|
|
34
|
+
export declare function listAvailableModels(ctx: Context, prices?: Record<string, PriceTier>): Promise<AvailableProvider[]>;
|
|
35
|
+
/** Best-effort provider lookup for a bare model id — used by old persisted
|
|
36
|
+
* tasks that carry `model` but no `modelProvider`. Returns the first
|
|
37
|
+
* registered provider whose catalog contains the id, or undefined. */
|
|
38
|
+
export declare function inferProvider(ctx: Context, model: string): Promise<string | undefined>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lowtide HTTP API (PLAN T1.3 / §7.3, Phase 1 subset) under /ds-lowtide.
|
|
3
|
+
* The Host/Origin trust fence (T2.8) is not yet mounted — the server binds
|
|
4
|
+
* loopback only and the README deployment guidance (SSH tunnel) is Phase 4.
|
|
5
|
+
*/
|
|
6
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
+
import type { Scheduler } from './scheduler.ts';
|
|
8
|
+
import { LowtideStore } from './store.ts';
|
|
9
|
+
export type { TriageAction } from './state-machine.ts';
|
|
10
|
+
export { canTransition } from './state-machine.ts';
|
|
11
|
+
export interface Routes {
|
|
12
|
+
store: LowtideStore;
|
|
13
|
+
scheduler: Scheduler;
|
|
14
|
+
}
|
|
15
|
+
export declare function registerRoutes(ctx: Context, routes: Routes): void;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lowtide runner (PLAN T1.4 / §7.2 + v2 execution strategies): preflight →
|
|
3
|
+
* unattended execution (single / iterative / sampling) → collection → retry.
|
|
4
|
+
* Built from the spike-1 route A template with all rc.7 findings applied
|
|
5
|
+
* (D4 preset mount, D5 setup shape, D6 message location, turn/end completion
|
|
6
|
+
* signal, no silence event) and the round-1 fixes (B1 crash→failed, B2 empty
|
|
7
|
+
* batch, B4 listener cleanup, B5 gitRef preflight, batch model pinning).
|
|
8
|
+
*
|
|
9
|
+
* Strategy semantics (PLAN v2 §1):
|
|
10
|
+
* - single: one turn, like a normal harness prompt;
|
|
11
|
+
* - iterative: one session, N turns; each later turn reviews and improves
|
|
12
|
+
* the previous output; early-stop when two consecutive turns
|
|
13
|
+
* converge (cheap bigram-Jaccard similarity);
|
|
14
|
+
* - sampling: N independent fresh sessions of the same prompt; N candidates
|
|
15
|
+
* are produced at night — the USER picks the best the next
|
|
16
|
+
* morning (no auto-selection, no synthesis).
|
|
17
|
+
*/
|
|
18
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
19
|
+
import { type MorningReport, type ReportTaskRow, type Task, type UsageLike } from 'lowtide-core';
|
|
20
|
+
import { LowtideStore } from './store.ts';
|
|
21
|
+
/** Price table from the store (undefined = official defaults; tierFor falls back). */
|
|
22
|
+
type PriceTable = Record<string, {
|
|
23
|
+
peak: {
|
|
24
|
+
input: number;
|
|
25
|
+
inputCached: number;
|
|
26
|
+
output: number;
|
|
27
|
+
};
|
|
28
|
+
off: {
|
|
29
|
+
input: number;
|
|
30
|
+
inputCached: number;
|
|
31
|
+
output: number;
|
|
32
|
+
};
|
|
33
|
+
}>;
|
|
34
|
+
/** Hypothetical peak-hour cost of a usage — the "what you saved" baseline.
|
|
35
|
+
* Only models with a real price entry (official table incl.
|
|
36
|
+
* deepseek-v4-flash-vision-exp, or a user price override) get a baseline;
|
|
37
|
+
* anything else yields 0 so no fake savings are ever reported. */
|
|
38
|
+
export declare function peakCostOf(usage: UsageLike, modelId: string, prices: PriceTable | undefined): number;
|
|
39
|
+
/** Retrying a quota/balance rejection is pointless (and costly): the account
|
|
40
|
+
* state cannot change inside one batch. Matches deepseek's 402 payload. */
|
|
41
|
+
export declare function isQuotaError(error: string | undefined): boolean;
|
|
42
|
+
/** Result of running one task through the batch pipeline. */
|
|
43
|
+
export interface RunTaskResult {
|
|
44
|
+
row: ReportTaskRow;
|
|
45
|
+
/** This task's peak-price counterfactual savings (0 when skipped). */
|
|
46
|
+
savedYuan: number;
|
|
47
|
+
/** Preflight skipped the task (stale / deferred) — excluded from the report list. */
|
|
48
|
+
skipped: boolean;
|
|
49
|
+
/** Preflight deferred it (window fit / budget) — counts toward deferredCount. */
|
|
50
|
+
deferred: boolean;
|
|
51
|
+
}
|
|
52
|
+
/** Run one task through preflight + strategy execution + one retry. */
|
|
53
|
+
export declare function runTask(ctx: Context, store: LowtideStore, task: Task, windowEndAt: Date): Promise<RunTaskResult>;
|
|
54
|
+
/**
|
|
55
|
+
* Schedule workspace groups through the batch runner with a concurrency cap.
|
|
56
|
+
* Each group is a SERIAL queue (same-workspace tasks must not overlap — git
|
|
57
|
+
* index locks and shared cwd state); different groups run in parallel, at
|
|
58
|
+
* most `maxConcurrency` groups at once (Kimi refactor plan §2, corrected:
|
|
59
|
+
* no agent-session reuse — every task keeps its own isolated session).
|
|
60
|
+
* Returns results in the input order.
|
|
61
|
+
*/
|
|
62
|
+
export declare function scheduleGroups<T>(items: T[], keyOf: (item: T) => string, maxConcurrency: number, runOne: (item: T) => Promise<{
|
|
63
|
+
skipped: boolean;
|
|
64
|
+
deferred: boolean;
|
|
65
|
+
}>): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Assemble the report rows in the ORIGINAL queue order (priority, then
|
|
68
|
+
* createdAt — the same sort runBatch applied when picking the queue).
|
|
69
|
+
* scheduleGroups finishes workspace groups in arbitrary order, so results
|
|
70
|
+
* are collected into a Map and re-ordered here; otherwise the morning
|
|
71
|
+
* report would list tasks in completion order (post-refactor review H-001).
|
|
72
|
+
* Deferred (preflight-skipped) tasks count toward deferredCount but never
|
|
73
|
+
* enter the report's task list.
|
|
74
|
+
*/
|
|
75
|
+
export declare function assembleReportRows(queue: Task[], results: Map<string, RunTaskResult>): {
|
|
76
|
+
rows: ReportTaskRow[];
|
|
77
|
+
savedTotal: number;
|
|
78
|
+
deferredCount: number;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Run the queued set with per-workspace serialization and cross-workspace
|
|
82
|
+
* concurrency (`batch.maxConcurrency`, default 3). Stops launching new tasks
|
|
83
|
+
* past windowEndAt but never interrupts a running task (PLAN §7.2). Returns
|
|
84
|
+
* null when nothing executed — no empty execution reports (review round 1, B2).
|
|
85
|
+
*/
|
|
86
|
+
export declare function runBatch(ctx: Context, store: LowtideStore, windowEndAt: Date, forced?: boolean): Promise<MorningReport | null>;
|
|
87
|
+
/** Helper for the /state aggregate: yesterday-style formatting for the report header. */
|
|
88
|
+
export declare function reportDateLabel(dateIso: string): string;
|
|
89
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lowtide scheduler (PLAN T1.4): minute-aligned tick driving the batch
|
|
3
|
+
* runner inside the configured window. The window end stops new launches but
|
|
4
|
+
* never interrupts a running task. `runNow` is the manual/test path.
|
|
5
|
+
*
|
|
6
|
+
* Fixes over the MVP tick (2026-08 review round 1):
|
|
7
|
+
* - once-per-window guard: a window (start-day keyed, midnight-crossing
|
|
8
|
+
* safe) runs the batch at most once even when the host restarts inside it;
|
|
9
|
+
* - deferred auto-recovery: at window start, preflight-deferred tasks
|
|
10
|
+
* (deferCount > 0) go back to queued (retry next window), user-triaged
|
|
11
|
+
* deferred tasks (deferCount === 0) reappear in pending-review — the
|
|
12
|
+
* "下一个裁定周期再出现" promise of PLAN §2.1;
|
|
13
|
+
* - skip the batch entirely when nothing is queued (no empty reports).
|
|
14
|
+
*/
|
|
15
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
16
|
+
import { LowtideStore } from './store.ts';
|
|
17
|
+
export interface Scheduler {
|
|
18
|
+
stop(): void;
|
|
19
|
+
runNow(): Promise<void>;
|
|
20
|
+
isRunning(): boolean;
|
|
21
|
+
batchStartedAt(): Date | null;
|
|
22
|
+
}
|
|
23
|
+
/** Maximum consecutive preflight deferrals before a task is marked failed. */
|
|
24
|
+
export declare const MAX_PREFLIGHT_DEFER = 3;
|
|
25
|
+
/** Whether `now` is inside [start, end) of the batch window (local tz). */
|
|
26
|
+
export declare function inBatchWindow(now: Date, window: string, tz?: string): boolean;
|
|
27
|
+
/** The batch window's end as an absolute Date (today's or the next occurrence). */
|
|
28
|
+
export declare function batchWindowEnd(now: Date, window: string, tz?: string): Date;
|
|
29
|
+
/**
|
|
30
|
+
* Identity of the batch window currently in progress, keyed by the calendar
|
|
31
|
+
* day the window STARTED (so a midnight-crossing window cannot run twice).
|
|
32
|
+
* Returns null when `now` is outside the window.
|
|
33
|
+
*/
|
|
34
|
+
export declare function currentWindowKey(now: Date, window: string, tz?: string): string | null;
|
|
35
|
+
/** Window-start recovery for deferred tasks (PLAN §2.1 + review B3). */
|
|
36
|
+
export declare function recoverDeferred(store: LowtideStore): void;
|
|
37
|
+
export declare function startScheduler(ctx: Context, store: LowtideStore, tickMs?: number): Scheduler;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface WorkspaceSession {
|
|
2
|
+
id: string;
|
|
3
|
+
/** Conversation title from the proj cache (null when never titled). */
|
|
4
|
+
title: string | null;
|
|
5
|
+
lastModified: number;
|
|
6
|
+
}
|
|
7
|
+
export interface WorkspaceSessions {
|
|
8
|
+
cwd: string;
|
|
9
|
+
label: string | null;
|
|
10
|
+
sessions: WorkspaceSession[];
|
|
11
|
+
}
|
|
12
|
+
export declare function storagesDir(): string;
|
|
13
|
+
/** List conversations per workspace, most recently active first. */
|
|
14
|
+
export declare function listWorkspaceSessions(limitPerWorkspace?: number): WorkspaceSessions[];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Triage state-machine (review round 1, B6): the transition table guarding
|
|
3
|
+
* every /tasks/:id/:action route. Kept dependency-free so the full matrix is
|
|
4
|
+
* unit-testable without pulling the host bundle.
|
|
5
|
+
*/
|
|
6
|
+
/** Triage actions accepted by the /tasks/:id/:action routes. */
|
|
7
|
+
export type TriageAction = 'approve' | 'defer' | 'drop' | 'cancel' | 'retry' | 'delete' | 'restore' | 'choose-candidate';
|
|
8
|
+
/**
|
|
9
|
+
* A transition is rejected unless the current status allows it — otherwise a
|
|
10
|
+
* running task could be re-queued behind the agent's back and the persisted
|
|
11
|
+
* state would fight the live execution.
|
|
12
|
+
*/
|
|
13
|
+
export declare function canTransition(status: string, action: TriageAction): boolean;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type MorningReport, type LowtideConfig, type Task, type TaskRun, type TaskStatus } from 'lowtide-core';
|
|
3
|
+
/**
|
|
4
|
+
* Partial config update accepted by PUT /ds-lowtide/config.
|
|
5
|
+
* Removed `.strict()` so older clients with extra fields don't break
|
|
6
|
+
* forward-compatibility (unknown keys are silently stripped by zod default).
|
|
7
|
+
*/
|
|
8
|
+
export declare const configUpdateSchema: z.ZodObject<{
|
|
9
|
+
autonomy: z.ZodOptional<z.ZodEnum<{
|
|
10
|
+
l1: "l1";
|
|
11
|
+
l2: "l2";
|
|
12
|
+
l3: "l3";
|
|
13
|
+
}>>;
|
|
14
|
+
batch: z.ZodOptional<z.ZodObject<{
|
|
15
|
+
gateLeadMin: z.ZodOptional<z.ZodNumber>;
|
|
16
|
+
maxTasksPerNight: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
maxDurationMin: z.ZodOptional<z.ZodNumber>;
|
|
18
|
+
paused: z.ZodOptional<z.ZodBoolean>;
|
|
19
|
+
maxConcurrency: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
20
|
+
window: z.ZodOptional<z.ZodString>;
|
|
21
|
+
tz: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
22
|
+
}, z.core.$strip>>;
|
|
23
|
+
windows: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
24
|
+
id: z.ZodString;
|
|
25
|
+
label: z.ZodOptional<z.ZodString>;
|
|
26
|
+
level: z.ZodEnum<{
|
|
27
|
+
custom: "custom";
|
|
28
|
+
off: "off";
|
|
29
|
+
peak: "peak";
|
|
30
|
+
}>;
|
|
31
|
+
days: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
32
|
+
multiplier: z.ZodOptional<z.ZodNumber>;
|
|
33
|
+
start: z.ZodString;
|
|
34
|
+
end: z.ZodString;
|
|
35
|
+
tz: z.ZodOptional<z.ZodString>;
|
|
36
|
+
}, z.core.$strip>>>;
|
|
37
|
+
prices: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
38
|
+
peak: z.ZodObject<{
|
|
39
|
+
input: z.ZodNumber;
|
|
40
|
+
inputCached: z.ZodNumber;
|
|
41
|
+
output: z.ZodNumber;
|
|
42
|
+
}, z.core.$strip>;
|
|
43
|
+
off: z.ZodObject<{
|
|
44
|
+
input: z.ZodNumber;
|
|
45
|
+
inputCached: z.ZodNumber;
|
|
46
|
+
output: z.ZodNumber;
|
|
47
|
+
}, z.core.$strip>;
|
|
48
|
+
}, z.core.$strip>>>;
|
|
49
|
+
budgetDailyYuan: z.ZodOptional<z.ZodNumber>;
|
|
50
|
+
maxReportHistory: z.ZodOptional<z.ZodNumber>;
|
|
51
|
+
}, z.core.$strip>;
|
|
52
|
+
export type ConfigUpdate = z.infer<typeof configUpdateSchema>;
|
|
53
|
+
export interface LedgerDay {
|
|
54
|
+
yuan: number;
|
|
55
|
+
savedYuan: number;
|
|
56
|
+
}
|
|
57
|
+
export interface StoreState {
|
|
58
|
+
version: 1;
|
|
59
|
+
config: LowtideConfig;
|
|
60
|
+
tasks: Task[];
|
|
61
|
+
reports: MorningReport[];
|
|
62
|
+
ledger: Record<string, LedgerDay>;
|
|
63
|
+
dismissedPeakDay?: string;
|
|
64
|
+
}
|
|
65
|
+
export declare function stateFilePath(): string;
|
|
66
|
+
/** Hard cap for locked-file snapshots: bigger files are refused (streamed
|
|
67
|
+
* hashing would still work, but a multi-GB locked file is almost certainly
|
|
68
|
+
* a mistake and would stall the batch). */
|
|
69
|
+
export declare const MAX_SNAPSHOT_BYTES: number;
|
|
70
|
+
/**
|
|
71
|
+
* File snapshot: sha256 + size via a STREAM (readFileSync would load a
|
|
72
|
+
* multi-GB file fully into memory — Kimi review H3).
|
|
73
|
+
*/
|
|
74
|
+
export declare function snapshotFile(path: string): Promise<{
|
|
75
|
+
sha256: string;
|
|
76
|
+
size: number;
|
|
77
|
+
}>;
|
|
78
|
+
export declare class LowtideStore {
|
|
79
|
+
private state;
|
|
80
|
+
private readonly file;
|
|
81
|
+
constructor(file: string, seed: StoreState);
|
|
82
|
+
static load(file: string): LowtideStore;
|
|
83
|
+
/** Startup scan: a dead process leaves running/preflight behind — requeue them. */
|
|
84
|
+
private recover;
|
|
85
|
+
snapshot(): StoreState;
|
|
86
|
+
/** Atomic-ish write: tmp in the same directory, then rename over.
|
|
87
|
+
* tmp is cleaned up in every path (including crashes during rename). */
|
|
88
|
+
save(): void;
|
|
89
|
+
private mutate;
|
|
90
|
+
get tasks(): readonly Task[];
|
|
91
|
+
get config(): LowtideConfig;
|
|
92
|
+
get reports(): readonly MorningReport[];
|
|
93
|
+
setConfig(config: LowtideConfig): void;
|
|
94
|
+
/** Merge a validated partial update into the live config (deep-merge batch). */
|
|
95
|
+
updateConfig(patch: ConfigUpdate): LowtideConfig;
|
|
96
|
+
addTask(task: Task): Task;
|
|
97
|
+
taskById(id: string): Task | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* Hard-delete a task: removes it from the persisted state for good
|
|
100
|
+
* (user-level "投错/不要了" path). Reports and the ledger are history
|
|
101
|
+
* snapshots and stay untouched; the route layer guards running tasks.
|
|
102
|
+
*/
|
|
103
|
+
deleteTask(id: string): boolean;
|
|
104
|
+
setStatus(id: string, status: TaskStatus, patch?: Partial<Task>): Task | undefined;
|
|
105
|
+
recordRun(id: string, run: TaskRun, finalStatus: TaskStatus): Task | undefined;
|
|
106
|
+
addSavings(savedYuan: number): void;
|
|
107
|
+
ledgerToday(now: Date): LedgerDay;
|
|
108
|
+
dismissPeakToday(): void;
|
|
109
|
+
isPeakDismissedToday(now: Date): boolean;
|
|
110
|
+
addReport(report: MorningReport): void;
|
|
111
|
+
/** Delete a single report by id (ledger and tasks are untouched evidence). */
|
|
112
|
+
deleteReport(reportId: string): boolean;
|
|
113
|
+
/** Clear all reports (keep ledger and tasks untouched). Returns removed count. */
|
|
114
|
+
clearReports(): number;
|
|
115
|
+
}
|
|
116
|
+
export declare function defaultState(): StoreState;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review dimensions for the smart-iterative strategy (Kimi refactor plan §3,
|
|
3
|
+
* corrected): per-task-type structured review dimensions instead of the
|
|
4
|
+
* vague "请改进". Pure data + a keyword-based task-type inferrer.
|
|
5
|
+
*/
|
|
6
|
+
export type TaskType = 'code-review' | 'doc-generation' | 'test-generation' | 'refactoring' | 'general';
|
|
7
|
+
export interface ReviewDimension {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
prompt: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const DIMENSIONS: Record<TaskType, ReviewDimension[]>;
|
|
13
|
+
/** Keyword heuristic for the task type (zh + en). */
|
|
14
|
+
export declare function inferTaskType(prompt: string): TaskType;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Iterative strategy (Kimi refactor plan §3, corrected):
|
|
3
|
+
*
|
|
4
|
+
* Round 0 (generate) → the task prompt, one turn.
|
|
5
|
+
* Round k (review) → structured review prompt (per inferred task type),
|
|
6
|
+
* strict-JSON issue list; parse with a degraded
|
|
7
|
+
* fallback.
|
|
8
|
+
* Stop when → no `high` issues and ≤1 `medium` (quality gate),
|
|
9
|
+
* OR bigram convergence with the previous round
|
|
10
|
+
* (cheap, deterministic), OR `rounds` exhausted.
|
|
11
|
+
* Round k (fix) → issue-driven repair prompt (fix listed issues only).
|
|
12
|
+
*
|
|
13
|
+
* Corrections vs the plan: same model throughout (no pro tier), no extra
|
|
14
|
+
* LLM convergence call (the quality gate + bigram cover it), cost is ~2
|
|
15
|
+
* calls per round and is accounted in the intake estimate.
|
|
16
|
+
*/
|
|
17
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
18
|
+
import { type TurnResult } from '../turns.ts';
|
|
19
|
+
import type { Task, ReasoningEffort, UsageLike } from 'lowtide-core';
|
|
20
|
+
export interface ReviewIssue {
|
|
21
|
+
dim?: string;
|
|
22
|
+
severity?: string;
|
|
23
|
+
location?: string;
|
|
24
|
+
issue?: string;
|
|
25
|
+
suggestion?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface ReviewReport {
|
|
28
|
+
issues: ReviewIssue[];
|
|
29
|
+
summary: string;
|
|
30
|
+
overallScore: number;
|
|
31
|
+
}
|
|
32
|
+
export interface SmartIterativeOutcome {
|
|
33
|
+
final: 'done' | 'failed' | 'timeout' | 'aborted';
|
|
34
|
+
turns: TurnResult[];
|
|
35
|
+
roundsRun: number;
|
|
36
|
+
elapsedMs: number;
|
|
37
|
+
reviewExcerpt?: string;
|
|
38
|
+
/** True when the draft round CONTINUED the requested conversation in place. */
|
|
39
|
+
resumed?: boolean;
|
|
40
|
+
/** True when the draft round ran in a NEW session seeded with the source's
|
|
41
|
+
* full history (fork-style continuation). */
|
|
42
|
+
forked?: boolean;
|
|
43
|
+
/** Set when the requested conversation resume failed and a fresh session was used. */
|
|
44
|
+
resumeNote?: string;
|
|
45
|
+
}
|
|
46
|
+
/** Extract the first JSON object from a model answer (degraded on failure).
|
|
47
|
+
* Tries markdown code blocks first, then uses a depth-limited brace match
|
|
48
|
+
* to avoid swallowing multiple objects or trailing prose (review fix). */
|
|
49
|
+
export declare function parseReviewReport(text: string): ReviewReport;
|
|
50
|
+
/** Quality gate: stop when no high-severity issues and ≤1 medium. */
|
|
51
|
+
export declare function qualityMet(report: ReviewReport): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Smart iterative: generate → review → fix until the quality gate or
|
|
54
|
+
* convergence, capped at `task.rounds` total turns (user-controlled).
|
|
55
|
+
*/
|
|
56
|
+
export declare function runSmartIterative(ctx: Context, task: Task, timeoutMs: number, reasoning?: ReasoningEffort,
|
|
57
|
+
/** Session to resume for Round 0 (generate). Subsequent review/fix rounds
|
|
58
|
+
* always use fresh sessions — only the draft continues the conversation. */
|
|
59
|
+
resumeSessionId?: string): Promise<SmartIterativeOutcome>;
|
|
60
|
+
/** Usage summed across all turns of a smart-iterative run. */
|
|
61
|
+
export declare function smartUsage(turns: TurnResult[]): UsageLike;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
+
import { type ReasoningEffort, type Task, type UsageLike } from 'lowtide-core';
|
|
4
|
+
export interface TurnOutcome {
|
|
5
|
+
kind: string;
|
|
6
|
+
text: string;
|
|
7
|
+
usage: UsageLike | null;
|
|
8
|
+
reason: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface TurnWatcher {
|
|
11
|
+
promise: Promise<TurnOutcome>;
|
|
12
|
+
off(): void;
|
|
13
|
+
}
|
|
14
|
+
export interface TurnResult {
|
|
15
|
+
text: string;
|
|
16
|
+
usage: UsageLike | null;
|
|
17
|
+
kind: string;
|
|
18
|
+
}
|
|
19
|
+
export interface SessionResult {
|
|
20
|
+
turns: TurnResult[];
|
|
21
|
+
elapsedMs: number;
|
|
22
|
+
status: 'done' | 'failed' | 'timeout' | 'aborted';
|
|
23
|
+
error?: string;
|
|
24
|
+
/** True when the task CONTINUED the ORIGINAL conversation (resume succeeded). */
|
|
25
|
+
resumed?: boolean;
|
|
26
|
+
/** True when a NEW session was seeded with the source conversation's FULL
|
|
27
|
+
* history (fork-style continuation — the lossless fallback when the
|
|
28
|
+
* source session is live in this runtime and cannot be resumed). */
|
|
29
|
+
forked?: boolean;
|
|
30
|
+
/** Set when the task was asked to CONTINUE an existing conversation but the
|
|
31
|
+
* resume failed and a fresh session was used instead (lossy fallback). */
|
|
32
|
+
resumeNote?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Unique, recognizable title for a lowtide task session. The timestamp makes
|
|
36
|
+
* repeated runs of the same task distinguishable in the session list, and the
|
|
37
|
+
* prefix separates ordinary task sessions from continuation sessions (which
|
|
38
|
+
* would otherwise inherit the source conversation's title and look identical).
|
|
39
|
+
*/
|
|
40
|
+
export declare function taskSessionTitle(task: Task, mode: 'task' | 'resumed' | 'forked', now?: Date): string;
|
|
41
|
+
/** Returns the next user message to send (index = round, 0-based), or null to stop. */
|
|
42
|
+
export type NextMessage = (index: number, turns: TurnResult[]) => string | null;
|
|
43
|
+
/**
|
|
44
|
+
* Cut index (exclusive) for seeding a continuation session from a source
|
|
45
|
+
* event log: through the LAST completed turn (turn/end), skipping any
|
|
46
|
+
* residual between-turn events — the same boundary the harness's own
|
|
47
|
+
* session.fork uses. Returns null when the log has no completed turn (a
|
|
48
|
+
* blank/unfinished conversation cannot be continued losslessly).
|
|
49
|
+
*/
|
|
50
|
+
export declare function forkSeedBoundary(events: readonly {
|
|
51
|
+
type: string;
|
|
52
|
+
seq: number;
|
|
53
|
+
}[]): number | null;
|
|
54
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
55
|
+
export declare function usageOf(raw: unknown): UsageLike;
|
|
56
|
+
/** Best-effort human-readable summary of a failed turn/end reason. */
|
|
57
|
+
export declare function summarizeReason(reason: unknown, kind: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* Resolve the model for batch execution. Trusts the user's live UI selection
|
|
60
|
+
* or task-level override without forcing a DeepSeek fallback. The caller
|
|
61
|
+
* (runner) is responsible for ensuring the chosen provider/model is usable in
|
|
62
|
+
* headless mode.
|
|
63
|
+
*/
|
|
64
|
+
export declare function resolveBatchModel(selection: {
|
|
65
|
+
provider: string;
|
|
66
|
+
model: string;
|
|
67
|
+
}, reasoning?: ReasoningEffort, modelOverride?: string, providerOverride?: string): {
|
|
68
|
+
provider: string;
|
|
69
|
+
model: string;
|
|
70
|
+
reasoningEffort?: import('@deepseek-ai/dsh-llm').ReasoningEffortId;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Wait for the next turn/end on a session, collecting assistant text + usage.
|
|
74
|
+
* Returns a watcher so the caller can detach the event listener when the
|
|
75
|
+
* timeout branch wins — otherwise every timed-out task leaks a listener (B4).
|
|
76
|
+
* The off is idempotent (Kimi review: double-cancel safety).
|
|
77
|
+
*/
|
|
78
|
+
export declare function waitTurnEnd(ctx: Context, sessionId: ReturnType<typeof SessionId>, firstSeq: number): TurnWatcher;
|
|
79
|
+
/**
|
|
80
|
+
* One session, sequential turns: create the agent once, send each message via
|
|
81
|
+
* followup, wait each turn/end, collect text + usage. Timeout or a failed
|
|
82
|
+
* turn aborts the session (listener detached first — B4). Every path —
|
|
83
|
+
* including exceptions — releases the agent session (try/finally, Kimi review).
|
|
84
|
+
*/
|
|
85
|
+
export declare function executeTurns(ctx: Context, task: Task, nextMessage: NextMessage, timeoutMs: number, reasoning?: ReasoningEffort,
|
|
86
|
+
/** Historical session id to RESUME — the agent continues that conversation
|
|
87
|
+
* (full context). A failed resume falls back to a fresh session so the
|
|
88
|
+
* task still runs. */
|
|
89
|
+
resumeSessionId?: string): Promise<SessionResult>;
|
|
90
|
+
/** The task prompt + file context (attachment paths) + user strategy hint.
|
|
91
|
+
* Continuation context comes from RESUMING a dsh conversation at execution
|
|
92
|
+
* time (executeTurns resumeSessionId), not from string injection. */
|
|
93
|
+
export declare function buildPrompt(task: Task): string;
|
|
94
|
+
export declare function sumUsage(a: UsageLike, b: UsageLike): UsageLike;
|
|
95
|
+
export declare function sumTurns(turns: TurnResult[]): UsageLike;
|
|
96
|
+
/** Cheap convergence check: bigram-Jaccard similarity above the threshold. */
|
|
97
|
+
export declare function isConverged(a: TurnResult, b: TurnResult): boolean;
|
package/package.json
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-lowtide",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "lowtide: human-adjudicated off-peak batch task pipeline for dsh (peak/valley pricing aware)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/types/src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/types/src/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./client": {
|
|
14
|
+
"types": "./lib/types/client/index.d.ts",
|
|
15
|
+
"default": "./lib/client.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"dsh": {
|
|
20
|
+
"bundle": {
|
|
21
|
+
"patch": "./cordis.patch.yml"
|
|
22
|
+
},
|
|
23
|
+
"client": {
|
|
24
|
+
"inject": [
|
|
25
|
+
"@deepseek-ai/dsh-client-locale",
|
|
26
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
27
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
28
|
+
"@deepseek-ai/dsh-client-ui-primitives",
|
|
29
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
30
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
31
|
+
"@deepseek-ai/dsh-client-connection"
|
|
32
|
+
],
|
|
33
|
+
"platform": "web"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"lib/index.js",
|
|
38
|
+
"lib/client.js",
|
|
39
|
+
"lib/client.js.map",
|
|
40
|
+
"lib/types/**/*.d.ts",
|
|
41
|
+
"cordis.patch.yml"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"bundle": "tsdown && tsc -p tsconfig.build.json",
|
|
45
|
+
"watch": "tsdown --watch",
|
|
46
|
+
"typecheck": "tsc --noEmit",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"dev": "npx @deepseek-ai/dsh@0.1.0-rc.7 web --patch ./cordis.dev.yml"
|
|
49
|
+
},
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"keywords": [
|
|
52
|
+
"dsh-plugin",
|
|
53
|
+
"deepseek-harness",
|
|
54
|
+
"deepseek",
|
|
55
|
+
"off-peak",
|
|
56
|
+
"batch-tasks",
|
|
57
|
+
"pricing",
|
|
58
|
+
"scheduler",
|
|
59
|
+
"productivity"
|
|
60
|
+
],
|
|
61
|
+
"author": "dsh-lowtide contributors",
|
|
62
|
+
"repository": {
|
|
63
|
+
"type": "git",
|
|
64
|
+
"url": "https://github.com/KelaoHu/dsh-lowtide",
|
|
65
|
+
"directory": "packages/dsh"
|
|
66
|
+
},
|
|
67
|
+
"peerDependencies": {
|
|
68
|
+
"@deepseek-ai/dsh-agent": "^0.1.0-rc.7",
|
|
69
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.7",
|
|
70
|
+
"@deepseek-ai/dsh-session": "^0.1.0-rc.7",
|
|
71
|
+
"@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
|
|
72
|
+
"@deepseek-ai/dsh-storage-domain": "^0.1.0-rc.7"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
76
|
+
"@deepseek-ai/dsh-agent": "^0.1.0-rc.7",
|
|
77
|
+
"@deepseek-ai/dsh-agent-default-model": "^0.1.0-rc.7",
|
|
78
|
+
"@deepseek-ai/dsh-agent-presets": "0.1.0-rc.7",
|
|
79
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.0-rc.7",
|
|
80
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.0-rc.7",
|
|
81
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.7",
|
|
82
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.7",
|
|
83
|
+
"@deepseek-ai/dsh-client-ui-layout": "0.1.0-rc.7",
|
|
84
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.7",
|
|
85
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.0-rc.7",
|
|
86
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.7",
|
|
87
|
+
"@deepseek-ai/dsh-host-apiproxy": "^0.1.0-rc.7",
|
|
88
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.7",
|
|
89
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.7",
|
|
90
|
+
"@deepseek-ai/dsh-permission-presets": "^0.1.0-rc.7",
|
|
91
|
+
"@deepseek-ai/dsh-session": "^0.1.0-rc.7",
|
|
92
|
+
"@deepseek-ai/dsh-settings": "^0.1.0-rc.7",
|
|
93
|
+
"@deepseek-ai/dsh-storage-domain": "^0.1.0-rc.7",
|
|
94
|
+
"@playwright/test": "^1.62.1",
|
|
95
|
+
"@types/node": "^22.20.0",
|
|
96
|
+
"@types/react": "~18.3.1",
|
|
97
|
+
"lightningcss": "^1.32.0",
|
|
98
|
+
"lowtide-core": "workspace:*",
|
|
99
|
+
"react": "^18.2.0",
|
|
100
|
+
"tsdown": "^0.22.2",
|
|
101
|
+
"typescript": "^6.0.3",
|
|
102
|
+
"vitest": "^4.1.8",
|
|
103
|
+
"zod": "^4.4.3"
|
|
104
|
+
}
|
|
105
|
+
}
|