@pellux/goodvibes-agent 1.2.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/dist/package/{ast-grep-napi.linux-x64-gnu-swtppvy9.node → ast-grep-napi.linux-x64-gnu-mkk8xwww.node} +0 -0
  3. package/dist/package/{ast-grep-napi.linux-x64-musl-ttfcdtap.node → ast-grep-napi.linux-x64-musl-ryqtgdv6.node} +0 -0
  4. package/dist/package/main.js +65740 -57156
  5. package/docs/README.md +3 -3
  6. package/docs/connected-host.md +2 -2
  7. package/docs/getting-started.md +11 -9
  8. package/docs/tools-and-commands.md +12 -10
  9. package/docs/voice-and-live-tts.md +2 -2
  10. package/package.json +1 -1
  11. package/release/release-notes.md +15 -144
  12. package/release/release-readiness.json +36 -36
  13. package/src/agent/competitive-feature-inventory.ts +42 -44
  14. package/src/agent/setup-wizard-artifact-receipts.ts +366 -0
  15. package/src/agent/setup-wizard.ts +267 -24
  16. package/src/cli/tui-startup.ts +2 -2
  17. package/src/input/agent-workspace-activation.ts +2 -2
  18. package/src/input/agent-workspace-categories.ts +2 -2
  19. package/src/input/agent-workspace-onboarding-categories.ts +4 -4
  20. package/src/input/agent-workspace-onboarding-finish.ts +21 -0
  21. package/src/input/agent-workspace-settings.ts +47 -1
  22. package/src/input/agent-workspace-setup-snapshot.ts +17 -6
  23. package/src/input/agent-workspace-setup.ts +20 -26
  24. package/src/input/agent-workspace-snapshot.ts +16 -3
  25. package/src/input/agent-workspace-types.ts +5 -2
  26. package/src/input/agent-workspace.ts +21 -6
  27. package/src/input/handler.ts +67 -5
  28. package/src/input/setup-wizard-live-receipts.ts +76 -0
  29. package/src/renderer/agent-workspace-context-lines.ts +17 -33
  30. package/src/renderer/agent-workspace.ts +434 -21
  31. package/src/runtime/tool-permission-safety.ts +1 -1
  32. package/src/tools/agent-harness-agent-orchestration-policy.ts +75 -0
  33. package/src/tools/agent-harness-agent-orchestration.ts +216 -128
  34. package/src/tools/agent-harness-autonomy-live-records.ts +154 -0
  35. package/src/tools/agent-harness-autonomy-queue-types.ts +1 -1
  36. package/src/tools/agent-harness-autonomy-queue.ts +19 -8
  37. package/src/tools/agent-harness-autonomy-watcher-read-models.ts +509 -0
  38. package/src/tools/agent-harness-background-processes.ts +8 -4
  39. package/src/tools/agent-harness-browser-cockpit-route.ts +188 -34
  40. package/src/tools/agent-harness-browser-control.ts +12 -4
  41. package/src/tools/agent-harness-browser-pwa-read-models.ts +622 -0
  42. package/src/tools/agent-harness-device-live-read-models.ts +366 -0
  43. package/src/tools/agent-harness-execution-posture.ts +3 -0
  44. package/src/tools/agent-harness-interactive-runtime-records.ts +421 -0
  45. package/src/tools/agent-harness-local-model-benchmarks.ts +71 -1
  46. package/src/tools/agent-harness-local-model-endpoints.ts +469 -354
  47. package/src/tools/agent-harness-local-model-smoke.ts +277 -0
  48. package/src/tools/agent-harness-local-model-url.ts +78 -0
  49. package/src/tools/agent-harness-media-posture.ts +27 -12
  50. package/src/tools/agent-harness-memory-external-providers.ts +796 -0
  51. package/src/tools/agent-harness-memory-posture.ts +253 -137
  52. package/src/tools/agent-harness-memory-provider-certification.ts +219 -0
  53. package/src/tools/agent-harness-memory-refinement.ts +340 -0
  54. package/src/tools/agent-harness-mode-catalog.ts +4 -2
  55. package/src/tools/agent-harness-model-provider-health.ts +139 -42
  56. package/src/tools/agent-harness-model-readiness.ts +31 -5
  57. package/src/tools/agent-harness-model-routing-types.ts +61 -0
  58. package/src/tools/agent-harness-model-routing.ts +31 -6
  59. package/src/tools/agent-harness-pairing-posture.ts +30 -9
  60. package/src/tools/agent-harness-personal-ops-certification.ts +116 -0
  61. package/src/tools/agent-harness-personal-ops-lanes.ts +81 -15
  62. package/src/tools/agent-harness-personal-ops-operations.ts +225 -0
  63. package/src/tools/agent-harness-personal-ops-provider-records.ts +358 -0
  64. package/src/tools/agent-harness-personal-ops-provider-task-records.ts +321 -0
  65. package/src/tools/agent-harness-personal-ops-records.ts +176 -224
  66. package/src/tools/agent-harness-personal-ops-types.ts +19 -1
  67. package/src/tools/agent-harness-personal-ops.ts +18 -11
  68. package/src/tools/agent-harness-remote-read-models.ts +541 -0
  69. package/src/tools/agent-harness-research-briefing.ts +26 -7
  70. package/src/tools/agent-harness-research-live-read-models.ts +500 -0
  71. package/src/tools/agent-harness-research-runs.ts +15 -3
  72. package/src/tools/agent-harness-research-workflow.ts +92 -19
  73. package/src/tools/agent-harness-setup-model-helpers.ts +1 -0
  74. package/src/tools/agent-harness-setup-posture.ts +7 -7
  75. package/src/tools/agent-harness-setup-smoke.ts +26 -1
  76. package/src/tools/agent-harness-tool-schema.ts +23 -1
  77. package/src/tools/agent-harness-tool-types.ts +5 -0
  78. package/src/tools/agent-harness-tool.ts +9 -1
  79. package/src/tools/agent-harness-workspace-actions.ts +1 -1
  80. package/src/tools/agent-memory-tool.ts +40 -1
  81. package/src/tools/agent-model-compare-run.ts +13 -0
  82. package/src/tools/agent-research-runner.ts +367 -0
  83. package/src/tools/agent-research-tool.ts +7 -179
  84. package/src/tools/agent-route-planner-candidates-surfaces.ts +1 -1
  85. package/src/version.ts +1 -1
@@ -6,68 +6,133 @@ import { readBoolean, readFiniteNumber, readRecord, readString, safeIso } from '
6
6
  function readPotentialProviderHealthSnapshot(source: unknown): unknown {
7
7
  if (!source || typeof source !== 'object') return null;
8
8
  const record = source as Record<string, unknown>;
9
- const directSnapshot = typeof record.getSnapshot === 'function'
10
- ? (record.getSnapshot as () => unknown)()
11
- : null;
9
+ const directSnapshot = ['getSnapshot', 'snapshot', 'list', 'listProviders', 'listRoutes', 'listHealth']
10
+ .map((method) => {
11
+ const fn = record[method];
12
+ if (typeof fn !== 'function') return null;
13
+ const result = (fn as () => unknown).call(record);
14
+ return result instanceof Promise ? null : result;
15
+ })
16
+ .find((result) => result !== null);
12
17
  return directSnapshot ?? record.snapshot ?? record.data ?? record.state ?? source;
13
18
  }
14
19
 
20
+ const PROVIDER_HEALTH_SECRET_PATTERNS: readonly [RegExp, string][] = [
21
+ [/("?\b(?:api[-_]?key|apikey|token|secret|password|passwd|credential|authorization)\b"?\s*:\s*)("[^"]*"|'[^']*'|[^\s,}]+)/gi, '$1"<redacted>"'],
22
+ [/\b([A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTHORIZATION|BEARER)[A-Z0-9_]*)=("[^"]*"|'[^']*'|[^\s]+)/gi, '$1=<redacted>'],
23
+ [/(\b(?:token|secret|password|passwd|api[-_]?key|apikey|authorization|credential)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,}]+)/gi, '$1<redacted>'],
24
+ [/(Authorization:\s*Bearer\s+)[A-Za-z0-9._~+/=-]+/gi, '$1<redacted>'],
25
+ ];
26
+
27
+ function redactProviderHealthText(value: string): string {
28
+ return PROVIDER_HEALTH_SECRET_PATTERNS.reduce((text, [pattern, replacement]) => text.replace(pattern, replacement), value);
29
+ }
30
+
31
+ function entryWithKey(key: unknown, entry: unknown): Record<string, unknown> {
32
+ const record = readRecord(entry);
33
+ const textKey = readString(key);
34
+ if (!textKey) return record;
35
+ const looksLikeRoute = textKey.includes(':') || textKey.includes('/');
36
+ return {
37
+ ...record,
38
+ ...(looksLikeRoute ? { modelRouteId: readString(record.modelRouteId) || readString(record.routeId) || textKey } : {}),
39
+ providerId: readString(record.providerId) || readString(record.provider) || (looksLikeRoute ? readString(record.providerId) : textKey),
40
+ };
41
+ }
42
+
43
+ function looksLikeProviderHealthRecord(record: Record<string, unknown>): boolean {
44
+ return Boolean(
45
+ readString(record.providerId)
46
+ || readString(record.provider)
47
+ || readString(record.modelRouteId)
48
+ || readString(record.routeId)
49
+ || readString(record.registryKey)
50
+ || readString(record.status)
51
+ || readFiniteNumber(record.avgLatencyMs) !== undefined
52
+ || Object.keys(readRecord(record.stats)).length > 0,
53
+ );
54
+ }
55
+
15
56
  function providerHealthCandidatesFromSnapshot(snapshot: unknown): readonly Record<string, unknown>[] {
16
57
  if (!snapshot || typeof snapshot !== 'object') return [];
17
58
  if (snapshot instanceof Map) {
18
- return [...snapshot.entries()].map(([key, entry]) => {
19
- const record = readRecord(entry);
20
- return { ...record, providerId: readString(record.providerId) || readString(key) };
21
- });
59
+ return [...snapshot.entries()].map(([key, entry]) => entryWithKey(key, entry));
22
60
  }
23
61
  const record = readRecord(snapshot);
24
- const providers = record.providers;
25
- const entries = record.entries;
26
- if (providers instanceof Map) {
27
- return [...providers.entries()].map(([key, entry]) => {
28
- const providerRecord = readRecord(entry);
29
- return { ...providerRecord, providerId: readString(providerRecord.providerId) || readString(key) };
30
- });
31
- }
32
- if (Array.isArray(providers)) {
33
- return providers.map((entry) => readRecord(entry));
34
- }
35
- if (providers && typeof providers === 'object') {
36
- return Object.entries(providers as Record<string, unknown>).map(([key, entry]) => {
37
- const providerRecord = readRecord(entry);
38
- return { ...providerRecord, providerId: readString(providerRecord.providerId) || key };
39
- });
40
- }
41
- if (entries instanceof Map) {
42
- return [...entries.entries()].map(([key, entry]) => {
43
- const entryRecord = readRecord(entry);
44
- return { ...entryRecord, providerId: readString(entryRecord.providerId) || readString(key) };
45
- });
62
+ for (const key of ['providers', 'providerHealth', 'routes', 'modelRoutes', 'routeHealth', 'providerRoutes', 'records', 'items', 'entries', 'health']) {
63
+ const value = record[key];
64
+ if (value instanceof Map) return [...value.entries()].map(([entryKey, entry]) => entryWithKey(entryKey, entry));
65
+ if (Array.isArray(value)) return value.map((entry) => readRecord(entry));
66
+ const valueRecord = readRecord(value);
67
+ if (Object.keys(valueRecord).length > 0) return Object.entries(valueRecord).map(([entryKey, entry]) => entryWithKey(entryKey, entry));
46
68
  }
47
- if (Array.isArray(entries)) {
48
- return entries.map((entry) => readRecord(entry));
49
- }
50
- return [];
69
+ return looksLikeProviderHealthRecord(record) ? [record] : [];
51
70
  }
52
71
 
53
- function providerHealthCandidateId(candidate: Record<string, unknown>): string {
72
+ function providerHealthCandidateProviderId(candidate: Record<string, unknown>): string {
54
73
  return readString(candidate.providerId)
55
- || readString(candidate.id)
56
74
  || readString(candidate.provider)
57
- || readString(candidate.name);
75
+ || readString(candidate.providerName)
76
+ || (readString(candidate.id).includes(':') ? '' : readString(candidate.id));
77
+ }
78
+
79
+ function providerHealthCandidateRouteId(candidate: Record<string, unknown>): string {
80
+ return readString(candidate.modelRouteId)
81
+ || readString(candidate.registryKey)
82
+ || readString(candidate.routeId)
83
+ || readString(candidate.modelRoute)
84
+ || (readString(candidate.id).includes(':') ? readString(candidate.id) : '');
85
+ }
86
+
87
+ function providerHealthCandidateMatchesProvider(candidate: Record<string, unknown>, providerId: string): boolean {
88
+ return providerHealthCandidateProviderId(candidate) === providerId;
58
89
  }
59
90
 
60
91
  function providerHealthRecordStats(candidate: Record<string, unknown>): Record<string, unknown> {
61
92
  return readRecord(candidate.stats);
62
93
  }
63
94
 
95
+ function providerHealthRecordRateLimit(candidate: Record<string, unknown>): Record<string, unknown> {
96
+ return readRecord(candidate.rateLimit);
97
+ }
98
+
99
+ function providerHealthRecordErrors(candidate: Record<string, unknown>): Record<string, unknown> {
100
+ return readRecord(candidate.errors);
101
+ }
102
+
103
+ function isoFromUnknown(value: unknown): string | null {
104
+ const numeric = readFiniteNumber(value);
105
+ if (numeric !== undefined) return safeIso(numeric);
106
+ const text = readString(value);
107
+ if (!text) return null;
108
+ const parsed = Date.parse(text);
109
+ return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
110
+ }
111
+
112
+ function firstTimestamp(values: readonly unknown[]): string | null {
113
+ for (const value of values) {
114
+ const timestamp = isoFromUnknown(value);
115
+ if (timestamp) return timestamp;
116
+ }
117
+ return null;
118
+ }
119
+
64
120
  function providerHealthTimestamp(candidate: Record<string, unknown>, stats: Record<string, unknown>, key: string): string | null {
65
- return safeIso(readFiniteNumber(candidate[key]) ?? readFiniteNumber(stats[key]));
121
+ return firstTimestamp([candidate[key], stats[key]]);
122
+ }
123
+
124
+ function nestedNumber(candidate: Record<string, unknown>, stats: Record<string, unknown>, nested: Record<string, unknown>, keys: readonly string[]): number | undefined {
125
+ for (const key of keys) {
126
+ const value = readFiniteNumber(candidate[key]) ?? readFiniteNumber(stats[key]) ?? readFiniteNumber(nested[key]);
127
+ if (value !== undefined) return value;
128
+ }
129
+ return undefined;
66
130
  }
67
131
 
68
- export function readProviderHealthSignal(context: CommandContext, providerId: string): ModelProviderHealthSignal {
132
+ export function readProviderHealthSignal(context: CommandContext, providerId: string, modelRouteId?: string): ModelProviderHealthSignal {
69
133
  const base: Omit<ModelProviderHealthSignal, 'status' | 'daemonPublication' | 'agentConsumption' | 'missingSignals' | 'policy'> = {
70
134
  providerId,
135
+ ...(modelRouteId ? { modelRouteId } : {}),
71
136
  sdkContract: {
72
137
  providerHealthTypes: 'available',
73
138
  importSurface: '@pellux/goodvibes-sdk/platform/runtime/ui',
@@ -77,10 +142,17 @@ export function readProviderHealthSignal(context: CommandContext, providerId: st
77
142
  const policy = 'Provider health is live route-condition evidence. Local benchmark artifacts remain separate task-fit evidence and must not be treated as provider-health publication.';
78
143
  const platform = context.platform as unknown as Record<string, unknown>;
79
144
  const readModels = readRecord(platform.readModels);
145
+ const clients = readRecord(context.clients as unknown as Record<string, unknown>);
146
+ const operator = readRecord(clients.operator);
80
147
  const sourceEntries: readonly { readonly path: string; readonly source: unknown }[] = [
81
148
  { path: 'context.platform.readModels.providerHealth', source: readModels.providerHealth },
82
149
  { path: 'context.platform.readModels.providersHealth', source: readModels.providersHealth },
150
+ { path: 'context.platform.readModels.modelRouteHealth', source: readModels.modelRouteHealth },
151
+ { path: 'context.platform.readModels.routeHealth', source: readModels.routeHealth },
152
+ { path: 'context.platform.readModels.models.providerHealth', source: readRecord(readModels.models).providerHealth },
153
+ { path: 'context.platform.readModels.models.routeHealth', source: readRecord(readModels.models).routeHealth },
83
154
  { path: 'context.platform.providerHealth', source: platform.providerHealth },
155
+ { path: 'context.clients.operator.providerHealth', source: operator.providerHealth },
84
156
  ];
85
157
 
86
158
  for (const entry of sourceEntries) {
@@ -88,7 +160,10 @@ export function readProviderHealthSignal(context: CommandContext, providerId: st
88
160
  try {
89
161
  const snapshot = readPotentialProviderHealthSnapshot(entry.source);
90
162
  const candidates = providerHealthCandidatesFromSnapshot(snapshot);
91
- const candidate = candidates.find((item) => providerHealthCandidateId(item) === providerId);
163
+ const candidate = (modelRouteId
164
+ ? candidates.find((item) => providerHealthCandidateRouteId(item) === modelRouteId)
165
+ : undefined)
166
+ ?? candidates.find((item) => providerHealthCandidateMatchesProvider(item, providerId));
92
167
  if (!candidate) {
93
168
  return {
94
169
  ...base,
@@ -109,10 +184,24 @@ export function readProviderHealthSignal(context: CommandContext, providerId: st
109
184
  }
110
185
 
111
186
  const stats = providerHealthRecordStats(candidate);
187
+ const rateLimit = providerHealthRecordRateLimit(candidate);
188
+ const errors = providerHealthRecordErrors(candidate);
112
189
  const avgLatencyMs = readFiniteNumber(candidate.avgLatencyMs) ?? readFiniteNumber(stats.avgLatencyMs);
113
190
  const minLatencyMs = readFiniteNumber(candidate.minLatencyMs) ?? readFiniteNumber(stats.minLatencyMs);
114
191
  const maxLatencyMs = readFiniteNumber(candidate.maxLatencyMs) ?? readFiniteNumber(candidate.p95LatencyMs) ?? readFiniteNumber(stats.maxLatencyMs);
115
192
  const healthStatus = readString(candidate.status) || 'unknown';
193
+ const rateLimitRemaining = nestedNumber(candidate, stats, rateLimit, ['rateLimitRemaining', 'remaining', 'requestsRemaining']);
194
+ const rateLimitLimit = nestedNumber(candidate, stats, rateLimit, ['rateLimitLimit', 'limit', 'requestsLimit']);
195
+ const rateLimitResetAt = firstTimestamp([candidate.rateLimitResetAt, stats.rateLimitResetAt, rateLimit.resetAt, rateLimit.resetsAt]);
196
+ const lastErrorAt = firstTimestamp([candidate.lastErrorAt, stats.lastErrorAt, errors.lastErrorAt]);
197
+ const lastErrorMessage = readString(candidate.lastErrorMessage)
198
+ || readString(stats.lastErrorMessage)
199
+ || readString(errors.lastErrorMessage)
200
+ || readString(errors.message);
201
+ const errorRate = nestedNumber(candidate, stats, errors, ['errorRate', 'recentErrorRate']);
202
+ const consecutiveErrors = nestedNumber(candidate, stats, errors, ['consecutiveErrors', 'failureStreak']);
203
+ const hasRateLimitPosture = rateLimitRemaining !== undefined || rateLimitLimit !== undefined || rateLimitResetAt !== null;
204
+ const hasErrorPosture = lastErrorAt !== null || lastErrorMessage || errorRate !== undefined || consecutiveErrors !== undefined;
116
205
  return {
117
206
  ...base,
118
207
  status: 'record-found',
@@ -126,6 +215,8 @@ export function readProviderHealthSignal(context: CommandContext, providerId: st
126
215
  readModelPath: entry.path,
127
216
  evidence: 'Agent consumed the published provider-health record for exact model-route readiness.',
128
217
  },
218
+ sourceRecordId: readString(candidate.recordId) || readString(candidate.id) || providerHealthCandidateRouteId(candidate) || providerHealthCandidateProviderId(candidate),
219
+ ...(providerHealthCandidateRouteId(candidate) ? { modelRouteId: providerHealthCandidateRouteId(candidate) } : {}),
129
220
  healthStatus,
130
221
  isConfigured: readBoolean(candidate.isConfigured),
131
222
  isActive: readBoolean(candidate.isActive),
@@ -133,14 +224,20 @@ export function readProviderHealthSignal(context: CommandContext, providerId: st
133
224
  minLatencyMs,
134
225
  maxLatencyMs,
135
226
  lastSuccessAt: providerHealthTimestamp(candidate, stats, 'lastSuccessAt'),
136
- lastErrorAt: providerHealthTimestamp(candidate, stats, 'lastErrorAt'),
137
- lastErrorMessage: readString(candidate.lastErrorMessage) || readString(stats.lastErrorMessage) || undefined,
227
+ lastErrorAt,
228
+ lastErrorMessage: lastErrorMessage ? previewHarnessText(redactProviderHealthText(lastErrorMessage), 160) : undefined,
229
+ errorRate,
230
+ consecutiveErrors,
138
231
  lastCheckedAt: providerHealthTimestamp(candidate, stats, 'lastCheckedAt'),
139
- rateLimitResetAt: providerHealthTimestamp(candidate, stats, 'rateLimitResetAt'),
232
+ rateLimitResetAt,
233
+ rateLimitRemaining,
234
+ rateLimitLimit,
140
235
  missingSignals: [
141
236
  ...(avgLatencyMs === undefined ? ['Provider-health record did not include average latency.'] : []),
142
237
  ...(minLatencyMs === undefined ? ['Provider-health record did not include minimum latency.'] : []),
143
238
  ...(maxLatencyMs === undefined ? ['Provider-health record did not include maximum or p95 latency.'] : []),
239
+ ...(hasRateLimitPosture ? [] : ['Provider-health record did not include rate-limit posture.']),
240
+ ...(hasErrorPosture ? [] : ['Provider-health record did not include error posture.']),
144
241
  ],
145
242
  policy,
146
243
  };
@@ -3,7 +3,7 @@ import type { CommandContext } from '../input/command-registry.ts';
3
3
  import { previewHarnessText } from './agent-harness-text.ts';
4
4
  import { readProviderHealthSignal } from './agent-harness-model-provider-health.ts';
5
5
  import { localStackFor } from './agent-harness-local-model-endpoints.ts';
6
- import type { LocalModelBenchmarkEvidence, LocalModelDetection, LocalModelHardwareProfile, LocalModelRecipe, LocalModelRecipeFit, ModelCandidate, ModelReadinessDimension, ModelReadinessScore, ModelRouteReadinessScore, ModelProviderHealthSignal } from './agent-harness-model-routing-types.ts';
6
+ import type { LocalModelBenchmarkEvidence, LocalModelBenchmarkRouteLatency, LocalModelDetection, LocalModelHardwareProfile, LocalModelRecipe, LocalModelRecipeFit, ModelCandidate, ModelReadinessDimension, ModelReadinessScore, ModelRouteReadinessScore, ModelProviderHealthSignal } from './agent-harness-model-routing-types.ts';
7
7
  import { readRecord } from './agent-harness-model-routing-utils.ts';
8
8
 
9
9
  export function localRecipeStackId(recipe: LocalModelRecipe): string {
@@ -169,6 +169,7 @@ export function latencyScore(
169
169
  local: boolean,
170
170
  benchmarkCompositeScore: number | null | undefined,
171
171
  providerHealth: ModelProviderHealthSignal,
172
+ localBenchmarkLatency: LocalModelBenchmarkRouteLatency | null | undefined,
172
173
  ): ModelReadinessDimension {
173
174
  const liveLatency = providerHealth.status === 'record-found' ? providerHealth.avgLatencyMs : undefined;
174
175
  if (liveLatency !== undefined) {
@@ -196,6 +197,24 @@ export function latencyScore(
196
197
  };
197
198
  }
198
199
 
200
+ if (localBenchmarkLatency && localBenchmarkLatency.status !== 'failed') {
201
+ const latency = localBenchmarkLatency.latencyMs;
202
+ const score = latency <= 750
203
+ ? 93
204
+ : latency <= 1500
205
+ ? 84
206
+ : latency <= 3000
207
+ ? 70
208
+ : 54;
209
+ return {
210
+ id: 'latency',
211
+ label: 'Latency',
212
+ score,
213
+ weight: 20,
214
+ summary: `Measured local benchmark latency is ${latency} ms from ${localBenchmarkLatency.artifactId}${localBenchmarkLatency.comparisonId ? ` (${localBenchmarkLatency.comparisonId})` : ''}.`,
215
+ };
216
+ }
217
+
199
218
  const score = local
200
219
  ? 55
201
220
  : benchmarkCompositeScore != null
@@ -222,9 +241,9 @@ export function weightedReadiness(dimensions: readonly ModelReadinessDimension[]
222
241
 
223
242
  export function modelReadinessScore(context: CommandContext, model: ModelCandidate): ModelRouteReadinessScore {
224
243
  const local = isLocalCandidate([model.providerId, model.registryKey, model.modelId, model.displayName]);
225
- const providerHealth = readProviderHealthSignal(context, model.providerId);
244
+ const providerHealth = readProviderHealthSignal(context, model.providerId, model.registryKey);
226
245
  const dimensions: readonly ModelReadinessDimension[] = [
227
- latencyScore(local, model.benchmarkCompositeScore, providerHealth),
246
+ latencyScore(local, model.benchmarkCompositeScore, providerHealth, model.localBenchmarkLatency),
228
247
  contextWindowScore(model.contextWindow),
229
248
  toolSupportScore(model.capabilities),
230
249
  visionScore(model.capabilities),
@@ -233,9 +252,10 @@ export function modelReadinessScore(context: CommandContext, model: ModelCandida
233
252
  ];
234
253
  const score = weightedReadiness(dimensions);
235
254
  const hasLiveLatency = providerHealth.status === 'record-found' && providerHealth.avgLatencyMs !== undefined;
255
+ const hasBenchmarkLatency = Boolean(model.localBenchmarkLatency && model.localBenchmarkLatency.status !== 'failed');
236
256
  const missingSignals = [
237
257
  ...providerHealth.missingSignals,
238
- ...(hasLiveLatency ? [] : ['No live latency benchmark has been recorded for this Agent route.']),
258
+ ...(hasLiveLatency || hasBenchmarkLatency ? [] : ['No live latency benchmark has been recorded for this Agent route.']),
239
259
  ...(model.contextWindow == null ? ['Context-window metadata is missing.'] : []),
240
260
  ...(capabilityEnabled(model.capabilities, 'toolCalling') == null ? ['Tool-calling support is unknown.'] : []),
241
261
  ...(capabilityEnabled(model.capabilities, 'multimodal') == null ? ['Vision support is unknown.'] : []),
@@ -246,6 +266,8 @@ export function modelReadinessScore(context: CommandContext, model: ModelCandida
246
266
  level: modelReadinessLevel(score),
247
267
  confidence: providerHealth.status === 'record-found' && missingSignals.length === 0
248
268
  ? 'provider-health-backed'
269
+ : hasBenchmarkLatency && missingSignals.length === 0
270
+ ? 'measured'
249
271
  : missingSignals.length === 0
250
272
  ? 'metadata-backed'
251
273
  : 'estimated',
@@ -253,9 +275,13 @@ export function modelReadinessScore(context: CommandContext, model: ModelCandida
253
275
  missingSignals,
254
276
  providerHealth,
255
277
  nextStep: local
256
- ? 'Run the local benchmark prompt before making this route the default.'
278
+ ? hasBenchmarkLatency
279
+ ? `Review local benchmark latency evidence ${model.localBenchmarkLatency?.artifactId}, then use a separate confirmed apply/update route only if the user wants this route as default.`
280
+ : 'Run the local benchmark prompt before making this route the default.'
257
281
  : providerHealth.status === 'record-found'
258
282
  ? 'Use provider-health-backed route posture for triage; run a task-specific comparison before changing the default model.'
283
+ : hasBenchmarkLatency
284
+ ? `Review benchmark latency evidence ${model.localBenchmarkLatency?.artifactId}; wait for daemon provider-health publication for live status, rate-limit, and error posture before changing defaults.`
259
285
  : 'Use this score for routing triage; wait for daemon provider-health publication or run a task-specific comparison before changing the default model.',
260
286
  };
261
287
  }
@@ -31,6 +31,7 @@ export interface ModelCandidate {
31
31
  readonly tier?: string;
32
32
  readonly benchmarkCompositeScore?: number | null;
33
33
  readonly benchmarkQualityTier?: string;
34
+ readonly localBenchmarkLatency?: LocalModelBenchmarkRouteLatency | null;
34
35
  readonly pinned: boolean;
35
36
  }
36
37
 
@@ -92,6 +93,8 @@ export interface ModelReadinessDimension {
92
93
  export interface ModelProviderHealthSignal {
93
94
  readonly status: 'not-reachable-in-command-context' | 'read-model-empty' | 'read-model-error' | 'record-found';
94
95
  readonly providerId: string;
96
+ readonly modelRouteId?: string;
97
+ readonly sourceRecordId?: string;
95
98
  readonly sdkContract: {
96
99
  readonly providerHealthTypes: 'available';
97
100
  readonly importSurface: string;
@@ -116,8 +119,12 @@ export interface ModelProviderHealthSignal {
116
119
  readonly lastSuccessAt?: string | null;
117
120
  readonly lastErrorAt?: string | null;
118
121
  readonly lastErrorMessage?: string;
122
+ readonly errorRate?: number;
123
+ readonly consecutiveErrors?: number;
119
124
  readonly lastCheckedAt?: string | null;
120
125
  readonly rateLimitResetAt?: string | null;
126
+ readonly rateLimitRemaining?: number;
127
+ readonly rateLimitLimit?: number;
121
128
  readonly missingSignals: readonly string[];
122
129
  readonly policy: string;
123
130
  }
@@ -157,6 +164,20 @@ export interface LocalModelBenchmarkWinner {
157
164
  readonly applyRoute: string;
158
165
  }
159
166
 
167
+ export interface LocalModelBenchmarkRouteLatency {
168
+ readonly registryKey: string;
169
+ readonly providerId: string;
170
+ readonly modelId: string;
171
+ readonly displayName: string;
172
+ readonly blindId: string;
173
+ readonly latencyMs: number;
174
+ readonly status: string;
175
+ readonly artifactId: string;
176
+ readonly comparisonId: string | null;
177
+ readonly createdAt: string | null;
178
+ readonly reviewRoute: string;
179
+ }
180
+
160
181
  export interface LocalModelBenchmarkEvidence {
161
182
  readonly status: 'unavailable' | 'unmeasured' | 'comparison-saved' | 'reviewed-winner';
162
183
  readonly comparisonCount: number;
@@ -165,6 +186,7 @@ export interface LocalModelBenchmarkEvidence {
165
186
  readonly hiddenJudgmentCount: number;
166
187
  readonly winnerStacks: readonly string[];
167
188
  readonly winnerModels: readonly LocalModelBenchmarkWinner[];
189
+ readonly routeLatencies: readonly LocalModelBenchmarkRouteLatency[];
168
190
  readonly summary: string;
169
191
  readonly confidence: 'estimated' | 'measured';
170
192
  }
@@ -188,6 +210,7 @@ export interface LocalModelServerEndpoint {
188
210
  readonly refreshRoute: string;
189
211
  readonly addProviderRoute: string | null;
190
212
  readonly notes: readonly string[];
213
+ readonly servingDiagnostics?: LocalModelServerServingDiagnostics;
191
214
  readonly diagnostics?: {
192
215
  readonly liveProbe: 'not-run';
193
216
  readonly successCriteria: readonly string[];
@@ -197,6 +220,43 @@ export interface LocalModelServerEndpoint {
197
220
  };
198
221
  }
199
222
 
223
+ export interface LocalModelServerServingDiagnostics {
224
+ readonly status: 'ready' | 'attention' | 'blocked' | 'unknown';
225
+ readonly source: string;
226
+ readonly summary: string;
227
+ readonly schemaStatus?: 'certified' | 'legacy';
228
+ readonly schemaVersion?: string;
229
+ readonly provenance?: readonly string[];
230
+ readonly publicationGuarantee?: string;
231
+ readonly publisher?: string;
232
+ readonly serverVersion?: string;
233
+ readonly loadedModelCount?: number;
234
+ readonly loadedModels: readonly string[];
235
+ readonly contextWindowTokens?: number;
236
+ readonly toolSupport?: boolean;
237
+ readonly resourcePressure: 'low' | 'moderate' | 'high' | 'unknown';
238
+ readonly resourceSummary?: string;
239
+ readonly lastCheckedAt?: string | null;
240
+ readonly startReceiptId?: string;
241
+ readonly repairReceiptId?: string;
242
+ readonly receiptStatus?: string;
243
+ readonly startRoute?: string;
244
+ readonly repairRoute?: string;
245
+ readonly inspectRoute: string;
246
+ readonly missingSignals: readonly string[];
247
+ readonly policy: string;
248
+ }
249
+
250
+ export interface LocalModelServerDiagnosticsPublication {
251
+ readonly status: 'not-published' | 'published-read-model' | 'read-model-empty' | 'read-model-error';
252
+ readonly requiredPaths: readonly string[];
253
+ readonly sourcePaths: readonly string[];
254
+ readonly recordCount: number;
255
+ readonly matchedEndpointCount: number;
256
+ readonly missingSignals: readonly string[];
257
+ readonly policy: string;
258
+ }
259
+
200
260
  export interface LocalModelServerDefaultEndpoint {
201
261
  readonly id: string;
202
262
  readonly label: string;
@@ -215,6 +275,7 @@ export interface LocalModelServerHealthMap {
215
275
  readonly returnedEndpoints: number;
216
276
  readonly endpoints: readonly LocalModelServerEndpoint[];
217
277
  readonly suggestedDefaults: readonly LocalModelServerDefaultEndpoint[];
278
+ readonly daemonDiagnostics: LocalModelServerDiagnosticsPublication;
218
279
  readonly nextActions: readonly string[];
219
280
  readonly policy: string;
220
281
  }
@@ -4,14 +4,16 @@ import type { CommandContext } from '../input/command-registry.ts';
4
4
  import { requireProviderApi } from '../input/commands/runtime-services.ts';
5
5
  import { previewHarnessText } from './agent-harness-text.ts';
6
6
  import { localModelCookbook } from './agent-harness-local-model-cookbook.ts';
7
- import { collectLocalServerEndpointCandidates, describeLocalServerEndpoint, localModelDetection, localModelServerHealthMap, runLocalModelServerSmoke } from './agent-harness-local-model-endpoints.ts';
7
+ import { localBenchmarkRouteLatencyMap } from './agent-harness-local-model-benchmarks.ts';
8
+ import { localModelDetection, localModelServerEndpoints, localModelServerHealthMap } from './agent-harness-local-model-endpoints.ts';
9
+ import { runLocalModelServerSmoke } from './agent-harness-local-model-smoke.ts';
8
10
  import { localHardwareProfile, modelReadinessScore } from './agent-harness-model-readiness.ts';
9
11
  import { contextWindowFor, loadModels, listProviderIds, modelBenchmarkCompositeScore, modelBenchmarkQualityTier, modelCapabilities, modelCurrent, modelDisplayName, modelModelId, modelProviderId, modelReasoning, modelRegistryKey, modelTier, readConfig, readProviderApi } from './agent-harness-model-catalog.ts';
10
12
  import type { AgentHarnessModelRoutingArgs, LocalModelServerEndpoint, ModelCandidate, ModelProviderHealthSignal, ModelRouteLookupSource, ModelRouteResolution, RouteCandidate } from './agent-harness-model-routing-types.ts';
11
13
  import { readLimit, readString } from './agent-harness-model-routing-utils.ts';
12
14
  export type { AgentHarnessModelRoutingArgs } from './agent-harness-model-routing-types.ts';
13
15
  export { localModelCookbook } from './agent-harness-local-model-cookbook.ts';
14
- export { runLocalModelServerSmoke } from './agent-harness-local-model-endpoints.ts';
16
+ export { runLocalModelServerSmoke } from './agent-harness-local-model-smoke.ts';
15
17
 
16
18
  async function readCurrentModel(context: CommandContext): Promise<unknown> {
17
19
  try {
@@ -21,6 +23,15 @@ async function readCurrentModel(context: CommandContext): Promise<unknown> {
21
23
  }
22
24
  }
23
25
 
26
+ function attachLocalBenchmarkLatencies(context: CommandContext, models: readonly ModelCandidate[]): readonly ModelCandidate[] {
27
+ const latencies = localBenchmarkRouteLatencyMap(context);
28
+ if (latencies.size === 0) return models;
29
+ return models.map((model) => ({
30
+ ...model,
31
+ localBenchmarkLatency: latencies.get(model.registryKey) ?? null,
32
+ }));
33
+ }
34
+
24
35
  function routeCandidates(context: CommandContext): readonly RouteCandidate[] {
25
36
  return [
26
37
  {
@@ -238,7 +249,12 @@ function compactProviderHealthSignal(signal: ModelProviderHealthSignal): Record<
238
249
  daemonPublication: signal.daemonPublication,
239
250
  agentConsumption: signal.agentConsumption,
240
251
  ...(signal.healthStatus ? { healthStatus: signal.healthStatus } : {}),
252
+ ...(signal.modelRouteId ? { modelRouteId: signal.modelRouteId } : {}),
253
+ ...(signal.sourceRecordId ? { sourceRecordId: signal.sourceRecordId } : {}),
241
254
  ...(signal.avgLatencyMs !== undefined ? { avgLatencyMs: signal.avgLatencyMs } : {}),
255
+ ...(signal.rateLimitRemaining !== undefined ? { rateLimitRemaining: signal.rateLimitRemaining } : {}),
256
+ ...(signal.rateLimitResetAt ? { rateLimitResetAt: signal.rateLimitResetAt } : {}),
257
+ ...(signal.lastErrorMessage ? { lastErrorMessage: signal.lastErrorMessage } : {}),
242
258
  missingSignals: signal.missingSignals,
243
259
  policy: signal.policy,
244
260
  };
@@ -260,6 +276,7 @@ function describeModel(model: ModelCandidate, options: { readonly context: Comma
260
276
  tier: model.tier ?? null,
261
277
  benchmarkCompositeScore: model.benchmarkCompositeScore ?? null,
262
278
  benchmarkQualityTier: model.benchmarkQualityTier ?? null,
279
+ localBenchmarkLatency: model.localBenchmarkLatency ?? null,
263
280
  readinessScore: readiness.score,
264
281
  readinessLevel: readiness.level,
265
282
  readiness: options.includeParameters
@@ -330,10 +347,11 @@ function modelCandidateModelRoute(): string {
330
347
  }
331
348
 
332
349
  export async function modelRoutingCatalogStatus(context: CommandContext): Promise<Record<string, unknown>> {
333
- const [models, providerIds] = await Promise.all([
350
+ const [rawModels, providerIds] = await Promise.all([
334
351
  loadModels(context),
335
352
  Promise.resolve(listProviderIds(context)),
336
353
  ]);
354
+ const models = attachLocalBenchmarkLatencies(context, rawModels);
337
355
  return {
338
356
  modes: ['model_routing', 'model_route'],
339
357
  status: readProviderApi(context) ? 'available' : 'degraded',
@@ -349,15 +367,20 @@ export async function modelRoutingCatalogStatus(context: CommandContext): Promis
349
367
  export async function modelRoutingSummary(context: CommandContext, args: AgentHarnessModelRoutingArgs): Promise<Record<string, unknown>> {
350
368
  const query = readString(args.query).toLowerCase();
351
369
  const includeParameters = args.includeParameters === true;
352
- const [models, providerIds, currentModel] = await Promise.all([
370
+ const [rawModels, providerIds, currentModel] = await Promise.all([
353
371
  loadModels(context),
354
372
  Promise.resolve(listProviderIds(context)),
355
373
  readCurrentModel(context),
356
374
  ]);
375
+ const models = attachLocalBenchmarkLatencies(context, rawModels);
357
376
  const routes = routeCandidates(context);
358
377
  const filteredRoutes = routes.filter((route) => !query || routeSearchText(route).includes(query));
359
378
  const filteredModels = models.filter((model) => !query || modelSearchText(model).includes(query));
360
379
  const limit = readLimit(args.limit, 100);
380
+ const currentRegistryKey = currentModel ? modelRegistryKey(currentModel) : '';
381
+ const loadedCurrentModel = currentRegistryKey
382
+ ? models.find((model) => model.registryKey === currentRegistryKey || model.modelId === modelModelId(currentModel))
383
+ : null;
361
384
  return {
362
385
  status: readProviderApi(context) ? 'available' : 'degraded',
363
386
  current: {
@@ -378,6 +401,7 @@ export async function modelRoutingSummary(context: CommandContext, args: AgentHa
378
401
  tier: modelTier(currentModel),
379
402
  benchmarkCompositeScore: modelBenchmarkCompositeScore(currentModel),
380
403
  benchmarkQualityTier: modelBenchmarkQualityTier(currentModel),
404
+ localBenchmarkLatency: loadedCurrentModel?.localBenchmarkLatency ?? null,
381
405
  pinned: false,
382
406
  }, { context, includeParameters }) : null,
383
407
  },
@@ -410,9 +434,10 @@ export async function describeHarnessModelRoute(context: CommandContext, args: A
410
434
  usage: 'model_route requires modelRouteId, target, or query. Prefer models action:"status" to inspect route, model, and local endpoint ids.',
411
435
  };
412
436
  }
413
- const [models] = await Promise.all([loadModels(context)]);
437
+ const [rawModels] = await Promise.all([loadModels(context)]);
438
+ const models = attachLocalBenchmarkLatencies(context, rawModels);
414
439
  const routes = routeCandidates(context);
415
- const endpoints = collectLocalServerEndpointCandidates(context).map((endpoint) => describeLocalServerEndpoint(endpoint, true));
440
+ const endpoints = localModelServerEndpoints(context, true);
416
441
  const normalized = lookup.input.toLowerCase();
417
442
  const exactRoute = routes.find((route) => route.id === lookup.input);
418
443
  if (exactRoute) return { status: 'found', route: describeRoute(exactRoute, { context, includeParameters: true, lookup: { ...lookup, resolvedBy: 'route-id' } }) };
@@ -7,6 +7,7 @@ import { buildAgentWorkspaceChannels } from '../input/agent-workspace-channels.t
7
7
  import { buildAgentWorkspaceVoiceMediaReadiness } from '../input/agent-workspace-voice-media.ts';
8
8
  import { requirePlatform, requireShellPaths } from '../input/commands/runtime-services.ts';
9
9
  import { browserControlPosture } from './agent-harness-browser-control.ts';
10
+ import { certifiedDeviceLiveRecords, deviceLiveReadModelSnapshot } from './agent-harness-device-live-read-models.ts';
10
11
  import { previewHarnessText } from './agent-harness-text.ts';
11
12
 
12
13
  export interface AgentHarnessPairingArgs {
@@ -370,6 +371,10 @@ function queryRequestsWholeDeviceMap(query: string): boolean {
370
371
  return tokens.includes('device') && (tokens.includes('capability') || tokens.includes('capabilities') || tokens.includes('map'));
371
372
  }
372
373
 
374
+ function liveDeviceEvidence(records: ReturnType<typeof certifiedDeviceLiveRecords>): Record<string, unknown> | undefined {
375
+ return records.length > 0 ? { certifiedLiveRecords: records.slice(0, 5) } : undefined;
376
+ }
377
+
373
378
  function buildDeviceCapabilityMap(context: CommandContext): DeviceCapabilityMap {
374
379
  const state = pairingState(context);
375
380
  const hasToken = tokenPresent(state);
@@ -389,6 +394,9 @@ function buildDeviceCapabilityMap(context: CommandContext): DeviceCapabilityMap
389
394
  const enabledChannels = channels.filter((channel) => channel.enabled);
390
395
  const notificationWebhookCount = readConfigStringArray(context, 'notifications.webhookUrls').length;
391
396
  const browserPosture = browserControlPosture(context);
397
+ const liveDevice = deviceLiveReadModelSnapshot(context);
398
+ const sensorRecords = certifiedDeviceLiveRecords(liveDevice, 'camera-location-sensors', ['camera', 'location', 'screen', 'device command', 'sensor']);
399
+ const sensorReady = sensorRecords.length > 0;
392
400
  const ttsStatus: DeviceCapabilityStatus = voiceReadiness.selectedTtsProviderStatus === 'ready' && voiceReadiness.ttsVoiceConfigured
393
401
  ? 'ready'
394
402
  : voiceReadiness.selectedTtsProviderStatus === 'ready' || voiceReadiness.selectedTtsProviderStatus === 'registered'
@@ -575,22 +583,35 @@ function buildDeviceCapabilityMap(context: CommandContext): DeviceCapabilityMap
575
583
  },
576
584
  {
577
585
  id: 'camera-location-sensors',
578
- label: 'Camera and location sensors',
586
+ label: 'Camera, screen, and location sensors',
579
587
  domain: 'device',
580
- status: 'not-published',
581
- userOutcome: 'Use phone camera or location only after the daemon/companion publishes permission-scoped records.',
582
- summary: 'Camera and location sensor adapters are not published by the current Agent-visible SDK/daemon contract.',
583
- nextStep: 'Inspect operator methods for newly published camera/location contracts before claiming device access.',
584
- capabilities: ['camera permission posture', 'location permission posture', 'device sensor commands'],
585
- modelRoute: 'host action:"methods" query:"camera location device"',
588
+ status: sensorReady ? 'ready' : 'not-published',
589
+ userOutcome: 'Use phone camera, screen, location, or local device commands only after the daemon/companion publishes permission-scoped records.',
590
+ summary: sensorReady
591
+ ? 'The SDK/daemon published certified permission-scoped camera, screen, location, or device-command capability records.'
592
+ : 'Camera, screen, location, and device-command adapters are not published by the current Agent-visible SDK/daemon contract.',
593
+ nextStep: sensorReady
594
+ ? 'Inspect the certified device record and use only its returned confirmed permission/control routes.'
595
+ : 'Inspect operator methods for newly published camera/location/screen contracts before claiming device access.',
596
+ capabilities: [
597
+ 'camera permission posture',
598
+ 'screen capture permission posture',
599
+ 'location permission posture',
600
+ 'local device command routes',
601
+ ...sensorRecords.flatMap((record) => record.capabilities),
602
+ ],
603
+ modelRoute: sensorRecords[0]?.modelRoute ?? 'host action:"methods" query:"camera location device"',
586
604
  setupRoutes: [
605
+ ...sensorRecords.slice(0, 3).map((record) => record.modelRoute),
587
606
  'host action:"methods" query:"camera location device"',
588
607
  'host action:"capabilities" includeParameters:true',
589
608
  ],
590
609
  evidence: {
591
- publishedByCurrentAgentContract: false,
610
+ publishedByCurrentAgentContract: sensorReady,
611
+ liveDeviceRecords: liveDevice.capabilities.length,
612
+ ...liveDeviceEvidence(sensorRecords),
592
613
  },
593
- policy: 'Agent reports unpublished device sensor APIs honestly instead of simulating camera or location access.',
614
+ policy: 'Agent reports unpublished device sensor APIs honestly and counts device access only when certified SDK/daemon permission-scoped records publish exact routes without raw payloads.',
594
615
  },
595
616
  ];
596
617
  capabilities.sort((left, right) => deviceStatusRank(left.status) - deviceStatusRank(right.status) || left.label.localeCompare(right.label));