@pellux/goodvibes-agent 1.5.2 → 1.5.3

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.
@@ -3,7 +3,7 @@ import { buildAgentWorkspaceRuntimeSnapshot } from '../input/agent-workspace-sna
3
3
  import type { AgentWorkspaceLocalLibraryItem } from '../input/agent-workspace-types.ts';
4
4
  import { previewHarnessText } from './agent-harness-text.ts';
5
5
  import { buildLearningCandidates, candidateSearchText } from './agent-harness-learning-curator-proposals.ts';
6
- import { clampScore, isReviewed, localRegistryRoute, missingRequirementCount, routeValue, scoresForItem } from './agent-harness-learning-curator-common.ts';
6
+ import { clampScore, localRegistryRoute, missingRequirementCount, routeValue, scoresForItem } from './agent-harness-learning-curator-common.ts';
7
7
  import type { AgentHarnessLearningCuratorArgs, LearningCandidate, LearningCandidateResolution, LearningConsolidationBatchPlan, LearningPromptPlan, LocalLearningCandidateDomain } from './agent-harness-learning-curator-types.ts';
8
8
  export type { LearningCandidate, LearningCandidateResolution, LearningConsolidationDiff, LearningConsolidationFields, LearningConsolidationPlan } from './agent-harness-learning-curator-types.ts';
9
9
 
@@ -184,7 +184,7 @@ function promptRecordPriority(item: AgentWorkspaceLocalLibraryItem): number {
184
184
  }
185
185
 
186
186
  function isPromptEligibleRecord(domain: LocalLearningCandidateDomain, item: AgentWorkspaceLocalLibraryItem): boolean {
187
- if (!isReviewed(item)) return false;
187
+ // Fully autonomous learning: review gate removed — records qualify without human review.
188
188
  if (domain === 'memory') return (item.confidence ?? 100) >= 70;
189
189
  if (domain === 'persona') return item.active === true;
190
190
  if (domain === 'skill' || domain === 'skill_bundle' || domain === 'routine') {
@@ -202,10 +202,10 @@ function promptRecordEntry(domain: LocalLearningCandidateDomain, item: AgentWork
202
202
  priority: promptRecordPriority(item),
203
203
  scores: scoresForItem(item),
204
204
  reason: domain === 'memory'
205
- ? 'Reviewed high-confidence memory is eligible for prompt recall.'
205
+ ? 'High-confidence memory (confidence ≥ 70) is eligible for prompt recall.'
206
206
  : domain === 'persona'
207
- ? 'Reviewed active persona is eligible for prompt personality context.'
208
- : 'Reviewed, enabled, setup-ready behavior is eligible for prompt context.',
207
+ ? 'Active persona is eligible for prompt personality context.'
208
+ : 'Enabled, setup-ready behavior is eligible for prompt context.',
209
209
  inspectRoute: localRegistryRoute(domain, 'get', item.id),
210
210
  ...(includeParameters ? {
211
211
  reviewState: item.reviewState,
@@ -280,9 +280,9 @@ function learningPromptPlan(context: CommandContext, candidates: readonly Learni
280
280
  consolidationQueue: consolidationCandidates.slice(0, includeParameters ? 8 : 3).map((candidate) => promptPlanCandidate(candidate, includeParameters)),
281
281
  suppressed,
282
282
  orderingRules: [
283
- 'Prompt context stays limited to reviewed high-confidence memory and reviewed setup-ready enabled behavior.',
284
- 'Usefulness, freshness, source-quality, and risk scores drive review priority before durable context expands.',
285
- 'Low-confidence, stale, setup-blocked, unreviewed, blocked VIBE.md, and duplicate records stay suppressed until reviewed or repaired.',
283
+ 'Prompt context is limited to high-confidence memory (confidence ≥ 70), active personas, and enabled setup-ready behavior.',
284
+ 'Usefulness, freshness, source-quality, and risk scores drive promotion priority and determine which records guide the assistant.',
285
+ 'Low-confidence, stale, setup-blocked, blocked VIBE.md, and duplicate records stay suppressed until their confidence or enabled/active state is repaired.',
286
286
  'Proposals from notes, completed work, completed research, and saved sessions require explicit create or promotion routes before they can guide the assistant.',
287
287
  ],
288
288
  routes: {
@@ -336,7 +336,7 @@ export function learningCuratorSummary(context: CommandContext, args: AgentHarne
336
336
  returned: Math.min(filtered.length, limit),
337
337
  total: all.length,
338
338
  nextActions: nextActions(all),
339
- policy: 'Learning curator is read-only. Proposed memory and behavior changes use reviewed notes, completed work-plan items, completed research runs, saved sessions, VIBE.md personality health cards, duplicate consolidation, and existing confirmed capture routes; durable context still requires provenance, review, rollback via stale/delete routes, and explicit user intent for writes or promotion.',
339
+ policy: 'Learning curator is read-only. Proposed memory and behavior changes use notes, completed work-plan items, completed research runs, saved sessions, VIBE.md personality health cards, duplicate consolidation, and existing confirmed capture routes; durable context still requires provenance, rollback via stale/delete routes, and explicit user intent for writes or promotion.',
340
340
  };
341
341
  }
342
342
 
@@ -100,6 +100,7 @@ export const HARNESS_MODE_DESCRIPTORS: readonly HarnessModeDescriptor[] = [
100
100
  { id: 'autonomy_queue_item', kind: 'inspect', family: 'personal-ops', summary: 'Inspect one autonomy card, live records, tails, and routes.', next: 'Prefer autonomy action:"item".', parameters: ['queueItemId', 'target', 'query'] },
101
101
  { id: 'learning_curator', kind: 'discover', family: 'personal-ops', summary: 'Rank memory, notes, personas, skills, routines review/proposals.', next: 'Prefer memory action:"curator"; use action:"candidate" for one card.', parameters: ['query', 'limit', 'includeParameters'] },
102
102
  { id: 'learning_candidate', kind: 'inspect', family: 'personal-ops', summary: 'Inspect one local-learning candidate and safe existing routes.', parameters: ['candidateId', 'target', 'query'] },
103
+ { id: 'learning_auto_promote', kind: 'effect', family: 'personal-ops', summary: 'Auto-promote ready-to-promote; consolidate duplicates autonomously.', next: 'Use memory action:"curator" to see eligible items, then invoke.', keywords: ['auto promote', 'autonomous promotion', 'consolidate duplicates', 'learning loop', 'auto learning'] },
103
104
  { id: 'research_briefing', kind: 'discover', family: 'research', summary: 'Show one next-action queue for research runs, sources, reports.', next: 'Use research action:"briefing".', keywords: ['deep research', 'research cockpit', 'research next actions', 'research queue', 'source review', 'report queue'], parameters: ['query', 'target', 'limit', 'includeParameters'] },
104
105
  { id: 'research_workflow', kind: 'discover', family: 'research', summary: 'Plan research routes across run, source, report, browser, Knowledge.', next: 'Use research action:"plan"; runner checks browser readiness only.', parameters: ['query', 'target', 'runId', 'includeParameters'] },
105
106
  { id: 'research_runs', kind: 'discover', family: 'research', summary: 'List deep-research runs, phase, log tails, and controls.', next: 'Use research_run for one run.', parameters: ['query', 'limit', 'includeParameters'] },
@@ -146,6 +147,21 @@ export const HARNESS_MODE_DESCRIPTORS: readonly HarnessModeDescriptor[] = [
146
147
  { id: 'daemon', kind: 'alias', family: 'connected-host', summary: 'GoodVibes daemon -> connected_host; mutations use confirmed methods.', next: 'Prefer host action:"capabilities".', aliases: ['connected_host'], parameters: ['includeParameters'] },
147
148
  { id: 'daemon_status', kind: 'alias', family: 'connected-host', summary: 'GoodVibes daemon status -> connected_host_status.', next: 'Prefer host action:"status".', aliases: ['connected_host_status'], parameters: ['includeParameters'] },
148
149
  { id: 'propose_skill_drafts', kind: 'effect', family: 'personal-ops', summary: 'Draft up to 3 skills from recent learning candidates for review.', requiresConfirmation: true, keywords: ['skill draft', 'propose skill', 'auto-propose', 'draft skills', 'learning draft'], parameters: ['confirm', 'explicitUserRequest'] },
150
+ { id: 'remote_snapshot', kind: 'inspect', family: 'remote', summary: 'Route for the remote execution snapshot.', next: 'Call agent_operator_method methodId:"remote.snapshot".', keywords: ['remote execution', 'remote peers', 'remote work', 'distributed execution', 'remote runner', 'pair requests', 'remote snapshot'], parameters: ['includeParameters'] },
151
+ { id: 'remote_peers', kind: 'inspect', family: 'remote', summary: 'Route to list connected remote peers.', next: 'Call agent_operator_method methodId:"remote.peers.list".', keywords: ['remote peers', 'connected peers', 'distributed execution', 'remote runner', 'peer list'], parameters: ['includeParameters'] },
152
+ { id: 'remote_work', kind: 'inspect', family: 'remote', summary: 'Route to list queued and leased remote work.', next: 'Call agent_operator_method methodId:"remote.work.list".', keywords: ['remote work', 'queued work', 'leased work', 'distributed execution', 'remote work items', 'work queue'], parameters: ['includeParameters'] },
153
+ { id: 'remote_pair_requests', kind: 'inspect', family: 'remote', summary: 'Route to list remote pair requests.', next: 'Call agent_operator_method methodId:"remote.pair.requests.list".', keywords: ['remote pairing', 'pair requests', 'pending pair', 'distributed execution', 'remote pair'], parameters: ['includeParameters'] },
154
+ { id: 'remote_pair_approve', kind: 'effect', family: 'remote', summary: 'Confirmed handoff to approve a remote pair request.', requiresConfirmation: true, next: 'Execute the returned modelRoute with agent_operator_method.', keywords: ['approve pair request', 'remote pairing', 'accept pair', 'distributed execution'], parameters: ['requestId', 'target', 'note', 'confirm', 'explicitUserRequest'] },
155
+ { id: 'remote_pair_reject', kind: 'effect', family: 'remote', summary: 'Confirmed handoff to reject a remote pair request.', requiresConfirmation: true, next: 'Execute the returned modelRoute with agent_operator_method.', keywords: ['reject pair request', 'remote pairing', 'deny pair', 'distributed execution'], parameters: ['requestId', 'target', 'note', 'confirm', 'explicitUserRequest'] },
156
+ { id: 'remote_peers_invoke', kind: 'effect', family: 'remote', summary: 'Confirmed handoff to invoke a command on a peer.', requiresConfirmation: true, next: 'Execute the returned modelRoute with agent_operator_method.', keywords: ['peer invoke', 'remote command', 'invoke peer', 'distributed execution', 'remote execution'], parameters: ['peerId', 'command', 'payload', 'target', 'confirm', 'explicitUserRequest'] },
157
+ { id: 'remote_work_cancel', kind: 'effect', family: 'remote', summary: 'Confirmed handoff to cancel a remote work item.', requiresConfirmation: true, next: 'Execute the returned modelRoute with agent_operator_method.', keywords: ['cancel work', 'remote work cancel', 'cancel remote work', 'distributed execution'], parameters: ['workId', 'target', 'reason', 'confirm', 'explicitUserRequest'] },
158
+ { id: 'unified_inbox', kind: 'inspect', family: 'comms', summary: 'Aggregated unified inbox: deliveries, messages, routes.', next: 'Call channel_triage first; inbox aggregates triage.', keywords: ['inbox', 'unified inbox', 'deliveries', 'surface messages', 'route bindings', 'channel inbox', 'message feed'], parameters: ['limit'] },
159
+ { id: 'channel_drafts', kind: 'discover', family: 'comms', summary: 'List or inspect channel message drafts.', next: 'Use draftId to inspect one draft.', keywords: ['draft', 'channel draft', 'message draft', 'saved draft', 'pending message'], parameters: ['draftId', 'draftStatus', 'target', 'query', 'limit'] },
160
+ { id: 'channel_draft_save', kind: 'effect', family: 'comms', summary: 'Save or update a channel message draft.', requiresConfirmation: true, next: 'After save, use channel_draft_send to deliver.', keywords: ['save draft', 'create draft', 'update draft', 'draft message', 'compose'], parameters: ['draftId', 'draftMessage', 'draftTitle', 'draftChannel', 'draftRoute', 'draftWebhook', 'draftLink', 'draftTags', 'confirm', 'explicitUserRequest'] },
161
+ { id: 'channel_draft_send', kind: 'effect', family: 'comms', summary: 'Queue a draft and send via confirmed delivery path.', requiresConfirmation: true, next: 'Sends via agent_channel_send delivery path.', keywords: ['send draft', 'deliver draft', 'queue draft', 'draft send', 'channel send draft'], parameters: ['draftId', 'target', 'confirm', 'explicitUserRequest'] },
162
+ { id: 'channel_routing', kind: 'discover', family: 'comms', summary: 'List or inspect channel-to-profile routing rules.', next: 'Use surfaceKind/routeId to inspect one route.', keywords: ['channel routing', 'profile routing', 'routing rules', 'channel profile', 'surface routing'], parameters: ['surfaceKind', 'draftRoute', 'target', 'query', 'limit'] },
163
+ { id: 'channel_routing_assign', kind: 'effect', family: 'comms', summary: 'Assign a channel surface to a profile route.', requiresConfirmation: true, next: 'Check channel_routing to see saved assignments.', keywords: ['assign channel', 'profile assign', 'routing assign', 'channel to profile', 'route assign'], parameters: ['surfaceKind', 'profileId', 'draftRoute', 'routeLabel', 'confirm', 'explicitUserRequest'] },
164
+ { id: 'channel_routing_remove', kind: 'effect', family: 'comms', summary: 'Remove a channel-to-profile routing assignment.', requiresConfirmation: true, next: 'Check channel_routing after removal.', keywords: ['remove routing', 'delete routing', 'routing remove', 'profile unassign', 'unassign channel'], parameters: ['draftRoute', 'target', 'confirm', 'explicitUserRequest'] },
149
165
  ] as const;
150
166
 
151
167
  function readString(value: unknown): string {
@@ -315,8 +315,9 @@ export function connectorToolCount(signals: readonly PersonalOpsConnectorSignal[
315
315
  }
316
316
 
317
317
  export function workflowStatus(methodIds: readonly string[], connectors: readonly PersonalOpsConnectorSignal[]): PersonalOpsWorkflowStatus {
318
- if (methodIds.length > 0 || connectorReady(connectors)) return 'ready';
318
+ if (connectorReady(connectors)) return 'ready';
319
319
  if (connectors.length > 0) return 'attention';
320
+ if (methodIds.length > 0) return 'ready';
320
321
  return 'needs-setup';
321
322
  }
322
323
 
@@ -5,6 +5,7 @@ import { providerBackedQueueRecords } from './agent-harness-personal-ops-provide
5
5
  import { providerBackedReminderRecords, providerBackedTaskRecords } from './agent-harness-personal-ops-provider-task-records.ts';
6
6
  import { channelRecords, connectorRecords, localRecord, refreshableSavedRecordCount, reminderOperationRecords, routineReceiptRecord, savedProviderEffectReceiptRecords, savedReviewArtifactRecords, savedReviewQueueRecords, taskOperationRecords } from './agent-harness-personal-ops-records.ts';
7
7
  import type { McpToolRecord, McpToolSchema, PersonalOpsLane, PersonalOpsLiveRecord } from './agent-harness-personal-ops-types.ts';
8
+ import { buildStyleReplyLaneAdditions } from '../agent/email/style-reply-lane.ts';
8
9
 
9
10
  export function buildLanes(
10
11
  context: CommandContext,
@@ -44,6 +45,8 @@ export function buildLanes(
44
45
  && routine.reviewState === 'reviewed'
45
46
  && (routine.missingRequirementCount ?? 0) === 0
46
47
  )).length;
48
+ const hasEmailCapability = emailMethods.length > 0 || emailConnectors.length > 0;
49
+ const styleReplyAdditions = buildStyleReplyLaneAdditions(hasEmailCapability);
47
50
 
48
51
  return [
49
52
  {
@@ -55,7 +58,7 @@ export function buildLanes(
55
58
  outcome: 'Triage inbound email or message inboxes, summarize threads, draft replies, and send only after confirmation.',
56
59
  current: inboxProviderRecords.length > 0
57
60
  ? 'Fresh provider-backed inbox thread records are published by the connected daemon or SDK read model; Personal Ops can triage current queue state without inventing sends, labels, or archive effects.'
58
- : emailMethods.length > 0
61
+ : emailMethods.length > 0 && emailConnectors.length === 0 && inboxArtifactRecords.length === 0 && inboxEffectReceiptRecords.length === 0
59
62
  ? 'The daemon contract exposes email-like methods; Personal Ops workflow cards now guide inbox triage and draft boundaries around exact methods.'
60
63
  : emailConnectors.length > 0
61
64
  ? 'A configured MCP connector looks email-capable; Personal Ops workflow cards now guide inbox triage, schema-derived operation records, and draft boundaries around its exact tools.'
@@ -66,7 +69,7 @@ export function buildLanes(
66
69
  : 'No email/IMAP/SMTP methods are present in the current GoodVibes SDK operator contract.',
67
70
  next: inboxProviderRecords.length > 0
68
71
  ? 'Inspect one fresh thread record, summarize or draft locally, and use only the published confirmed follow-up routes for replies, sends, labels, or archive.'
69
- : emailMethods.length > 0
72
+ : emailMethods.length > 0 && emailConnectors.length === 0 && inboxArtifactRecords.length === 0 && inboxEffectReceiptRecords.length === 0
70
73
  ? 'Use the inbox workflow cards to inspect exact methods, read selected threads, summarize priorities, and keep send as a separate confirmation.'
71
74
  : emailConnectors.length > 0
72
75
  ? 'Use the inbox workflow cards and operation records to inspect matching MCP connector schemas, then route triage only through reviewed connector actions.'
@@ -89,13 +92,14 @@ export function buildLanes(
89
92
  ],
90
93
  methodIds: emailMethods,
91
94
  connectorSignals: emailConnectors,
92
- workflows: inboxWorkflows(emailMethods, emailConnectors),
95
+ workflows: [...inboxWorkflows(emailMethods, emailConnectors), styleReplyAdditions.workflow],
93
96
  liveRecords: [
94
97
  ...inboxProviderRecords,
95
98
  ...inboxReviewQueueRecords,
96
99
  ...inboxArtifactRecords,
97
100
  ...inboxEffectReceiptRecords,
98
101
  ...connectorRecords(emailConnectors, 'Inbox', 'inbox'),
102
+ styleReplyAdditions.liveRecord,
99
103
  ],
100
104
  },
101
105
  {
@@ -107,7 +111,7 @@ export function buildLanes(
107
111
  outcome: 'Read agenda context, identify conflicts, prepare briefings, and create reminders for calendar-driven work.',
108
112
  current: calendarProviderRecords.length > 0
109
113
  ? 'Fresh provider-backed calendar event records are published by the connected daemon or SDK read model; Personal Ops can brief current agenda and conflict state without inventing event edits or RSVP effects.'
110
- : calendarMethods.length > 0
114
+ : calendarMethods.length > 0 && calendarConnectors.length === 0 && calendarArtifactRecords.length === 0 && calendarEffectReceiptRecords.length === 0
111
115
  ? 'The daemon contract exposes calendar-like methods; Personal Ops workflow cards now guide agenda briefing and conflict-scan boundaries.'
112
116
  : calendarConnectors.length > 0
113
117
  ? 'A configured MCP connector looks calendar-capable; Personal Ops workflow cards now guide agenda briefing, schema-derived operation records, and conflict-scan boundaries around its exact tools.'
@@ -118,7 +122,7 @@ export function buildLanes(
118
122
  : 'No calendar/CalDAV/agenda methods are present in the current GoodVibes SDK operator contract.',
119
123
  next: calendarProviderRecords.length > 0
120
124
  ? 'Inspect one fresh event record, brief the agenda or conflicts locally, and use only the published confirmed follow-up routes for edits, RSVP, reschedule, or delete.'
121
- : calendarMethods.length > 0
125
+ : calendarMethods.length > 0 && calendarConnectors.length === 0 && calendarArtifactRecords.length === 0 && calendarEffectReceiptRecords.length === 0
122
126
  ? 'Use the calendar workflow cards to inspect exact methods, fetch a bounded agenda window, and propose reminders or follow-ups.'
123
127
  : calendarConnectors.length > 0
124
128
  ? 'Use the calendar workflow cards and operation records to inspect matching MCP connector schemas, then route agenda work only through reviewed connector actions.'
@@ -0,0 +1,220 @@
1
+ import type { CommandContext } from '../input/command-registry.ts';
2
+ import { previewHarnessText } from './agent-harness-text.ts';
3
+
4
+ export interface AgentHarnessRemoteArgs {
5
+ readonly target?: unknown;
6
+ readonly query?: unknown;
7
+ readonly peerId?: unknown;
8
+ readonly requestId?: unknown;
9
+ readonly workId?: unknown;
10
+ readonly command?: unknown;
11
+ readonly payload?: unknown;
12
+ readonly note?: unknown;
13
+ readonly reason?: unknown;
14
+ readonly limit?: unknown;
15
+ readonly includeParameters?: unknown;
16
+ }
17
+
18
+ type RemoteReadSurface =
19
+ | 'remote_snapshot'
20
+ | 'remote_peers'
21
+ | 'remote_work'
22
+ | 'remote_pair_requests';
23
+
24
+ type RemoteMutationSurface =
25
+ | 'remote_pair_approve'
26
+ | 'remote_pair_reject'
27
+ | 'remote_peers_invoke'
28
+ | 'remote_work_cancel';
29
+
30
+ type RemoteSurface = RemoteReadSurface | RemoteMutationSurface;
31
+
32
+ interface RemoteMethodHandoff {
33
+ readonly surface: RemoteSurface;
34
+ readonly methodId: string;
35
+ readonly route: string;
36
+ readonly effect: 'confirmed-connected-host-state';
37
+ readonly confirmationRequired: true;
38
+ readonly input: Record<string, unknown>;
39
+ readonly modelRoute: string;
40
+ readonly policy: string;
41
+ }
42
+
43
+ function readString(value: unknown): string {
44
+ return typeof value === 'string' ? value.trim() : '';
45
+ }
46
+
47
+ function readRecord(value: unknown): Record<string, unknown> {
48
+ return value && typeof value === 'object' && !Array.isArray(value)
49
+ ? value as Record<string, unknown>
50
+ : {};
51
+ }
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // Catalog status (for summary mode inclusion)
55
+ // ---------------------------------------------------------------------------
56
+
57
+ export function remoteCatalogStatus(_context: CommandContext): Record<string, unknown> {
58
+ return {
59
+ modes: [
60
+ 'remote_snapshot',
61
+ 'remote_peers',
62
+ 'remote_work',
63
+ 'remote_pair_requests',
64
+ 'remote_pair_approve',
65
+ 'remote_pair_reject',
66
+ 'remote_peers_invoke',
67
+ 'remote_work_cancel',
68
+ ],
69
+ readModes: 4,
70
+ mutationModes: 4,
71
+ policy:
72
+ 'Distributed remote execution surface. Read modes call remote.snapshot, remote.peers.list, remote.work.list, and remote.pair.requests.list via agent_operator_method. Mutation modes (approve/reject pair requests, invoke peer command, cancel work) require confirm:true and explicitUserRequest and return an agent_operator_method handoff.',
73
+ };
74
+ }
75
+
76
+ // ---------------------------------------------------------------------------
77
+ // Read surfaces — return route descriptors; agent_operator_method executes them
78
+ // ---------------------------------------------------------------------------
79
+
80
+ export function remoteSnapshotSummary(_args: AgentHarnessRemoteArgs): Record<string, unknown> {
81
+ return {
82
+ surface: 'remote_snapshot',
83
+ methodId: 'remote.snapshot',
84
+ route: 'GET /api/remote',
85
+ effect: 'read-only-network',
86
+ confirmationRequired: false,
87
+ modelRoute: previewHarnessText('agent_operator_method methodId:"remote.snapshot" input:{}'),
88
+ description:
89
+ 'Returns a distributed remote execution snapshot: connected peers, queued/leased work, and pair request counts.',
90
+ policy:
91
+ 'Read-only. Call agent_operator_method methodId:"remote.snapshot" input:{} to fetch the live snapshot from the daemon.',
92
+ };
93
+ }
94
+
95
+ export function remotePeersSummary(_args: AgentHarnessRemoteArgs): Record<string, unknown> {
96
+ return {
97
+ surface: 'remote_peers',
98
+ methodId: 'remote.peers.list',
99
+ route: 'GET /api/remote/peers',
100
+ effect: 'read-only-network',
101
+ confirmationRequired: false,
102
+ modelRoute: previewHarnessText('agent_operator_method methodId:"remote.peers.list" input:{}'),
103
+ description: 'Lists connected and recently-disconnected remote peers.',
104
+ policy:
105
+ 'Read-only. Call agent_operator_method methodId:"remote.peers.list" input:{} to fetch the live peer list.',
106
+ };
107
+ }
108
+
109
+ export function remoteWorkSummary(_args: AgentHarnessRemoteArgs): Record<string, unknown> {
110
+ return {
111
+ surface: 'remote_work',
112
+ methodId: 'remote.work.list',
113
+ route: 'GET /api/remote/work',
114
+ effect: 'read-only-network',
115
+ confirmationRequired: false,
116
+ modelRoute: previewHarnessText('agent_operator_method methodId:"remote.work.list" input:{}'),
117
+ description: 'Lists queued and leased remote work items.',
118
+ policy:
119
+ 'Read-only. Call agent_operator_method methodId:"remote.work.list" input:{} to fetch queued and leased work.',
120
+ };
121
+ }
122
+
123
+ export function remotePairRequestsSummary(_args: AgentHarnessRemoteArgs): Record<string, unknown> {
124
+ return {
125
+ surface: 'remote_pair_requests',
126
+ methodId: 'remote.pair.requests.list',
127
+ route: 'GET /api/remote/pair/requests',
128
+ effect: 'read-only-network',
129
+ confirmationRequired: false,
130
+ modelRoute: previewHarnessText('agent_operator_method methodId:"remote.pair.requests.list" input:{}'),
131
+ description: 'Lists pending, approved, and rejected remote pairing requests.',
132
+ policy:
133
+ 'Read-only. Call agent_operator_method methodId:"remote.pair.requests.list" input:{} to fetch the current pair request queue.',
134
+ };
135
+ }
136
+
137
+ // ---------------------------------------------------------------------------
138
+ // Mutation surfaces — confirmed, return agent_operator_method handoff objects
139
+ // ---------------------------------------------------------------------------
140
+
141
+ export function remotePairApproveHandoff(args: AgentHarnessRemoteArgs): RemoteMethodHandoff {
142
+ const requestId = readString(args.requestId || args.target);
143
+ const note = readString(args.note);
144
+ const input: Record<string, unknown> = { requestId };
145
+ if (note) input.note = note;
146
+ return {
147
+ surface: 'remote_pair_approve',
148
+ methodId: 'remote.pair.requests.approve',
149
+ route: 'POST /api/remote/pair/requests/{requestId}/approve',
150
+ effect: 'confirmed-connected-host-state',
151
+ confirmationRequired: true,
152
+ input,
153
+ modelRoute: previewHarnessText(
154
+ `agent_operator_method methodId:"remote.pair.requests.approve" input:${JSON.stringify(input)} confirm:true explicitUserRequest:"..."`,
155
+ ),
156
+ policy:
157
+ 'Mutation. Approves a pending remote pair request by requestId. Route it through agent_operator_method with confirm:true and the user\'s explicit request.',
158
+ };
159
+ }
160
+
161
+ export function remotePairRejectHandoff(args: AgentHarnessRemoteArgs): RemoteMethodHandoff {
162
+ const requestId = readString(args.requestId || args.target);
163
+ const note = readString(args.note);
164
+ const input: Record<string, unknown> = { requestId };
165
+ if (note) input.note = note;
166
+ return {
167
+ surface: 'remote_pair_reject',
168
+ methodId: 'remote.pair.requests.reject',
169
+ route: 'POST /api/remote/pair/requests/{requestId}/reject',
170
+ effect: 'confirmed-connected-host-state',
171
+ confirmationRequired: true,
172
+ input,
173
+ modelRoute: previewHarnessText(
174
+ `agent_operator_method methodId:"remote.pair.requests.reject" input:${JSON.stringify(input)} confirm:true explicitUserRequest:"..."`,
175
+ ),
176
+ policy:
177
+ 'Mutation. Rejects a pending remote pair request by requestId. Route it through agent_operator_method with confirm:true and the user\'s explicit request.',
178
+ };
179
+ }
180
+
181
+ export function remotePeersInvokeHandoff(args: AgentHarnessRemoteArgs): RemoteMethodHandoff {
182
+ const peerId = readString(args.peerId || args.target);
183
+ const command = readString(args.command);
184
+ const payload = readRecord(args.payload);
185
+ const input: Record<string, unknown> = { peerId, command };
186
+ if (Object.keys(payload).length > 0) input.payload = payload;
187
+ return {
188
+ surface: 'remote_peers_invoke',
189
+ methodId: 'remote.peers.invoke',
190
+ route: 'POST /api/remote/peers/{peerId}/invoke',
191
+ effect: 'confirmed-connected-host-state',
192
+ confirmationRequired: true,
193
+ input,
194
+ modelRoute: previewHarnessText(
195
+ `agent_operator_method methodId:"remote.peers.invoke" input:${JSON.stringify(input)} confirm:true explicitUserRequest:"..."`,
196
+ ),
197
+ policy:
198
+ 'Mutation. Invokes a command on a connected remote peer. Requires peerId and command. Route through agent_operator_method with confirm:true and the user\'s explicit request.',
199
+ };
200
+ }
201
+
202
+ export function remoteWorkCancelHandoff(args: AgentHarnessRemoteArgs): RemoteMethodHandoff {
203
+ const workId = readString(args.workId || args.target);
204
+ const reason = readString(args.reason);
205
+ const input: Record<string, unknown> = { workId };
206
+ if (reason) input.reason = reason;
207
+ return {
208
+ surface: 'remote_work_cancel',
209
+ methodId: 'remote.work.cancel',
210
+ route: 'POST /api/remote/work/{workId}/cancel',
211
+ effect: 'confirmed-connected-host-state',
212
+ confirmationRequired: true,
213
+ input,
214
+ modelRoute: previewHarnessText(
215
+ `agent_operator_method methodId:"remote.work.cancel" input:${JSON.stringify(input)} confirm:true explicitUserRequest:"..."`,
216
+ ),
217
+ policy:
218
+ 'Mutation. Cancels a queued or leased remote work item by workId. Route through agent_operator_method with confirm:true and the user\'s explicit request.',
219
+ };
220
+ }
@@ -65,10 +65,8 @@ export function installSmokePlan(
65
65
  {
66
66
  id: 'first-assistant-turn',
67
67
  label: 'First assistant turn responds',
68
- status: modelReady ? 'user-run' : 'blocked',
69
- evidence: modelReady
70
- ? 'Ask the main assistant for a short ready response after model routing is selected.'
71
- : 'A first assistant turn needs a provider/model route first.',
68
+ status: 'user-run',
69
+ evidence: 'Ask the main assistant for a short ready response once a provider/model route is selected.',
72
70
  route: 'Ask the assistant: "Say ready in one sentence and list the active model route."',
73
71
  },
74
72
  ];
@@ -15,7 +15,7 @@ export const AGENT_HARNESS_MODES = [
15
15
  'personal_ops_briefing', 'personal_ops', 'personal_ops_queue', 'personal_ops_intake', 'personal_ops_lane', 'run_personal_ops_read',
16
16
  'memory_posture', 'memory_provider', 'memory_refinement', 'run_memory_refinement',
17
17
  'autonomy_intake', 'autonomy_queue', 'autonomy_queue_item',
18
- 'learning_curator', 'learning_candidate',
18
+ 'learning_curator', 'learning_candidate', 'learning_auto_promote',
19
19
  'research_briefing', 'research_workflow', 'research_runs', 'research_run',
20
20
  'research_queue', 'research_source',
21
21
  'document_ops', 'document_ops_lane',
@@ -33,6 +33,11 @@ export const AGENT_HARNESS_MODES = [
33
33
  'connected_host', 'connected_host_status', 'connected_host_capability',
34
34
  'daemon', 'daemon_status',
35
35
  'propose_skill_drafts',
36
+ 'remote_snapshot', 'remote_peers', 'remote_work', 'remote_pair_requests',
37
+ 'remote_pair_approve', 'remote_pair_reject', 'remote_peers_invoke', 'remote_work_cancel',
38
+ 'unified_inbox',
39
+ 'channel_drafts', 'channel_draft_save', 'channel_draft_send',
40
+ 'channel_routing', 'channel_routing_assign', 'channel_routing_remove',
36
41
  ] as const;
37
42
 
38
43
  const BACKGROUND_PROCESS_ACTION_VALUES = [
@@ -344,4 +349,78 @@ export const AGENT_HARNESS_PARAMETER_PROPERTIES = {
344
349
  type: 'string',
345
350
  description: 'User request authorizing a confirmed harness effect.',
346
351
  },
352
+ peerId: {
353
+ type: 'string',
354
+ description: 'Remote peer id for remote_peers_invoke or remote_peers_disconnect.',
355
+ },
356
+ requestId: {
357
+ type: 'string',
358
+ description: 'Pair request id for remote_pair_approve or remote_pair_reject.',
359
+ },
360
+ workId: {
361
+ type: 'string',
362
+ description: 'Remote work item id for remote_work_cancel.',
363
+ },
364
+ payload: {
365
+ type: 'object',
366
+ description: 'Command payload for remote_peers_invoke.',
367
+ },
368
+ note: {
369
+ type: 'string',
370
+ description: 'Optional note for remote pair request approve/reject.',
371
+ },
372
+ reason: {
373
+ type: 'string',
374
+ description: 'Optional cancellation reason for remote_work_cancel.',
375
+ },
376
+ draftId: {
377
+ type: 'string',
378
+ description: 'Channel draft id for channel_drafts, channel_draft_send.',
379
+ },
380
+ draftStatus: {
381
+ type: 'string',
382
+ enum: ['draft', 'queued', 'sent', 'failed'],
383
+ description: 'Status filter for channel_drafts list.',
384
+ },
385
+ draftTitle: {
386
+ type: 'string',
387
+ description: 'Optional title for channel_draft_save.',
388
+ },
389
+ draftMessage: {
390
+ type: 'string',
391
+ description: 'Message body for channel_draft_save.',
392
+ },
393
+ draftChannel: {
394
+ type: 'string',
395
+ description: 'Channel target for channel_draft_save.',
396
+ },
397
+ draftRoute: {
398
+ type: 'string',
399
+ description: 'Route id for channel_draft_save or channel_routing_remove.',
400
+ },
401
+ draftWebhook: {
402
+ type: 'string',
403
+ description: 'Webhook URL for channel_draft_save.',
404
+ },
405
+ draftLink: {
406
+ type: 'string',
407
+ description: 'Link target for channel_draft_save.',
408
+ },
409
+ draftTags: {
410
+ type: 'array',
411
+ items: { type: 'string' },
412
+ description: 'Tags for channel_draft_save.',
413
+ },
414
+ surfaceKind: {
415
+ type: 'string',
416
+ description: 'Channel surface kind for channel_routing or channel_routing_assign.',
417
+ },
418
+ profileId: {
419
+ type: 'string',
420
+ description: 'Profile id for channel_routing_assign.',
421
+ },
422
+ routeLabel: {
423
+ type: 'string',
424
+ description: 'Optional label for channel_routing_assign.',
425
+ },
347
426
  } as const;
@@ -74,6 +74,24 @@ export interface AgentHarnessToolArgs {
74
74
  readonly pane?: unknown;
75
75
  readonly confirm?: unknown;
76
76
  readonly explicitUserRequest?: unknown;
77
+ readonly peerId?: unknown;
78
+ readonly requestId?: unknown;
79
+ readonly workId?: unknown;
80
+ readonly payload?: unknown;
81
+ readonly note?: unknown;
82
+ readonly reason?: unknown;
83
+ readonly draftId?: unknown;
84
+ readonly draftStatus?: unknown;
85
+ readonly draftTitle?: unknown;
86
+ readonly draftMessage?: unknown;
87
+ readonly draftChannel?: unknown;
88
+ readonly draftRoute?: unknown;
89
+ readonly draftWebhook?: unknown;
90
+ readonly draftLink?: unknown;
91
+ readonly draftTags?: unknown;
92
+ readonly surfaceKind?: unknown;
93
+ readonly profileId?: unknown;
94
+ readonly routeLabel?: unknown;
77
95
  }
78
96
 
79
97
  export interface AgentHarnessToolDeps {
@@ -8,6 +8,7 @@ import { channelReadinessCatalogStatus, describeHarnessChannel, describeHarnessC
8
8
  import { blockedHarnessCliCommandTokens, describeHarnessCliCommand, listHarnessCliCommands, totalHarnessCliCommands } from './agent-harness-cli-metadata.ts';
9
9
  import { describeHarnessCommand, listHarnessCommands } from './agent-harness-command-catalog.ts';
10
10
  import { describeLearningCandidate, learningCuratorCatalogStatus, learningCuratorSummary } from './agent-harness-learning-curator.ts';
11
+ import { runAutoPromoter } from './agent-harness-learning-auto-promote.ts';
11
12
  import { runSkillDraftProposer } from '../agent/skill-draft-runner.ts';
12
13
  import { AgentSkillRegistry } from '../agent/skill-registry.ts';
13
14
  import { delegationPostureCatalogStatus, delegationPostureSummary, describeHarnessDelegationRoute } from './agent-harness-delegation-posture.ts';
@@ -55,6 +56,8 @@ import { AGENT_WORKSPACE_CATEGORIES, allWorkspaceActions, buildWorkspaceEditorCo
55
56
  import { connectedHostSummary, describeConnectedHostCapability, settingsPolicySummary } from './agent-harness-metadata.ts';
56
57
  import { countHarnessSettings, formatHarnessError, listHarnessSettings, resetHarnessSetting, resolveHarnessSetting, setHarnessSetting } from '../agent/harness-control.ts';
57
58
  import { buildAssistantCockpitFromSummaries } from '../agent/assistant-cockpit.ts';
59
+ import { remoteCatalogStatus, remotePairApproveHandoff, remotePairRejectHandoff, remotePairRequestsSummary, remotePeersInvokeHandoff, remotePeersSummary, remoteSnapshotSummary, remoteWorkCancelHandoff, remoteWorkSummary } from './agent-harness-remote.ts';
60
+ import { channelDraftSaveHandoff, channelDraftSendHandoff, channelDraftsSummary, channelRoutingAssignHandoff, channelRoutingRemoveHandoff, channelRoutingSummary, unifiedInboxSummary } from './agent-harness-comms.ts';
58
61
 
59
62
  function isMode(value: unknown): value is AgentHarnessMode {
60
63
  return typeof value === 'string' && AGENT_HARNESS_MODES.includes(value as AgentHarnessMode);
@@ -200,6 +203,7 @@ export function createAgentHarnessTool(deps: AgentHarnessToolDeps): Tool {
200
203
  const releaseReadiness = releaseReadinessInventoryStatus();
201
204
  const operatorMethods = operatorMethodCatalogStatus();
202
205
  const servicePosture = servicePostureCatalogStatus();
206
+ const remote = remoteCatalogStatus(deps.commandContext);
203
207
  const connectedHost = connectedHostSummary(deps.commandContext, deps.toolRegistry, {
204
208
  includeParameters: args.includeParameters === true,
205
209
  });
@@ -260,6 +264,7 @@ export function createAgentHarnessTool(deps: AgentHarnessToolDeps): Tool {
260
264
  releaseReadiness,
261
265
  operatorMethods,
262
266
  servicePosture,
267
+ remote,
263
268
  modeGuide: compactHarnessModeGuide(),
264
269
  ...(args.includeParameters === true ? { modelAccess: detailedHarnessModelAccessGuide() } : {}),
265
270
  settingsPolicy: settingsPolicySummary(),
@@ -487,6 +492,21 @@ export function createAgentHarnessTool(deps: AgentHarnessToolDeps): Tool {
487
492
  return error(resolved.usage);
488
493
  }
489
494
  if (args.mode === 'learning_curator') return output(learningCuratorSummary(deps.commandContext, args));
495
+ if (args.mode === 'learning_auto_promote') {
496
+ const shellPaths = deps.commandContext.workspace?.shellPaths;
497
+ if (!shellPaths) return error('learning_auto_promote requires an active workspace.');
498
+ const memoryApi = deps.commandContext.clients?.agentKnowledgeApi?.memory;
499
+ if (!memoryApi) return error('learning_auto_promote requires an active memory registry.');
500
+ const skillRegistry = AgentSkillRegistry.fromShellPaths(shellPaths);
501
+ const result = await runAutoPromoter(deps.commandContext, skillRegistry, memoryApi);
502
+ return output({
503
+ ...result,
504
+ message: result.promoted > 0
505
+ ? `Promoted ${result.promoted} item(s); consolidated ${result.consolidated} duplicate(s).`
506
+ : 'No items eligible for autonomous promotion this pass.',
507
+ policy: 'Autonomous promotion. Skills are created via the skill-draft runner. Memory, persona, routine creates call registry directly. Consolidation runs the full merge-stale-delete pipeline. Secret scanning enforced by each registry create().',
508
+ });
509
+ }
490
510
  if (args.mode === 'learning_candidate') {
491
511
  const resolved = describeLearningCandidate(deps.commandContext, args);
492
512
  if (resolved.status === 'found') return output(resolved.candidate);
@@ -721,6 +741,47 @@ export function createAgentHarnessTool(deps: AgentHarnessToolDeps): Tool {
721
741
  policy: 'Drafted skills are disabled and require review before use. Enable them under Memory > Skills.',
722
742
  });
723
743
  }
744
+ if (args.mode === 'remote_snapshot') return output(remoteSnapshotSummary(args));
745
+ if (args.mode === 'remote_peers') return output(remotePeersSummary(args));
746
+ if (args.mode === 'remote_work') return output(remoteWorkSummary(args));
747
+ if (args.mode === 'remote_pair_requests') return output(remotePairRequestsSummary(args));
748
+ if (args.mode === 'remote_pair_approve') {
749
+ const confirmationError = requireConfirmedAction(args, 'Remote pair request approval');
750
+ if (confirmationError) return error(confirmationError);
751
+ return output(remotePairApproveHandoff(args));
752
+ }
753
+ if (args.mode === 'remote_pair_reject') {
754
+ const confirmationError = requireConfirmedAction(args, 'Remote pair request rejection');
755
+ if (confirmationError) return error(confirmationError);
756
+ return output(remotePairRejectHandoff(args));
757
+ }
758
+ if (args.mode === 'remote_peers_invoke') {
759
+ const confirmationError = requireConfirmedAction(args, 'Remote peer command invocation');
760
+ if (confirmationError) return error(confirmationError);
761
+ return output(remotePeersInvokeHandoff(args));
762
+ }
763
+ if (args.mode === 'remote_work_cancel') {
764
+ const confirmationError = requireConfirmedAction(args, 'Remote work cancellation');
765
+ if (confirmationError) return error(confirmationError);
766
+ return output(remoteWorkCancelHandoff(args));
767
+ }
768
+ if (args.mode === 'unified_inbox') return output(await unifiedInboxSummary(deps.commandContext, args));
769
+ if (args.mode === 'channel_drafts') return output(channelDraftsSummary(deps.commandContext, args));
770
+ if (args.mode === 'channel_draft_save') {
771
+ const result = channelDraftSaveHandoff(deps.commandContext, args); return typeof result === 'string' ? error(result) : output(result);
772
+ }
773
+ if (args.mode === 'channel_draft_send') {
774
+ const result = await channelDraftSendHandoff(deps.commandContext, args); return typeof result === 'string' ? error(result) : output(result);
775
+ }
776
+ if (args.mode === 'channel_routing') return output(channelRoutingSummary(deps.commandContext, args));
777
+ if (args.mode === 'channel_routing_assign') {
778
+ const result = channelRoutingAssignHandoff(deps.commandContext, args);
779
+ return typeof result === 'string' ? error(result) : output(result);
780
+ }
781
+ if (args.mode === 'channel_routing_remove') {
782
+ const result = channelRoutingRemoveHandoff(deps.commandContext, args);
783
+ return typeof result === 'string' ? error(result) : output(result);
784
+ }
724
785
  return error(`Unhandled agent_harness mode: ${args.mode}`);
725
786
  } catch (err) {
726
787
  return error(formatHarnessError(err));