byterover-cli 3.11.0 → 3.13.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/.env.production +2 -1
- package/dist/agent/infra/tools/implementations/curate-tool.js +18 -8
- package/dist/oclif/commands/curate/index.js +6 -0
- package/dist/oclif/commands/providers/connect.d.ts +26 -1
- package/dist/oclif/commands/providers/connect.js +95 -17
- package/dist/oclif/commands/providers/list.d.ts +10 -1
- package/dist/oclif/commands/providers/list.js +35 -3
- package/dist/oclif/commands/query.js +6 -0
- package/dist/oclif/commands/status.js +4 -0
- package/dist/oclif/lib/billing-line.d.ts +8 -0
- package/dist/oclif/lib/billing-line.js +45 -0
- package/dist/oclif/lib/format-billing-line.d.ts +2 -0
- package/dist/oclif/lib/format-billing-line.js +19 -0
- package/dist/oclif/lib/insufficient-credits.d.ts +11 -0
- package/dist/oclif/lib/insufficient-credits.js +36 -0
- package/dist/server/config/environment.d.ts +1 -0
- package/dist/server/config/environment.js +3 -0
- package/dist/server/constants.d.ts +6 -0
- package/dist/server/constants.js +11 -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 +1420 -11
- package/dist/server/core/domain/transport/schemas.js +160 -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/services/i-billing-service.d.ts +26 -0
- package/dist/server/core/interfaces/services/i-billing-service.js +1 -0
- package/dist/server/core/interfaces/storage/i-billing-config-store.d.ts +4 -0
- package/dist/server/core/interfaces/storage/i-billing-config-store.js +1 -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/billing/billing-state-endpoint.d.ts +4 -0
- package/dist/server/infra/billing/billing-state-endpoint.js +7 -0
- package/dist/server/infra/billing/build-status-billing.d.ts +9 -0
- package/dist/server/infra/billing/build-status-billing.js +36 -0
- package/dist/server/infra/billing/http-billing-service.d.ts +19 -0
- package/dist/server/infra/billing/http-billing-service.js +57 -0
- package/dist/server/infra/billing/paid-organizations-endpoint.d.ts +8 -0
- package/dist/server/infra/billing/paid-organizations-endpoint.js +18 -0
- package/dist/server/infra/billing/resolve-billing-source.d.ts +13 -0
- package/dist/server/infra/billing/resolve-billing-source.js +36 -0
- package/dist/server/infra/billing/resolve-billing-team.d.ts +5 -0
- package/dist/server/infra/billing/resolve-billing-team.js +8 -0
- package/dist/server/infra/connectors/rules/rules-connector.js +7 -2
- package/dist/server/infra/connectors/shared/constants.d.ts +9 -0
- package/dist/server/infra/connectors/shared/constants.js +31 -5
- package/dist/server/infra/daemon/agent-process.js +10 -8
- package/dist/server/infra/daemon/brv-server.js +48 -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-fetchers.js +10 -4
- package/dist/server/infra/process/feature-handlers.d.ts +3 -1
- package/dist/server/infra/process/feature-handlers.js +26 -2
- 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-billing-config-store.d.ts +13 -0
- package/dist/server/infra/storage/file-billing-config-store.js +55 -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/server/infra/transport/handlers/auth-handler.d.ts +4 -0
- package/dist/server/infra/transport/handlers/auth-handler.js +20 -2
- package/dist/server/infra/transport/handlers/billing-handler.d.ts +30 -0
- package/dist/server/infra/transport/handlers/billing-handler.js +132 -0
- package/dist/server/infra/transport/handlers/index.d.ts +4 -0
- package/dist/server/infra/transport/handlers/index.js +2 -0
- package/dist/server/infra/transport/handlers/init-handler.js +2 -0
- package/dist/server/infra/transport/handlers/status-handler.d.ts +14 -0
- package/dist/server/infra/transport/handlers/status-handler.js +16 -0
- package/dist/server/infra/transport/handlers/team-handler.d.ts +19 -0
- package/dist/server/infra/transport/handlers/team-handler.js +40 -0
- package/dist/shared/transport/events/auth-events.d.ts +3 -0
- package/dist/shared/transport/events/billing-events.d.ts +48 -0
- package/dist/shared/transport/events/billing-events.js +8 -0
- package/dist/shared/transport/events/index.d.ts +16 -0
- package/dist/shared/transport/events/index.js +6 -0
- package/dist/shared/transport/events/task-events.d.ts +204 -1
- package/dist/shared/transport/events/task-events.js +11 -0
- package/dist/shared/transport/events/team-events.d.ts +8 -0
- package/dist/shared/transport/events/team-events.js +3 -0
- package/dist/shared/transport/types/dto.d.ts +80 -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-B9JmEFOK.js +130 -0
- package/dist/webui/assets/index-CMIKsBMr.css +1 -0
- package/dist/webui/index.html +2 -2
- package/dist/webui/sw.js +1 -1
- package/oclif.manifest.json +653 -645
- package/package.json +1 -1
- package/dist/webui/assets/index--sXE__bc.css +0 -1
- package/dist/webui/assets/index-Bkkx961b.js +0 -130
|
@@ -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)
|
|
@@ -267,6 +278,8 @@ export const TransportAgentEventNames = {
|
|
|
267
278
|
*/
|
|
268
279
|
export const TransportStateEventNames = {
|
|
269
280
|
GET_AUTH: 'state:getAuth',
|
|
281
|
+
GET_BILLING_CONFIG: 'state:getBillingConfig',
|
|
282
|
+
GET_PAID_ORGANIZATIONS: 'state:getPaidOrganizations',
|
|
270
283
|
GET_PROJECT_CONFIG: 'state:getProjectConfig',
|
|
271
284
|
GET_PROVIDER_CONFIG: 'state:getProviderConfig',
|
|
272
285
|
};
|
|
@@ -275,6 +288,7 @@ export const TransportStateEventNames = {
|
|
|
275
288
|
* Used to notify agent child processes of global state changes.
|
|
276
289
|
*/
|
|
277
290
|
export const TransportDaemonEventNames = {
|
|
291
|
+
BILLING_PIN_CHANGED: 'billing:pinChanged',
|
|
278
292
|
PROVIDER_UPDATED: 'provider:updated',
|
|
279
293
|
};
|
|
280
294
|
/**
|
|
@@ -386,6 +400,10 @@ export const TaskCreatedSchema = z.object({
|
|
|
386
400
|
files: z.array(z.string()).optional(),
|
|
387
401
|
/** Folder path for curate-folder task type */
|
|
388
402
|
folderPath: z.string().optional(),
|
|
403
|
+
/** Active model id at task creation time */
|
|
404
|
+
model: z.string().optional(),
|
|
405
|
+
/** Active provider id at task creation time */
|
|
406
|
+
provider: z.string().optional(),
|
|
389
407
|
/** Unique task identifier */
|
|
390
408
|
taskId: z.string(),
|
|
391
409
|
/** Task type */
|
|
@@ -443,7 +461,9 @@ export const TaskQueryResultEventSchema = z.object({
|
|
|
443
461
|
})
|
|
444
462
|
.optional(),
|
|
445
463
|
taskId: z.string(),
|
|
446
|
-
tier: z.custom((val) => new Set(QUERY_LOG_TIERS).has(val), {
|
|
464
|
+
tier: z.custom((val) => new Set(QUERY_LOG_TIERS).has(val), {
|
|
465
|
+
message: 'Invalid query log tier',
|
|
466
|
+
}),
|
|
447
467
|
timing: z.object({ durationMs: z.number() }),
|
|
448
468
|
});
|
|
449
469
|
/**
|
|
@@ -563,11 +583,38 @@ export const TaskCancelResponseSchema = z.object({
|
|
|
563
583
|
/**
|
|
564
584
|
* task:list - Snapshot of active and recently-completed tasks for a project.
|
|
565
585
|
* Used by the web UI Tasks tab to populate state without replaying history.
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
586
|
+
*
|
|
587
|
+
* M2.16: cursor pagination dropped; numbered pagination (page/pageSize) +
|
|
588
|
+
* full filter dimensions (search/provider/model/time/duration).
|
|
589
|
+
*/
|
|
590
|
+
export const TaskListRequestSchema = z
|
|
591
|
+
.object({
|
|
592
|
+
/** Created at >= this epoch ms (M2.16). */
|
|
593
|
+
createdAfter: z.number().optional(),
|
|
594
|
+
/** Created at <= this epoch ms (M2.16). */
|
|
595
|
+
createdBefore: z.number().optional(),
|
|
596
|
+
/** Maximum elapsed time (ms) for terminal tasks (M2.16). */
|
|
597
|
+
maxDurationMs: z.number().optional(),
|
|
598
|
+
/** Minimum elapsed time (ms) for terminal tasks; only matches startedAt+completedAt rows (M2.16). */
|
|
599
|
+
minDurationMs: z.number().optional(),
|
|
600
|
+
/** Optional model id filter (M2.16). */
|
|
601
|
+
model: z.array(z.string()).optional(),
|
|
602
|
+
/** 1-based page index — server clamps to >= 1; defaults to 1 (M2.16). */
|
|
603
|
+
page: z.number().int().min(1).optional(),
|
|
604
|
+
/** Page size — server clamps to 1..1000; defaults to 50 (M2.16). */
|
|
605
|
+
pageSize: z.number().int().min(1).max(1000).optional(),
|
|
606
|
+
/** Optional project filter — defaults to caller's registered project. */
|
|
569
607
|
projectPath: z.string().optional(),
|
|
570
|
-
|
|
608
|
+
/** Optional provider id filter (M2.16). */
|
|
609
|
+
provider: z.array(z.string()).optional(),
|
|
610
|
+
/** Case-insensitive substring search over content + result + error.message (M2.16). */
|
|
611
|
+
searchText: z.string().optional(),
|
|
612
|
+
/** Optional status filter — return only tasks whose status matches one of these. */
|
|
613
|
+
status: z.array(z.enum(['cancelled', 'completed', 'created', 'error', 'started'])).optional(),
|
|
614
|
+
/** Optional task-type filter — e.g. ['curate'], ['query']. */
|
|
615
|
+
type: z.array(z.string()).optional(),
|
|
616
|
+
})
|
|
617
|
+
.strict();
|
|
571
618
|
export const TaskListItemStatusSchema = z.enum(['cancelled', 'completed', 'created', 'error', 'started']);
|
|
572
619
|
export const TaskListItemSchema = z.object({
|
|
573
620
|
completedAt: z.number().optional(),
|
|
@@ -578,15 +625,122 @@ export const TaskListItemSchema = z.object({
|
|
|
578
625
|
files: z.array(z.string()).optional(),
|
|
579
626
|
/** Folder path for `curate-folder` tasks */
|
|
580
627
|
folderPath: z.string().optional(),
|
|
628
|
+
/** Active model id at task creation time */
|
|
629
|
+
model: z.string().optional(),
|
|
581
630
|
projectPath: z.string().optional(),
|
|
631
|
+
/** Active provider id at task creation time */
|
|
632
|
+
provider: z.string().optional(),
|
|
633
|
+
/**
|
|
634
|
+
* Result string. Only present for in-memory completed tasks (toListItem
|
|
635
|
+
* populates from TaskInfo.result). Persisted entries from the index do not
|
|
636
|
+
* carry result by 2-tier design — detail panel uses task:get for full text.
|
|
637
|
+
*/
|
|
582
638
|
result: z.string().optional(),
|
|
583
639
|
startedAt: z.number().optional(),
|
|
584
640
|
status: TaskListItemStatusSchema,
|
|
585
641
|
taskId: z.string(),
|
|
586
642
|
type: z.string(),
|
|
587
643
|
});
|
|
588
|
-
|
|
644
|
+
/** Status histogram used by FE filter-bar breakdown (M2.16). */
|
|
645
|
+
export const TaskListCountsSchema = z.object({
|
|
646
|
+
all: z.number().int().nonnegative(),
|
|
647
|
+
cancelled: z.number().int().nonnegative(),
|
|
648
|
+
completed: z.number().int().nonnegative(),
|
|
649
|
+
/** Tasks with status === 'error'. */
|
|
650
|
+
failed: z.number().int().nonnegative(),
|
|
651
|
+
/** Tasks with status === 'created' || 'started'. */
|
|
652
|
+
running: z.number().int().nonnegative(),
|
|
653
|
+
});
|
|
654
|
+
/** (providerId, modelId) pair from history (M2.16). */
|
|
655
|
+
export const TaskListAvailableModelSchema = z.object({
|
|
656
|
+
modelId: z.string(),
|
|
657
|
+
providerId: z.string(),
|
|
658
|
+
});
|
|
659
|
+
export const TaskListResponseSchema = z
|
|
660
|
+
.object({
|
|
661
|
+
/** Distinct (providerId, modelId) pairs in candidate set. History-derived. */
|
|
662
|
+
availableModels: z.array(TaskListAvailableModelSchema),
|
|
663
|
+
/** Distinct providerId values in candidate set. History-derived (includes uninstalled). */
|
|
664
|
+
availableProviders: z.array(z.string()),
|
|
665
|
+
/**
|
|
666
|
+
* Status histogram matching current filter scope (Model A — post-filter,
|
|
667
|
+
* `counts.all === total` invariant). FE filter-bar chip count = visible
|
|
668
|
+
* row count.
|
|
669
|
+
*/
|
|
670
|
+
counts: TaskListCountsSchema,
|
|
671
|
+
/**
|
|
672
|
+
* 1-based page index, echoed back as-sent. Server clamps lower bound only
|
|
673
|
+
* (page < 1 → 1). NOT clamped against `pageCount`: a request for `page=9999`
|
|
674
|
+
* against a 1-page result returns `{page: 9999, tasks: []}` so the caller
|
|
675
|
+
* can detect an out-of-range page and correct itself.
|
|
676
|
+
*/
|
|
677
|
+
page: z.number().int().min(1),
|
|
678
|
+
/** Total page count = max(ceil(total/pageSize), 1). */
|
|
679
|
+
pageCount: z.number().int().min(1),
|
|
680
|
+
/** Page size echoed back, clamped to [1, 1000]. */
|
|
681
|
+
pageSize: z.number().int().min(1).max(1000),
|
|
682
|
+
/** Page slice of items after all filters. */
|
|
589
683
|
tasks: z.array(TaskListItemSchema),
|
|
684
|
+
/** Total count of items matching ALL filters (incl. status). */
|
|
685
|
+
total: z.number().int().nonnegative(),
|
|
686
|
+
})
|
|
687
|
+
.strict();
|
|
688
|
+
/**
|
|
689
|
+
* task:get — fetch full Level 2 detail for a single persisted task.
|
|
690
|
+
* Returns null when the task is unknown or its data file is corrupt.
|
|
691
|
+
*/
|
|
692
|
+
export const TaskGetRequestSchema = z.object({
|
|
693
|
+
taskId: z.string(),
|
|
694
|
+
});
|
|
695
|
+
export const TaskGetResponseSchema = z.object({
|
|
696
|
+
task: TaskHistoryEntrySchema.nullable(),
|
|
697
|
+
});
|
|
698
|
+
/**
|
|
699
|
+
* task:delete — remove a single task from the per-project history store.
|
|
700
|
+
* Idempotent: deleting a non-existent task returns success: true.
|
|
701
|
+
*/
|
|
702
|
+
export const TaskDeleteRequestSchema = z.object({
|
|
703
|
+
taskId: z.string(),
|
|
704
|
+
});
|
|
705
|
+
export const TaskDeleteResponseSchema = z.object({
|
|
706
|
+
error: z.string().optional(),
|
|
707
|
+
/**
|
|
708
|
+
* `true` when the task was actually removed (was live in-memory or persisted),
|
|
709
|
+
* `false` when the call was a no-op (taskId unknown or already tombstoned).
|
|
710
|
+
* Idempotent semantics on `success` are preserved — `success: true` indicates
|
|
711
|
+
* the request was valid; `removed` distinguishes "actually removed" from
|
|
712
|
+
* "no-op". `task:deleteBulk` uses this to compute an accurate `deletedCount`.
|
|
713
|
+
*/
|
|
714
|
+
removed: z.boolean().optional(),
|
|
715
|
+
success: z.boolean(),
|
|
716
|
+
});
|
|
717
|
+
/**
|
|
718
|
+
* task:deleteBulk — delete many tasks at once. `deletedCount` reports actual removals.
|
|
719
|
+
*/
|
|
720
|
+
export const TaskDeleteBulkRequestSchema = z.object({
|
|
721
|
+
taskIds: z.array(z.string()),
|
|
722
|
+
});
|
|
723
|
+
export const TaskDeleteBulkResponseSchema = z.object({
|
|
724
|
+
deletedCount: z.number(),
|
|
725
|
+
error: z.string().optional(),
|
|
726
|
+
});
|
|
727
|
+
/**
|
|
728
|
+
* task:clearCompleted — remove all terminal-state tasks (completed/error/cancelled)
|
|
729
|
+
* from the project's history. Active tasks (created/started) are preserved.
|
|
730
|
+
*/
|
|
731
|
+
export const TaskClearCompletedRequestSchema = z.object({
|
|
732
|
+
projectPath: z.string().optional(),
|
|
733
|
+
});
|
|
734
|
+
export const TaskClearCompletedResponseSchema = z.object({
|
|
735
|
+
deletedCount: z.number(),
|
|
736
|
+
error: z.string().optional(),
|
|
737
|
+
});
|
|
738
|
+
/**
|
|
739
|
+
* task:deleted — broadcast to project room when a task is removed from history.
|
|
740
|
+
* Lets other clients (TUI, other webui tabs) drop the row from their local view.
|
|
741
|
+
*/
|
|
742
|
+
export const TaskDeletedEventSchema = z.object({
|
|
743
|
+
taskId: z.string(),
|
|
590
744
|
});
|
|
591
745
|
// ============================================================================
|
|
592
746
|
// 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,26 @@
|
|
|
1
|
+
import type { BillingFreeUserLimitDTO, BillingOrganizationTierDTO, BillingUsageDTO } from '../../../../shared/transport/types/dto.js';
|
|
2
|
+
/**
|
|
3
|
+
* Reads compute-unit usage from the ByteRover billing service.
|
|
4
|
+
* Implementations may be HTTP-based (production) or stubbed (tests).
|
|
5
|
+
*/
|
|
6
|
+
export interface IBillingService {
|
|
7
|
+
/**
|
|
8
|
+
* Returns the user's free-tier daily/monthly limits.
|
|
9
|
+
*
|
|
10
|
+
* @param sessionKey Authenticated session token (passed via x-byterover-session-id).
|
|
11
|
+
*/
|
|
12
|
+
getFreeUserLimit: (sessionKey: string) => Promise<BillingFreeUserLimitDTO>;
|
|
13
|
+
/**
|
|
14
|
+
* Returns the tier (FREE/PRO/ENTERPRISE) for every org the user belongs to.
|
|
15
|
+
*
|
|
16
|
+
* @param sessionKey Authenticated session token (passed via x-byterover-session-id).
|
|
17
|
+
*/
|
|
18
|
+
getTiers: (sessionKey: string) => Promise<BillingOrganizationTierDTO[]>;
|
|
19
|
+
/**
|
|
20
|
+
* Fetches usage for every organization the authenticated user belongs to in
|
|
21
|
+
* a single round trip. Replaces the old per-org `getUsageByProjects` fan-out.
|
|
22
|
+
*
|
|
23
|
+
* @param sessionKey Authenticated session token (passed via x-byterover-session-id).
|
|
24
|
+
*/
|
|
25
|
+
getUsages: (sessionKey: string) => Promise<BillingUsageDTO[]>;
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { BillingStateRequest, BillingStateResponse } from '../../core/domain/transport/schemas.js';
|
|
2
|
+
import type { IBillingConfigStore } from '../../core/interfaces/storage/i-billing-config-store.js';
|
|
3
|
+
export type BillingConfigStoreFactory = (projectPath: string) => IBillingConfigStore;
|
|
4
|
+
export declare function createBillingStateHandler(storeFactory: BillingConfigStoreFactory): (data: BillingStateRequest) => Promise<BillingStateResponse>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BillingFreeUserLimitDTO, BillingUsageDTO, StatusBillingDTO } from '../../../shared/transport/types/dto.js';
|
|
2
|
+
export interface BuildStatusBillingInput {
|
|
3
|
+
activeProvider: string;
|
|
4
|
+
freeUserLimit?: BillingFreeUserLimitDTO;
|
|
5
|
+
isAuthenticated: boolean;
|
|
6
|
+
paidUsages: readonly BillingUsageDTO[];
|
|
7
|
+
pinnedTeamId?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function buildStatusBilling(input: BuildStatusBillingInput): StatusBillingDTO | undefined;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { resolveBillingTeamId } from './resolve-billing-team.js';
|
|
2
|
+
const BYTEROVER_PROVIDER_ID = 'byterover';
|
|
3
|
+
export function buildStatusBilling(input) {
|
|
4
|
+
if (!input.isAuthenticated)
|
|
5
|
+
return undefined;
|
|
6
|
+
if (input.activeProvider !== BYTEROVER_PROVIDER_ID) {
|
|
7
|
+
return { activeProvider: input.activeProvider, source: 'other-provider' };
|
|
8
|
+
}
|
|
9
|
+
const paidIds = input.paidUsages.map((u) => u.organizationId);
|
|
10
|
+
const resolved = resolveBillingTeamId({
|
|
11
|
+
paidOrganizationIds: paidIds,
|
|
12
|
+
pinnedTeamId: input.pinnedTeamId,
|
|
13
|
+
});
|
|
14
|
+
if (resolved === undefined)
|
|
15
|
+
return freeSource(input.freeUserLimit);
|
|
16
|
+
const usage = input.paidUsages.find((u) => u.organizationId === resolved);
|
|
17
|
+
if (!usage)
|
|
18
|
+
return { organizationId: resolved, source: 'paid' };
|
|
19
|
+
return {
|
|
20
|
+
organizationId: usage.organizationId,
|
|
21
|
+
organizationName: usage.organizationName,
|
|
22
|
+
remaining: usage.remaining,
|
|
23
|
+
source: 'paid',
|
|
24
|
+
tier: usage.tier,
|
|
25
|
+
total: usage.totalLimit,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function freeSource(freeLimit) {
|
|
29
|
+
if (!freeLimit)
|
|
30
|
+
return { source: 'free' };
|
|
31
|
+
return {
|
|
32
|
+
remaining: freeLimit.monthly.remaining,
|
|
33
|
+
source: 'free',
|
|
34
|
+
total: freeLimit.monthly.limit,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BillingFreeUserLimitDTO, BillingOrganizationTierDTO, BillingUsageDTO } from '../../../shared/transport/types/dto.js';
|
|
2
|
+
import type { IBillingService } from '../../core/interfaces/services/i-billing-service.js';
|
|
3
|
+
export type BillingServiceConfig = {
|
|
4
|
+
apiBaseUrl: string;
|
|
5
|
+
timeout?: number;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* HTTP-backed billing service. `getUsages` joins `/billing/usages` and
|
|
9
|
+
* `/billing/organizations/tiers` in parallel so consumers see tier alongside
|
|
10
|
+
* credit usage in a single DTO. `getFreeUserLimit` mirrors its endpoint 1:1.
|
|
11
|
+
*/
|
|
12
|
+
export declare class HttpBillingService implements IBillingService {
|
|
13
|
+
private readonly config;
|
|
14
|
+
constructor(config: BillingServiceConfig);
|
|
15
|
+
getFreeUserLimit(sessionKey: string): Promise<BillingFreeUserLimitDTO>;
|
|
16
|
+
getTiers(sessionKey: string): Promise<BillingOrganizationTierDTO[]>;
|
|
17
|
+
getUsages(sessionKey: string): Promise<BillingUsageDTO[]>;
|
|
18
|
+
private fetchRawUsages;
|
|
19
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { getErrorMessage } from '../../utils/error-helpers.js';
|
|
2
|
+
import { AuthenticatedHttpClient } from '../http/authenticated-http-client.js';
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
4
|
+
/**
|
|
5
|
+
* HTTP-backed billing service. `getUsages` joins `/billing/usages` and
|
|
6
|
+
* `/billing/organizations/tiers` in parallel so consumers see tier alongside
|
|
7
|
+
* credit usage in a single DTO. `getFreeUserLimit` mirrors its endpoint 1:1.
|
|
8
|
+
*/
|
|
9
|
+
export class HttpBillingService {
|
|
10
|
+
config;
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.config = {
|
|
13
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
14
|
+
timeout: config.timeout ?? DEFAULT_TIMEOUT_MS,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
async getFreeUserLimit(sessionKey) {
|
|
18
|
+
try {
|
|
19
|
+
const httpClient = new AuthenticatedHttpClient(sessionKey);
|
|
20
|
+
const url = `${this.config.apiBaseUrl}/billing/usage/free-user/limit`;
|
|
21
|
+
return await httpClient.get(url, { timeout: this.config.timeout });
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw new Error(`Failed to fetch free-user limit: ${getErrorMessage(error)}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async getTiers(sessionKey) {
|
|
28
|
+
try {
|
|
29
|
+
const httpClient = new AuthenticatedHttpClient(sessionKey);
|
|
30
|
+
const url = `${this.config.apiBaseUrl}/billing/organizations/tiers`;
|
|
31
|
+
const response = await httpClient.get(url, { timeout: this.config.timeout });
|
|
32
|
+
return response.organizations;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
throw new Error(`Failed to fetch billing tiers: ${getErrorMessage(error)}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async getUsages(sessionKey) {
|
|
39
|
+
const [rawUsages, tiers] = await Promise.all([this.fetchRawUsages(sessionKey), this.getTiers(sessionKey)]);
|
|
40
|
+
const tierByOrg = new Map(tiers.map((t) => [t.organizationId, t]));
|
|
41
|
+
return rawUsages.map((usage) => {
|
|
42
|
+
const tier = tierByOrg.get(usage.organizationId);
|
|
43
|
+
return { ...usage, isTrialing: tier?.isTrialing ?? false, tier: tier?.tier ?? 'FREE' };
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async fetchRawUsages(sessionKey) {
|
|
47
|
+
try {
|
|
48
|
+
const httpClient = new AuthenticatedHttpClient(sessionKey);
|
|
49
|
+
const url = `${this.config.apiBaseUrl}/billing/usages`;
|
|
50
|
+
const response = await httpClient.get(url, { timeout: this.config.timeout });
|
|
51
|
+
return response.organizations;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
throw new Error(`Failed to fetch billing usages: ${getErrorMessage(error)}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PaidOrganizationsResponse } from '../../core/domain/transport/schemas.js';
|
|
2
|
+
import type { IBillingService } from '../../core/interfaces/services/i-billing-service.js';
|
|
3
|
+
import type { IAuthStateStore } from '../../core/interfaces/state/i-auth-state-store.js';
|
|
4
|
+
export interface PaidOrganizationsHandlerDeps {
|
|
5
|
+
authStateStore: IAuthStateStore;
|
|
6
|
+
billingService: IBillingService;
|
|
7
|
+
}
|
|
8
|
+
export declare function createPaidOrganizationsHandler(deps: PaidOrganizationsHandlerDeps): () => Promise<PaidOrganizationsResponse>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getErrorMessage } from '../../utils/error-helpers.js';
|
|
2
|
+
export function createPaidOrganizationsHandler(deps) {
|
|
3
|
+
return async () => {
|
|
4
|
+
const token = deps.authStateStore.getToken();
|
|
5
|
+
if (!token?.isValid())
|
|
6
|
+
return { organizationIds: [] };
|
|
7
|
+
try {
|
|
8
|
+
const tiers = await deps.billingService.getTiers(token.sessionKey);
|
|
9
|
+
const organizationIds = tiers
|
|
10
|
+
.filter((tier) => tier.tier !== 'FREE')
|
|
11
|
+
.map((tier) => tier.organizationId);
|
|
12
|
+
return { organizationIds };
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
return { error: getErrorMessage(error), organizationIds: [] };
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { StatusBillingDTO } from '../../../shared/transport/types/dto.js';
|
|
2
|
+
import type { IProviderConfigStore } from '../../core/interfaces/i-provider-config-store.js';
|
|
3
|
+
import type { IBillingService } from '../../core/interfaces/services/i-billing-service.js';
|
|
4
|
+
import type { IAuthStateStore } from '../../core/interfaces/state/i-auth-state-store.js';
|
|
5
|
+
import type { IBillingConfigStore } from '../../core/interfaces/storage/i-billing-config-store.js';
|
|
6
|
+
export interface ResolveBillingDeps {
|
|
7
|
+
authStateStore: IAuthStateStore;
|
|
8
|
+
billingConfigStoreFactory: (projectPath: string) => IBillingConfigStore;
|
|
9
|
+
billingService: IBillingService;
|
|
10
|
+
projectPath: string;
|
|
11
|
+
providerConfigStore: IProviderConfigStore;
|
|
12
|
+
}
|
|
13
|
+
export declare function resolveBillingForProject(deps: ResolveBillingDeps): Promise<StatusBillingDTO | undefined>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { buildStatusBilling } from './build-status-billing.js';
|
|
2
|
+
const BYTEROVER_PROVIDER_ID = 'byterover';
|
|
3
|
+
export async function resolveBillingForProject(deps) {
|
|
4
|
+
const activeProvider = await deps.providerConfigStore.getActiveProvider().catch(() => '');
|
|
5
|
+
const token = deps.authStateStore.getToken();
|
|
6
|
+
if (!token?.isValid()) {
|
|
7
|
+
return buildStatusBilling({ activeProvider, isAuthenticated: false, paidUsages: [] });
|
|
8
|
+
}
|
|
9
|
+
if (activeProvider !== BYTEROVER_PROVIDER_ID) {
|
|
10
|
+
return buildStatusBilling({ activeProvider, isAuthenticated: true, paidUsages: [] });
|
|
11
|
+
}
|
|
12
|
+
const { sessionKey } = token;
|
|
13
|
+
const [pinnedTeamId, usagesResult] = await Promise.all([
|
|
14
|
+
deps
|
|
15
|
+
.billingConfigStoreFactory(deps.projectPath)
|
|
16
|
+
.getPinnedTeamId()
|
|
17
|
+
.catch(() => undefined),
|
|
18
|
+
deps.billingService
|
|
19
|
+
.getUsages(sessionKey)
|
|
20
|
+
.then((usages) => ({ ok: true, usages }))
|
|
21
|
+
.catch(() => ({ ok: false })),
|
|
22
|
+
]);
|
|
23
|
+
if (!usagesResult.ok)
|
|
24
|
+
return undefined;
|
|
25
|
+
const paidUsages = usagesResult.usages.filter((u) => u.tier !== 'FREE');
|
|
26
|
+
const freeUserLimit = paidUsages.length === 0
|
|
27
|
+
? await deps.billingService.getFreeUserLimit(sessionKey).catch(() => undefined)
|
|
28
|
+
: undefined;
|
|
29
|
+
return buildStatusBilling({
|
|
30
|
+
activeProvider,
|
|
31
|
+
freeUserLimit,
|
|
32
|
+
isAuthenticated: true,
|
|
33
|
+
paidUsages,
|
|
34
|
+
pinnedTeamId,
|
|
35
|
+
});
|
|
36
|
+
}
|