byterover-cli 3.10.3 → 3.12.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.
- package/README.md +4 -2
- package/dist/agent/core/domain/llm/registry.d.ts +12 -0
- package/dist/agent/core/domain/llm/registry.js +49 -0
- package/dist/agent/core/domain/llm/types.d.ts +6 -0
- package/dist/agent/core/interfaces/i-content-generator.d.ts +8 -0
- package/dist/agent/infra/llm/agent-llm-service.js +18 -6
- package/dist/agent/infra/llm/context/context-manager.d.ts +4 -1
- package/dist/agent/infra/llm/context/context-manager.js +5 -1
- package/dist/agent/infra/llm/generators/ai-sdk-content-generator.d.ts +13 -0
- package/dist/agent/infra/llm/generators/ai-sdk-content-generator.js +19 -6
- package/dist/agent/infra/llm/generators/ai-sdk-message-converter.js +16 -4
- package/dist/agent/infra/llm/generators/byterover-content-generator.d.ts +1 -0
- package/dist/agent/infra/llm/generators/byterover-content-generator.js +4 -1
- package/dist/agent/infra/llm/model-capabilities.d.ts +2 -1
- package/dist/agent/infra/llm/model-capabilities.js +6 -4
- package/dist/agent/infra/llm/providers/anthropic.js +2 -0
- package/dist/agent/infra/llm/providers/deepseek.d.ts +10 -0
- package/dist/agent/infra/llm/providers/deepseek.js +33 -0
- package/dist/agent/infra/llm/providers/glm-coding-plan.d.ts +9 -0
- package/dist/agent/infra/llm/providers/glm-coding-plan.js +32 -0
- package/dist/agent/infra/llm/providers/index.js +4 -0
- package/dist/agent/infra/llm/providers/openrouter.js +2 -0
- package/dist/agent/infra/tools/implementations/curate-tool.js +18 -8
- package/dist/oclif/commands/query.js +7 -1
- package/dist/oclif/lib/task-client.d.ts +9 -0
- package/dist/oclif/lib/task-client.js +11 -1
- package/dist/server/constants.d.ts +6 -0
- package/dist/server/constants.js +11 -0
- package/dist/server/core/domain/entities/provider-registry.js +26 -0
- package/dist/server/core/domain/entities/task-history-entry.d.ts +775 -0
- package/dist/server/core/domain/entities/task-history-entry.js +88 -0
- package/dist/server/core/domain/transport/schemas.d.ts +1403 -11
- package/dist/server/core/domain/transport/schemas.js +157 -6
- package/dist/server/core/domain/transport/task-info.d.ts +18 -0
- package/dist/server/core/interfaces/process/i-task-lifecycle-hook.d.ts +7 -0
- package/dist/server/core/interfaces/storage/i-task-history-store.d.ts +62 -0
- package/dist/server/core/interfaces/storage/i-task-history-store.js +1 -0
- package/dist/server/infra/daemon/brv-server.js +43 -18
- package/dist/server/infra/dream/dream-response-schemas.d.ts +24 -0
- package/dist/server/infra/dream/dream-response-schemas.js +7 -0
- package/dist/server/infra/dream/operations/consolidate.js +21 -8
- package/dist/server/infra/dream/operations/synthesize.js +35 -8
- package/dist/server/infra/http/provider-model-fetcher-registry.js +5 -0
- package/dist/server/infra/http/provider-model-fetchers.js +54 -27
- package/dist/server/infra/process/query-log-handler.d.ts +6 -0
- package/dist/server/infra/process/query-log-handler.js +23 -0
- package/dist/server/infra/process/task-history-entry-builder.d.ts +36 -0
- package/dist/server/infra/process/task-history-entry-builder.js +101 -0
- package/dist/server/infra/process/task-history-hook.d.ts +37 -0
- package/dist/server/infra/process/task-history-hook.js +70 -0
- package/dist/server/infra/process/task-history-store-cache.d.ts +25 -0
- package/dist/server/infra/process/task-history-store-cache.js +106 -0
- package/dist/server/infra/process/task-router.d.ts +72 -0
- package/dist/server/infra/process/task-router.js +690 -15
- package/dist/server/infra/process/transport-handlers.d.ts +8 -0
- package/dist/server/infra/process/transport-handlers.js +2 -0
- package/dist/server/infra/storage/file-task-history-store.d.ts +294 -0
- package/dist/server/infra/storage/file-task-history-store.js +912 -0
- package/dist/shared/transport/events/index.d.ts +5 -0
- package/dist/shared/transport/events/task-events.d.ts +204 -1
- package/dist/shared/transport/events/task-events.js +11 -0
- package/dist/tui/features/tasks/hooks/use-task-subscriptions.js +7 -0
- package/dist/tui/features/tasks/stores/tasks-store.d.ts +4 -16
- package/dist/tui/features/tasks/stores/tasks-store.js +7 -0
- package/dist/tui/types/messages.d.ts +2 -9
- package/dist/webui/assets/index-DyVvFoM6.css +1 -0
- package/dist/webui/assets/index-lr0byHh9.js +130 -0
- package/dist/webui/index.html +2 -2
- package/dist/webui/sw.js +1 -1
- package/dist/webui/workbox-9c191d2f.js +1 -0
- package/oclif.manifest.json +985 -985
- package/package.json +1 -1
- package/dist/webui/assets/index-CvcqpMYn.css +0 -1
- package/dist/webui/assets/index-thSZZahh.js +0 -130
- package/dist/webui/workbox-8c29f6e4.js +0 -1
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { z } from 'zod';
|
|
11
11
|
import { QUERY_LOG_TIERS } from '../../domain/entities/query-log-entry.js';
|
|
12
|
+
import { TaskHistoryEntrySchema } from '../entities/task-history-entry.js';
|
|
12
13
|
// ============================================================================
|
|
13
14
|
// Zod Schemas for Runtime Validation (mirrors domain types)
|
|
14
15
|
// ============================================================================
|
|
@@ -211,12 +212,22 @@ export const TransportTaskEventNames = {
|
|
|
211
212
|
CANCEL: 'task:cancel',
|
|
212
213
|
// Task terminal states
|
|
213
214
|
CANCELLED: 'task:cancelled',
|
|
215
|
+
// Bulk delete terminal entries (M2.09)
|
|
216
|
+
CLEAR_COMPLETED: 'task:clearCompleted',
|
|
214
217
|
COMPLETED: 'task:completed',
|
|
215
218
|
CREATE: 'task:create',
|
|
216
219
|
CREATED: 'task:created',
|
|
220
|
+
// Single delete (M2.09)
|
|
221
|
+
DELETE: 'task:delete',
|
|
222
|
+
// Multi delete (M2.09)
|
|
223
|
+
DELETE_BULK: 'task:deleteBulk',
|
|
224
|
+
// Broadcast on successful removal (M2.09)
|
|
225
|
+
DELETED: 'task:deleted',
|
|
217
226
|
ERROR: 'task:error',
|
|
218
227
|
// Internal (Transport → Agent)
|
|
219
228
|
EXECUTE: 'task:execute',
|
|
229
|
+
// Single-task detail fetch (M2.09)
|
|
230
|
+
GET: 'task:get',
|
|
220
231
|
// Snapshot query (Client → Transport)
|
|
221
232
|
LIST: 'task:list',
|
|
222
233
|
// Query metadata (Agent → Daemon, before task:completed)
|
|
@@ -386,6 +397,10 @@ export const TaskCreatedSchema = z.object({
|
|
|
386
397
|
files: z.array(z.string()).optional(),
|
|
387
398
|
/** Folder path for curate-folder task type */
|
|
388
399
|
folderPath: z.string().optional(),
|
|
400
|
+
/** Active model id at task creation time */
|
|
401
|
+
model: z.string().optional(),
|
|
402
|
+
/** Active provider id at task creation time */
|
|
403
|
+
provider: z.string().optional(),
|
|
389
404
|
/** Unique task identifier */
|
|
390
405
|
taskId: z.string(),
|
|
391
406
|
/** Task type */
|
|
@@ -443,7 +458,9 @@ export const TaskQueryResultEventSchema = z.object({
|
|
|
443
458
|
})
|
|
444
459
|
.optional(),
|
|
445
460
|
taskId: z.string(),
|
|
446
|
-
tier: z.custom((val) => new Set(QUERY_LOG_TIERS).has(val), {
|
|
461
|
+
tier: z.custom((val) => new Set(QUERY_LOG_TIERS).has(val), {
|
|
462
|
+
message: 'Invalid query log tier',
|
|
463
|
+
}),
|
|
447
464
|
timing: z.object({ durationMs: z.number() }),
|
|
448
465
|
});
|
|
449
466
|
/**
|
|
@@ -563,11 +580,38 @@ export const TaskCancelResponseSchema = z.object({
|
|
|
563
580
|
/**
|
|
564
581
|
* task:list - Snapshot of active and recently-completed tasks for a project.
|
|
565
582
|
* Used by the web UI Tasks tab to populate state without replaying history.
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
583
|
+
*
|
|
584
|
+
* M2.16: cursor pagination dropped; numbered pagination (page/pageSize) +
|
|
585
|
+
* full filter dimensions (search/provider/model/time/duration).
|
|
586
|
+
*/
|
|
587
|
+
export const TaskListRequestSchema = z
|
|
588
|
+
.object({
|
|
589
|
+
/** Created at >= this epoch ms (M2.16). */
|
|
590
|
+
createdAfter: z.number().optional(),
|
|
591
|
+
/** Created at <= this epoch ms (M2.16). */
|
|
592
|
+
createdBefore: z.number().optional(),
|
|
593
|
+
/** Maximum elapsed time (ms) for terminal tasks (M2.16). */
|
|
594
|
+
maxDurationMs: z.number().optional(),
|
|
595
|
+
/** Minimum elapsed time (ms) for terminal tasks; only matches startedAt+completedAt rows (M2.16). */
|
|
596
|
+
minDurationMs: z.number().optional(),
|
|
597
|
+
/** Optional model id filter (M2.16). */
|
|
598
|
+
model: z.array(z.string()).optional(),
|
|
599
|
+
/** 1-based page index — server clamps to >= 1; defaults to 1 (M2.16). */
|
|
600
|
+
page: z.number().int().min(1).optional(),
|
|
601
|
+
/** Page size — server clamps to 1..1000; defaults to 50 (M2.16). */
|
|
602
|
+
pageSize: z.number().int().min(1).max(1000).optional(),
|
|
603
|
+
/** Optional project filter — defaults to caller's registered project. */
|
|
569
604
|
projectPath: z.string().optional(),
|
|
570
|
-
|
|
605
|
+
/** Optional provider id filter (M2.16). */
|
|
606
|
+
provider: z.array(z.string()).optional(),
|
|
607
|
+
/** Case-insensitive substring search over content + result + error.message (M2.16). */
|
|
608
|
+
searchText: z.string().optional(),
|
|
609
|
+
/** Optional status filter — return only tasks whose status matches one of these. */
|
|
610
|
+
status: z.array(z.enum(['cancelled', 'completed', 'created', 'error', 'started'])).optional(),
|
|
611
|
+
/** Optional task-type filter — e.g. ['curate'], ['query']. */
|
|
612
|
+
type: z.array(z.string()).optional(),
|
|
613
|
+
})
|
|
614
|
+
.strict();
|
|
571
615
|
export const TaskListItemStatusSchema = z.enum(['cancelled', 'completed', 'created', 'error', 'started']);
|
|
572
616
|
export const TaskListItemSchema = z.object({
|
|
573
617
|
completedAt: z.number().optional(),
|
|
@@ -578,15 +622,122 @@ export const TaskListItemSchema = z.object({
|
|
|
578
622
|
files: z.array(z.string()).optional(),
|
|
579
623
|
/** Folder path for `curate-folder` tasks */
|
|
580
624
|
folderPath: z.string().optional(),
|
|
625
|
+
/** Active model id at task creation time */
|
|
626
|
+
model: z.string().optional(),
|
|
581
627
|
projectPath: z.string().optional(),
|
|
628
|
+
/** Active provider id at task creation time */
|
|
629
|
+
provider: z.string().optional(),
|
|
630
|
+
/**
|
|
631
|
+
* Result string. Only present for in-memory completed tasks (toListItem
|
|
632
|
+
* populates from TaskInfo.result). Persisted entries from the index do not
|
|
633
|
+
* carry result by 2-tier design — detail panel uses task:get for full text.
|
|
634
|
+
*/
|
|
582
635
|
result: z.string().optional(),
|
|
583
636
|
startedAt: z.number().optional(),
|
|
584
637
|
status: TaskListItemStatusSchema,
|
|
585
638
|
taskId: z.string(),
|
|
586
639
|
type: z.string(),
|
|
587
640
|
});
|
|
588
|
-
|
|
641
|
+
/** Status histogram used by FE filter-bar breakdown (M2.16). */
|
|
642
|
+
export const TaskListCountsSchema = z.object({
|
|
643
|
+
all: z.number().int().nonnegative(),
|
|
644
|
+
cancelled: z.number().int().nonnegative(),
|
|
645
|
+
completed: z.number().int().nonnegative(),
|
|
646
|
+
/** Tasks with status === 'error'. */
|
|
647
|
+
failed: z.number().int().nonnegative(),
|
|
648
|
+
/** Tasks with status === 'created' || 'started'. */
|
|
649
|
+
running: z.number().int().nonnegative(),
|
|
650
|
+
});
|
|
651
|
+
/** (providerId, modelId) pair from history (M2.16). */
|
|
652
|
+
export const TaskListAvailableModelSchema = z.object({
|
|
653
|
+
modelId: z.string(),
|
|
654
|
+
providerId: z.string(),
|
|
655
|
+
});
|
|
656
|
+
export const TaskListResponseSchema = z
|
|
657
|
+
.object({
|
|
658
|
+
/** Distinct (providerId, modelId) pairs in candidate set. History-derived. */
|
|
659
|
+
availableModels: z.array(TaskListAvailableModelSchema),
|
|
660
|
+
/** Distinct providerId values in candidate set. History-derived (includes uninstalled). */
|
|
661
|
+
availableProviders: z.array(z.string()),
|
|
662
|
+
/**
|
|
663
|
+
* Status histogram matching current filter scope (Model A — post-filter,
|
|
664
|
+
* `counts.all === total` invariant). FE filter-bar chip count = visible
|
|
665
|
+
* row count.
|
|
666
|
+
*/
|
|
667
|
+
counts: TaskListCountsSchema,
|
|
668
|
+
/**
|
|
669
|
+
* 1-based page index, echoed back as-sent. Server clamps lower bound only
|
|
670
|
+
* (page < 1 → 1). NOT clamped against `pageCount`: a request for `page=9999`
|
|
671
|
+
* against a 1-page result returns `{page: 9999, tasks: []}` so the caller
|
|
672
|
+
* can detect an out-of-range page and correct itself.
|
|
673
|
+
*/
|
|
674
|
+
page: z.number().int().min(1),
|
|
675
|
+
/** Total page count = max(ceil(total/pageSize), 1). */
|
|
676
|
+
pageCount: z.number().int().min(1),
|
|
677
|
+
/** Page size echoed back, clamped to [1, 1000]. */
|
|
678
|
+
pageSize: z.number().int().min(1).max(1000),
|
|
679
|
+
/** Page slice of items after all filters. */
|
|
589
680
|
tasks: z.array(TaskListItemSchema),
|
|
681
|
+
/** Total count of items matching ALL filters (incl. status). */
|
|
682
|
+
total: z.number().int().nonnegative(),
|
|
683
|
+
})
|
|
684
|
+
.strict();
|
|
685
|
+
/**
|
|
686
|
+
* task:get — fetch full Level 2 detail for a single persisted task.
|
|
687
|
+
* Returns null when the task is unknown or its data file is corrupt.
|
|
688
|
+
*/
|
|
689
|
+
export const TaskGetRequestSchema = z.object({
|
|
690
|
+
taskId: z.string(),
|
|
691
|
+
});
|
|
692
|
+
export const TaskGetResponseSchema = z.object({
|
|
693
|
+
task: TaskHistoryEntrySchema.nullable(),
|
|
694
|
+
});
|
|
695
|
+
/**
|
|
696
|
+
* task:delete — remove a single task from the per-project history store.
|
|
697
|
+
* Idempotent: deleting a non-existent task returns success: true.
|
|
698
|
+
*/
|
|
699
|
+
export const TaskDeleteRequestSchema = z.object({
|
|
700
|
+
taskId: z.string(),
|
|
701
|
+
});
|
|
702
|
+
export const TaskDeleteResponseSchema = z.object({
|
|
703
|
+
error: z.string().optional(),
|
|
704
|
+
/**
|
|
705
|
+
* `true` when the task was actually removed (was live in-memory or persisted),
|
|
706
|
+
* `false` when the call was a no-op (taskId unknown or already tombstoned).
|
|
707
|
+
* Idempotent semantics on `success` are preserved — `success: true` indicates
|
|
708
|
+
* the request was valid; `removed` distinguishes "actually removed" from
|
|
709
|
+
* "no-op". `task:deleteBulk` uses this to compute an accurate `deletedCount`.
|
|
710
|
+
*/
|
|
711
|
+
removed: z.boolean().optional(),
|
|
712
|
+
success: z.boolean(),
|
|
713
|
+
});
|
|
714
|
+
/**
|
|
715
|
+
* task:deleteBulk — delete many tasks at once. `deletedCount` reports actual removals.
|
|
716
|
+
*/
|
|
717
|
+
export const TaskDeleteBulkRequestSchema = z.object({
|
|
718
|
+
taskIds: z.array(z.string()),
|
|
719
|
+
});
|
|
720
|
+
export const TaskDeleteBulkResponseSchema = z.object({
|
|
721
|
+
deletedCount: z.number(),
|
|
722
|
+
error: z.string().optional(),
|
|
723
|
+
});
|
|
724
|
+
/**
|
|
725
|
+
* task:clearCompleted — remove all terminal-state tasks (completed/error/cancelled)
|
|
726
|
+
* from the project's history. Active tasks (created/started) are preserved.
|
|
727
|
+
*/
|
|
728
|
+
export const TaskClearCompletedRequestSchema = z.object({
|
|
729
|
+
projectPath: z.string().optional(),
|
|
730
|
+
});
|
|
731
|
+
export const TaskClearCompletedResponseSchema = z.object({
|
|
732
|
+
deletedCount: z.number(),
|
|
733
|
+
error: z.string().optional(),
|
|
734
|
+
});
|
|
735
|
+
/**
|
|
736
|
+
* task:deleted — broadcast to project room when a task is removed from history.
|
|
737
|
+
* Lets other clients (TUI, other webui tabs) drop the row from their local view.
|
|
738
|
+
*/
|
|
739
|
+
export const TaskDeletedEventSchema = z.object({
|
|
740
|
+
taskId: z.string(),
|
|
590
741
|
});
|
|
591
742
|
// ============================================================================
|
|
592
743
|
// Session Schemas (client → server commands)
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import type { ReasoningContentItem, ToolCallEvent } from '../../../../shared/transport/events/task-events.js';
|
|
1
2
|
import type { TaskErrorData, TaskListItemStatus, TaskType } from './schemas.js';
|
|
2
3
|
/**
|
|
3
4
|
* Tracked task metadata used by TaskRouter for routing events
|
|
4
5
|
* and by ConnectionCoordinator for agent disconnect cleanup.
|
|
6
|
+
*
|
|
7
|
+
* Level 2 fields (`responseContent`, `reasoningContents`, `toolCalls`,
|
|
8
|
+
* `sessionId`) are accumulated from `llmservice:*` events and persisted
|
|
9
|
+
* by `TaskHistoryHook` so a tab refresh mid-stream can render the in-flight
|
|
10
|
+
* state.
|
|
5
11
|
*/
|
|
6
12
|
export type TaskInfo = {
|
|
7
13
|
/** Client's working directory for file validation */
|
|
@@ -18,8 +24,16 @@ export type TaskInfo = {
|
|
|
18
24
|
folderPath?: string;
|
|
19
25
|
/** Log entry ID set by lifecycle hook after onTaskCreate */
|
|
20
26
|
logId?: string;
|
|
27
|
+
/** Active model id at task creation time */
|
|
28
|
+
model?: string;
|
|
21
29
|
/** Project path this task belongs to (for multi-project routing) */
|
|
22
30
|
projectPath?: string;
|
|
31
|
+
/** Active provider id at task creation time */
|
|
32
|
+
provider?: string;
|
|
33
|
+
/** Accumulated reasoning/thinking entries from `llmservice:thinking` + `llmservice:chunk` (type=reasoning). */
|
|
34
|
+
reasoningContents?: ReasoningContentItem[];
|
|
35
|
+
/** Final assistant response set by `llmservice:response` (overwrites on multi-step). */
|
|
36
|
+
responseContent?: string;
|
|
23
37
|
/** Set on successful completion */
|
|
24
38
|
result?: string;
|
|
25
39
|
/**
|
|
@@ -29,11 +43,15 @@ export type TaskInfo = {
|
|
|
29
43
|
* the user toggles the flag mid-task.
|
|
30
44
|
*/
|
|
31
45
|
reviewDisabled?: boolean;
|
|
46
|
+
/** LLM session id set alongside `responseContent` */
|
|
47
|
+
sessionId?: string;
|
|
32
48
|
/** Set when agent picks up the task */
|
|
33
49
|
startedAt?: number;
|
|
34
50
|
/** Lifecycle status — defaults to 'created' on construction */
|
|
35
51
|
status?: TaskListItemStatus;
|
|
36
52
|
taskId: string;
|
|
53
|
+
/** Accumulated tool-call lifecycle entries from `llmservice:toolCall` + `:toolResult`. */
|
|
54
|
+
toolCalls?: ToolCallEvent[];
|
|
37
55
|
type: TaskType;
|
|
38
56
|
/** Workspace root (linked subdir or projectRoot if unlinked) */
|
|
39
57
|
worktreeRoot?: string;
|
|
@@ -28,6 +28,13 @@ export interface ITaskLifecycleHook {
|
|
|
28
28
|
}>;
|
|
29
29
|
/** Called when a task fails with an error. */
|
|
30
30
|
onTaskError?(taskId: string, errorMessage: string, task: TaskInfo): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Called by the throttled flush (~100ms window) for in-flight task mutations
|
|
33
|
+
* — `task:started` transition + every `llmservice:*` accumulator update.
|
|
34
|
+
* Optional: existing handlers (CurateLogHandler, QueryLogHandler) don't
|
|
35
|
+
* implement it. Implementations must never throw.
|
|
36
|
+
*/
|
|
37
|
+
onTaskUpdate?(task: TaskInfo): Promise<void>;
|
|
31
38
|
/** Called when an LLM tool result event is received for an ACTIVE task (not grace-period). */
|
|
32
39
|
onToolResult?(taskId: string, payload: LlmToolResultEvent): void;
|
|
33
40
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { TaskListItem } from '../../../../shared/transport/events/task-events.js';
|
|
2
|
+
import type { TaskHistoryEntry, TaskHistoryStatus } from '../../domain/entities/task-history-entry.js';
|
|
3
|
+
export type { TaskHistoryEntry, TaskHistoryStatus } from '../../domain/entities/task-history-entry.js';
|
|
4
|
+
export interface ITaskHistoryStore {
|
|
5
|
+
/**
|
|
6
|
+
* Tombstone all matching entries. Defaults to terminal statuses
|
|
7
|
+
* (`'cancelled' | 'completed' | 'error'`) when `statuses` is omitted,
|
|
8
|
+
* so active tasks are preserved.
|
|
9
|
+
* Returns the list of removed taskIds (caller broadcasts `task:deleted` per id).
|
|
10
|
+
*/
|
|
11
|
+
clear(options?: {
|
|
12
|
+
projectPath?: string;
|
|
13
|
+
statuses?: TaskHistoryStatus[];
|
|
14
|
+
}): Promise<{
|
|
15
|
+
deletedCount: number;
|
|
16
|
+
taskIds: string[];
|
|
17
|
+
}>;
|
|
18
|
+
/** Remove a single entry by taskId. Idempotent — returns false on missing/already-deleted. */
|
|
19
|
+
delete(taskId: string): Promise<boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* Bulk-delete by taskIds. Returns the subset of input ids that were live
|
|
22
|
+
* (and have now been tombstoned) — invalid, unknown, and already-tombstoned
|
|
23
|
+
* ids are dropped. Callers can rely on the returned array length as the
|
|
24
|
+
* `deletedCount` and on the array contents for per-id broadcasts.
|
|
25
|
+
*/
|
|
26
|
+
deleteMany(taskIds: string[]): Promise<string[]>;
|
|
27
|
+
/** Retrieve an entry's full Level 2 detail by taskId. Returns undefined if not found or corrupt. */
|
|
28
|
+
getById(taskId: string): Promise<TaskHistoryEntry | undefined>;
|
|
29
|
+
/**
|
|
30
|
+
* List entries (summary shape) sorted newest-first.
|
|
31
|
+
*
|
|
32
|
+
* Returns the wire-friendly `TaskListItem` shape — no `responseContent`, `toolCalls`,
|
|
33
|
+
* `reasoningContents`, `sessionId`, or `result`. Callers fetch full detail via `getById`.
|
|
34
|
+
*
|
|
35
|
+
* M2.16: param names align with the wire schema (`createdAfter` / `createdBefore`
|
|
36
|
+
* instead of legacy `after` / `before`). Pagination moved to the handler — store
|
|
37
|
+
* returns ALL matches; no `limit`.
|
|
38
|
+
*
|
|
39
|
+
* Note on `provider` / `model` / `status` filters: `handleTaskList` does NOT push
|
|
40
|
+
* these down — pivot filters run at the handler so derivative sets (counts,
|
|
41
|
+
* availableProviders, availableModels) can apply their exclusion rules. The
|
|
42
|
+
* options remain on the interface for direct store callers (tests, future CLI
|
|
43
|
+
* commands like `brv query-log`-style scans) that don't need pivot semantics.
|
|
44
|
+
*/
|
|
45
|
+
list(options?: {
|
|
46
|
+
/** Include only entries with createdAt >= this epoch ms. */
|
|
47
|
+
createdAfter?: number;
|
|
48
|
+
/** Include only entries with createdAt <= this epoch ms. */
|
|
49
|
+
createdBefore?: number;
|
|
50
|
+
/** Include only entries matching these model ids. Direct-caller use only. */
|
|
51
|
+
model?: string[];
|
|
52
|
+
projectPath?: string;
|
|
53
|
+
/** Include only entries matching these provider ids. Direct-caller use only. */
|
|
54
|
+
provider?: string[];
|
|
55
|
+
/** Include only entries matching these statuses. Direct-caller use only. */
|
|
56
|
+
status?: TaskHistoryStatus[];
|
|
57
|
+
/** Include only entries matching these task types. */
|
|
58
|
+
type?: string[];
|
|
59
|
+
}): Promise<TaskListItem[]>;
|
|
60
|
+
/** Persist (create or overwrite) a history entry. Validates with Zod — throws on invalid shape. */
|
|
61
|
+
save(entry: TaskHistoryEntry): Promise<void>;
|
|
62
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -44,6 +44,8 @@ import { broadcastToProjectRoom } from '../process/broadcast-utils.js';
|
|
|
44
44
|
import { CurateLogHandler } from '../process/curate-log-handler.js';
|
|
45
45
|
import { setupFeatureHandlers } from '../process/feature-handlers.js';
|
|
46
46
|
import { QueryLogHandler } from '../process/query-log-handler.js';
|
|
47
|
+
import { TaskHistoryHook } from '../process/task-history-hook.js';
|
|
48
|
+
import { getStore as getTaskHistoryStore } from '../process/task-history-store-cache.js';
|
|
47
49
|
import { TransportHandlers } from '../process/transport-handlers.js';
|
|
48
50
|
import { ProjectRegistry } from '../project/project-registry.js';
|
|
49
51
|
import { createProviderOAuthTokenStore } from '../provider-oauth/provider-oauth-token-store.js';
|
|
@@ -334,6 +336,30 @@ async function main() {
|
|
|
334
336
|
broadcastToProjectRoom(projectRegistry, projectRouter, info.projectPath, ReviewEvents.NOTIFY, payload, info.clientId);
|
|
335
337
|
});
|
|
336
338
|
const queryLogHandler = new QueryLogHandler();
|
|
339
|
+
// Task-history hook — persists every lifecycle transition + accumulated
|
|
340
|
+
// llmservice events to a per-project FileTaskHistoryStore. The store
|
|
341
|
+
// factory is module-scoped so M2.09 wire handlers can read from the
|
|
342
|
+
// same instances this hook writes to.
|
|
343
|
+
const taskHistoryHook = new TaskHistoryHook({ getStore: getTaskHistoryStore });
|
|
344
|
+
// Provider config/keychain stores — shared between feature handlers and state endpoint.
|
|
345
|
+
// Hoisted ahead of `new TransportHandlers` so the resolveActiveProvider callback below
|
|
346
|
+
// can close over them and call resolveProviderConfig synchronously at task-create time.
|
|
347
|
+
const providerConfigStore = new FileProviderConfigStore();
|
|
348
|
+
const providerKeychainStore = createProviderKeychainStore();
|
|
349
|
+
const providerOAuthTokenStore = createProviderOAuthTokenStore();
|
|
350
|
+
// Token refresh manager — transparently refreshes OAuth tokens before they expire
|
|
351
|
+
const tokenRefreshManager = new TokenRefreshManager({
|
|
352
|
+
providerConfigStore,
|
|
353
|
+
providerKeychainStore,
|
|
354
|
+
providerOAuthTokenStore,
|
|
355
|
+
transport: transportServer,
|
|
356
|
+
});
|
|
357
|
+
// Clear stale provider config on startup (e.g. migration from v1 system keychain to v2 file keystore).
|
|
358
|
+
// If a provider is configured but its API key is no longer accessible, disconnect it so the user
|
|
359
|
+
// is returned to the onboarding flow rather than hitting a cryptic API key error mid-task.
|
|
360
|
+
await clearStaleProviderConfig(providerConfigStore, providerKeychainStore, providerOAuthTokenStore);
|
|
361
|
+
// State endpoint: provider config — agents request this on startup and after provider:updated
|
|
362
|
+
transportServer.onRequest(TransportStateEventNames.GET_PROVIDER_CONFIG, async () => resolveProviderConfig({ authStateStore, providerConfigStore, providerKeychainStore, tokenRefreshManager }));
|
|
337
363
|
const transportHandlers = new TransportHandlers({
|
|
338
364
|
agentPool,
|
|
339
365
|
clientManager,
|
|
@@ -341,6 +367,7 @@ async function main() {
|
|
|
341
367
|
// so peer clients (TUI / MCP) can render drift indicators without an
|
|
342
368
|
// extra round-trip.
|
|
343
369
|
daemonVersion: version,
|
|
370
|
+
getTaskHistoryStore,
|
|
344
371
|
// Resolves the project's review-disabled flag once at task-create. The result
|
|
345
372
|
// is stamped onto TaskInfo + TaskExecute so daemon hooks (CurateLogHandler) and
|
|
346
373
|
// the agent process (curate-tool backups, dream review entries) all observe a
|
|
@@ -348,7 +375,7 @@ async function main() {
|
|
|
348
375
|
// idle-dream dispatch above so review semantics are identical regardless of
|
|
349
376
|
// dispatch source (CLI task:create vs agent-idle trigger).
|
|
350
377
|
isReviewDisabled: resolveReviewDisabled,
|
|
351
|
-
lifecycleHooks: [curateLogHandler, queryLogHandler],
|
|
378
|
+
lifecycleHooks: [curateLogHandler, queryLogHandler, taskHistoryHook],
|
|
352
379
|
// Daemon-side gate for dream task:create — mirrors the idle-trigger pre-check
|
|
353
380
|
// in this file so the CLI path (brv dream without --force) actually honors
|
|
354
381
|
// gate 3 (queue). The agent-side check kept gate 3 hardcoded to skip,
|
|
@@ -369,6 +396,21 @@ async function main() {
|
|
|
369
396
|
},
|
|
370
397
|
projectRegistry,
|
|
371
398
|
projectRouter,
|
|
399
|
+
// Stamp the active provider/model snapshot onto every created task so the
|
|
400
|
+
// Web UI can display which provider handled which task. Failures are
|
|
401
|
+
// swallowed by TaskRouter's safeResolveActiveProvider — never blocks dispatch.
|
|
402
|
+
async resolveActiveProvider() {
|
|
403
|
+
const config = await resolveProviderConfig({
|
|
404
|
+
authStateStore,
|
|
405
|
+
providerConfigStore,
|
|
406
|
+
providerKeychainStore,
|
|
407
|
+
tokenRefreshManager,
|
|
408
|
+
});
|
|
409
|
+
return {
|
|
410
|
+
...(config.activeModel ? { model: config.activeModel } : {}),
|
|
411
|
+
...(config.activeProvider ? { provider: config.activeProvider } : {}),
|
|
412
|
+
};
|
|
413
|
+
},
|
|
372
414
|
transport: transportServer,
|
|
373
415
|
});
|
|
374
416
|
transportHandlers.setup();
|
|
@@ -526,23 +568,6 @@ async function main() {
|
|
|
526
568
|
running: transportServer.isRunning(),
|
|
527
569
|
},
|
|
528
570
|
}));
|
|
529
|
-
// Provider config/keychain stores — shared between feature handlers and state endpoint
|
|
530
|
-
const providerConfigStore = new FileProviderConfigStore();
|
|
531
|
-
const providerKeychainStore = createProviderKeychainStore();
|
|
532
|
-
const providerOAuthTokenStore = createProviderOAuthTokenStore();
|
|
533
|
-
// Token refresh manager — transparently refreshes OAuth tokens before they expire
|
|
534
|
-
const tokenRefreshManager = new TokenRefreshManager({
|
|
535
|
-
providerConfigStore,
|
|
536
|
-
providerKeychainStore,
|
|
537
|
-
providerOAuthTokenStore,
|
|
538
|
-
transport: transportServer,
|
|
539
|
-
});
|
|
540
|
-
// Clear stale provider config on startup (e.g. migration from v1 system keychain to v2 file keystore).
|
|
541
|
-
// If a provider is configured but its API key is no longer accessible, disconnect it so the user
|
|
542
|
-
// is returned to the onboarding flow rather than hitting a cryptic API key error mid-task.
|
|
543
|
-
await clearStaleProviderConfig(providerConfigStore, providerKeychainStore, providerOAuthTokenStore);
|
|
544
|
-
// State endpoint: provider config — agents request this on startup and after provider:updated
|
|
545
|
-
transportServer.onRequest(TransportStateEventNames.GET_PROVIDER_CONFIG, async () => resolveProviderConfig({ authStateStore, providerConfigStore, providerKeychainStore, tokenRefreshManager }));
|
|
546
571
|
// Feature handlers (auth, init, status, push, pull, etc.) require async OIDC discovery.
|
|
547
572
|
// Placed after daemon:getState so the debug endpoint is available immediately,
|
|
548
573
|
// without waiting for OIDC discovery (~400ms).
|
|
@@ -86,10 +86,16 @@ export declare const SynthesisCandidateSchema: z.ZodObject<{
|
|
|
86
86
|
domain: string;
|
|
87
87
|
fact: string;
|
|
88
88
|
}>, "many">;
|
|
89
|
+
keywords: z.ZodArray<z.ZodString, "many">;
|
|
89
90
|
placement: z.ZodString;
|
|
91
|
+
summary: z.ZodString;
|
|
92
|
+
tags: z.ZodArray<z.ZodString, "many">;
|
|
90
93
|
title: z.ZodString;
|
|
91
94
|
}, "strip", z.ZodTypeAny, {
|
|
95
|
+
summary: string;
|
|
96
|
+
tags: string[];
|
|
92
97
|
title: string;
|
|
98
|
+
keywords: string[];
|
|
93
99
|
confidence: number;
|
|
94
100
|
claim: string;
|
|
95
101
|
evidence: {
|
|
@@ -98,7 +104,10 @@ export declare const SynthesisCandidateSchema: z.ZodObject<{
|
|
|
98
104
|
}[];
|
|
99
105
|
placement: string;
|
|
100
106
|
}, {
|
|
107
|
+
summary: string;
|
|
108
|
+
tags: string[];
|
|
101
109
|
title: string;
|
|
110
|
+
keywords: string[];
|
|
102
111
|
confidence: number;
|
|
103
112
|
claim: string;
|
|
104
113
|
evidence: {
|
|
@@ -121,10 +130,16 @@ export declare const SynthesizeResponseSchema: z.ZodObject<{
|
|
|
121
130
|
domain: string;
|
|
122
131
|
fact: string;
|
|
123
132
|
}>, "many">;
|
|
133
|
+
keywords: z.ZodArray<z.ZodString, "many">;
|
|
124
134
|
placement: z.ZodString;
|
|
135
|
+
summary: z.ZodString;
|
|
136
|
+
tags: z.ZodArray<z.ZodString, "many">;
|
|
125
137
|
title: z.ZodString;
|
|
126
138
|
}, "strip", z.ZodTypeAny, {
|
|
139
|
+
summary: string;
|
|
140
|
+
tags: string[];
|
|
127
141
|
title: string;
|
|
142
|
+
keywords: string[];
|
|
128
143
|
confidence: number;
|
|
129
144
|
claim: string;
|
|
130
145
|
evidence: {
|
|
@@ -133,7 +148,10 @@ export declare const SynthesizeResponseSchema: z.ZodObject<{
|
|
|
133
148
|
}[];
|
|
134
149
|
placement: string;
|
|
135
150
|
}, {
|
|
151
|
+
summary: string;
|
|
152
|
+
tags: string[];
|
|
136
153
|
title: string;
|
|
154
|
+
keywords: string[];
|
|
137
155
|
confidence: number;
|
|
138
156
|
claim: string;
|
|
139
157
|
evidence: {
|
|
@@ -144,7 +162,10 @@ export declare const SynthesizeResponseSchema: z.ZodObject<{
|
|
|
144
162
|
}>, "many">;
|
|
145
163
|
}, "strip", z.ZodTypeAny, {
|
|
146
164
|
syntheses: {
|
|
165
|
+
summary: string;
|
|
166
|
+
tags: string[];
|
|
147
167
|
title: string;
|
|
168
|
+
keywords: string[];
|
|
148
169
|
confidence: number;
|
|
149
170
|
claim: string;
|
|
150
171
|
evidence: {
|
|
@@ -155,7 +176,10 @@ export declare const SynthesizeResponseSchema: z.ZodObject<{
|
|
|
155
176
|
}[];
|
|
156
177
|
}, {
|
|
157
178
|
syntheses: {
|
|
179
|
+
summary: string;
|
|
180
|
+
tags: string[];
|
|
158
181
|
title: string;
|
|
182
|
+
keywords: string[];
|
|
159
183
|
confidence: number;
|
|
160
184
|
claim: string;
|
|
161
185
|
evidence: {
|
|
@@ -13,6 +13,10 @@ export const ConsolidateResponseSchema = z.object({
|
|
|
13
13
|
actions: z.array(ConsolidationActionSchema),
|
|
14
14
|
});
|
|
15
15
|
// ── Synthesize ───────────────────────────────────────────────────────────────
|
|
16
|
+
// Bounds are slightly above the prompt's soft targets (200 chars / 3-5 tags /
|
|
17
|
+
// 5-10 keywords) so a model that goes a little over still produces a usable
|
|
18
|
+
// synthesis instead of being rejected outright; the caps still prevent a
|
|
19
|
+
// runaway model from landing oversized text directly in card-mode YAML.
|
|
16
20
|
export const SynthesisCandidateSchema = z.object({
|
|
17
21
|
claim: z.string(),
|
|
18
22
|
confidence: z.number().min(0).max(1),
|
|
@@ -20,7 +24,10 @@ export const SynthesisCandidateSchema = z.object({
|
|
|
20
24
|
domain: z.string(),
|
|
21
25
|
fact: z.string(),
|
|
22
26
|
})),
|
|
27
|
+
keywords: z.array(z.string()).max(15),
|
|
23
28
|
placement: z.string(),
|
|
29
|
+
summary: z.string().max(500),
|
|
30
|
+
tags: z.array(z.string()).max(8),
|
|
24
31
|
title: z.string(),
|
|
25
32
|
});
|
|
26
33
|
export const SynthesizeResponseSchema = z.object({
|
|
@@ -15,6 +15,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
15
15
|
import { access, mkdir, readdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
|
|
16
16
|
import { dirname, join } from 'node:path';
|
|
17
17
|
import { warnSidecarFailure } from '../../../core/domain/knowledge/sidecar-logging.js';
|
|
18
|
+
import { isExcludedFromSync } from '../../context-tree/derived-artifact.js';
|
|
18
19
|
import { ConsolidateResponseSchema } from '../dream-response-schemas.js';
|
|
19
20
|
import { parseDreamResponse } from '../parse-dream-response.js';
|
|
20
21
|
/**
|
|
@@ -226,7 +227,7 @@ function addFrontmatterFields(content, fields) {
|
|
|
226
227
|
if (parsed && typeof parsed === 'object') {
|
|
227
228
|
// Spread preserves existing key order; new fields are appended at end.
|
|
228
229
|
const merged = { ...parsed, ...fields };
|
|
229
|
-
const newYaml = yamlDump(merged, { flowLevel:
|
|
230
|
+
const newYaml = yamlDump(merged, { flowLevel: 1, lineWidth: -1, sortKeys: false }).trimEnd();
|
|
230
231
|
return `---\n${newYaml}\n---\n${body}`;
|
|
231
232
|
}
|
|
232
233
|
}
|
|
@@ -236,7 +237,7 @@ function addFrontmatterFields(content, fields) {
|
|
|
236
237
|
}
|
|
237
238
|
}
|
|
238
239
|
// No valid frontmatter — prepend
|
|
239
|
-
const yaml = yamlDump(fields, { flowLevel:
|
|
240
|
+
const yaml = yamlDump(fields, { flowLevel: 1, lineWidth: -1, sortKeys: false }).trimEnd();
|
|
240
241
|
return `---\n${yaml}\n---\n${content}`;
|
|
241
242
|
}
|
|
242
243
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
@@ -458,8 +459,10 @@ async function executeCrossReference(action, ctx) {
|
|
|
458
459
|
await Promise.all(Object.entries(previousTexts).map(([file, content]) => reviewBackupStore.save(file, content).catch(() => { })));
|
|
459
460
|
}
|
|
460
461
|
// For each file, add the other files to its related frontmatter
|
|
461
|
-
|
|
462
|
-
|
|
462
|
+
// Skip derived-artifact targets so we never write related: onto them.
|
|
463
|
+
const eligibleFiles = action.files.filter((f) => !isExcludedFromSync(f));
|
|
464
|
+
await Promise.all(eligibleFiles.map((file) => {
|
|
465
|
+
const otherFiles = eligibleFiles.filter((f) => f !== file);
|
|
463
466
|
return addRelatedLinks(join(contextTreeDir, file), otherFiles);
|
|
464
467
|
}));
|
|
465
468
|
return {
|
|
@@ -472,6 +475,8 @@ async function executeCrossReference(action, ctx) {
|
|
|
472
475
|
};
|
|
473
476
|
}
|
|
474
477
|
async function addRelatedLinks(filePath, relatedPaths) {
|
|
478
|
+
// Skip paths that won't be pushed — they'd be dangling refs on remote.
|
|
479
|
+
const incoming = relatedPaths.filter((p) => !isExcludedFromSync(p));
|
|
475
480
|
let content;
|
|
476
481
|
try {
|
|
477
482
|
content = await readFile(filePath, 'utf8');
|
|
@@ -491,8 +496,14 @@ async function addRelatedLinks(filePath, relatedPaths) {
|
|
|
491
496
|
try {
|
|
492
497
|
const parsed = yamlLoad(yamlBlock);
|
|
493
498
|
if (parsed && typeof parsed === 'object') {
|
|
494
|
-
const
|
|
495
|
-
parsed.related
|
|
499
|
+
const hadRelated = Array.isArray(parsed.related);
|
|
500
|
+
const existing = (Array.isArray(parsed.related) ? parsed.related : [])
|
|
501
|
+
.filter((p) => !isExcludedFromSync(p));
|
|
502
|
+
const merged = [...new Set([...existing, ...incoming])];
|
|
503
|
+
// Don't introduce a related: [] key into a file that didn't have one.
|
|
504
|
+
if (!hadRelated && merged.length === 0)
|
|
505
|
+
return;
|
|
506
|
+
parsed.related = merged;
|
|
496
507
|
const newYaml = yamlDump(parsed, { flowLevel: 1, lineWidth: -1, sortKeys: false }).trimEnd();
|
|
497
508
|
await atomicWrite(filePath, `---\n${newYaml}\n---\n${body}`);
|
|
498
509
|
return;
|
|
@@ -503,8 +514,10 @@ async function addRelatedLinks(filePath, relatedPaths) {
|
|
|
503
514
|
}
|
|
504
515
|
}
|
|
505
516
|
}
|
|
506
|
-
// No existing frontmatter — add one with related field
|
|
507
|
-
|
|
517
|
+
// No existing frontmatter — add one with related field, unless filter left nothing to add.
|
|
518
|
+
if (incoming.length === 0)
|
|
519
|
+
return;
|
|
520
|
+
const yaml = yamlDump({ related: incoming }, { flowLevel: 1, lineWidth: -1, sortKeys: false }).trimEnd();
|
|
508
521
|
await atomicWrite(filePath, `---\n${yaml}\n---\n${content}`);
|
|
509
522
|
}
|
|
510
523
|
async function determineNeedsReview(actionType, files, opts) {
|