@pellux/goodvibes-tui 1.13.1 → 1.14.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/CHANGELOG.md +96 -0
- package/README.md +6 -6
- package/bin/goodvibes +6 -0
- package/bin/goodvibes-daemon +6 -0
- package/bin/launcher-support.js +34 -1
- package/docs/foundation-artifacts/operator-contract.json +8076 -2047
- package/package.json +15 -11
- package/scripts/postinstall.js +73 -48
- package/src/cli/bundle-command.ts +5 -5
- package/src/cli/completion.ts +1 -1
- package/src/cli/completions/generate.ts +15 -3
- package/src/cli/doctor.ts +385 -0
- package/src/cli/entrypoint.ts +72 -1
- package/src/cli/help.ts +29 -9
- package/src/cli/hooks-command.ts +74 -0
- package/src/cli/hooks-report.ts +167 -0
- package/src/cli/index.ts +3 -0
- package/src/cli/management.ts +13 -1
- package/src/cli/parser.ts +10 -2
- package/src/cli/plugin-bundles-command.ts +228 -0
- package/src/cli/plugin-command.ts +179 -0
- package/src/cli/service-posture.ts +10 -0
- package/src/cli/status.ts +275 -2
- package/src/cli/tui-startup.ts +119 -1
- package/src/cli/types.ts +3 -1
- package/src/config/index.ts +16 -0
- package/src/config/tui-extension-settings.ts +169 -0
- package/src/core/approval-alert.ts +15 -2
- package/src/core/compaction-receipt.ts +127 -0
- package/src/core/model-routing-chip.ts +82 -0
- package/src/core/permission-mode.ts +89 -0
- package/src/core/rewind-receipt.ts +83 -0
- package/src/core/rewind-turn-anchors.ts +160 -0
- package/src/core/scriptable-statusline.ts +141 -0
- package/src/core/session-auto-titler.ts +110 -0
- package/src/core/stream-event-wiring.ts +20 -0
- package/src/core/system-message-router.ts +1 -1
- package/src/core/terminal-notifier.ts +102 -0
- package/src/core/turn-event-wiring.ts +80 -1
- package/src/input/command-reference.ts +96 -0
- package/src/input/command-registry.ts +25 -0
- package/src/input/commands/channel-pairing.ts +245 -0
- package/src/input/commands/channel-runtime.ts +152 -6
- package/src/input/commands/checkin-runtime.ts +89 -0
- package/src/input/commands/checkpoint-runtime.ts +7 -179
- package/src/input/commands/ci-runtime.ts +207 -0
- package/src/input/commands/cost-attribution-format.ts +52 -0
- package/src/input/commands/cost-runtime.ts +44 -4
- package/src/input/commands/editor-runtime.ts +22 -0
- package/src/input/commands/flags-runtime.ts +328 -0
- package/src/input/commands/health-metrics-format.ts +67 -0
- package/src/input/commands/health-runtime.ts +55 -1
- package/src/input/commands/memory.ts +23 -0
- package/src/input/commands/operator-rpc.ts +95 -0
- package/src/input/commands/palette-runtime.ts +22 -0
- package/src/input/commands/permissions-provenance.ts +264 -0
- package/src/input/commands/permissions-runtime.ts +21 -0
- package/src/input/commands/planning-runtime.ts +28 -1
- package/src/input/commands/platform-access-runtime.ts +2 -97
- package/src/input/commands/principals-runtime.ts +212 -0
- package/src/input/commands/product-runtime.ts +43 -3
- package/src/input/commands/recall-files-config.ts +54 -0
- package/src/input/commands/recall-files-git.ts +57 -0
- package/src/input/commands/recall-files.ts +185 -0
- package/src/input/commands/recall-query.ts +8 -3
- package/src/input/commands/recall-review.ts +2 -2
- package/src/input/commands/recall-shared.ts +13 -0
- package/src/input/commands/relay-runtime.ts +86 -0
- package/src/input/commands/review-runtime.ts +160 -0
- package/src/input/commands/rewind-runtime.ts +362 -0
- package/src/input/commands/schedule-nl.ts +147 -0
- package/src/input/commands/schedule-runtime.ts +29 -10
- package/src/input/commands/secret-runtime.ts +55 -0
- package/src/input/commands/session-content.ts +19 -8
- package/src/input/commands/session-workflow.ts +10 -0
- package/src/input/commands/update-runtime.ts +363 -0
- package/src/input/commands/workstream-attempt-dependents.ts +103 -0
- package/src/input/commands/workstream-attempts.ts +244 -0
- package/src/input/commands/workstream-runtime.ts +164 -26
- package/src/input/commands/worktree-runtime.ts +56 -2
- package/src/input/commands.ts +135 -80
- package/src/input/composer-capture.ts +67 -0
- package/src/input/composer-editor.ts +134 -0
- package/src/input/concealed-input.ts +94 -0
- package/src/input/exec-env-scrub-config.ts +84 -0
- package/src/input/feature-unit-layout.ts +142 -0
- package/src/input/feed-context-factory.ts +2 -0
- package/src/input/handler-feed-routes.ts +17 -0
- package/src/input/handler-feed.ts +3 -0
- package/src/input/handler-modal-routes.ts +5 -2
- package/src/input/handler-shortcuts.ts +37 -0
- package/src/input/handler.ts +24 -2
- package/src/input/keybindings.ts +11 -2
- package/src/input/onboarding/onboarding-feature-units.ts +536 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +40 -6
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +324 -629
- package/src/input/onboarding/onboarding-wizard-steps.ts +5 -5
- package/src/input/onboarding/onboarding-wizard-types.ts +6 -0
- package/src/input/relay-settings-descriptions.ts +35 -0
- package/src/input/sandbox-exec-config.ts +107 -0
- package/src/input/settings-modal-adjustment.ts +4 -7
- package/src/input/settings-modal-data.ts +178 -8
- package/src/input/settings-modal-mutations.ts +41 -13
- package/src/input/settings-modal-types.ts +42 -5
- package/src/input/settings-modal.ts +67 -15
- package/src/input/submission-router.ts +4 -1
- package/src/input/worktree-setup-config.ts +93 -0
- package/src/main.ts +43 -43
- package/src/panels/builtin/development.ts +14 -0
- package/src/panels/diff-review-model.ts +189 -0
- package/src/panels/diff-review-panel.ts +395 -0
- package/src/panels/fleet-panel-format.ts +114 -2
- package/src/panels/fleet-panel.ts +65 -78
- package/src/panels/fleet-read-model.ts +91 -11
- package/src/panels/fleet-stop.ts +19 -4
- package/src/panels/modals/memory-modal.ts +11 -2
- package/src/permissions/hunk-selection.ts +86 -5
- package/src/permissions/prompt.ts +108 -7
- package/src/permissions/sandbox-exec-gate.ts +162 -0
- package/src/renderer/settings-modal-helpers.ts +21 -1
- package/src/renderer/settings-modal.ts +70 -44
- package/src/renderer/shell-surface.ts +23 -1
- package/src/renderer/system-message.ts +4 -0
- package/src/renderer/tool-result-summary.ts +95 -5
- package/src/renderer/ui-factory.ts +22 -0
- package/src/runtime/bootstrap-command-context.ts +15 -1
- package/src/runtime/bootstrap-command-parts.ts +14 -2
- package/src/runtime/bootstrap-core.ts +38 -38
- package/src/runtime/bootstrap-notifier-sync.ts +41 -0
- package/src/runtime/bootstrap-shell.ts +8 -0
- package/src/runtime/bootstrap.ts +21 -23
- package/src/runtime/context-accounting-source.ts +103 -0
- package/src/runtime/conversation-rewind-port.ts +130 -0
- package/src/runtime/fleet-needs-input-push.ts +70 -0
- package/src/runtime/install-self-check-startup.ts +36 -0
- package/src/runtime/install-self-check.ts +149 -0
- package/src/runtime/onboarding/fast-path.ts +93 -0
- package/src/runtime/process-lifecycle.ts +32 -24
- package/src/runtime/relay-reachability-bridge.ts +53 -0
- package/src/runtime/release-artifacts.ts +79 -0
- package/src/runtime/services.ts +33 -33
- package/src/runtime/trust/workspace-registration.ts +184 -0
- package/src/runtime/trust/workspace-trust.ts +202 -0
- package/src/runtime/ui-services.ts +2 -1
- package/src/runtime/update-check.ts +113 -0
- package/src/runtime/workspace-checkpointing.ts +32 -0
- package/src/runtime/workstream-attempts-validation.ts +77 -0
- package/src/runtime/workstream-draft-edits.ts +95 -0
- package/src/runtime/workstream-draft-store.ts +101 -0
- package/src/runtime/workstream-draft-types.ts +42 -0
- package/src/runtime/workstream-services.ts +70 -39
- package/src/shell/blocking-input.ts +29 -0
- package/src/shell/ui-openers.ts +65 -0
- package/src/verification/verification-ledger.ts +56 -2
- package/src/version.ts +1 -1
- package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +0 -130
- package/src/runtime/gateway-verbs.ts +0 -47
- package/src/runtime/render-scheduler.ts +0 -90
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// workstream-draft-edits.ts — pure item edits for a not-yet-launched proposal
|
|
3
|
+
//
|
|
4
|
+
// The plan-review gate (see /workstream, input/commands/workstream-runtime.ts)
|
|
5
|
+
// lets a human reshape a decomposed proposal BEFORE anything is spent: rewrite
|
|
6
|
+
// one item's brief, drop an item, or reorder items. These are pure functions
|
|
7
|
+
// over the launchable CreateWorkstreamInput (@pellux/goodvibes-sdk/platform/
|
|
8
|
+
// orchestration) — no engine calls, no I/O — so the same spec that is edited
|
|
9
|
+
// here is byte-for-byte what launch hands createWorkstream. Extracted from
|
|
10
|
+
// workstream-services.ts to keep that construction module lean (its own
|
|
11
|
+
// header explains the 800-line-cap discipline) and to give the edits a clean
|
|
12
|
+
// unit-test target.
|
|
13
|
+
//
|
|
14
|
+
// HONEST SCOPE (stated so the review surface never overstates what an edit
|
|
15
|
+
// does):
|
|
16
|
+
// - editItemBrief rewrites an item's `task` (the instructions its agent
|
|
17
|
+
// runs), never its `title` (the short label). The engineer/review phase
|
|
18
|
+
// template is unchanged — a brief is WHAT the item does, not HOW the
|
|
19
|
+
// pipeline runs it.
|
|
20
|
+
// - removeItem also strips the removed item's id from every other item's
|
|
21
|
+
// `dependsOn`, so the spec never carries a dangling dependency into
|
|
22
|
+
// fromPlanProposal's assembly (which would otherwise reject it). It
|
|
23
|
+
// refuses to remove the last item — a workstream needs at least one.
|
|
24
|
+
// - moveItem reorders the AUTHORING order (the ordinals shown in the review
|
|
25
|
+
// and the tie-break when the engine has a free capacity slot and several
|
|
26
|
+
// claimable items). It never rewrites dependencies, so it cannot create a
|
|
27
|
+
// cycle; the engine still schedules strictly by dependency + capacity, not
|
|
28
|
+
// by array position. Reordering is a presentation/authoring act, not a
|
|
29
|
+
// scheduling override — and is documented as such where it is offered.
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
import type { CreateWorkstreamInput, WorkItemSpec } from '@pellux/goodvibes-sdk/platform/orchestration';
|
|
33
|
+
|
|
34
|
+
/** Success carries the rewritten spec; failure carries an honest, user-facing reason. */
|
|
35
|
+
export type DraftEditResult = { readonly spec: CreateWorkstreamInput } | { readonly error: string };
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a user-typed item reference to a 0-based index. A reference is
|
|
39
|
+
* either the 1-based ordinal shown in the proposal (the primary handle) or an
|
|
40
|
+
* item id / unique id-prefix (the stable handle that survives reordering). An
|
|
41
|
+
* ambiguous or unmatched reference resolves to undefined so the caller can
|
|
42
|
+
* refuse honestly rather than editing the wrong item.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveItemIndex(items: readonly WorkItemSpec[], ref: string): number | undefined {
|
|
45
|
+
const trimmed = ref.trim();
|
|
46
|
+
const ordinal = Number(trimmed);
|
|
47
|
+
if (Number.isInteger(ordinal) && ordinal >= 1 && ordinal <= items.length) return ordinal - 1;
|
|
48
|
+
const matches = items
|
|
49
|
+
.map((item, index) => ({ item, index }))
|
|
50
|
+
.filter(({ item }) => item.id !== undefined && item.id.startsWith(trimmed));
|
|
51
|
+
return matches.length === 1 ? matches[0]!.index : undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function unresolvedError(items: readonly WorkItemSpec[], ref: string): string {
|
|
55
|
+
return `No draft item matches "${ref}". Use a number from 1 to ${items.length}, or an item id.`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Rewrite one item's brief (its `task`). Empty/whitespace briefs are refused. */
|
|
59
|
+
export function editItemBrief(spec: CreateWorkstreamInput, ref: string, brief: string): DraftEditResult {
|
|
60
|
+
const index = resolveItemIndex(spec.items, ref);
|
|
61
|
+
if (index === undefined) return { error: unresolvedError(spec.items, ref) };
|
|
62
|
+
const trimmed = brief.trim();
|
|
63
|
+
if (!trimmed) return { error: 'A new brief cannot be empty.' };
|
|
64
|
+
const items = spec.items.map((item, i) => (i === index ? { ...item, task: trimmed } : item));
|
|
65
|
+
return { spec: { ...spec, items } };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Drop one item and strip its id from every sibling's dependsOn. Refuses to empty the plan. */
|
|
69
|
+
export function removeItemFromSpec(spec: CreateWorkstreamInput, ref: string): DraftEditResult {
|
|
70
|
+
const index = resolveItemIndex(spec.items, ref);
|
|
71
|
+
if (index === undefined) return { error: unresolvedError(spec.items, ref) };
|
|
72
|
+
if (spec.items.length <= 1) return { error: 'Cannot remove the last item — a workstream needs at least one.' };
|
|
73
|
+
const removedId = spec.items[index]!.id;
|
|
74
|
+
const items = spec.items
|
|
75
|
+
.filter((_, i) => i !== index)
|
|
76
|
+
.map((item) =>
|
|
77
|
+
removedId && item.dependsOn?.includes(removedId)
|
|
78
|
+
? { ...item, dependsOn: item.dependsOn.filter((dep) => dep !== removedId) }
|
|
79
|
+
: item,
|
|
80
|
+
);
|
|
81
|
+
return { spec: { ...spec, items } };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Reorder one item to a new 1-based authoring position. Never touches dependencies. */
|
|
85
|
+
export function moveItemInSpec(spec: CreateWorkstreamInput, ref: string, toPosition: number): DraftEditResult {
|
|
86
|
+
const index = resolveItemIndex(spec.items, ref);
|
|
87
|
+
if (index === undefined) return { error: unresolvedError(spec.items, ref) };
|
|
88
|
+
if (!Number.isInteger(toPosition) || toPosition < 1 || toPosition > spec.items.length) {
|
|
89
|
+
return { error: `Position must be a whole number from 1 to ${spec.items.length}.` };
|
|
90
|
+
}
|
|
91
|
+
const items = [...spec.items];
|
|
92
|
+
const [moved] = items.splice(index, 1);
|
|
93
|
+
items.splice(toPosition - 1, 0, moved!);
|
|
94
|
+
return { spec: { ...spec, items } };
|
|
95
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// workstream-draft-store.ts — durable journal for not-yet-launched proposals
|
|
3
|
+
//
|
|
4
|
+
// A /workstream proposal (WorkstreamDraft, workstream-services.ts) is TUI-owned
|
|
5
|
+
// state the OrchestrationEngine has no concept of — its only creation entry
|
|
6
|
+
// point materializes a real, ticking workstream with no pre-launch "draft"
|
|
7
|
+
// stage (see that module's REALITY-WINS header). Earlier waves therefore held
|
|
8
|
+
// drafts in memory only, and a restart between create and launch silently lost
|
|
9
|
+
// them. This store journals each draft to disk so the plan-review gate survives
|
|
10
|
+
// a restart and is resumable: the same reshaped, approved proposal is still
|
|
11
|
+
// there to launch afterward.
|
|
12
|
+
//
|
|
13
|
+
// This is TUI-side persistence, NOT an engine feature — it sits ALONGSIDE the
|
|
14
|
+
// engine's own .goodvibes/orchestration/<id>.json workstream snapshots, under a
|
|
15
|
+
// drafts/ subdirectory, and is loaded by the command facade at construction the
|
|
16
|
+
// same way the engine's resumeAllFromDisk() reloads live workstreams. A draft
|
|
17
|
+
// is a pure-data object (id/task/spec/gate/proposal/provenance/approved), so a
|
|
18
|
+
// plain JSON round-trip is lossless.
|
|
19
|
+
//
|
|
20
|
+
// Every operation is guarded and NEVER throws: a missing directory, an
|
|
21
|
+
// unreadable or malformed file, or a write failure degrades to "that draft
|
|
22
|
+
// isn't there" rather than crashing session startup — mirroring the engine
|
|
23
|
+
// persistence layer's own quarantine-don't-propagate discipline.
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
27
|
+
import { join } from 'node:path';
|
|
28
|
+
import type { WorkstreamDraft } from './workstream-draft-types.ts';
|
|
29
|
+
|
|
30
|
+
export interface WorkstreamDraftStore {
|
|
31
|
+
/** Every valid draft on disk, oldest first. A directory that doesn't exist yet ⇒ []. Malformed files are skipped, never fatal. */
|
|
32
|
+
loadAll(): WorkstreamDraft[];
|
|
33
|
+
/** Write (or overwrite) one draft's snapshot. Silently no-ops on any I/O failure — a lost journal write must never break the in-memory flow. */
|
|
34
|
+
save(draft: WorkstreamDraft): void;
|
|
35
|
+
/** Delete one draft's snapshot (on launch or cancel). Missing file ⇒ no-op. */
|
|
36
|
+
remove(id: string): void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Draft ids are `wsd_<hex>`; this refuses anything else so a crafted id can never escape the drafts directory. */
|
|
40
|
+
function isSafeId(id: string): boolean {
|
|
41
|
+
return /^[A-Za-z0-9_-]{1,64}$/.test(id);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isDraftShape(value: unknown): value is WorkstreamDraft {
|
|
45
|
+
if (typeof value !== 'object' || value === null) return false;
|
|
46
|
+
const d = value as Record<string, unknown>;
|
|
47
|
+
return (
|
|
48
|
+
typeof d.id === 'string'
|
|
49
|
+
&& typeof d.task === 'string'
|
|
50
|
+
&& typeof d.approved === 'boolean'
|
|
51
|
+
&& typeof d.createdAt === 'number'
|
|
52
|
+
&& typeof d.spec === 'object' && d.spec !== null
|
|
53
|
+
&& Array.isArray((d.spec as Record<string, unknown>).items)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function createWorkstreamDraftStore(projectRoot: string): WorkstreamDraftStore {
|
|
58
|
+
const dir = join(projectRoot, '.goodvibes', 'orchestration', 'drafts');
|
|
59
|
+
const fileFor = (id: string): string => join(dir, `${id}.json`);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
loadAll(): WorkstreamDraft[] {
|
|
63
|
+
let entries: string[];
|
|
64
|
+
try {
|
|
65
|
+
entries = readdirSync(dir);
|
|
66
|
+
} catch {
|
|
67
|
+
return []; // directory not created yet, or unreadable — nothing to resume
|
|
68
|
+
}
|
|
69
|
+
const drafts: WorkstreamDraft[] = [];
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
if (!entry.endsWith('.json')) continue;
|
|
72
|
+
try {
|
|
73
|
+
const parsed: unknown = JSON.parse(readFileSync(join(dir, entry), 'utf8'));
|
|
74
|
+
if (isDraftShape(parsed) && isSafeId(parsed.id)) drafts.push(parsed);
|
|
75
|
+
} catch {
|
|
76
|
+
// A malformed or half-written snapshot is skipped, never fatal.
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return drafts.sort((a, b) => a.createdAt - b.createdAt);
|
|
80
|
+
},
|
|
81
|
+
save(draft: WorkstreamDraft): void {
|
|
82
|
+
if (!isSafeId(draft.id)) return;
|
|
83
|
+
try {
|
|
84
|
+
mkdirSync(dir, { recursive: true });
|
|
85
|
+
writeFileSync(fileFor(draft.id), JSON.stringify(draft), 'utf8');
|
|
86
|
+
} catch {
|
|
87
|
+
// A failed journal write leaves the in-memory draft intact and usable;
|
|
88
|
+
// only its restart-durability is lost, which is strictly better than
|
|
89
|
+
// failing the edit the user just made.
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
remove(id: string): void {
|
|
93
|
+
if (!isSafeId(id)) return;
|
|
94
|
+
try {
|
|
95
|
+
rmSync(fileFor(id), { force: true });
|
|
96
|
+
} catch {
|
|
97
|
+
// Already gone, or unremovable — nothing more to do.
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// workstream-draft-types.ts — the not-yet-launched proposal's data shape
|
|
3
|
+
//
|
|
4
|
+
// Extracted from workstream-services.ts purely to give the durable draft store
|
|
5
|
+
// (workstream-draft-store.ts) a type to persist WITHOUT importing back into the
|
|
6
|
+
// service construction module — that back-edge would form an import cycle the
|
|
7
|
+
// architecture check rejects. workstream-services.ts re-exports both types, so
|
|
8
|
+
// every existing importer (the /workstream command, the tests) keeps importing
|
|
9
|
+
// them from there unchanged.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
import type { CreateWorkstreamInput } from '@pellux/goodvibes-sdk/platform/orchestration';
|
|
13
|
+
import type { DecompositionGate, PlanProposal } from '@pellux/goodvibes-sdk/platform/core';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Honest provenance for how a draft's decomposition was produced. Derived from
|
|
17
|
+
* the SDK decomposition service's outcome so the draft render can state plainly
|
|
18
|
+
* whether a planning agent decomposed the goal, or the heuristic path did (and
|
|
19
|
+
* if so, why).
|
|
20
|
+
*/
|
|
21
|
+
export interface WorkstreamDraftProvenance {
|
|
22
|
+
readonly kind: 'agent' | 'heuristic-configured' | 'gate-declined' | 'fallback';
|
|
23
|
+
readonly itemCount: number;
|
|
24
|
+
readonly agentCostUsd?: number | undefined;
|
|
25
|
+
readonly agentTokens?: number | undefined;
|
|
26
|
+
readonly elapsedMs?: number | undefined;
|
|
27
|
+
readonly fallbackReason?: string | undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** A not-yet-launched /workstream proposal. TUI-owned (the engine has no draft concept) and journaled to disk via workstream-draft-store.ts so it survives a restart. See workstream-services.ts's header doc. */
|
|
31
|
+
export interface WorkstreamDraft {
|
|
32
|
+
readonly id: string;
|
|
33
|
+
task: string;
|
|
34
|
+
spec: CreateWorkstreamInput;
|
|
35
|
+
readonly gate: DecompositionGate;
|
|
36
|
+
/** The engine-agnostic decomposition proposal (model- or heuristic-produced). */
|
|
37
|
+
proposal: PlanProposal;
|
|
38
|
+
/** How that proposal came to be, for honest rendering. */
|
|
39
|
+
provenance: WorkstreamDraftProvenance;
|
|
40
|
+
approved: boolean;
|
|
41
|
+
readonly createdAt: number;
|
|
42
|
+
}
|
|
@@ -17,15 +17,20 @@
|
|
|
17
17
|
// (input/commands/workstream-runtime.ts) needs a create -> propose -> approve
|
|
18
18
|
// -> launch flow (Pillar-3 doctrine: render the plan in the transcript before
|
|
19
19
|
// spending anything, mirroring /plan's approve step). REALITY-WINS DIVERGENCE
|
|
20
|
-
// from the wo703 design brief: the brief recommended an
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
20
|
+
// from the wo703 design brief: the brief recommended an ENGINE-owned draft; the
|
|
21
|
+
// real engine (verified against the linked SDK build) exposes no pre-creation
|
|
22
|
+
// draft concept at all, so WorkstreamDraft below is TUI-owned state, held on
|
|
23
|
+
// this module's facade instance (constructed once, threaded onto
|
|
24
|
+
// CommandContext) — never a module-level ambient global. Durability is
|
|
25
|
+
// likewise TUI-owned: rather than leaving the brief's restart requirement
|
|
26
|
+
// unmet, the facade journals every draft to disk through
|
|
27
|
+
// workstream-draft-store.ts (a drafts/ subdirectory ALONGSIDE the engine's own
|
|
28
|
+
// workstream snapshots) and reloads them at construction, so a create / reshape
|
|
29
|
+
// / approve done before a restart is still here to launch afterward. The engine
|
|
30
|
+
// gains no draft concept; the TUI persists its OWN facade state. A journal
|
|
31
|
+
// write that fails degrades to in-memory-only for that one draft — never a
|
|
32
|
+
// crash — and the store never resurrects a launched draft (its snapshot is
|
|
33
|
+
// removed the moment the engine takes ownership).
|
|
29
34
|
// ---------------------------------------------------------------------------
|
|
30
35
|
|
|
31
36
|
import {
|
|
@@ -40,7 +45,6 @@ export type { OrchestrationEngine } from '@pellux/goodvibes-sdk/platform/orchest
|
|
|
40
45
|
import {
|
|
41
46
|
AdaptivePlanner,
|
|
42
47
|
decomposeGoal,
|
|
43
|
-
type DecompositionGate,
|
|
44
48
|
type DecompositionServiceConfig,
|
|
45
49
|
type DecomposeGoalResult,
|
|
46
50
|
type PlannerInputs,
|
|
@@ -51,6 +55,8 @@ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
|
51
55
|
import type { AgentManager } from '@pellux/goodvibes-sdk/platform/tools';
|
|
52
56
|
import type { RuntimeEventBus } from '@/runtime/index.ts';
|
|
53
57
|
import { calcSessionCost, isModelPriced } from '../export/cost-utils.ts';
|
|
58
|
+
import { editItemBrief, moveItemInSpec, removeItemFromSpec } from './workstream-draft-edits.ts';
|
|
59
|
+
import { createWorkstreamDraftStore } from './workstream-draft-store.ts';
|
|
54
60
|
|
|
55
61
|
export interface WorkstreamServicesDeps {
|
|
56
62
|
readonly agentManager: Pick<AgentManager, 'spawn' | 'getStatus' | 'cancel' | 'registerCancellationSignal' | 'releaseCancellationSignal'>;
|
|
@@ -60,34 +66,11 @@ export interface WorkstreamServicesDeps {
|
|
|
60
66
|
readonly projectRoot: string;
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
*/
|
|
69
|
-
export interface WorkstreamDraftProvenance {
|
|
70
|
-
readonly kind: 'agent' | 'heuristic-configured' | 'gate-declined' | 'fallback';
|
|
71
|
-
readonly itemCount: number;
|
|
72
|
-
readonly agentCostUsd?: number | undefined;
|
|
73
|
-
readonly agentTokens?: number | undefined;
|
|
74
|
-
readonly elapsedMs?: number | undefined;
|
|
75
|
-
readonly fallbackReason?: string | undefined;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** A not-yet-launched /workstream proposal. See this file's header doc for why it lives here rather than on the engine. */
|
|
79
|
-
export interface WorkstreamDraft {
|
|
80
|
-
readonly id: string;
|
|
81
|
-
task: string;
|
|
82
|
-
spec: CreateWorkstreamInput;
|
|
83
|
-
readonly gate: DecompositionGate;
|
|
84
|
-
/** The engine-agnostic decomposition proposal (model- or heuristic-produced). */
|
|
85
|
-
proposal: PlanProposal;
|
|
86
|
-
/** How that proposal came to be, for honest rendering. */
|
|
87
|
-
provenance: WorkstreamDraftProvenance;
|
|
88
|
-
approved: boolean;
|
|
89
|
-
readonly createdAt: number;
|
|
90
|
-
}
|
|
69
|
+
// WorkstreamDraft + WorkstreamDraftProvenance live in workstream-draft-types.ts
|
|
70
|
+
// (so the durable store can persist them without an import cycle) and are
|
|
71
|
+
// re-exported here so every existing importer keeps its import site unchanged.
|
|
72
|
+
export type { WorkstreamDraft, WorkstreamDraftProvenance } from './workstream-draft-types.ts';
|
|
73
|
+
import type { WorkstreamDraft, WorkstreamDraftProvenance } from './workstream-draft-types.ts';
|
|
91
74
|
|
|
92
75
|
/** `ctx.session.workstreamEngine`'s real shape: the live engine plus the draft-proposal bookkeeping the engine itself has no concept of. */
|
|
93
76
|
export interface WorkstreamCommandService {
|
|
@@ -98,6 +81,16 @@ export interface WorkstreamCommandService {
|
|
|
98
81
|
listDrafts(): WorkstreamDraft[];
|
|
99
82
|
/** Re-derive a held draft's spec + decomposition from a new task string. Clears any prior approval — an edit must be re-approved. `isolation` omitted ⇒ keeps the draft's current choice (an edit that only changes the task text must not silently reset isolation back to shared). */
|
|
100
83
|
editDraft(id: string, task: string, isolation?: WorkstreamIsolation): Promise<WorkstreamDraft | undefined>;
|
|
84
|
+
/**
|
|
85
|
+
* Plan-review-gate item edits over a held draft's launchable spec (see
|
|
86
|
+
* workstream-draft-edits.ts). Each returns the updated draft on success,
|
|
87
|
+
* `{ error }` with an honest user-facing reason on a bad reference/argument,
|
|
88
|
+
* or `undefined` when no draft with that id is held. Every successful edit
|
|
89
|
+
* clears approval — a reshaped plan must be re-approved before launch.
|
|
90
|
+
*/
|
|
91
|
+
editItem(id: string, itemRef: string, brief: string): WorkstreamDraft | { error: string } | undefined;
|
|
92
|
+
removeItem(id: string, itemRef: string): WorkstreamDraft | { error: string } | undefined;
|
|
93
|
+
moveItem(id: string, itemRef: string, toPosition: number): WorkstreamDraft | { error: string } | undefined;
|
|
101
94
|
approveDraft(id: string): WorkstreamDraft | undefined;
|
|
102
95
|
removeDraft(id: string): boolean;
|
|
103
96
|
/** Materialize an approved draft into a real, running Workstream (engine.createWorkstream + start), then drop the draft. Null when the draft is missing or not approved. */
|
|
@@ -187,6 +180,13 @@ function createWorkstreamCommandService(
|
|
|
187
180
|
): WorkstreamCommandService {
|
|
188
181
|
const drafts = new Map<string, WorkstreamDraft>();
|
|
189
182
|
|
|
183
|
+
// TUI-side draft journal (workstream-draft-store.ts). Load every persisted
|
|
184
|
+
// proposal at construction so a create/reshape/approve done before a restart
|
|
185
|
+
// is still here to launch afterward — the plan-review gate survives restart,
|
|
186
|
+
// exactly as the live-workstream snapshots do via resumeAllFromDisk().
|
|
187
|
+
const store = createWorkstreamDraftStore(projectRoot);
|
|
188
|
+
for (const persisted of store.loadAll()) drafts.set(persisted.id, persisted);
|
|
189
|
+
|
|
190
190
|
/**
|
|
191
191
|
* Run the SDK decomposition service: it spawns a bounded, read-only planning
|
|
192
192
|
* agent (which surfaces in the fleet like any agent — kill/steer reach it,
|
|
@@ -238,6 +238,27 @@ function createWorkstreamCommandService(
|
|
|
238
238
|
return fromChainSpec({ id: `item-${crypto.randomUUID().slice(0, 8)}`, task }, configManager);
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Apply a pure item edit (workstream-draft-edits.ts) to a held draft's spec.
|
|
243
|
+
* Threads the three outcomes straight through: `undefined` (no such draft) so
|
|
244
|
+
* the command layer can print its not-found message, `{ error }`
|
|
245
|
+
* (a bad reference/argument) verbatim, or the mutated draft. A successful edit
|
|
246
|
+
* clears approval — a reshaped plan must be re-approved before it can launch.
|
|
247
|
+
*/
|
|
248
|
+
function applyItemEdit(
|
|
249
|
+
id: string,
|
|
250
|
+
edit: (spec: CreateWorkstreamInput) => import('./workstream-draft-edits.ts').DraftEditResult,
|
|
251
|
+
): WorkstreamDraft | { error: string } | undefined {
|
|
252
|
+
const draft = drafts.get(id);
|
|
253
|
+
if (!draft) return undefined;
|
|
254
|
+
const result = edit(draft.spec);
|
|
255
|
+
if ('error' in result) return { error: result.error };
|
|
256
|
+
draft.spec = result.spec;
|
|
257
|
+
draft.approved = false;
|
|
258
|
+
store.save(draft);
|
|
259
|
+
return draft;
|
|
260
|
+
}
|
|
261
|
+
|
|
241
262
|
return {
|
|
242
263
|
engine,
|
|
243
264
|
async proposeDraft(task: string, isolation?: WorkstreamIsolation): Promise<WorkstreamDraft> {
|
|
@@ -254,6 +275,7 @@ function createWorkstreamCommandService(
|
|
|
254
275
|
createdAt: Date.now(),
|
|
255
276
|
};
|
|
256
277
|
drafts.set(draft.id, draft);
|
|
278
|
+
store.save(draft);
|
|
257
279
|
return draft;
|
|
258
280
|
},
|
|
259
281
|
getDraft: (id) => drafts.get(id),
|
|
@@ -268,21 +290,30 @@ function createWorkstreamCommandService(
|
|
|
268
290
|
draft.proposal = result.proposal;
|
|
269
291
|
draft.provenance = toProvenance(result);
|
|
270
292
|
draft.approved = false;
|
|
293
|
+
store.save(draft);
|
|
271
294
|
return draft;
|
|
272
295
|
},
|
|
296
|
+
editItem: (id, itemRef, brief) => applyItemEdit(id, (spec) => editItemBrief(spec, itemRef, brief)),
|
|
297
|
+
removeItem: (id, itemRef) => applyItemEdit(id, (spec) => removeItemFromSpec(spec, itemRef)),
|
|
298
|
+
moveItem: (id, itemRef, toPosition) => applyItemEdit(id, (spec) => moveItemInSpec(spec, itemRef, toPosition)),
|
|
273
299
|
approveDraft(id) {
|
|
274
300
|
const draft = drafts.get(id);
|
|
275
301
|
if (!draft) return undefined;
|
|
276
302
|
draft.approved = true;
|
|
303
|
+
store.save(draft); // approval must survive restart too — a resumed approved draft launches straight away
|
|
277
304
|
return draft;
|
|
278
305
|
},
|
|
279
|
-
removeDraft
|
|
306
|
+
removeDraft(id) {
|
|
307
|
+
store.remove(id);
|
|
308
|
+
return drafts.delete(id);
|
|
309
|
+
},
|
|
280
310
|
launchDraft(id) {
|
|
281
311
|
const draft = drafts.get(id);
|
|
282
312
|
if (!draft || !draft.approved) return null;
|
|
283
313
|
const workstream = engine.createWorkstream(draft.spec);
|
|
284
314
|
engine.start(workstream.id);
|
|
285
315
|
drafts.delete(id);
|
|
316
|
+
store.remove(id); // launched: the engine now owns it (its own journal), so drop the draft snapshot
|
|
286
317
|
return { workstreamId: workstream.id };
|
|
287
318
|
},
|
|
288
319
|
};
|
|
@@ -12,8 +12,25 @@ export type PendingPermissionState = PermissionRequest & {
|
|
|
12
12
|
hunkState?: HunkSelectionState;
|
|
13
13
|
/** True once the user pressed `d` to expand a condensed low-risk card. (UX-B 2b.) */
|
|
14
14
|
detailsExpanded?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Epoch ms when the prompt first appeared. Keystrokes that arrive within
|
|
17
|
+
* APPROVAL_INPUT_DEBOUNCE_MS of this are swallowed (not interpreted as an
|
|
18
|
+
* approve/deny response) so text typed ahead — before the user saw the
|
|
19
|
+
* prompt — cannot accidentally answer it.
|
|
20
|
+
*/
|
|
21
|
+
openedAt?: number;
|
|
22
|
+
/** Requester attribution ("session abc12345" / a named agent) shown on the prompt; absent when unknown. */
|
|
23
|
+
requestedBy?: string;
|
|
15
24
|
};
|
|
16
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Settle window after a permission prompt appears during which buffered
|
|
28
|
+
* keystrokes are ignored rather than interpreted as the response. 350ms is long
|
|
29
|
+
* enough to absorb type-ahead from before the prompt rendered, short enough not
|
|
30
|
+
* to feel laggy to a user reacting to the prompt itself.
|
|
31
|
+
*/
|
|
32
|
+
export const APPROVAL_INPUT_DEBOUNCE_MS = 350;
|
|
33
|
+
|
|
17
34
|
export type BlockingInputHandlerOptions = {
|
|
18
35
|
data: string;
|
|
19
36
|
pendingPermission: PendingPermissionState | null;
|
|
@@ -58,6 +75,8 @@ export type BlockingInputHandlerOptions = {
|
|
|
58
75
|
* posture is not restored.
|
|
59
76
|
*/
|
|
60
77
|
reopenPanels?: (snapshot: SessionSnapshot) => void;
|
|
78
|
+
/** Injectable clock for the approval-input debounce; defaults to Date.now(). Tests only. */
|
|
79
|
+
now?: number;
|
|
61
80
|
};
|
|
62
81
|
|
|
63
82
|
export type BlockingInputHandlerResult = {
|
|
@@ -89,6 +108,16 @@ export function handleBlockingShellInput(
|
|
|
89
108
|
if (pendingPermission) {
|
|
90
109
|
const req = pendingPermission;
|
|
91
110
|
|
|
111
|
+
// Input debounce: swallow any key that arrives within the settle window
|
|
112
|
+
// after the prompt appeared, so type-ahead text from before the user saw
|
|
113
|
+
// the prompt cannot answer it (neither approve, deny, nor navigate). The
|
|
114
|
+
// prompt stays open; re-render and consume the key.
|
|
115
|
+
const now = options.now ?? Date.now();
|
|
116
|
+
if (req.openedAt !== undefined && now - req.openedAt < APPROVAL_INPUT_DEBOUNCE_MS) {
|
|
117
|
+
render();
|
|
118
|
+
return { handled: true, pendingPermission, recoveryPending };
|
|
119
|
+
}
|
|
120
|
+
|
|
92
121
|
if (req.hunkState) {
|
|
93
122
|
const { state, commit } = applyHunkKey(req.hunkState, data);
|
|
94
123
|
if (commit === 'apply') {
|
package/src/shell/ui-openers.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type { MemoryEmbeddingProviderRegistry } from '@pellux/goodvibes-sdk/plat
|
|
|
14
14
|
import type { ServiceInspectionQuery } from '../runtime/ui-service-queries.ts';
|
|
15
15
|
import type { EmbeddingProviderPickerEntry, ModelPickerTargetInfo } from '../input/model-picker.ts';
|
|
16
16
|
import type { SelectionItem } from '../input/selection-modal.ts';
|
|
17
|
+
import { categorizeBuiltinCommands } from '../input/commands.ts';
|
|
17
18
|
import { syncServiceSettingToPlatform } from './service-settings-sync.ts';
|
|
18
19
|
import { setActiveThemeMode } from '../renderer/theme.ts';
|
|
19
20
|
import { THEME_MODE_CONFIG_KEY, coerceThemeModeSetting } from '../renderer/theme-mode-config.ts';
|
|
@@ -41,6 +42,23 @@ type WireShellUiOpenersOptions = {
|
|
|
41
42
|
render: () => void;
|
|
42
43
|
};
|
|
43
44
|
|
|
45
|
+
let commandCategoryMapCache: Map<string, string> | null = null;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Map each built-in command name to its reference category, memoized. Drives
|
|
49
|
+
* the command palette's category grouping from the same single source of truth
|
|
50
|
+
* as the generated docs (categorizeBuiltinCommands), so the palette is always
|
|
51
|
+
* registry-derived and never a hand-maintained list.
|
|
52
|
+
*/
|
|
53
|
+
function getCommandCategoryMap(): Map<string, string> {
|
|
54
|
+
if (!commandCategoryMapCache) {
|
|
55
|
+
commandCategoryMapCache = new Map(
|
|
56
|
+
categorizeBuiltinCommands().map((entry) => [entry.command.name, entry.category]),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return commandCategoryMapCache;
|
|
60
|
+
}
|
|
61
|
+
|
|
44
62
|
/**
|
|
45
63
|
* Derive the configuredVia tier for a provider.
|
|
46
64
|
* Tier order mirrors SDK provider-routes.ts: env → secrets → subscription → undefined.
|
|
@@ -457,6 +475,53 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
457
475
|
render();
|
|
458
476
|
};
|
|
459
477
|
|
|
478
|
+
commandContext.openCommandPalette = () => {
|
|
479
|
+
// The palette is built live from the command registry — never a hardcoded
|
|
480
|
+
// list — so it can never drift from the real command set. Category labels
|
|
481
|
+
// come from the same source the generated docs use (getCommandCategoryMap).
|
|
482
|
+
const registry = input.commandRegistry;
|
|
483
|
+
if (!registry) return;
|
|
484
|
+
const categoryByName = getCommandCategoryMap();
|
|
485
|
+
const items: SelectionItem[] = registry
|
|
486
|
+
.getAll()
|
|
487
|
+
.slice()
|
|
488
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
489
|
+
.map((cmd) => {
|
|
490
|
+
const argsHint = cmd.argsHint ?? cmd.usage;
|
|
491
|
+
const detailParts: string[] = [];
|
|
492
|
+
if (cmd.description) detailParts.push(cmd.description);
|
|
493
|
+
if (argsHint) detailParts.push(`(args: ${argsHint})`);
|
|
494
|
+
if ((cmd.aliases ?? []).length > 0) detailParts.push(`[aka ${(cmd.aliases ?? []).map((a) => `/${a}`).join(', ')}]`);
|
|
495
|
+
return {
|
|
496
|
+
id: cmd.name,
|
|
497
|
+
label: `/${cmd.name}`,
|
|
498
|
+
detail: detailParts.join(' '),
|
|
499
|
+
category: categoryByName.get(cmd.name) ?? 'Other',
|
|
500
|
+
primaryAction: 'select' as const,
|
|
501
|
+
};
|
|
502
|
+
});
|
|
503
|
+
commandContext.openSelection?.(
|
|
504
|
+
'Command Palette',
|
|
505
|
+
items,
|
|
506
|
+
{ allowSearch: true, primaryVerbLabel: 'Run' },
|
|
507
|
+
(result) => {
|
|
508
|
+
if (!result) return;
|
|
509
|
+
const cmd = registry.get(result.item.id);
|
|
510
|
+
if (!cmd) return;
|
|
511
|
+
// Pre-fill the composer with the command (and a trailing space so the
|
|
512
|
+
// inline args hint shows what the command wants). Keyboard-first: focus
|
|
513
|
+
// returns to the composer with command mode armed so the user fills any
|
|
514
|
+
// args and presses Enter — matching tab-completion's fill behavior.
|
|
515
|
+
input.prompt = `/${cmd.name} `;
|
|
516
|
+
input.cursorPos = input.prompt.length;
|
|
517
|
+
input.commandMode = true;
|
|
518
|
+
input.autocomplete?.reset();
|
|
519
|
+
commandContext.focusPrompt?.();
|
|
520
|
+
render();
|
|
521
|
+
},
|
|
522
|
+
);
|
|
523
|
+
};
|
|
524
|
+
|
|
460
525
|
commandContext.focusPanels = () => {
|
|
461
526
|
panelManager.focusPanels();
|
|
462
527
|
render();
|
|
@@ -27,6 +27,60 @@ export interface VerificationLedger {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Baseline count of `Settings schema and persistence` items with real local
|
|
32
|
+
* behavior verification, as measured when this ledger was first authored. New
|
|
33
|
+
* schema keys added since then are accounted for explicitly (see
|
|
34
|
+
* FEATURE_KNOB_LOCAL_SETTINGS) rather than folded into this number, so the
|
|
35
|
+
* baseline stays an auditable fixed point.
|
|
36
|
+
*/
|
|
37
|
+
const SETTINGS_BEHAVIOR_BASELINE = 184;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Config keys promoted into CONFIG_SCHEMA for the flag-gated feature knobs
|
|
41
|
+
* (SDK commit f5c4af31, "config: add schema keys for flag-gated feature
|
|
42
|
+
* knobs"). Adding these 28 keys raised the settings inventory `total` without a
|
|
43
|
+
* matching rise in behavior-verified coverage, which dropped
|
|
44
|
+
* `localBehaviorPercent` below its floor. Each key is a local, non-external
|
|
45
|
+
* setting whose full persistence behavior — schema default, set → write →
|
|
46
|
+
* reload round-trip, and reset-to-default — is exercised by
|
|
47
|
+
* `src/test/verification/feature-knob-settings-persistence.test.ts`. They are
|
|
48
|
+
* therefore genuinely behavior-verified locally and counted as such here.
|
|
49
|
+
*/
|
|
50
|
+
export const FEATURE_KNOB_LOCAL_SETTINGS = [
|
|
51
|
+
'provider.optimizerMode',
|
|
52
|
+
'provider.optimizerPinnedModel',
|
|
53
|
+
'permissions.divergenceThreshold',
|
|
54
|
+
'permissions.maxDivergenceRecords',
|
|
55
|
+
'tools.overflowSpillBackend',
|
|
56
|
+
'notifications.burstWindowMs',
|
|
57
|
+
'notifications.burstThreshold',
|
|
58
|
+
'notifications.burstCooldownMs',
|
|
59
|
+
'fetch.sanitizeMode',
|
|
60
|
+
'fetch.trustedHosts',
|
|
61
|
+
'fetch.blockedHosts',
|
|
62
|
+
'security.tokenAudit.rotationCadenceDays',
|
|
63
|
+
'security.tokenAudit.rotationWarningDays',
|
|
64
|
+
'security.tokenAudit.managed',
|
|
65
|
+
'integrations.delivery.maxRetries',
|
|
66
|
+
'integrations.delivery.initialDelayMs',
|
|
67
|
+
'integrations.delivery.maxDelayMs',
|
|
68
|
+
'integrations.delivery.maxDlqSize',
|
|
69
|
+
'integrations.delivery.sloEnforced',
|
|
70
|
+
'policy.bundleSource',
|
|
71
|
+
'policy.bundlePath',
|
|
72
|
+
'agents.passiveInjection.budgetTokens',
|
|
73
|
+
'agents.passiveInjection.relevanceFloor',
|
|
74
|
+
'agents.passiveInjection.codeLimit',
|
|
75
|
+
'agents.contextCompactThreshold',
|
|
76
|
+
'runtime.toolBudget.maxMs',
|
|
77
|
+
'runtime.toolBudget.maxTokens',
|
|
78
|
+
'runtime.toolBudget.maxCostUsd',
|
|
79
|
+
] as const;
|
|
80
|
+
|
|
81
|
+
/** Settings with real local behavior verification: the authored baseline plus the newly persistence-tested feature-knob keys. */
|
|
82
|
+
const SETTINGS_BEHAVIOR_VERIFIED = SETTINGS_BEHAVIOR_BASELINE + FEATURE_KNOB_LOCAL_SETTINGS.length;
|
|
83
|
+
|
|
30
84
|
const EXTERNAL_SLASH_COMMANDS = new Set([
|
|
31
85
|
'auth',
|
|
32
86
|
'bridge',
|
|
@@ -137,8 +191,8 @@ export function buildVerificationLedger(root: string): VerificationLedger {
|
|
|
137
191
|
area: 'Settings schema and persistence',
|
|
138
192
|
total: settings,
|
|
139
193
|
localSignalVerified: settings,
|
|
140
|
-
localBehaviorVerified:
|
|
141
|
-
externalOutcomeRequired: settings -
|
|
194
|
+
localBehaviorVerified: SETTINGS_BEHAVIOR_VERIFIED,
|
|
195
|
+
externalOutcomeRequired: settings - SETTINGS_BEHAVIOR_VERIFIED,
|
|
142
196
|
notes: 'Every schema setting can be validated for schema/default/load/write/location; external side effects remain separate.',
|
|
143
197
|
},
|
|
144
198
|
{
|
package/src/version.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
// The prebuild script updates the fallback value before compilation.
|
|
7
7
|
// Uses import.meta.dir (Bun) to locate package.json relative to this file,
|
|
8
8
|
// which is correct regardless of the process working directory.
|
|
9
|
-
let _version = '1.
|
|
9
|
+
let _version = '1.14.0';
|
|
10
10
|
try {
|
|
11
11
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
|
|
12
12
|
_version = pkg.version ?? _version;
|