@pellux/goodvibes-agent 1.1.7 → 1.3.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 +329 -0
- package/README.md +46 -30
- package/dist/package/{ast-grep-napi.linux-x64-gnu-mkk8xwww.node → ast-grep-napi.linux-x64-gnu-swtppvy9.node} +0 -0
- package/dist/package/{ast-grep-napi.linux-x64-musl-ryqtgdv6.node → ast-grep-napi.linux-x64-musl-ttfcdtap.node} +0 -0
- package/dist/package/main.js +138903 -91477
- package/docs/README.md +14 -7
- package/docs/channels-remote-and-api.md +8 -5
- package/docs/connected-host.md +14 -12
- package/docs/getting-started.md +58 -30
- package/docs/knowledge-artifacts-and-multimodal.md +7 -5
- package/docs/project-planning.md +2 -2
- package/docs/providers-and-routing.md +11 -3
- package/docs/tools-and-commands.md +116 -45
- package/docs/voice-and-live-tts.md +3 -1
- package/package.json +3 -2
- package/release/release-notes.md +142 -5
- package/release/release-readiness.json +124 -94
- package/src/agent/assistant-cockpit.ts +247 -0
- package/src/agent/autonomy-schedule-format.ts +96 -0
- package/src/agent/autonomy-schedule.ts +514 -0
- package/src/agent/channel-delivery-receipts.ts +292 -0
- package/src/agent/competitive-feature-inventory.ts +294 -0
- package/src/agent/document-registry-types.ts +106 -0
- package/src/agent/document-registry.ts +734 -0
- package/src/agent/harness-control.ts +2 -2
- package/src/agent/memory-prompt.ts +2 -2
- package/src/agent/operator-actions.ts +50 -6
- package/src/agent/persona-registry.ts +8 -0
- package/src/agent/project-context-files.ts +273 -0
- package/src/agent/prompt-context-receipts.ts +502 -0
- package/src/agent/reminder-schedule-format.ts +16 -2
- package/src/agent/research-run-registry.ts +582 -0
- package/src/agent/research-source-registry.ts +441 -0
- package/src/agent/routine-registry.ts +19 -3
- package/src/agent/routine-schedule-format.ts +27 -2
- package/src/agent/runtime-profile-starters.ts +7 -0
- package/src/agent/runtime-profile.ts +106 -4
- package/src/agent/schedule-edit-format.ts +129 -0
- package/src/agent/schedule-edit.ts +496 -0
- package/src/agent/schedule-next-routes.ts +42 -0
- package/src/agent/setup-wizard-artifact-receipts.ts +366 -0
- package/src/agent/setup-wizard-checkpoint.ts +140 -0
- package/src/agent/setup-wizard.ts +683 -0
- package/src/agent/skill-registry-types.ts +102 -0
- package/src/agent/skill-registry.ts +81 -107
- package/src/agent/vibe-confirmation-routes.ts +62 -0
- package/src/agent/vibe-file.ts +285 -0
- package/src/cli/agent-knowledge-command.ts +45 -2
- package/src/cli/help.ts +3 -0
- package/src/cli/profiles-command.ts +25 -11
- package/src/config/agent-settings-policy.ts +1 -18
- package/src/input/agent-workspace-activation.ts +53 -0
- package/src/input/agent-workspace-artifact-browser-editor.ts +494 -0
- package/src/input/agent-workspace-artifact-metadata.ts +17 -0
- package/src/input/agent-workspace-basic-command-editors.ts +5 -1
- package/src/input/agent-workspace-categories.ts +153 -155
- package/src/input/agent-workspace-channel-triage.ts +481 -0
- package/src/input/agent-workspace-channels.ts +263 -0
- package/src/input/agent-workspace-command-editor.ts +152 -0
- package/src/input/agent-workspace-context-snapshot.ts +349 -0
- package/src/input/agent-workspace-delegation-editor-submission.ts +8 -0
- package/src/input/agent-workspace-document-editor.ts +502 -0
- package/src/input/agent-workspace-document-ops-editor.ts +523 -0
- package/src/input/agent-workspace-host-category.ts +1 -1
- package/src/input/agent-workspace-local-library-snapshot.ts +167 -0
- package/src/input/agent-workspace-model-compare-editor.ts +376 -0
- package/src/input/agent-workspace-model-compare-prompt-submission.ts +552 -0
- package/src/input/agent-workspace-model-compare-types.ts +82 -0
- package/src/input/agent-workspace-model-compare-utils.ts +35 -0
- package/src/input/agent-workspace-onboarding-categories.ts +141 -0
- package/src/input/agent-workspace-operations-command-editor-submission.ts +53 -0
- package/src/input/agent-workspace-operations-command-editors.ts +22 -0
- package/src/input/agent-workspace-research-report-editor.ts +212 -0
- package/src/input/agent-workspace-research-run-editor.ts +144 -0
- package/src/input/agent-workspace-research-source-editor.ts +167 -0
- package/src/input/agent-workspace-review-packet-snapshot.ts +593 -0
- package/src/input/agent-workspace-review-packet-utils.ts +265 -0
- package/src/input/agent-workspace-settings.ts +313 -30
- package/src/input/agent-workspace-setup-checkpoint-action.ts +127 -0
- package/src/input/agent-workspace-setup-snapshot.ts +217 -0
- package/src/input/agent-workspace-setup.ts +61 -1
- package/src/input/agent-workspace-snapshot.ts +184 -149
- package/src/input/agent-workspace-types.ts +287 -2
- package/src/input/agent-workspace.ts +23 -1
- package/src/input/command-registry.ts +9 -2
- package/src/input/commands/agent-runtime-profile-runtime.ts +28 -13
- package/src/input/commands/channels-runtime.ts +102 -114
- package/src/input/commands/delegation-runtime.ts +87 -24
- package/src/input/commands/knowledge-browser-flags.ts +12 -0
- package/src/input/commands/knowledge.ts +28 -14
- package/src/input/commands/operator-actions-runtime.ts +6 -3
- package/src/input/commands/schedule-runtime.ts +71 -14
- package/src/input/commands/session-content.ts +2 -2
- package/src/input/commands/shell-core.ts +2 -0
- package/src/input/commands/vibe-runtime.ts +140 -0
- package/src/input/commands.ts +2 -0
- package/src/input/connected-host-routes.ts +123 -0
- package/src/input/settings-modal-types.ts +14 -0
- package/src/input/setup-wizard-live-receipts.ts +76 -0
- package/src/main.ts +15 -15
- package/src/panels/qr-panel.ts +2 -2
- package/src/renderer/agent-workspace-context-lines.ts +701 -0
- package/src/renderer/agent-workspace.ts +68 -421
- package/src/renderer/settings-modal-helpers.ts +12 -0
- package/src/renderer/settings-modal.ts +12 -0
- package/src/runtime/bootstrap-command-context.ts +11 -1
- package/src/runtime/bootstrap-command-parts.ts +14 -3
- package/src/runtime/bootstrap-core.ts +39 -1
- package/src/runtime/bootstrap-shell.ts +6 -0
- package/src/runtime/bootstrap.ts +113 -30
- package/src/runtime/connected-host-auth.ts +3 -2
- package/src/runtime/execution-ledger.ts +231 -0
- package/src/runtime/services.ts +88 -10
- package/src/runtime/tool-permission-safety.ts +199 -5
- package/src/shell/session-continuity-hints.ts +39 -0
- package/src/tools/agent-artifacts-tool.ts +606 -0
- package/src/tools/agent-audit-tool.ts +155 -0
- package/src/tools/agent-autonomy-schedule-tool.ts +238 -0
- package/src/tools/agent-autonomy-tool.ts +140 -0
- package/src/tools/agent-channel-send-tool.ts +24 -2
- package/src/tools/agent-channels-tool.ts +140 -0
- package/src/tools/agent-computer-tool.ts +168 -0
- package/src/tools/agent-context-policy.ts +137 -7
- package/src/tools/agent-context-tool.ts +143 -0
- package/src/tools/agent-delegation-tool.ts +128 -0
- package/src/tools/agent-device-tool.ts +240 -0
- package/src/tools/agent-documents-tool.ts +684 -0
- package/src/tools/agent-execution-tool.ts +230 -0
- package/src/tools/agent-harness-agent-orchestration-policy.ts +75 -0
- package/src/tools/agent-harness-agent-orchestration.ts +791 -0
- package/src/tools/agent-harness-autonomy-intake.ts +785 -0
- package/src/tools/agent-harness-autonomy-live-records.ts +742 -0
- package/src/tools/agent-harness-autonomy-queue-types.ts +88 -0
- package/src/tools/agent-harness-autonomy-queue.ts +571 -0
- package/src/tools/agent-harness-autonomy-watcher-read-models.ts +509 -0
- package/src/tools/agent-harness-background-processes-types.ts +35 -0
- package/src/tools/agent-harness-background-processes.ts +798 -0
- package/src/tools/agent-harness-browser-cockpit-route.ts +373 -0
- package/src/tools/agent-harness-browser-control.ts +346 -0
- package/src/tools/agent-harness-browser-pwa-read-models.ts +622 -0
- package/src/tools/agent-harness-channel-metadata.ts +168 -10
- package/src/tools/agent-harness-cli-command-policy.ts +110 -0
- package/src/tools/agent-harness-command-catalog.ts +4 -2
- package/src/tools/agent-harness-command-runner.ts +42 -0
- package/src/tools/agent-harness-connected-host-status.ts +7 -4
- package/src/tools/agent-harness-delegation-posture.ts +234 -19
- package/src/tools/agent-harness-device-live-read-models.ts +366 -0
- package/src/tools/agent-harness-document-ops-reviewer-readiness.ts +234 -0
- package/src/tools/agent-harness-document-ops-types.ts +72 -0
- package/src/tools/agent-harness-document-ops.ts +671 -0
- package/src/tools/agent-harness-execution-history.ts +489 -0
- package/src/tools/agent-harness-execution-posture.ts +385 -0
- package/src/tools/agent-harness-file-recovery.ts +135 -0
- package/src/tools/agent-harness-interactive-runtime-records.ts +421 -0
- package/src/tools/agent-harness-keybinding-metadata.ts +16 -12
- package/src/tools/agent-harness-learning-curator-common.ts +102 -0
- package/src/tools/agent-harness-learning-curator-consolidation.ts +295 -0
- package/src/tools/agent-harness-learning-curator-proposals.ts +606 -0
- package/src/tools/agent-harness-learning-curator-types.ts +151 -0
- package/src/tools/agent-harness-learning-curator.ts +417 -0
- package/src/tools/agent-harness-local-model-benchmarks.ts +269 -0
- package/src/tools/agent-harness-local-model-cookbook.ts +238 -0
- package/src/tools/agent-harness-local-model-endpoints.ts +788 -0
- package/src/tools/agent-harness-local-model-smoke.ts +277 -0
- package/src/tools/agent-harness-local-model-url.ts +78 -0
- package/src/tools/agent-harness-media-posture.ts +223 -2
- package/src/tools/agent-harness-memory-external-providers.ts +796 -0
- package/src/tools/agent-harness-memory-posture.ts +672 -0
- package/src/tools/agent-harness-memory-provider-certification.ts +219 -0
- package/src/tools/agent-harness-memory-refinement.ts +340 -0
- package/src/tools/agent-harness-metadata.ts +100 -168
- package/src/tools/agent-harness-mode-catalog.ts +86 -33
- package/src/tools/agent-harness-model-catalog.ts +162 -0
- package/src/tools/agent-harness-model-provider-health.ts +283 -0
- package/src/tools/agent-harness-model-readiness.ts +432 -0
- package/src/tools/agent-harness-model-routing-types.ts +327 -0
- package/src/tools/agent-harness-model-routing-utils.ts +30 -0
- package/src/tools/agent-harness-model-routing.ts +165 -193
- package/src/tools/agent-harness-operator-methods.ts +115 -133
- package/src/tools/agent-harness-pairing-posture.ts +452 -18
- package/src/tools/agent-harness-personal-ops-certification.ts +116 -0
- package/src/tools/agent-harness-personal-ops-discovery.ts +533 -0
- package/src/tools/agent-harness-personal-ops-intake.ts +385 -0
- package/src/tools/agent-harness-personal-ops-lanes.ts +270 -0
- package/src/tools/agent-harness-personal-ops-operations.ts +225 -0
- package/src/tools/agent-harness-personal-ops-provider-records.ts +358 -0
- package/src/tools/agent-harness-personal-ops-provider-task-records.ts +321 -0
- package/src/tools/agent-harness-personal-ops-records.ts +684 -0
- package/src/tools/agent-harness-personal-ops-runner.ts +637 -0
- package/src/tools/agent-harness-personal-ops-types.ts +240 -0
- package/src/tools/agent-harness-personal-ops.ts +613 -0
- package/src/tools/agent-harness-project-context.ts +144 -0
- package/src/tools/agent-harness-prompt-context.ts +589 -0
- package/src/tools/agent-harness-provider-account-metadata.ts +2 -2
- package/src/tools/agent-harness-release-evidence.ts +9 -7
- package/src/tools/agent-harness-release-readiness.ts +9 -7
- package/src/tools/agent-harness-remote-read-models.ts +541 -0
- package/src/tools/agent-harness-research-briefing.ts +446 -0
- package/src/tools/agent-harness-research-live-read-models.ts +500 -0
- package/src/tools/agent-harness-research-queue.ts +250 -0
- package/src/tools/agent-harness-research-runs.ts +307 -0
- package/src/tools/agent-harness-research-workflow.ts +395 -0
- package/src/tools/agent-harness-security-posture.ts +2 -2
- package/src/tools/agent-harness-service-posture.ts +9 -9
- package/src/tools/agent-harness-session-metadata.ts +3 -3
- package/src/tools/agent-harness-setup-connected-host.ts +501 -0
- package/src/tools/agent-harness-setup-describe.ts +557 -0
- package/src/tools/agent-harness-setup-handoffs.ts +502 -0
- package/src/tools/agent-harness-setup-model-helpers.ts +102 -0
- package/src/tools/agent-harness-setup-plan.ts +253 -0
- package/src/tools/agent-harness-setup-posture-types.ts +218 -0
- package/src/tools/agent-harness-setup-posture-utils.ts +173 -0
- package/src/tools/agent-harness-setup-posture.ts +449 -221
- package/src/tools/agent-harness-setup-smoke.ts +533 -0
- package/src/tools/agent-harness-sudo-posture.ts +114 -0
- package/src/tools/agent-harness-tool-schema.ts +142 -7
- package/src/tools/agent-harness-tool-types.ts +83 -0
- package/src/tools/agent-harness-tool-utils.ts +43 -0
- package/src/tools/agent-harness-tool.ts +319 -370
- package/src/tools/agent-harness-ui-surface-metadata.ts +76 -69
- package/src/tools/agent-harness-ui-surface-types.ts +46 -0
- package/src/tools/agent-harness-vibe-health.ts +105 -0
- package/src/tools/agent-harness-workspace-action-runner.ts +149 -0
- package/src/tools/agent-harness-workspace-actions.ts +114 -8
- package/src/tools/agent-harness-workspace-editor-execution.ts +214 -0
- package/src/tools/agent-harness-workspace-editor-runner.ts +755 -0
- package/src/tools/agent-host-tool.ts +159 -0
- package/src/tools/agent-knowledge-ingest-tool.ts +34 -1
- package/src/tools/agent-learning-consolidation-core.ts +327 -0
- package/src/tools/agent-learning-consolidation-tool.ts +758 -0
- package/src/tools/agent-memory-tool.ts +253 -0
- package/src/tools/agent-model-compare-export.ts +315 -0
- package/src/tools/agent-model-compare-handoff.ts +592 -0
- package/src/tools/agent-model-compare-judgment.ts +633 -0
- package/src/tools/agent-model-compare-run.ts +735 -0
- package/src/tools/agent-model-compare-tool.ts +698 -0
- package/src/tools/agent-model-compare-types.ts +191 -0
- package/src/tools/agent-model-compare-utils.ts +93 -0
- package/src/tools/agent-models-tool.ts +208 -0
- package/src/tools/agent-operator-action-tool.ts +1 -1
- package/src/tools/agent-operator-method-tool.ts +643 -0
- package/src/tools/agent-personal-ops-tool.ts +197 -0
- package/src/tools/agent-policy-explanation.ts +415 -0
- package/src/tools/agent-research-report-tool.ts +608 -0
- package/src/tools/agent-research-runner.ts +367 -0
- package/src/tools/agent-research-runs-tool.ts +434 -0
- package/src/tools/agent-research-sources-tool.ts +443 -0
- package/src/tools/agent-research-tool.ts +515 -0
- package/src/tools/agent-review-packet-presets-core.ts +757 -0
- package/src/tools/agent-review-packet-presets-tool.ts +466 -0
- package/src/tools/agent-review-packet-share-tool.ts +305 -0
- package/src/tools/agent-route-planner-candidates-setup.ts +411 -0
- package/src/tools/agent-route-planner-candidates-surfaces.ts +432 -0
- package/src/tools/agent-route-planner-candidates-work.ts +251 -0
- package/src/tools/agent-route-planner-helpers.ts +667 -0
- package/src/tools/agent-route-planner.ts +185 -0
- package/src/tools/agent-route-tool.ts +105 -0
- package/src/tools/agent-schedule-edit-tool.ts +210 -0
- package/src/tools/agent-schedule-tool.ts +282 -0
- package/src/tools/agent-security-tool.ts +145 -0
- package/src/tools/agent-sessions-tool.ts +122 -0
- package/src/tools/agent-settings-import-tool.ts +104 -0
- package/src/tools/agent-settings-tool.ts +176 -0
- package/src/tools/agent-setup-tool.ts +226 -0
- package/src/tools/agent-support-tool.ts +122 -0
- package/src/tools/agent-terminal-process-tools.ts +167 -0
- package/src/tools/agent-tool-policy-guard-types.ts +77 -0
- package/src/tools/agent-tool-policy-guard.ts +106 -85
- package/src/tools/agent-vibe-tool.ts +297 -0
- package/src/tools/agent-work-plan-tool.ts +265 -6
- package/src/tools/agent-workspace-tool.ts +305 -0
- package/src/tools/artifact-archive.ts +169 -0
- package/src/tools/tool-definition-compaction.ts +36 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import type { CommandContext } from '../input/command-registry.ts';
|
|
2
|
+
import type { AgentHarnessModelRoutingArgs, LocalModelServerDefaultEndpoint, LocalModelServerEndpoint, LocalModelSmokeTarget } from './agent-harness-model-routing-types.ts';
|
|
3
|
+
import { previewHarnessText } from './agent-harness-text.ts';
|
|
4
|
+
import { readLimit, readRecord, readString } from './agent-harness-model-routing-utils.ts';
|
|
5
|
+
import { collectLocalServerEndpointCandidates, describeLocalServerEndpoint, isPrivateOrLocalUrl, localEndpointSmokeRoute, localModelServerDefaults, parseUrlCandidate } from './agent-harness-local-model-endpoints.ts';
|
|
6
|
+
|
|
7
|
+
function localModelSmokeTargetFromEndpoint(endpoint: LocalModelServerEndpoint): LocalModelSmokeTarget {
|
|
8
|
+
return {
|
|
9
|
+
kind: endpoint.kind,
|
|
10
|
+
id: endpoint.id,
|
|
11
|
+
label: `Local model server ${endpoint.baseUrl}`,
|
|
12
|
+
providerId: endpoint.providerId,
|
|
13
|
+
stack: endpoint.stack,
|
|
14
|
+
baseUrl: endpoint.baseUrl,
|
|
15
|
+
modelsUrl: endpoint.modelsUrl,
|
|
16
|
+
smokeCommand: endpoint.smokeCommand,
|
|
17
|
+
smokeRoute: endpoint.smokeRoute,
|
|
18
|
+
refreshRoute: endpoint.refreshRoute,
|
|
19
|
+
addProviderRoute: endpoint.addProviderRoute,
|
|
20
|
+
source: endpoint.sources.join(', ') || 'local-endpoint',
|
|
21
|
+
notes: endpoint.notes,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function localModelSmokeTargetFromDefault(endpoint: LocalModelServerDefaultEndpoint): LocalModelSmokeTarget {
|
|
26
|
+
return {
|
|
27
|
+
kind: 'suggested-local-server',
|
|
28
|
+
id: endpoint.id,
|
|
29
|
+
label: endpoint.label,
|
|
30
|
+
providerId: null,
|
|
31
|
+
stack: endpoint.stack,
|
|
32
|
+
baseUrl: endpoint.baseUrl,
|
|
33
|
+
modelsUrl: endpoint.modelsUrl,
|
|
34
|
+
smokeCommand: endpoint.smokeCommand,
|
|
35
|
+
smokeRoute: localEndpointSmokeRoute(endpoint.id),
|
|
36
|
+
refreshRoute: 'agent_harness mode:"run_command" command:"/refresh-models" confirm:true explicitUserRequest:"Refresh models after verifying the local server."',
|
|
37
|
+
addProviderRoute: endpoint.addProviderRoute,
|
|
38
|
+
source: 'suggested-default',
|
|
39
|
+
notes: [endpoint.startHint],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function localSmokeTargetSearchText(target: LocalModelSmokeTarget): string {
|
|
44
|
+
return [
|
|
45
|
+
target.kind,
|
|
46
|
+
target.id,
|
|
47
|
+
target.label,
|
|
48
|
+
target.providerId ?? '',
|
|
49
|
+
target.stack ?? '',
|
|
50
|
+
target.baseUrl,
|
|
51
|
+
target.modelsUrl,
|
|
52
|
+
target.source,
|
|
53
|
+
...target.notes,
|
|
54
|
+
].join('\n').toLowerCase();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function localModelSmokeLookup(args: AgentHarnessModelRoutingArgs): string {
|
|
58
|
+
const fields = readRecord(args.fields);
|
|
59
|
+
return readString(args.modelRouteId)
|
|
60
|
+
|| readString(args.target)
|
|
61
|
+
|| readString(args.query)
|
|
62
|
+
|| readString(fields.endpointId)
|
|
63
|
+
|| readString(fields.modelRouteId)
|
|
64
|
+
|| readString(fields.baseUrl)
|
|
65
|
+
|| readString(fields.modelsUrl);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function localModelSmokeTargets(context: CommandContext, args: AgentHarnessModelRoutingArgs): Record<string, unknown> | readonly LocalModelSmokeTarget[] {
|
|
69
|
+
const endpoints = collectLocalServerEndpointCandidates(context)
|
|
70
|
+
.map((endpoint) => localModelSmokeTargetFromEndpoint(describeLocalServerEndpoint(endpoint, true)));
|
|
71
|
+
const defaults = localModelServerDefaults().map(localModelSmokeTargetFromDefault);
|
|
72
|
+
const lookup = localModelSmokeLookup(args);
|
|
73
|
+
const allTargets = [...endpoints, ...defaults];
|
|
74
|
+
if (lookup) {
|
|
75
|
+
const normalized = lookup.toLowerCase();
|
|
76
|
+
const exact = allTargets.filter((target) => target.id === lookup || target.baseUrl === lookup || target.modelsUrl === lookup);
|
|
77
|
+
if (exact.length === 1) return exact;
|
|
78
|
+
if (exact.length > 1) {
|
|
79
|
+
return {
|
|
80
|
+
status: 'ambiguous',
|
|
81
|
+
input: lookup,
|
|
82
|
+
candidates: exact.slice(0, 8).map((target) => ({
|
|
83
|
+
kind: target.kind,
|
|
84
|
+
id: target.id,
|
|
85
|
+
label: target.label,
|
|
86
|
+
baseUrl: target.baseUrl,
|
|
87
|
+
modelsUrl: target.modelsUrl,
|
|
88
|
+
})),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const searched = allTargets.filter((target) => localSmokeTargetSearchText(target).includes(normalized));
|
|
92
|
+
if (searched.length === 1) return searched;
|
|
93
|
+
if (searched.length > 1) {
|
|
94
|
+
return {
|
|
95
|
+
status: 'ambiguous',
|
|
96
|
+
input: lookup,
|
|
97
|
+
candidates: searched.slice(0, 8).map((target) => ({
|
|
98
|
+
kind: target.kind,
|
|
99
|
+
id: target.id,
|
|
100
|
+
label: target.label,
|
|
101
|
+
baseUrl: target.baseUrl,
|
|
102
|
+
modelsUrl: target.modelsUrl,
|
|
103
|
+
})),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
status: 'missing_lookup',
|
|
108
|
+
input: lookup,
|
|
109
|
+
usage: 'Unknown local model endpoint. Use models action:"local" includeParameters:true to inspect local endpoint ids, or omit the lookup to check detected/default local servers.',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const pool = endpoints.length ? endpoints : defaults;
|
|
113
|
+
return pool.slice(0, readLimit(args.limit, 4));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function readSmokeTimeoutMs(value: unknown): number {
|
|
117
|
+
const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
|
|
118
|
+
if (typeof parsed !== 'number' || !Number.isFinite(parsed)) return 1500;
|
|
119
|
+
return Math.max(250, Math.min(10000, Math.trunc(parsed)));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function localSmokeNetworkScope(modelsUrl: string): { readonly allowed: boolean; readonly scope: string; readonly reason?: string } {
|
|
123
|
+
const url = parseUrlCandidate(modelsUrl);
|
|
124
|
+
if (!url || !/^https?:$/.test(url.protocol)) return { allowed: false, scope: 'invalid-url', reason: 'The model-list URL is not a valid HTTP(S) URL.' };
|
|
125
|
+
const host = url.hostname.replace(/^\[|\]$/g, '').toLowerCase();
|
|
126
|
+
if (host === '0.0.0.0') {
|
|
127
|
+
return { allowed: false, scope: 'bind-all-host', reason: '0.0.0.0 is a bind address, not a client URL. Use 127.0.0.1 or the intended LAN host.' };
|
|
128
|
+
}
|
|
129
|
+
if (!isPrivateOrLocalUrl(url.href)) {
|
|
130
|
+
return { allowed: false, scope: 'non-local-host', reason: 'Local model smoke only probes loopback, local-name, or private LAN endpoints.' };
|
|
131
|
+
}
|
|
132
|
+
if (host === 'localhost' || host === '127.0.0.1' || host === '::1') return { allowed: true, scope: 'loopback' };
|
|
133
|
+
if (host.endsWith('.local') || !host.includes('.')) return { allowed: true, scope: 'local-name' };
|
|
134
|
+
return { allowed: true, scope: 'private-lan' };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function extractModelIdsFromPayload(payload: unknown): readonly string[] {
|
|
138
|
+
const record = readRecord(payload);
|
|
139
|
+
const candidates = Array.isArray(record.data)
|
|
140
|
+
? record.data
|
|
141
|
+
: Array.isArray(record.models)
|
|
142
|
+
? record.models
|
|
143
|
+
: Array.isArray(payload)
|
|
144
|
+
? payload
|
|
145
|
+
: [];
|
|
146
|
+
const ids = candidates.map((entry) => {
|
|
147
|
+
if (typeof entry === 'string') return entry;
|
|
148
|
+
const item = readRecord(entry);
|
|
149
|
+
return readString(item.id) || readString(item.name) || readString(item.model);
|
|
150
|
+
}).filter(Boolean);
|
|
151
|
+
return [...new Set(ids)].slice(0, 12);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function safeSmokeError(error: unknown): string {
|
|
155
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
156
|
+
return previewHarnessText(message.replace(/https?:\/\/\S+/g, '[redacted-url]'), 180);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function smokeOneLocalModelTarget(target: LocalModelSmokeTarget, timeoutMs: number): Promise<Record<string, unknown>> {
|
|
160
|
+
const network = localSmokeNetworkScope(target.modelsUrl);
|
|
161
|
+
if (!network.allowed) {
|
|
162
|
+
return {
|
|
163
|
+
...target,
|
|
164
|
+
status: 'blocked',
|
|
165
|
+
liveProbe: 'confirmed',
|
|
166
|
+
networkScope: network.scope,
|
|
167
|
+
failure: network.reason,
|
|
168
|
+
nextActions: ['Inspect the endpoint route and correct the base URL before running smoke again.'],
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const controller = new AbortController();
|
|
173
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
174
|
+
const started = Date.now();
|
|
175
|
+
try {
|
|
176
|
+
const response = await fetch(target.modelsUrl, {
|
|
177
|
+
method: 'GET',
|
|
178
|
+
headers: { Accept: 'application/json' },
|
|
179
|
+
signal: controller.signal,
|
|
180
|
+
});
|
|
181
|
+
const elapsedMs = Date.now() - started;
|
|
182
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
183
|
+
const text = await response.text();
|
|
184
|
+
let payload: unknown = null;
|
|
185
|
+
let jsonValid = false;
|
|
186
|
+
try {
|
|
187
|
+
payload = text ? JSON.parse(text) : null;
|
|
188
|
+
jsonValid = true;
|
|
189
|
+
} catch {
|
|
190
|
+
jsonValid = false;
|
|
191
|
+
}
|
|
192
|
+
const modelIds = jsonValid ? extractModelIdsFromPayload(payload) : [];
|
|
193
|
+
const status = !response.ok
|
|
194
|
+
? 'http-error'
|
|
195
|
+
: !jsonValid
|
|
196
|
+
? 'invalid-json'
|
|
197
|
+
: modelIds.length === 0
|
|
198
|
+
? 'no-models'
|
|
199
|
+
: 'passed';
|
|
200
|
+
return {
|
|
201
|
+
...target,
|
|
202
|
+
status,
|
|
203
|
+
liveProbe: 'confirmed',
|
|
204
|
+
networkScope: network.scope,
|
|
205
|
+
httpStatus: response.status,
|
|
206
|
+
contentType,
|
|
207
|
+
elapsedMs,
|
|
208
|
+
jsonValid,
|
|
209
|
+
modelCount: modelIds.length,
|
|
210
|
+
sampleModelIds: modelIds.slice(0, 5),
|
|
211
|
+
success: status === 'passed',
|
|
212
|
+
nextActions: status === 'passed'
|
|
213
|
+
? ['Refresh the model catalog, then run a local benchmark before changing the default model.']
|
|
214
|
+
: ['Start or fix the local server, confirm /v1/models returns model ids, then retry this smoke check.'],
|
|
215
|
+
};
|
|
216
|
+
} catch (error) {
|
|
217
|
+
const elapsedMs = Date.now() - started;
|
|
218
|
+
const aborted = controller.signal.aborted;
|
|
219
|
+
return {
|
|
220
|
+
...target,
|
|
221
|
+
status: aborted ? 'timeout' : 'unreachable',
|
|
222
|
+
liveProbe: 'confirmed',
|
|
223
|
+
networkScope: network.scope,
|
|
224
|
+
elapsedMs,
|
|
225
|
+
timeoutMs,
|
|
226
|
+
success: false,
|
|
227
|
+
failure: aborted ? `Timed out after ${timeoutMs}ms.` : safeSmokeError(error),
|
|
228
|
+
nextActions: ['Start the local server, load at least one model, verify the base URL, then retry this smoke check.'],
|
|
229
|
+
};
|
|
230
|
+
} finally {
|
|
231
|
+
clearTimeout(timeout);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export async function runLocalModelServerSmoke(context: CommandContext, args: AgentHarnessModelRoutingArgs): Promise<Record<string, unknown>> {
|
|
236
|
+
const targets = localModelSmokeTargets(context, args);
|
|
237
|
+
if (!Array.isArray(targets)) {
|
|
238
|
+
return {
|
|
239
|
+
kind: 'local-model-smoke',
|
|
240
|
+
liveProbe: 'not-run',
|
|
241
|
+
...targets,
|
|
242
|
+
policy: 'No local model endpoint was probed because the requested endpoint lookup did not resolve exactly.',
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
if (targets.length === 0) {
|
|
246
|
+
return {
|
|
247
|
+
kind: 'local-model-smoke',
|
|
248
|
+
status: 'no-candidates',
|
|
249
|
+
liveProbe: 'not-run',
|
|
250
|
+
endpoints: [],
|
|
251
|
+
nextActions: ['Use the local model cookbook to start a local server or configure a local provider endpoint.'],
|
|
252
|
+
cookbookRoute: 'models action:"local" includeParameters:true',
|
|
253
|
+
policy: 'No local model endpoint was probed because no candidate endpoints were available.',
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const timeoutMs = readSmokeTimeoutMs(args.timeoutMs);
|
|
257
|
+
const checkedAt = new Date().toISOString();
|
|
258
|
+
const results = await Promise.all(targets.map((target) => smokeOneLocalModelTarget(target, timeoutMs)));
|
|
259
|
+
const passed = results.filter((result) => result.success === true);
|
|
260
|
+
const blocked = results.filter((result) => result.status === 'blocked');
|
|
261
|
+
return {
|
|
262
|
+
kind: 'local-model-smoke',
|
|
263
|
+
status: passed.length > 0 ? 'ready' : blocked.length === results.length ? 'blocked' : 'needs-attention',
|
|
264
|
+
liveProbe: 'confirmed',
|
|
265
|
+
checkedAt,
|
|
266
|
+
timeoutMs,
|
|
267
|
+
endpointCount: results.length,
|
|
268
|
+
passedCount: passed.length,
|
|
269
|
+
failedCount: results.length - passed.length,
|
|
270
|
+
endpoints: results,
|
|
271
|
+
nextActions: passed.length > 0
|
|
272
|
+
? ['Refresh the model catalog and run the local benchmark action before changing the default route.']
|
|
273
|
+
: ['Start a local model server, load one model, and rerun this confirmed smoke check.'],
|
|
274
|
+
cookbookRoute: 'models action:"local" includeParameters:true',
|
|
275
|
+
policy: 'Confirmed read-only local model smoke. Agent only sends bounded GET requests to discovered or suggested local/private model-list endpoints; it does not add providers, refresh catalogs, benchmark, download models, or change routes.',
|
|
276
|
+
};
|
|
277
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export function localStackFor(value: string): string | null {
|
|
2
|
+
const normalized = value.toLowerCase();
|
|
3
|
+
if (/ollama[-_\s]?cloud/.test(normalized)) return null;
|
|
4
|
+
if (/\bollama\b/.test(normalized)) return 'ollama';
|
|
5
|
+
if (/llama[.-]?cpp|llamacpp/.test(normalized)) return 'llama.cpp';
|
|
6
|
+
if (/\bvllm\b/.test(normalized)) return 'vllm';
|
|
7
|
+
if (/lm[-_\s]?studio/.test(normalized)) return 'openai-compatible';
|
|
8
|
+
if (/localai|text-generation-inference|\btgi\b/.test(normalized)) return 'openai-compatible';
|
|
9
|
+
if (/localhost|127\.0\.0\.1|\[?::1\]?/.test(normalized)) return 'openai-compatible';
|
|
10
|
+
if (/openai-compatible|openai compatible|custom-provider|custom provider/.test(normalized)) return 'openai-compatible';
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function cleanUrlCandidate(value: string): string {
|
|
15
|
+
return value.trim().replace(/[),.;]+$/g, '');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function extractUrls(value: string): readonly string[] {
|
|
19
|
+
const matches = value.match(/https?:\/\/[^\s"'`<>]+/gi) ?? [];
|
|
20
|
+
return [...new Set(matches.map(cleanUrlCandidate).filter(Boolean))];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function parseUrlCandidate(raw: string): URL | null {
|
|
24
|
+
const trimmed = cleanUrlCandidate(raw);
|
|
25
|
+
if (!trimmed) return null;
|
|
26
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) ? trimmed : `http://${trimmed}`;
|
|
27
|
+
try {
|
|
28
|
+
return new URL(withScheme);
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isPrivateOrLocalHost(hostname: string): boolean {
|
|
35
|
+
const host = hostname.replace(/^\[|\]$/g, '').toLowerCase();
|
|
36
|
+
if (!host) return false;
|
|
37
|
+
if (host === 'localhost' || host === '0.0.0.0' || host === '::1') return true;
|
|
38
|
+
if (host.endsWith('.local')) return true;
|
|
39
|
+
if (host.includes(':')) return host.startsWith('fc') || host.startsWith('fd') || host.startsWith('fe80:');
|
|
40
|
+
const octets = host.split('.').map((entry) => Number(entry));
|
|
41
|
+
if (octets.length === 4 && octets.every((entry) => Number.isInteger(entry) && entry >= 0 && entry <= 255)) {
|
|
42
|
+
const [first, second] = octets as [number, number, number, number];
|
|
43
|
+
return first === 10
|
|
44
|
+
|| first === 127
|
|
45
|
+
|| (first === 172 && second >= 16 && second <= 31)
|
|
46
|
+
|| (first === 192 && second === 168)
|
|
47
|
+
|| (first === 169 && second === 254)
|
|
48
|
+
|| first === 0;
|
|
49
|
+
}
|
|
50
|
+
return !host.includes('.');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function isPrivateOrLocalUrl(raw: string): boolean {
|
|
54
|
+
const url = parseUrlCandidate(raw);
|
|
55
|
+
if (!url || !/^https?:$/.test(url.protocol)) return false;
|
|
56
|
+
return isPrivateOrLocalHost(url.hostname);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function normalizeLocalBaseUrl(raw: string, stackHint?: string | null): string | null {
|
|
60
|
+
const url = parseUrlCandidate(raw);
|
|
61
|
+
if (!url || !/^https?:$/.test(url.protocol)) return null;
|
|
62
|
+
const stack = stackHint ?? localStackFor(raw) ?? (isPrivateOrLocalHost(url.hostname) ? 'openai-compatible' : null);
|
|
63
|
+
if (!isPrivateOrLocalUrl(url.href)) return null;
|
|
64
|
+
|
|
65
|
+
let pathname = url.pathname.replace(/\/+$/g, '');
|
|
66
|
+
if (pathname.endsWith('/models')) pathname = pathname.slice(0, -'/models'.length);
|
|
67
|
+
if (pathname.endsWith('/api/tags')) pathname = pathname.slice(0, -'/api/tags'.length);
|
|
68
|
+
const needsOpenAiPath = stack === 'ollama' || stack === 'llama.cpp' || stack === 'vllm' || stack === 'openai-compatible';
|
|
69
|
+
if (needsOpenAiPath && (!pathname || pathname === '/')) pathname = '/v1';
|
|
70
|
+
url.pathname = pathname || '';
|
|
71
|
+
url.search = '';
|
|
72
|
+
url.hash = '';
|
|
73
|
+
return url.toString().replace(/\/+$/g, '');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function modelsUrlFor(baseUrl: string): string {
|
|
77
|
+
return `${baseUrl.replace(/\/+$/g, '')}/models`;
|
|
78
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { buildAgentWorkspaceVoiceMediaReadiness } from '../input/agent-workspace-voice-media.ts';
|
|
2
2
|
import type { AgentWorkspaceVoiceMediaProviderStatus } from '../input/agent-workspace-voice-media.ts';
|
|
3
3
|
import type { CommandContext } from '../input/command-registry.ts';
|
|
4
|
+
import { certifiedDeviceLiveRecords, deviceLiveReadModelSnapshot } from './agent-harness-device-live-read-models.ts';
|
|
4
5
|
import { previewHarnessText } from './agent-harness-text.ts';
|
|
5
6
|
|
|
6
7
|
export interface AgentHarnessMediaArgs {
|
|
@@ -16,6 +17,8 @@ type MediaProviderResolution =
|
|
|
16
17
|
| { readonly status: 'ambiguous'; readonly input: string; readonly candidates: readonly Record<string, unknown>[] }
|
|
17
18
|
| { readonly status: 'missing_lookup'; readonly usage: string };
|
|
18
19
|
|
|
20
|
+
type VoiceWorkflowStatus = 'ready' | 'attention' | 'setup-needed' | 'not-published';
|
|
21
|
+
|
|
19
22
|
interface RuntimeProviderStatus {
|
|
20
23
|
readonly id: string;
|
|
21
24
|
readonly state: string;
|
|
@@ -23,6 +26,21 @@ interface RuntimeProviderStatus {
|
|
|
23
26
|
readonly detail?: string;
|
|
24
27
|
}
|
|
25
28
|
|
|
29
|
+
interface VoiceInteractionWorkflow {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly label: string;
|
|
32
|
+
readonly status: VoiceWorkflowStatus;
|
|
33
|
+
readonly userOutcome: string;
|
|
34
|
+
readonly summary: string;
|
|
35
|
+
readonly nextStep: string;
|
|
36
|
+
readonly capabilities: readonly string[];
|
|
37
|
+
readonly modelRoute: string;
|
|
38
|
+
readonly userRoute?: string;
|
|
39
|
+
readonly setupRoutes: readonly string[];
|
|
40
|
+
readonly evidence: Record<string, unknown>;
|
|
41
|
+
readonly policy: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
26
44
|
function readString(value: unknown): string {
|
|
27
45
|
return typeof value === 'string' ? value.trim() : '';
|
|
28
46
|
}
|
|
@@ -70,6 +88,198 @@ function providerSearchText(provider: AgentWorkspaceVoiceMediaProviderStatus, ru
|
|
|
70
88
|
].join('\n').toLowerCase();
|
|
71
89
|
}
|
|
72
90
|
|
|
91
|
+
function voiceWorkflowSearchText(workflow: VoiceInteractionWorkflow): string {
|
|
92
|
+
return [
|
|
93
|
+
workflow.id,
|
|
94
|
+
workflow.label,
|
|
95
|
+
workflow.status,
|
|
96
|
+
workflow.userOutcome,
|
|
97
|
+
workflow.summary,
|
|
98
|
+
workflow.nextStep,
|
|
99
|
+
workflow.modelRoute,
|
|
100
|
+
workflow.userRoute ?? '',
|
|
101
|
+
...workflow.capabilities,
|
|
102
|
+
].join('\n').toLowerCase();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function voiceWorkflowMatches(workflow: VoiceInteractionWorkflow, query: string): boolean {
|
|
106
|
+
if (!query) return true;
|
|
107
|
+
const text = voiceWorkflowSearchText(workflow);
|
|
108
|
+
if (text.includes(query)) return true;
|
|
109
|
+
const tokens = query.split(/\s+/).map((token) => token.trim()).filter(Boolean);
|
|
110
|
+
return tokens.length > 0 && tokens.every((token) => text.includes(token));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function voiceWorkflowSummary(workflows: readonly VoiceInteractionWorkflow[]): Record<string, unknown> {
|
|
114
|
+
return {
|
|
115
|
+
total: workflows.length,
|
|
116
|
+
ready: workflows.filter((workflow) => workflow.status === 'ready').length,
|
|
117
|
+
attention: workflows.filter((workflow) => workflow.status === 'attention').length,
|
|
118
|
+
setupNeeded: workflows.filter((workflow) => workflow.status === 'setup-needed').length,
|
|
119
|
+
notPublished: workflows.filter((workflow) => workflow.status === 'not-published').length,
|
|
120
|
+
primaryNextStep: workflows.find((workflow) => workflow.status !== 'ready')?.nextStep
|
|
121
|
+
?? 'Voice workflows are ready; keep effects on explicit user-visible routes.',
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function hasReadyVoiceFeature(readiness: ReturnType<typeof buildReadiness>, featureIds: readonly string[]): boolean {
|
|
126
|
+
return readiness.voiceProviders.some((provider) => (
|
|
127
|
+
provider.setupState === 'ready'
|
|
128
|
+
&& featureIds.some((feature) => provider.features.includes(feature))
|
|
129
|
+
));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function hasRegisteredVoiceFeature(readiness: ReturnType<typeof buildReadiness>, featureIds: readonly string[]): boolean {
|
|
133
|
+
return readiness.voiceProviders.some((provider) => featureIds.some((feature) => provider.features.includes(feature)));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function buildVoiceInteractionWorkflows(
|
|
137
|
+
context: CommandContext,
|
|
138
|
+
readiness: ReturnType<typeof buildReadiness>,
|
|
139
|
+
): readonly VoiceInteractionWorkflow[] {
|
|
140
|
+
const liveDevice = deviceLiveReadModelSnapshot(context);
|
|
141
|
+
const pushToTalkRecords = certifiedDeviceLiveRecords(liveDevice, 'push-to-talk', ['push to talk', 'speech input', 'microphone']);
|
|
142
|
+
const transcriptionRecords = certifiedDeviceLiveRecords(liveDevice, 'voice-memo-transcription', ['voice memo', 'speech-to-text', 'audio transcription']);
|
|
143
|
+
const spokenResponseRecords = certifiedDeviceLiveRecords(liveDevice, 'spoken-responses', ['tts', 'spoken response', 'speaker']);
|
|
144
|
+
const wakeRecords = certifiedDeviceLiveRecords(liveDevice, 'wake-and-speak', ['wake word', 'always listening']);
|
|
145
|
+
const voiceEnabled = readConfigBoolean(context, 'ui.voiceEnabled', false);
|
|
146
|
+
const spokenTurnRuntime = typeof context.submitSpokenInput === 'function';
|
|
147
|
+
const stopSpokenOutputRuntime = typeof context.stopSpokenOutput === 'function';
|
|
148
|
+
const transcribeRuntime = typeof context.platform.voiceService?.transcribe === 'function';
|
|
149
|
+
const speechInputReady = hasReadyVoiceFeature(readiness, ['stt', 'realtime']);
|
|
150
|
+
const speechInputRegistered = hasRegisteredVoiceFeature(readiness, ['stt', 'realtime']);
|
|
151
|
+
const selectedTtsReady = readiness.selectedTtsProviderStatus === 'ready';
|
|
152
|
+
const spokenReady = spokenResponseRecords.length > 0 || (spokenTurnRuntime && selectedTtsReady && readiness.ttsVoiceConfigured);
|
|
153
|
+
const spokenAttention = spokenTurnRuntime && selectedTtsReady && !readiness.ttsVoiceConfigured;
|
|
154
|
+
const pushToTalkReady = pushToTalkRecords.length > 0 || (voiceEnabled && spokenTurnRuntime && speechInputReady);
|
|
155
|
+
const pushToTalkAttention = voiceEnabled && (spokenTurnRuntime || speechInputRegistered);
|
|
156
|
+
const transcriptionReady = transcriptionRecords.length > 0 || (transcribeRuntime && speechInputReady);
|
|
157
|
+
const transcriptionAttention = transcribeRuntime || speechInputRegistered;
|
|
158
|
+
|
|
159
|
+
return [
|
|
160
|
+
{
|
|
161
|
+
id: 'push-to-talk',
|
|
162
|
+
label: 'Push-to-talk input',
|
|
163
|
+
status: pushToTalkReady ? 'ready' : pushToTalkAttention ? 'attention' : 'setup-needed',
|
|
164
|
+
userOutcome: 'Speak a short prompt when the local voice surface and speech-input provider are ready.',
|
|
165
|
+
summary: pushToTalkReady
|
|
166
|
+
? pushToTalkRecords.length > 0 ? 'The SDK/daemon published a certified push-to-talk route with permission-scoped microphone evidence.' : 'Voice surface, spoken-turn runtime, and a ready STT/realtime provider are available.'
|
|
167
|
+
: pushToTalkAttention
|
|
168
|
+
? 'Some voice input pieces are present, but the full push-to-talk route is not ready.'
|
|
169
|
+
: 'Voice input needs the voice surface plus a ready STT or realtime provider.',
|
|
170
|
+
nextStep: pushToTalkReady
|
|
171
|
+
? 'Use the visible voice surface when the user asks to speak to the assistant.'
|
|
172
|
+
: 'Review /voice and media provider posture before presenting push-to-talk as available.',
|
|
173
|
+
capabilities: ['push-to-talk', 'speech input', 'stt', 'realtime voice'],
|
|
174
|
+
modelRoute: 'agent_harness mode:"media_posture" query:"push to talk" includeParameters:true',
|
|
175
|
+
userRoute: '/voice review',
|
|
176
|
+
setupRoutes: [
|
|
177
|
+
'settings action:"get" query:"ui.voiceEnabled" includeParameters:true',
|
|
178
|
+
'agent_harness mode:"media_posture" query:"stt realtime" includeParameters:true',
|
|
179
|
+
],
|
|
180
|
+
evidence: {
|
|
181
|
+
voiceSurfaceEnabled: voiceEnabled,
|
|
182
|
+
spokenTurnRuntime,
|
|
183
|
+
speechInputReady,
|
|
184
|
+
speechInputRegistered,
|
|
185
|
+
...(pushToTalkRecords.length > 0 ? { certifiedLiveRecords: pushToTalkRecords.slice(0, 5) } : {}),
|
|
186
|
+
},
|
|
187
|
+
policy: 'Voice input stays a visible local operator surface; provider setup and transcript submission are separate explicit routes.',
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: 'voice-memo-transcription',
|
|
191
|
+
label: 'Voice memo transcription',
|
|
192
|
+
status: transcriptionReady ? 'ready' : transcriptionAttention ? 'attention' : 'setup-needed',
|
|
193
|
+
userOutcome: 'Transcribe an audio note only when a speech-to-text provider and runtime route are both present.',
|
|
194
|
+
summary: transcriptionReady
|
|
195
|
+
? transcriptionRecords.length > 0 ? 'The SDK/daemon published a certified voice memo transcription route.' : 'Speech-to-text provider and voiceService.transcribe are available.'
|
|
196
|
+
: transcriptionAttention
|
|
197
|
+
? 'A speech-to-text provider or runtime route is present, but transcription is not fully ready.'
|
|
198
|
+
: 'No ready speech-to-text transcription route is available.',
|
|
199
|
+
nextStep: transcriptionReady
|
|
200
|
+
? 'Route audio transcription through reviewed media or connected-host voice routes when the user supplies audio.'
|
|
201
|
+
: 'Configure a provider with STT capability and confirm the runtime exposes voiceService.transcribe.',
|
|
202
|
+
capabilities: ['voice memo', 'speech-to-text', 'audio transcription'],
|
|
203
|
+
modelRoute: 'agent_harness mode:"media_posture" query:"voice memo transcription" includeParameters:true',
|
|
204
|
+
setupRoutes: [
|
|
205
|
+
'agent_harness mode:"media_posture" query:"stt" includeParameters:true',
|
|
206
|
+
'host action:"methods" query:"voice.stt"',
|
|
207
|
+
],
|
|
208
|
+
evidence: {
|
|
209
|
+
transcribeRuntime,
|
|
210
|
+
speechInputReady,
|
|
211
|
+
speechInputRegistered,
|
|
212
|
+
...(transcriptionRecords.length > 0 ? { certifiedLiveRecords: transcriptionRecords.slice(0, 5) } : {}),
|
|
213
|
+
},
|
|
214
|
+
policy: 'Audio bytes are not printed into chat; transcription must use reviewed media or connected-host voice routes.',
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: 'spoken-responses',
|
|
218
|
+
label: 'Spoken responses',
|
|
219
|
+
status: spokenReady ? 'ready' : spokenAttention ? 'attention' : 'setup-needed',
|
|
220
|
+
userOutcome: 'Play an assistant answer aloud with a predictable TTS provider and voice.',
|
|
221
|
+
summary: spokenReady
|
|
222
|
+
? spokenResponseRecords.length > 0 ? 'The SDK/daemon published a certified spoken-response route.' : 'Spoken-turn runtime, selected TTS provider, and voice setting are ready.'
|
|
223
|
+
: spokenAttention
|
|
224
|
+
? 'Spoken-turn runtime and provider are ready, but the exact voice is not configured.'
|
|
225
|
+
: 'Spoken responses need a ready selected TTS provider and runtime spoken-turn route.',
|
|
226
|
+
nextStep: spokenReady
|
|
227
|
+
? 'Use /tts only when the user asks for spoken output.'
|
|
228
|
+
: 'Choose a ready TTS provider and voice, then verify the runtime exposes submitSpokenInput.',
|
|
229
|
+
capabilities: ['tts', 'spoken answer', 'stop spoken output'],
|
|
230
|
+
modelRoute: 'agent_harness mode:"media_posture" query:"spoken responses" includeParameters:true',
|
|
231
|
+
userRoute: '/tts <prompt>',
|
|
232
|
+
setupRoutes: [
|
|
233
|
+
'agent_harness mode:"open_ui_surface" surfaceId:"tts-provider-picker" confirm:true explicitUserRequest:"..."',
|
|
234
|
+
'agent_harness mode:"open_ui_surface" surfaceId:"tts-voice-picker" confirm:true explicitUserRequest:"..."',
|
|
235
|
+
],
|
|
236
|
+
evidence: {
|
|
237
|
+
spokenTurnRuntime,
|
|
238
|
+
stopSpokenOutputRuntime,
|
|
239
|
+
selectedTtsProviderStatus: readiness.selectedTtsProviderStatus,
|
|
240
|
+
ttsVoiceConfigured: readiness.ttsVoiceConfigured,
|
|
241
|
+
...(spokenResponseRecords.length > 0 ? { certifiedLiveRecords: spokenResponseRecords.slice(0, 5) } : {}),
|
|
242
|
+
},
|
|
243
|
+
policy: 'Spoken turns submit normal assistant prompts and may call model/speech providers; playback stop is local runtime control.',
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
id: 'wake-and-speak',
|
|
247
|
+
label: 'Wake and speak',
|
|
248
|
+
status: wakeRecords.length > 0 ? 'ready' : 'not-published',
|
|
249
|
+
userOutcome: 'Use wake-word or always-listening input only after a permission-scoped runtime contract exists.',
|
|
250
|
+
summary: wakeRecords.length > 0
|
|
251
|
+
? 'The SDK/daemon published certified wake-word evidence with permission scope, receipt metadata, and exact control routes.'
|
|
252
|
+
: 'Wake-word or always-listening voice capture is not published by the current Agent runtime contract.',
|
|
253
|
+
nextStep: wakeRecords.length > 0
|
|
254
|
+
? 'Inspect the certified wake-word route and keep microphone capture on visible permission controls.'
|
|
255
|
+
: 'Use explicit voice input or /tts until a wake-word route is published with visible permission controls.',
|
|
256
|
+
capabilities: ['wake word', 'always listening', 'permission repair'],
|
|
257
|
+
modelRoute: wakeRecords[0]?.modelRoute ?? 'agent_harness mode:"media_posture" query:"wake word" includeParameters:true',
|
|
258
|
+
setupRoutes: [
|
|
259
|
+
...wakeRecords.slice(0, 3).map((record) => record.modelRoute),
|
|
260
|
+
'agent_harness mode:"media_posture" query:"push to talk" includeParameters:true',
|
|
261
|
+
'agent_harness mode:"pairing_posture" query:"device" includeParameters:true',
|
|
262
|
+
],
|
|
263
|
+
evidence: {
|
|
264
|
+
publishedByCurrentAgentContract: wakeRecords.length > 0,
|
|
265
|
+
...(wakeRecords.length > 0 ? { certifiedLiveRecords: wakeRecords.slice(0, 5) } : {}),
|
|
266
|
+
},
|
|
267
|
+
policy: 'Agent does not claim always-listening behavior without an explicit permission-scoped runtime contract and certified SDK/daemon receipt evidence.',
|
|
268
|
+
},
|
|
269
|
+
];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function describeVoiceWorkflow(workflow: VoiceInteractionWorkflow, includeParameters: boolean): Record<string, unknown> {
|
|
273
|
+
if (includeParameters) return { ...workflow };
|
|
274
|
+
return {
|
|
275
|
+
workflowId: workflow.id,
|
|
276
|
+
label: workflow.label,
|
|
277
|
+
status: workflow.status,
|
|
278
|
+
summary: previewHarnessText(workflow.summary),
|
|
279
|
+
modelRoute: workflow.modelRoute,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
73
283
|
function describeProviderCandidate(provider: AgentWorkspaceVoiceMediaProviderStatus): Record<string, unknown> {
|
|
74
284
|
return {
|
|
75
285
|
mediaProviderId: `${provider.domain}:${provider.id}`,
|
|
@@ -124,7 +334,7 @@ function describeProvider(
|
|
|
124
334
|
inspectPosture: 'agent_harness mode:"media_posture"',
|
|
125
335
|
inspectProvider: 'agent_harness mode:"media_provider"',
|
|
126
336
|
generateMedia: 'agent_media_generate with confirm:true and explicitUserRequest',
|
|
127
|
-
ttsSettings: '
|
|
337
|
+
ttsSettings: 'settings action:"list|get|set" for tts.provider, tts.voice, tts.llmProvider, and tts.llmModel',
|
|
128
338
|
},
|
|
129
339
|
policy: {
|
|
130
340
|
effect: 'read-only',
|
|
@@ -179,12 +389,14 @@ function buildReadiness(context: CommandContext) {
|
|
|
179
389
|
|
|
180
390
|
export function mediaPostureCatalogStatus(context: CommandContext): Record<string, unknown> {
|
|
181
391
|
const readiness = buildReadiness(context);
|
|
392
|
+
const voiceWorkflows = buildVoiceInteractionWorkflows(context, readiness);
|
|
182
393
|
return {
|
|
183
394
|
modes: ['media_posture', 'media_provider'],
|
|
184
395
|
voiceProviders: readiness.voiceProviders.length,
|
|
185
396
|
mediaProviders: readiness.mediaProviders.length,
|
|
186
397
|
readyVoiceProviders: readiness.readyVoiceProviderCount,
|
|
187
398
|
readyMediaProviders: readiness.readyMediaProviderCount,
|
|
399
|
+
voiceWorkflowSummary: voiceWorkflowSummary(voiceWorkflows),
|
|
188
400
|
readOnly: true,
|
|
189
401
|
};
|
|
190
402
|
}
|
|
@@ -199,9 +411,14 @@ export async function mediaPostureSummary(context: CommandContext, args: AgentHa
|
|
|
199
411
|
...readiness.mediaProviders.map((provider) => ({ provider, runtimeStatus: mediaStatuses.get(provider.id) })),
|
|
200
412
|
];
|
|
201
413
|
const query = readString(args.query).toLowerCase();
|
|
414
|
+
const limit = readLimit(args.limit, 100);
|
|
415
|
+
const voiceWorkflows = buildVoiceInteractionWorkflows(context, readiness);
|
|
202
416
|
const filtered = providers
|
|
203
417
|
.filter(({ provider, runtimeStatus }) => !query || providerSearchText(provider, runtimeStatus).includes(query))
|
|
204
|
-
.slice(0,
|
|
418
|
+
.slice(0, limit);
|
|
419
|
+
const filteredVoiceWorkflows = voiceWorkflows
|
|
420
|
+
.filter((workflow) => voiceWorkflowMatches(workflow, query))
|
|
421
|
+
.slice(0, limit);
|
|
205
422
|
return {
|
|
206
423
|
status: 'available',
|
|
207
424
|
summary: {
|
|
@@ -215,6 +432,8 @@ export async function mediaPostureSummary(context: CommandContext, args: AgentHa
|
|
|
215
432
|
ttsResponseRouteConfigured: readiness.ttsResponseRouteConfigured,
|
|
216
433
|
voiceSurfaceEnabled: readConfigBoolean(context, 'ui.voiceEnabled', false),
|
|
217
434
|
browserToolState: readiness.browserToolState,
|
|
435
|
+
voiceWorkflows: voiceWorkflowSummary(voiceWorkflows),
|
|
436
|
+
returnedVoiceWorkflows: filteredVoiceWorkflows.length,
|
|
218
437
|
artifactStoreAvailable: Boolean(context.platform.artifactStore),
|
|
219
438
|
ttsProviderSetting: readConfigString(context, 'tts.provider') || null,
|
|
220
439
|
ttsVoiceSettingConfigured: readConfigString(context, 'tts.voice').length > 0,
|
|
@@ -223,6 +442,7 @@ export async function mediaPostureSummary(context: CommandContext, args: AgentHa
|
|
|
223
442
|
providers: filtered.map(({ provider, runtimeStatus }) => describeProvider(provider, runtimeStatus, {
|
|
224
443
|
includeParameters: args.includeParameters === true,
|
|
225
444
|
})),
|
|
445
|
+
voiceWorkflows: filteredVoiceWorkflows.map((workflow) => describeVoiceWorkflow(workflow, args.includeParameters === true)),
|
|
226
446
|
returned: filtered.length,
|
|
227
447
|
total: providers.length,
|
|
228
448
|
policy: 'Read-only voice/media posture. Media generation, voice enable/disable, TTS setting changes, and bundle export stay confirmation-gated through first-class tools, settings modes, workspace actions, or slash-command mirrors.',
|
|
@@ -232,6 +452,7 @@ export async function mediaPostureSummary(context: CommandContext, args: AgentHa
|
|
|
232
452
|
singleProviderMode: 'media_provider',
|
|
233
453
|
ttsProviderPicker: 'agent_harness mode:"open_ui_surface" surfaceId:"tts-provider-picker" confirm:true explicitUserRequest:"..."',
|
|
234
454
|
ttsVoicePicker: 'agent_harness mode:"open_ui_surface" surfaceId:"tts-voice-picker" confirm:true explicitUserRequest:"..."',
|
|
455
|
+
voiceWorkflowPosture: 'agent_harness mode:"media_posture" query:"push to talk" includeParameters:true',
|
|
235
456
|
commands: ['/media providers', '/voice review', '/tts <prompt>', '/image <path>'],
|
|
236
457
|
} } : {}),
|
|
237
458
|
};
|