@pellux/goodvibes-agent 1.8.0 → 1.8.2
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 +9 -0
- package/README.md +1 -1
- package/dist/package/{ast-grep-napi.linux-x64-gnu-mkk8xwww.node → ast-grep-napi.linux-x64-gnu-44ar68xs.node} +0 -0
- package/dist/package/{ast-grep-napi.linux-x64-musl-ryqtgdv6.node → ast-grep-napi.linux-x64-musl-asvc2tv1.node} +0 -0
- package/dist/package/main.js +135434 -118764
- package/dist/package/{web-tree-sitter-vmqc5d26.wasm → web-tree-sitter-jbz042ba.wasm} +0 -0
- package/docs/tools-and-commands.md +9 -0
- package/package.json +3 -2
- package/src/agent/capability-registry.ts +300 -0
- package/src/agent/email/style-reply-lane.ts +13 -4
- package/src/agent/memory-consolidation-receipts.ts +79 -0
- package/src/agent/memory-prompt.ts +3 -1
- package/src/agent/operator-gateway-call.ts +134 -0
- package/src/agent/principal-attribution.ts +97 -0
- package/src/agent/prompt-context-receipts.ts +5 -1
- package/src/agent/unified-inbox.ts +17 -1
- package/src/cli/bundle-command.ts +2 -1
- package/src/cli/channel-profiles-command.ts +168 -0
- package/src/cli/ci-command.ts +214 -0
- package/src/cli/completion.ts +5 -0
- package/src/cli/connected-host-metrics.ts +165 -0
- package/src/cli/entrypoint.ts +25 -1
- package/src/cli/fleet-command.ts +178 -0
- package/src/cli/help.ts +115 -0
- package/src/cli/import-command.ts +208 -0
- package/src/cli/index.ts +1 -0
- package/src/cli/management-commands.ts +9 -8
- package/src/cli/management.ts +42 -0
- package/src/cli/memory-command.ts +4 -3
- package/src/cli/openclaw-import.ts +382 -0
- package/src/cli/operator-command-args.ts +88 -0
- package/src/cli/parser.ts +12 -0
- package/src/cli/personas-command.ts +3 -2
- package/src/cli/principals-command.ts +230 -0
- package/src/cli/profiles-command.ts +2 -1
- package/src/cli/relay-command.ts +100 -0
- package/src/cli/routines-command.ts +4 -3
- package/src/cli/skill-bundle-command.ts +3 -2
- package/src/cli/skills-command.ts +3 -2
- package/src/cli/status.ts +114 -2
- package/src/cli/temporal-label.ts +89 -0
- package/src/cli/types.ts +7 -0
- package/src/cli/workspaces-command.ts +135 -0
- package/src/config/agent-settings-policy.ts +8 -0
- package/src/config/checkpoint-settings.ts +113 -0
- package/src/config/workspace-registration.ts +299 -0
- package/src/core/rewind-turn-anchors.ts +82 -0
- package/src/input/agent-workspace-categories.ts +1 -0
- package/src/input/agent-workspace-channel-triage.ts +27 -8
- package/src/input/agent-workspace-local-library-snapshot.ts +5 -1
- package/src/input/agent-workspace-onboarding-categories.ts +17 -1
- package/src/input/agent-workspace-snapshot.ts +25 -3
- package/src/input/agent-workspace-types.ts +5 -5
- package/src/input/commands/connected-host-admin-runtime.ts +75 -0
- package/src/input/commands.ts +2 -0
- package/src/input/settings-modal-types.ts +16 -5
- package/src/main.ts +17 -12
- package/src/permissions/approval-posture.ts +40 -5
- package/src/permissions/prompt.ts +35 -1
- package/src/renderer/settings-modal-helpers.ts +11 -0
- package/src/renderer/settings-modal.ts +21 -0
- package/src/renderer/terminal-escapes.ts +65 -12
- package/src/runtime/agent-runtime-events.ts +102 -3
- package/src/runtime/bootstrap-core.ts +111 -10
- package/src/runtime/bootstrap-external-services.ts +156 -0
- package/src/runtime/bootstrap.ts +73 -69
- package/src/runtime/context-accounting-source.ts +114 -0
- package/src/runtime/conversation-rewind-port.ts +133 -0
- package/src/runtime/index.ts +8 -28
- package/src/runtime/memory-consolidation-scheduler.ts +67 -0
- package/src/runtime/memory-consolidation-wiring.ts +56 -0
- package/src/runtime/memory-usage-wiring.ts +75 -0
- package/src/runtime/services.ts +257 -6
- package/src/runtime/tool-permission-safety.ts +18 -1
- package/src/shell/blocking-input.ts +38 -8
- package/src/shell/startup-wiring.ts +26 -1
- package/src/tools/agent-context-policy.ts +1 -1
- package/src/tools/agent-harness-cli-command-policy.ts +29 -3
- package/src/tools/agent-harness-learning-auto-promote.ts +7 -3
- package/src/tools/agent-harness-memory-posture.ts +57 -2
- package/src/tools/agent-harness-metadata.ts +12 -0
- package/src/tools/agent-harness-mode-catalog.ts +1 -1
- package/src/tools/agent-harness-personal-ops-lanes.ts +8 -6
- package/src/tools/agent-harness-prompt-context.ts +3 -1
- package/src/tools/agent-harness-tool.ts +3 -2
- package/src/tools/agent-policy-explanation.ts +40 -0
- package/src/tools/tool-definition-compaction.ts +2 -0
- package/src/version.ts +1 -1
package/src/runtime/services.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
+
import { StepUpService } from '@pellux/goodvibes-sdk/daemon';
|
|
2
3
|
import { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
3
4
|
import { shell as runtimeShell } from '@pellux/goodvibes-sdk/platform/runtime';
|
|
4
5
|
import type { shell as RuntimeShell } from '@pellux/goodvibes-sdk/platform/runtime';
|
|
5
6
|
import { SecretsManager } from '../config/secrets.ts';
|
|
7
|
+
import { readCheckpointGuardSettings, readCheckpointRegistrationSetting } from '../config/checkpoint-settings.ts';
|
|
8
|
+
import { createWorkspaceRegistrationLiveChecker, migrateLegacyWorkspaceRegistryIfNeeded } from '../config/workspace-registration.ts';
|
|
6
9
|
import { FocusTracker } from '../core/focus-tracker.ts';
|
|
7
10
|
import { ServiceRegistry } from '@pellux/goodvibes-sdk/platform/config';
|
|
8
11
|
import { SubscriptionManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
@@ -10,6 +13,13 @@ import { AutomationDeliveryManager, AutomationManager, AutomationRouteStore } fr
|
|
|
10
13
|
import { ChannelPluginRegistry, ChannelPolicyManager, RouteBindingManager, SurfaceRegistry } from '@pellux/goodvibes-sdk/platform/channels';
|
|
11
14
|
import { ChannelDeliveryRouter } from '@pellux/goodvibes-sdk/platform/channels';
|
|
12
15
|
import { ApprovalBroker, GatewayMethodCatalog, SharedSessionBroker } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
16
|
+
import { attachWsOnlyGatewayVerbHandlers, createArchivableFleetRegistry } from '@pellux/goodvibes-terminal-shell';
|
|
17
|
+
import { createSessionConversationRewindPort } from './conversation-rewind-port.ts';
|
|
18
|
+
// Not re-exported by @pellux/goodvibes-terminal-shell (only the gateway-verb
|
|
19
|
+
// composition and the registry factory are) — reached directly per the SDK
|
|
20
|
+
// adoption convention of going straight to the platform package for whatever
|
|
21
|
+
// terminal-shell does not already wrap, rather than hand-rolling the bridge.
|
|
22
|
+
import { attachFleetEmitBridge } from '@pellux/goodvibes-sdk/platform/runtime/fleet';
|
|
13
23
|
import type { SharedSessionRoutingIntent } from '@pellux/goodvibes-sdk/platform/control-plane';
|
|
14
24
|
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
15
25
|
import { AGENT_SPINE_PARTICIPANT, SessionSpineClient } from '@pellux/goodvibes-sdk/platform/runtime/session-spine';
|
|
@@ -45,9 +55,17 @@ import { AgentOrchestrator } from '@pellux/goodvibes-sdk/platform/agents';
|
|
|
45
55
|
import { ArchetypeLoader } from '@pellux/goodvibes-sdk/platform/agents';
|
|
46
56
|
import { CodeIndexStore } from '@pellux/goodvibes-sdk/platform/state';
|
|
47
57
|
import { CodeIndexReindexScheduler } from '@pellux/goodvibes-sdk/platform/state';
|
|
48
|
-
import { createProcessRegistry, withFleetArchive } from '@pellux/goodvibes-sdk/platform/runtime/fleet';
|
|
49
58
|
import { createOrchestrationEngine } from '@pellux/goodvibes-sdk/platform/orchestration';
|
|
50
59
|
import { WorkspaceCheckpointManager } from '@pellux/goodvibes-sdk/platform/workspace';
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Structural mirror of the SDK's CheckpointSessionResolver (checkpoint/manager):
|
|
63
|
+
* maps a triggering turn/agent lifecycle event to its owning session id. Declared
|
|
64
|
+
* locally because the type is re-exported only from the deep checkpoint subpath,
|
|
65
|
+
* not the `platform/workspace` barrel this module already imports; the shape is
|
|
66
|
+
* exact, so a resolver typed against it is assignable to the constructor option.
|
|
67
|
+
*/
|
|
68
|
+
type CheckpointSessionResolver = (ctx: { readonly turnId?: string | undefined; readonly agentId?: string | undefined }) => string | undefined;
|
|
51
69
|
import { ProcessManager } from '@pellux/goodvibes-sdk/platform/tools';
|
|
52
70
|
import { ModeManager } from '@pellux/goodvibes-sdk/platform/state';
|
|
53
71
|
import { FileUndoManager } from '@pellux/goodvibes-sdk/platform/state';
|
|
@@ -95,6 +113,7 @@ import { FileStateCache } from '@pellux/goodvibes-sdk/platform/state';
|
|
|
95
113
|
import { ProjectIndex } from '@pellux/goodvibes-sdk/platform/state';
|
|
96
114
|
import { IdempotencyStore } from '@/runtime/index.ts';
|
|
97
115
|
import { OverflowHandler } from '@pellux/goodvibes-sdk/platform/tools';
|
|
116
|
+
import { ContextAccountingHolder } from '@pellux/goodvibes-sdk/platform/tools';
|
|
98
117
|
import { ToolLLM } from '@pellux/goodvibes-sdk/platform/config';
|
|
99
118
|
import { ComponentHealthMonitor } from '@/runtime/index.ts';
|
|
100
119
|
import { SandboxSessionRegistry } from '@/runtime/index.ts';
|
|
@@ -397,6 +416,16 @@ export interface RuntimeServicesOptions {
|
|
|
397
416
|
readonly localUserAuthManager?: UserAuthManager;
|
|
398
417
|
readonly featureFlags?: FeatureFlagManager;
|
|
399
418
|
readonly getConversationTitle?: () => string | undefined;
|
|
419
|
+
/**
|
|
420
|
+
* Maps a triggering turn/agent lifecycle event to the owning session id so the
|
|
421
|
+
* WorkspaceCheckpointManager can stamp automatic (and defaulted explicit)
|
|
422
|
+
* checkpoints with it. Without this, same-launch checkpoints are written with
|
|
423
|
+
* an undefined session id and the session-scoped restore/rewind lookup filters
|
|
424
|
+
* them out — so a checkpoint made this launch could not be activated until a
|
|
425
|
+
* restart. Consulted live at each event, so a resolver reading a mutable
|
|
426
|
+
* session-id ref reflects the current session without re-wiring.
|
|
427
|
+
*/
|
|
428
|
+
readonly resolveSessionId?: CheckpointSessionResolver;
|
|
400
429
|
readonly workingDir: string;
|
|
401
430
|
readonly homeDirectory: string;
|
|
402
431
|
}
|
|
@@ -549,6 +578,15 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
549
578
|
secretsManager,
|
|
550
579
|
subscriptionManager,
|
|
551
580
|
});
|
|
581
|
+
// Settable holder for the context_accounting tool's session source (SDK
|
|
582
|
+
// 1.6.1). Constructed here (unbound) so it exists for the whole runtime
|
|
583
|
+
// services lifetime and every consumer of `services.contextAccountingHolder`
|
|
584
|
+
// sees the SAME instance the tool roster is registered against — bootstrap.ts
|
|
585
|
+
// binds an Orchestrator-backed ContextAccountingSource onto it once the
|
|
586
|
+
// interactive Orchestrator exists (see bindOrchestratorContextAccounting in
|
|
587
|
+
// context-accounting-source.ts). Left unbound here: the tool honestly
|
|
588
|
+
// reports "no live session context bound" until that bind call runs.
|
|
589
|
+
const contextAccountingHolder = new ContextAccountingHolder();
|
|
552
590
|
const providerCapabilityRegistry = new ProviderCapabilityRegistry();
|
|
553
591
|
const cacheHitTracker = new CacheHitTracker();
|
|
554
592
|
const favoritesStore = new FavoritesStore({ dir: shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT) });
|
|
@@ -913,9 +951,14 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
913
951
|
// never fires for the Agent.
|
|
914
952
|
// • processRegistry — fleet observability over the managers the Agent
|
|
915
953
|
// already owns (agents, wrfc, processes, watchers).
|
|
916
|
-
// • workspaceCheckpointManager —
|
|
917
|
-
//
|
|
918
|
-
//
|
|
954
|
+
// • workspaceCheckpointManager — a runtimeBus (hence automatic turn/agent-
|
|
955
|
+
// lifecycle snapshots) is only passed when
|
|
956
|
+
// `workingDirectory` is a registered workspace (or
|
|
957
|
+
// the unregistered-workspaces override is
|
|
958
|
+
// "guarded"); otherwise it stays inert unless
|
|
959
|
+
// explicitly invoked. See the construction site
|
|
960
|
+
// below for the full registered-workspaces-only
|
|
961
|
+
// rule (owner ruling, 2026-07-10).
|
|
919
962
|
const orchestrationEngine = createOrchestrationEngine({
|
|
920
963
|
agentManager,
|
|
921
964
|
configManager,
|
|
@@ -934,7 +977,10 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
934
977
|
});
|
|
935
978
|
// Archive-aware: finished agent/swarm subtrees can be moved out of the
|
|
936
979
|
// live fleet view into a session-scoped archive (SDK fleet/archive.ts).
|
|
937
|
-
|
|
980
|
+
// createArchivableFleetRegistry is the shared terminal-shell wrapper over the
|
|
981
|
+
// SDK's withFleetArchive(createProcessRegistry(...)) — one named seam both
|
|
982
|
+
// daemon front-ends build the registry through so it cannot drift.
|
|
983
|
+
const processRegistry = createArchivableFleetRegistry({
|
|
938
984
|
agentManager,
|
|
939
985
|
wrfcController,
|
|
940
986
|
orchestrationEngine,
|
|
@@ -947,17 +993,222 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
947
993
|
messageBus: agentMessageBus,
|
|
948
994
|
automationManager,
|
|
949
995
|
runtimeBus: options.runtimeBus,
|
|
950
|
-
})
|
|
996
|
+
});
|
|
997
|
+
// Root/retention guard options come from the user's `checkpoints.*` settings
|
|
998
|
+
// (see config/checkpoint-settings.ts); any key the user did not set is omitted
|
|
999
|
+
// so the SDK manager applies its own default. These guards are defense in
|
|
1000
|
+
// depth UNDER the registered-workspaces-only rule below, not replaced by it:
|
|
1001
|
+
// even a registered root can still be refused as too broad or too large.
|
|
1002
|
+
//
|
|
1003
|
+
// Registered-workspaces-only (owner ruling, 2026-07-10; live-gating extended
|
|
1004
|
+
// 2026-07-11 so registering mid-launch takes effect without a restart — the
|
|
1005
|
+
// same stale-until-restart defect class fixed for permission-mode metadata).
|
|
1006
|
+
// The SDK's WorkspaceCheckpointManager only takes automatic turn/agent-
|
|
1007
|
+
// lifecycle snapshots when constructed WITH a runtimeBus (see the SDK's own
|
|
1008
|
+
// platform/runtime/services.ts, which always passes one). This composition
|
|
1009
|
+
// root ALWAYS passes runtimeBus now (so the automatic-snapshot subscription
|
|
1010
|
+
// is live from process start regardless of boot-time registration state),
|
|
1011
|
+
// and instead gates each individual automatic snapshot attempt through a
|
|
1012
|
+
// LIVE re-check via `checkpointsCurrentlyAllowed` below — patched onto this
|
|
1013
|
+
// one manager instance's own `create` method, since the SDK has no built-in
|
|
1014
|
+
// predicate hook for this. `checkpointsCurrentlyAllowed` is cheap to call on
|
|
1015
|
+
// every event: `createWorkspaceRegistrationLiveChecker` probes the git
|
|
1016
|
+
// worktree link ONCE (a `git` subprocess spawn) and every call after that
|
|
1017
|
+
// only re-reads the shared registration JSON file. "Allowed" means
|
|
1018
|
+
// `workingDirectory` resolves as COVERED by the shared registration store
|
|
1019
|
+
// (SDK 1.6.1 platform/workspace/registration, via
|
|
1020
|
+
// config/workspace-registration.ts — the successor to this fork's local
|
|
1021
|
+
// per-user registry, migrated in once below), or the owner has explicitly
|
|
1022
|
+
// opted an unregistered workspace back in via
|
|
1023
|
+
// `checkpoints.unregisteredWorkspaces: "guarded"` (config/checkpoint-settings.ts;
|
|
1024
|
+
// default "off"). Coverage flows down a registered root's subtree AND
|
|
1025
|
+
// through the git worktree→main-repo link, so an orchestration-spawned
|
|
1026
|
+
// worktree of a registered repo checkpoints automatically without being
|
|
1027
|
+
// registered itself. A workspace SWAP at runtime is still not tracked here
|
|
1028
|
+
// (WorkspaceSwapManager's rerootStores does not re-root this manager; a swap
|
|
1029
|
+
// needs a restart to pick up a new root's registration) — only the
|
|
1030
|
+
// registration STATE of this fixed workingDirectory is now live.
|
|
1031
|
+
const registrationMigration = migrateLegacyWorkspaceRegistryIfNeeded(shellPaths);
|
|
1032
|
+
if (registrationMigration) {
|
|
1033
|
+
logger.info('Migrated the local workspace registry into the shared registration store', { ...registrationMigration });
|
|
1034
|
+
}
|
|
1035
|
+
const checkpointsRegistrationStatus = createWorkspaceRegistrationLiveChecker(shellPaths, workingDirectory);
|
|
1036
|
+
const checkpointsCurrentlyAllowed = (): boolean =>
|
|
1037
|
+
checkpointsRegistrationStatus() === 'covered' || readCheckpointRegistrationSetting(configManager) === 'guarded';
|
|
951
1038
|
const workspaceCheckpointManager = new WorkspaceCheckpointManager({
|
|
952
1039
|
workspaceRoot: workingDirectory,
|
|
1040
|
+
...readCheckpointGuardSettings(configManager),
|
|
1041
|
+
runtimeBus: options.runtimeBus,
|
|
1042
|
+
// Stamp automatic snapshots with the live session id so a checkpoint created
|
|
1043
|
+
// during this launch is found by the session-scoped restore/rewind lookup —
|
|
1044
|
+
// the fix that makes same-launch activation work without a restart. The
|
|
1045
|
+
// resolver is consulted at each lifecycle event, so it tracks the current
|
|
1046
|
+
// session even though the id is finalized after this manager is built.
|
|
1047
|
+
...(options.resolveSessionId ? { resolveSessionId: options.resolveSessionId } : {}),
|
|
953
1048
|
});
|
|
1049
|
+
// Gate AUTOMATIC snapshots (kind 'turn' | 'agent-run', fired internally by
|
|
1050
|
+
// the manager's own bus subscription via `this.create(...)`) on the live
|
|
1051
|
+
// registration check, by overriding this instance's own `create` method —
|
|
1052
|
+
// the manager has no predicate-hook option, and this is the one seam that
|
|
1053
|
+
// every automatic-snapshot call and every explicit caller both go through.
|
|
1054
|
+
// 'manual' (explicit, gateway-invoked) creates are NOT re-gated here: they
|
|
1055
|
+
// already go through checkpointsGatewayManager.create's own live check
|
|
1056
|
+
// below, which throws an actionable message BEFORE ever reaching this
|
|
1057
|
+
// method — this override would just be a silent, redundant pass for that
|
|
1058
|
+
// path. For an automatic snapshot there is no caller to throw to (the
|
|
1059
|
+
// manager's own subscription callbacks only `.catch()`-log a rejection), so
|
|
1060
|
+
// an automatic attempt against an unregistered/un-guarded workspace resolves
|
|
1061
|
+
// to `null` quietly — the same "cheap no-op" contract `create()` already
|
|
1062
|
+
// documents for an unchanged tree, not a new error path.
|
|
1063
|
+
const originalCreateCheckpoint = workspaceCheckpointManager.create.bind(workspaceCheckpointManager);
|
|
1064
|
+
workspaceCheckpointManager.create = ((opts) => {
|
|
1065
|
+
if (opts.kind !== 'manual' && !checkpointsCurrentlyAllowed()) return Promise.resolve(null);
|
|
1066
|
+
return originalCreateCheckpoint(opts);
|
|
1067
|
+
}) as typeof workspaceCheckpointManager.create;
|
|
1068
|
+
// Eagerly initialize so the automatic snapshot subscription is wired up
|
|
1069
|
+
// immediately (mirrors the SDK's own default runtime services) rather than
|
|
1070
|
+
// only on first explicit checkpoints.* call — otherwise the very first
|
|
1071
|
+
// TURN_COMPLETED could arrive before anything has touched the manager. Now
|
|
1072
|
+
// unconditional (previously skipped for an unregistered-at-boot workspace):
|
|
1073
|
+
// the subscription itself is inert-by-gate rather than inert-by-absence, so
|
|
1074
|
+
// a workspace registered mid-launch starts producing automatic snapshots on
|
|
1075
|
+
// the very next eligible event, with no restart needed to (re)wire the bus.
|
|
1076
|
+
void workspaceCheckpointManager.init().catch((err) => {
|
|
1077
|
+
logger.warn('WorkspaceCheckpointManager.init failed', { error: err instanceof Error ? err.message : String(err) });
|
|
1078
|
+
});
|
|
1079
|
+
// Explicit user-invoked checkpoint creation (the ws-only `checkpoints.create`
|
|
1080
|
+
// gateway verb) is gated the SAME way: refuse with an actionable hint rather
|
|
1081
|
+
// than silently registering the workspace on the caller's behalf. This is
|
|
1082
|
+
// the chosen policy for the pending owner ruling (the alternative —
|
|
1083
|
+
// register-and-proceed — was NOT chosen, to keep "create a checkpoint" from
|
|
1084
|
+
// having a side effect the caller did not ask for). list/diff/restore/
|
|
1085
|
+
// sessionChanges are read/restore operations over checkpoints that may
|
|
1086
|
+
// already exist (e.g. from a since-unregistered workspace) and are left
|
|
1087
|
+
// unrestricted here.
|
|
1088
|
+
const checkpointsGatewayManager: Pick<WorkspaceCheckpointManager, 'list' | 'create' | 'diff' | 'restore' | 'sessionChanges' | 'workspaceRoot'> = {
|
|
1089
|
+
workspaceRoot: workspaceCheckpointManager.workspaceRoot,
|
|
1090
|
+
list: workspaceCheckpointManager.list.bind(workspaceCheckpointManager),
|
|
1091
|
+
diff: workspaceCheckpointManager.diff.bind(workspaceCheckpointManager),
|
|
1092
|
+
restore: workspaceCheckpointManager.restore.bind(workspaceCheckpointManager),
|
|
1093
|
+
sessionChanges: workspaceCheckpointManager.sessionChanges.bind(workspaceCheckpointManager),
|
|
1094
|
+
create: (opts) => {
|
|
1095
|
+
// Re-reads the registry and setting live via the same cheap checker the
|
|
1096
|
+
// automatic-snapshot gate above uses, so registering the workspace via
|
|
1097
|
+
// `goodvibes-agent workspaces register` takes effect for the NEXT
|
|
1098
|
+
// explicit create call (same as it always has) — and, as of the live
|
|
1099
|
+
// gate above, for automatic snapshots too, without a restart either way.
|
|
1100
|
+
if (!checkpointsCurrentlyAllowed()) {
|
|
1101
|
+
throw new Error(
|
|
1102
|
+
`Checkpoints are off for this workspace: ${workingDirectory} is not registered. `
|
|
1103
|
+
+ 'Register it first, then retry: goodvibes-agent workspaces register --yes '
|
|
1104
|
+
+ '(or set checkpoints.unregisteredWorkspaces to "guarded" to opt this workspace out of the registration gate).',
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
// Default the session stamp from the live resolver when the caller omits it
|
|
1108
|
+
// (the resolveSessionId hook only auto-stamps AUTOMATIC snapshots, not
|
|
1109
|
+
// explicit create calls). Without this, an explicit checkpoint made this
|
|
1110
|
+
// launch would be written unstamped and excluded by the session-scoped
|
|
1111
|
+
// restore lookup — the same same-launch activation gap the resolver closes
|
|
1112
|
+
// for automatic snapshots. An explicitly supplied sessionId always wins.
|
|
1113
|
+
const sessionId = opts.sessionId ?? options.resolveSessionId?.({});
|
|
1114
|
+
return workspaceCheckpointManager.create(sessionId ? { ...opts, sessionId } : opts);
|
|
1115
|
+
},
|
|
1116
|
+
};
|
|
1117
|
+
|
|
1118
|
+
// Attach handlers for every ws-only gateway verb group (fleet.* including
|
|
1119
|
+
// the archive verbs, checkpoints.*, sessions.search, push.*, principals.*,
|
|
1120
|
+
// channels.profiles.*, ci.*, and — when the deps below are all present —
|
|
1121
|
+
// checkin.*). Without this call the catalog carries descriptors but no
|
|
1122
|
+
// handlers, and every one of those verbs answers 501 "Gateway method is not
|
|
1123
|
+
// invokable" — the same gap the companion app found on the TUI-vendored
|
|
1124
|
+
// daemon. Mirrors the SDK runtime's composition root (goodvibes-sdk
|
|
1125
|
+
// platform/runtime/services.ts). attachWsOnlyGatewayVerbHandlers is the
|
|
1126
|
+
// shared terminal-shell wrapper over the SDK's registerGatewayVerbGroups —
|
|
1127
|
+
// the single named call site both front-ends bind these handlers through,
|
|
1128
|
+
// gated by the package's conformance check.
|
|
1129
|
+
//
|
|
1130
|
+
// configManager and runtimeStore are required (SDK 1.6.1): they back
|
|
1131
|
+
// sessions.permissionMode.get/set and sessions.contextUsage.get. The four
|
|
1132
|
+
// check-in deps (channelDeliveryRouter, providerRegistry, automationManager,
|
|
1133
|
+
// sessionLister) are each optional individually, but the check-in verb
|
|
1134
|
+
// group (checkin.config.get/set, checkin.run, checkin.receipts.list) is
|
|
1135
|
+
// registered ONLY when all four are present — every one of them is already
|
|
1136
|
+
// constructed above in this composition root, so the Agent wires all four:
|
|
1137
|
+
// the proactive check-in loop runs for real here, not as a facade. Off by
|
|
1138
|
+
// default via checkin.enabled (see config/schema-domain-runtime.ts).
|
|
1139
|
+
attachWsOnlyGatewayVerbHandlers(gatewayMethods, {
|
|
1140
|
+
processRegistry,
|
|
1141
|
+
workspaceCheckpointManager: checkpointsGatewayManager,
|
|
1142
|
+
sessionBroker,
|
|
1143
|
+
secretsManager,
|
|
1144
|
+
approvalBroker,
|
|
1145
|
+
shellPaths,
|
|
1146
|
+
configManager,
|
|
1147
|
+
runtimeStore: options.runtimeStore,
|
|
1148
|
+
channelDeliveryRouter,
|
|
1149
|
+
providerRegistry,
|
|
1150
|
+
automationManager,
|
|
1151
|
+
sessionLister: sessionBroker,
|
|
1152
|
+
// The best-of-N surface (fleet.attempts.list/pick/judge): the
|
|
1153
|
+
// orchestration engine already implements FleetAttemptsController
|
|
1154
|
+
// (listHeldMergeGroups/pickAttemptWinner/proposeAttemptWinner) — wiring
|
|
1155
|
+
// it here is what turns those verbs from cataloged-but-unhandled into
|
|
1156
|
+
// real handlers (see src/test/daemon/gateway-ws-only-invokable.test.ts).
|
|
1157
|
+
attemptsController: orchestrationEngine,
|
|
1158
|
+
// worktrees.setup.run (SDK 1.6.1): the rerun affordance for worktree
|
|
1159
|
+
// cold-start setup, registered over a WorktreeRegistry rooted at this
|
|
1160
|
+
// daemon's own working directory (matching worktrees.snapshot's reader)
|
|
1161
|
+
// only when workingDirectory is present. It already is here — this was a
|
|
1162
|
+
// real cataloged-but-unhandled gap (found by the gateway parity pin
|
|
1163
|
+
// sweep, see src/test/daemon/gateway-parity-verb-families.test.ts) with
|
|
1164
|
+
// no reason to leave unfixed: the daemon's own working directory was
|
|
1165
|
+
// already in scope in this composition root (see
|
|
1166
|
+
// checkpointsGatewayManager above).
|
|
1167
|
+
workingDirectory,
|
|
1168
|
+
// Conversation half of the unified rewind (rewind.plan/apply with scope
|
|
1169
|
+
// 'conversation' or 'both'): this Agent's own ConversationManager (see
|
|
1170
|
+
// src/core/conversation.ts) IS a genuine in-process mutable conversation
|
|
1171
|
+
// store — the exact shape RewindConversationPort assumes ("a daemon-hosted
|
|
1172
|
+
// mutable conversation store") — so it is wired here rather than left
|
|
1173
|
+
// null, ported from goodvibes-tui's identical seam
|
|
1174
|
+
// (conversation-rewind-port.ts, registerSessionConversation at bootstrap;
|
|
1175
|
+
// see bootstrap-core.ts). createSessionConversationRewindPort() reads the
|
|
1176
|
+
// live per-session registry lazily (no conversation reference needed at
|
|
1177
|
+
// construction time), so it can be called here even though bootstrap
|
|
1178
|
+
// wires the actual session registration separately. See
|
|
1179
|
+
// src/test/daemon/gateway-rewind-conversation-scope.test.ts for the live
|
|
1180
|
+
// proof this resolves a real conversation, not a stub.
|
|
1181
|
+
conversationRewindPort: createSessionConversationRewindPort(),
|
|
1182
|
+
});
|
|
1183
|
+
// Turn the fleet registry's coalesced snapshot tick into poll-free
|
|
1184
|
+
// spawn/progress/attention/completion events on the runtime event bus's
|
|
1185
|
+
// 'fleet' domain (SDK 1.6.1, runtime/fleet/emit-bridge.ts) — the SDK's own
|
|
1186
|
+
// doc note on this bridge: "The control-plane gateway already fans every
|
|
1187
|
+
// runtime-bus domain out to subscribed SSE/WebSocket clients, so no
|
|
1188
|
+
// gateway/channel change is needed once the fleet domain exists." Without
|
|
1189
|
+
// this call the fleet.* gateway verbs above still answer pull queries
|
|
1190
|
+
// (fleet.snapshot/fleet.list), but nothing pushes live deltas — a webui
|
|
1191
|
+
// FleetView watching this agent's orchestrator-spawned sub-agents would see
|
|
1192
|
+
// them only on manual refresh, never their attention state flipping to
|
|
1193
|
+
// needs-input in real time. No unsubscribe is kept: the bridge is meant to
|
|
1194
|
+
// live for the registry's lifetime (same non-disposed pattern as the
|
|
1195
|
+
// fleet/push verb registrations just above; see the SDK's own doc comment
|
|
1196
|
+
// on attachFleetEmitBridge).
|
|
1197
|
+
attachFleetEmitBridge({ registry: processRegistry, bus: options.runtimeBus });
|
|
1198
|
+
|
|
1199
|
+
// The relay step-up ceremony service the SDK's RuntimeServices requires (and
|
|
1200
|
+
// the daemon facade dereferences at start). Constructed exactly as the SDK
|
|
1201
|
+
// composition root does, from the public @pellux/goodvibes-sdk/daemon export.
|
|
1202
|
+
const stepUpService = new StepUpService({ secrets: secretsManager });
|
|
954
1203
|
|
|
955
1204
|
return {
|
|
1205
|
+
stepUpService,
|
|
956
1206
|
workingDirectory,
|
|
957
1207
|
homeDirectory,
|
|
958
1208
|
shellPaths,
|
|
959
1209
|
configManager,
|
|
960
1210
|
featureFlags,
|
|
1211
|
+
contextAccountingHolder,
|
|
961
1212
|
orchestrationEngine,
|
|
962
1213
|
codeIndexStore,
|
|
963
1214
|
codeIndexReindexScheduler,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PermissionCategory, PermissionCheckResult } from '@pellux/goodvibes-sdk/platform/permissions';
|
|
2
2
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
3
|
+
import { HARNESS_MODE_DESCRIPTORS } from '../tools/agent-harness-mode-catalog.ts';
|
|
3
4
|
|
|
4
5
|
type PermissionManagerLike = {
|
|
5
6
|
check(toolName: string, args: Record<string, unknown>): Promise<boolean>;
|
|
@@ -18,6 +19,7 @@ const READ_TOOL_NAMES = new Set([
|
|
|
18
19
|
'state',
|
|
19
20
|
'registry',
|
|
20
21
|
'route',
|
|
22
|
+
'repo_map',
|
|
21
23
|
'goodvibes_context',
|
|
22
24
|
'autonomy',
|
|
23
25
|
'channels',
|
|
@@ -36,7 +38,6 @@ const READ_TOOL_NAMES = new Set([
|
|
|
36
38
|
'settings',
|
|
37
39
|
'vibe',
|
|
38
40
|
'workspace',
|
|
39
|
-
'agent_harness',
|
|
40
41
|
'agent_knowledge',
|
|
41
42
|
'agent_operator_briefing',
|
|
42
43
|
]);
|
|
@@ -76,6 +77,18 @@ const READ_ONLY_AUTONOMY_ACTIONS = new Set(['', 'intake', 'request', 'route', 'p
|
|
|
76
77
|
const READ_ONLY_DELEGATION_ACTIONS = new Set(['', 'status', 'summary', 'overview', 'policy', 'decision', 'decisions', 'delegation_posture', 'routes', 'list', 'catalog', 'posture', 'route', 'item', 'card', 'show', 'inspect', 'delegation_route']);
|
|
77
78
|
const READ_ONLY_EXECUTION_ACTIONS = new Set(['', 'status', 'summary', 'overview', 'routes', 'posture', 'execution_posture', 'route', 'show_route', 'inspect_route', 'execution_route', 'history', 'activity', 'records', 'execution_history', 'record', 'item', 'show', 'inspect', 'execution_history_item', 'processes', 'background', 'backgrounds', 'background_processes', 'capabilities', 'process_capabilities', 'process', 'background_process', 'recovery', 'file_recovery', 'undo_redo']);
|
|
78
79
|
|
|
80
|
+
// The agent_harness tool takes a `mode`. Every mode whose catalog kind is not
|
|
81
|
+
// 'effect' only inspects, lists, or aliases read-only state, so it classifies as
|
|
82
|
+
// 'read'. Every 'effect' mode changes settings, memory, skills, personas,
|
|
83
|
+
// routines, keybindings, drafts, or runs a promotion/consolidation/command pass,
|
|
84
|
+
// so it classifies as a write. A mode string that is absent from the catalog
|
|
85
|
+
// (unknown or misspelled) is deliberately treated as a write, never a read.
|
|
86
|
+
const READ_ONLY_HARNESS_MODES: ReadonlySet<string> = new Set(
|
|
87
|
+
HARNESS_MODE_DESCRIPTORS
|
|
88
|
+
.filter((descriptor) => descriptor.kind !== 'effect')
|
|
89
|
+
.map((descriptor) => descriptor.id),
|
|
90
|
+
);
|
|
91
|
+
|
|
79
92
|
type MarkedPermissionManager = PermissionManagerLike & { [SAFETY_MARKER]?: true };
|
|
80
93
|
|
|
81
94
|
export function installPermissionManagerSafetyGuard(manager: PermissionManagerLike): void {
|
|
@@ -275,6 +288,10 @@ export function fallbackPermissionCategoryForArgs(toolName: string, args: Record
|
|
|
275
288
|
: '';
|
|
276
289
|
return READ_ONLY_EXECUTION_ACTIONS.has(action) ? 'read' : 'write';
|
|
277
290
|
}
|
|
291
|
+
if (toolName === 'agent_harness') {
|
|
292
|
+
const mode = typeof args.mode === 'string' ? args.mode.trim() : '';
|
|
293
|
+
return READ_ONLY_HARNESS_MODES.has(mode) ? 'read' : 'write';
|
|
294
|
+
}
|
|
278
295
|
if (toolName === 'agent_artifacts') {
|
|
279
296
|
const mode = typeof args.mode === 'string' ? args.mode.trim() : '';
|
|
280
297
|
return mode === 'list' || mode === 'show' ? 'read' : 'write';
|
|
@@ -3,15 +3,28 @@ import type { PermissionRequest } from '@pellux/goodvibes-sdk/platform/permissio
|
|
|
3
3
|
import type { SessionSnapshot } from '@/runtime/index.ts';
|
|
4
4
|
import type { SystemMessageRouter } from '../core/system-message-router.ts';
|
|
5
5
|
import { readConversationMessageSnapshots } from '../core/conversation-message-snapshot.ts';
|
|
6
|
+
import { answerWorkspaceRegistrationPrompt, type StoreShellPaths } from '../config/workspace-registration.ts';
|
|
6
7
|
|
|
7
8
|
export type PendingPermissionState = PermissionRequest & {
|
|
8
9
|
resolve: (approved: boolean, remember?: boolean) => void;
|
|
9
10
|
};
|
|
10
11
|
|
|
12
|
+
/**
|
|
13
|
+
* First-start registration prompt (owner-approved design): the root that was
|
|
14
|
+
* offered, and the shellPaths needed to answer it (register or decline)
|
|
15
|
+
* against the shared registration store without threading a separate
|
|
16
|
+
* callback through every caller — see answerWorkspaceRegistrationPrompt.
|
|
17
|
+
*/
|
|
18
|
+
export type PendingWorkspaceRegistrationState = {
|
|
19
|
+
readonly root: string;
|
|
20
|
+
readonly shellPaths: StoreShellPaths;
|
|
21
|
+
};
|
|
22
|
+
|
|
11
23
|
export type BlockingInputHandlerOptions = {
|
|
12
24
|
data: string;
|
|
13
25
|
pendingPermission: PendingPermissionState | null;
|
|
14
26
|
recoveryPending: boolean;
|
|
27
|
+
pendingWorkspaceRegistration: PendingWorkspaceRegistrationState | null;
|
|
15
28
|
abortTurn: () => void;
|
|
16
29
|
conversation: ConversationManager;
|
|
17
30
|
systemMessageRouter: SystemMessageRouter;
|
|
@@ -24,6 +37,7 @@ export type BlockingInputHandlerResult = {
|
|
|
24
37
|
handled: boolean;
|
|
25
38
|
pendingPermission: PendingPermissionState | null;
|
|
26
39
|
recoveryPending: boolean;
|
|
40
|
+
pendingWorkspaceRegistration: PendingWorkspaceRegistrationState | null;
|
|
27
41
|
};
|
|
28
42
|
|
|
29
43
|
export function handleBlockingShellInput(
|
|
@@ -33,6 +47,7 @@ export function handleBlockingShellInput(
|
|
|
33
47
|
data,
|
|
34
48
|
pendingPermission,
|
|
35
49
|
recoveryPending,
|
|
50
|
+
pendingWorkspaceRegistration,
|
|
36
51
|
abortTurn,
|
|
37
52
|
conversation,
|
|
38
53
|
systemMessageRouter,
|
|
@@ -48,24 +63,24 @@ export function handleBlockingShellInput(
|
|
|
48
63
|
if (key === 'y') {
|
|
49
64
|
req.resolve(true, false);
|
|
50
65
|
render();
|
|
51
|
-
return { handled: true, pendingPermission: null, recoveryPending };
|
|
66
|
+
return { handled: true, pendingPermission: null, recoveryPending, pendingWorkspaceRegistration };
|
|
52
67
|
}
|
|
53
68
|
|
|
54
69
|
if (key === 'a') {
|
|
55
70
|
req.resolve(true, true);
|
|
56
71
|
render();
|
|
57
|
-
return { handled: true, pendingPermission: null, recoveryPending };
|
|
72
|
+
return { handled: true, pendingPermission: null, recoveryPending, pendingWorkspaceRegistration };
|
|
58
73
|
}
|
|
59
74
|
|
|
60
75
|
if (key === 'n' || data === '\x1b' || data === '\x03') {
|
|
61
76
|
req.resolve(false, false);
|
|
62
77
|
abortTurn();
|
|
63
78
|
render();
|
|
64
|
-
return { handled: true, pendingPermission: null, recoveryPending };
|
|
79
|
+
return { handled: true, pendingPermission: null, recoveryPending, pendingWorkspaceRegistration };
|
|
65
80
|
}
|
|
66
81
|
|
|
67
82
|
render();
|
|
68
|
-
return { handled: true, pendingPermission, recoveryPending };
|
|
83
|
+
return { handled: true, pendingPermission, recoveryPending, pendingWorkspaceRegistration };
|
|
69
84
|
}
|
|
70
85
|
|
|
71
86
|
if (recoveryPending) {
|
|
@@ -79,21 +94,36 @@ export function handleBlockingShellInput(
|
|
|
79
94
|
}
|
|
80
95
|
deleteRecoveryFile();
|
|
81
96
|
render();
|
|
82
|
-
return { handled: true, pendingPermission: null, recoveryPending: false };
|
|
97
|
+
return { handled: true, pendingPermission: null, recoveryPending: false, pendingWorkspaceRegistration };
|
|
83
98
|
}
|
|
84
99
|
|
|
85
100
|
if (data === '\x1b' || data === '\x03') {
|
|
86
101
|
systemMessageRouter.high('[Recovery] Discarded recovery data.');
|
|
87
102
|
deleteRecoveryFile();
|
|
88
103
|
render();
|
|
89
|
-
return { handled: true, pendingPermission: null, recoveryPending: false };
|
|
104
|
+
return { handled: true, pendingPermission: null, recoveryPending: false, pendingWorkspaceRegistration };
|
|
90
105
|
}
|
|
91
106
|
|
|
92
107
|
systemMessageRouter.high('[Recovery] Ignored saved session; starting a new prompt.');
|
|
93
108
|
deleteRecoveryFile();
|
|
94
109
|
render();
|
|
95
|
-
return { handled: false, pendingPermission: null, recoveryPending: false };
|
|
110
|
+
return { handled: false, pendingPermission: null, recoveryPending: false, pendingWorkspaceRegistration };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// First-start registration prompt: 'y' registers, EVERY other key (Escape,
|
|
114
|
+
// Enter-through, Ctrl+C, any stray key) declines — default no, matching the
|
|
115
|
+
// owner-approved design. Subtree-scoped: never asks again at this root
|
|
116
|
+
// either way, since both add() and decline() are recorded against it.
|
|
117
|
+
if (pendingWorkspaceRegistration) {
|
|
118
|
+
const { root, shellPaths } = pendingWorkspaceRegistration;
|
|
119
|
+
const accepted = data.toLowerCase().trim() === 'y';
|
|
120
|
+
answerWorkspaceRegistrationPrompt(shellPaths, root, accepted);
|
|
121
|
+
systemMessageRouter.high(accepted
|
|
122
|
+
? `[Workspace] Registered ${root} — automatic checkpoints are now allowed here.`
|
|
123
|
+
: `[Workspace] Not registered — automatic checkpoints stay off here (won't ask again for this location).`);
|
|
124
|
+
render();
|
|
125
|
+
return { handled: true, pendingPermission: null, recoveryPending, pendingWorkspaceRegistration: null };
|
|
96
126
|
}
|
|
97
127
|
|
|
98
|
-
return { handled: false, pendingPermission, recoveryPending };
|
|
128
|
+
return { handled: false, pendingPermission, recoveryPending, pendingWorkspaceRegistration };
|
|
99
129
|
}
|
|
@@ -3,6 +3,7 @@ import type { HookDispatcher, HookEventPath, HookPhase, HookCategory } from '@pe
|
|
|
3
3
|
import type { SessionManager } from '@pellux/goodvibes-sdk/platform/sessions';
|
|
4
4
|
import {
|
|
5
5
|
checkRecoveryFile,
|
|
6
|
+
createShellPathService,
|
|
6
7
|
formatReturnContextForDisplay,
|
|
7
8
|
persistConversation,
|
|
8
9
|
writeRecoveryFile,
|
|
@@ -14,6 +15,8 @@ import {
|
|
|
14
15
|
} from '../runtime/onboarding/index.ts';
|
|
15
16
|
import { deriveOnboardingState } from '../runtime/onboarding/onboarding-state.ts';
|
|
16
17
|
import { buildSetupIncompleteHint } from '../core/setup-incomplete-hint.ts';
|
|
18
|
+
import { isBroadWorkspaceRoot, normalizeWorkspaceRoot, resolveWorkspaceRegistrationSync } from '../config/workspace-registration.ts';
|
|
19
|
+
import type { PendingWorkspaceRegistrationState } from './blocking-input.ts';
|
|
17
20
|
|
|
18
21
|
export interface SessionPersistenceAndRecoveryDeps {
|
|
19
22
|
readonly buildCurrentSessionSnapshot: () => SessionSnapshot;
|
|
@@ -41,6 +44,8 @@ export interface SessionPersistenceAndRecoveryResult {
|
|
|
41
44
|
recoveryInterval: ReturnType<typeof setInterval>;
|
|
42
45
|
/** True if an unsaved recovery session was found and the user prompt was shown. */
|
|
43
46
|
recoveryPending: boolean;
|
|
47
|
+
/** Set when the first-start registration prompt was shown this launch (see below). */
|
|
48
|
+
pendingWorkspaceRegistration: PendingWorkspaceRegistrationState | null;
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
/**
|
|
@@ -69,6 +74,7 @@ export function wireSessionPersistenceAndRecovery(
|
|
|
69
74
|
sessionManager,
|
|
70
75
|
onStreamSpeedUpdate,
|
|
71
76
|
} = deps;
|
|
77
|
+
const shellPaths = createShellPathService({ workingDirectory: workingDir, homeDirectory });
|
|
72
78
|
|
|
73
79
|
// --- Streaming speed + tool preview wiring ---
|
|
74
80
|
let streamStartTime = 0;
|
|
@@ -125,7 +131,26 @@ export function wireSessionPersistenceAndRecovery(
|
|
|
125
131
|
);
|
|
126
132
|
}, 60_000);
|
|
127
133
|
|
|
128
|
-
|
|
134
|
+
// First-start registration prompt (owner-approved design): only when this
|
|
135
|
+
// directory resolves to unknown (not covered, not declined) AND it isn't a
|
|
136
|
+
// broad root the store would refuse anyway (see isBroadWorkspaceRoot's own
|
|
137
|
+
// doc comment on why an "offer" check is safe to keep separate from the
|
|
138
|
+
// store's authoritative write-time guard). Skipped when a recovery prompt
|
|
139
|
+
// already claimed this launch's ambient attention, and while onboarding is
|
|
140
|
+
// still incomplete — the full-screen onboarding wizard would otherwise
|
|
141
|
+
// compete with this prompt for the very next keypress the owner types.
|
|
142
|
+
let pendingWorkspaceRegistration: PendingWorkspaceRegistrationState | null = null;
|
|
143
|
+
const onboardingDone = Boolean(readOnboardingCompletionMarker(shellPaths, 'user').payload);
|
|
144
|
+
if (!recoveryPending && onboardingDone) {
|
|
145
|
+
const resolution = resolveWorkspaceRegistrationSync(shellPaths, workingDir);
|
|
146
|
+
if (resolution.status === 'unknown' && !isBroadWorkspaceRoot(shellPaths, workingDir)) {
|
|
147
|
+
systemMessageRouter.high(`[Workspace] "${workingDir}" is not a registered workspace, so automatic (turn-end) checkpoints are off here. Register it? Press "y" to register, any other key to decline (won't ask again for this location).`);
|
|
148
|
+
render();
|
|
149
|
+
pendingWorkspaceRegistration = { root: normalizeWorkspaceRoot(workingDir), shellPaths };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { recoveryInterval, recoveryPending, pendingWorkspaceRegistration };
|
|
129
154
|
}
|
|
130
155
|
|
|
131
156
|
/**
|
|
@@ -147,7 +147,7 @@ function buildAgentCapabilitiesContract(registry?: ToolRegistry): Record<string,
|
|
|
147
147
|
{
|
|
148
148
|
area: 'Project and computer work',
|
|
149
149
|
can: 'Read, search, analyze, edit, and write workspace files; run bounded shell commands; inspect diffs and project structure; recover recent file edit/write snapshots.',
|
|
150
|
-
tools: ['read', 'find', 'analyze', 'inspect', 'edit', 'write', 'exec', optionalTool('execution'), optionalTool('delegation')].filter(has),
|
|
150
|
+
tools: ['read', 'find', 'analyze', 'inspect', 'edit', 'write', 'exec', optionalTool('repo_map'), optionalTool('execution'), optionalTool('delegation')].filter(has),
|
|
151
151
|
inspect: 'execution action:"status"; delegation action:"status"',
|
|
152
152
|
},
|
|
153
153
|
{
|
|
@@ -68,12 +68,30 @@ export function describeCliCommandPolicy(commandName: string): CommandExecutionP
|
|
|
68
68
|
boundary: 'Diagnostics and posture commands are readable from Agent-owned settings, provider, model, and connected-host capability surfaces without taking connected-host lifecycle ownership.',
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
|
-
if (root === 'profiles' || root === 'personas' || root === 'skills' || root === 'memory' || root === 'routines' || root === 'sessions' || root === 'bundle') {
|
|
71
|
+
if (root === 'profiles' || root === 'personas' || root === 'skills' || root === 'memory' || root === 'routines' || root === 'sessions' || root === 'bundle' || root === 'import' || root === 'workspaces' || root === 'fleet') {
|
|
72
72
|
return {
|
|
73
73
|
effect: 'local-state',
|
|
74
74
|
confirmation,
|
|
75
|
-
preferredModelTool: root === 'profiles' ? agentHarnessModes('workspace_actions', 'workspace_action', 'run_workspace_action') : 'agent_local_registry',
|
|
76
|
-
boundary:
|
|
75
|
+
preferredModelTool: root === 'profiles' || root === 'workspaces' || root === 'fleet' ? agentHarnessModes('workspace_actions', 'workspace_action', 'run_workspace_action') : 'agent_local_registry',
|
|
76
|
+
boundary: root === 'workspaces'
|
|
77
|
+
? 'The registered-workspace list that gates automatic checkpoints (owner ruling, 2026-07-10) is Agent-local, user-scoped state. There is no dedicated model tool yet; registration/unregistration is a mutating action and requires explicit user intent (the CLI itself requires --yes).'
|
|
78
|
+
: root === 'fleet'
|
|
79
|
+
? 'Best-of-N held-merge attempt groups (SDK 1.6.1) are this Agent\'s own orchestration engine state, not a connected-host call — fleet.attempts.* is ws-only with no HTTP binding, so agent_operator_method cannot reach it either. There is no dedicated model tool yet; picking a winner is a mutating, worktree-cleaning action and requires explicit user intent (the CLI itself requires --yes).'
|
|
80
|
+
: 'Local library/profile/session/bundle/import CLI commands operate on Agent-local data. Mutations require explicit user intent and should use first-class Agent-local tools where available.',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (root === 'ci' || root === 'principals' || root === 'channel-profiles') {
|
|
84
|
+
return {
|
|
85
|
+
effect: 'connected-host-state',
|
|
86
|
+
confirmation,
|
|
87
|
+
// agent_operator_method, NOT agent_operator_action: the action tool's
|
|
88
|
+
// allowlist covers only approvals.* and automation.* and cannot invoke
|
|
89
|
+
// ci.*/principals.*/channels.profiles.* — pointing the model there would
|
|
90
|
+
// dead-end in "unknown action". The generic operator-method tool routes
|
|
91
|
+
// these directly (read-only methods run without confirmation; writes
|
|
92
|
+
// require confirm + explicitUserRequest).
|
|
93
|
+
preferredModelTool: 'agent_operator_method (methodId "ci.status", "ci.watches.*", "principals.*", "channels.profiles.*")',
|
|
94
|
+
boundary: 'CI status/watches, principal identity mappings, and per-channel profile defaults live on the connected host. Reads run through agent_operator_method without confirmation; writes require confirm + explicitUserRequest there, and mutating CLI subcommands require --yes.',
|
|
77
95
|
};
|
|
78
96
|
}
|
|
79
97
|
if (root === 'knowledge' || root === 'ask' || root === 'search') {
|
|
@@ -92,6 +110,14 @@ export function describeCliCommandPolicy(commandName: string): CommandExecutionP
|
|
|
92
110
|
boundary: 'Delegation is explicit user-directed work only; no hidden background review or separate Agent job should be created implicitly.',
|
|
93
111
|
};
|
|
94
112
|
}
|
|
113
|
+
if (root === 'relay') {
|
|
114
|
+
return {
|
|
115
|
+
effect: 'read-only',
|
|
116
|
+
confirmation,
|
|
117
|
+
preferredModelTool: `${settingsActions('list', 'get')} (category "relay") or ${agentHarnessModes('tools')}`,
|
|
118
|
+
boundary: 'relay status reports the connected host\'s imported relay.* configuration and the relay-connect feature flag only — it is not a live check (Agent hosts no daemon and the SDK has no remote relay-status route). relay pair always honestly refuses: minting a pairing payload needs the relay identity private key, which only the daemon actually holding the relay identity has.',
|
|
119
|
+
};
|
|
120
|
+
}
|
|
95
121
|
if (root === 'subscription' || root === 'secrets' || root === 'pair') {
|
|
96
122
|
return {
|
|
97
123
|
effect: root === 'pair' ? 'external-network' : 'mixed',
|