@slopus/happy-terminal 0.3.0-beta.13
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/LICENSE +21 -0
- package/LICENSE-CODEX +203 -0
- package/LICENSE-GROK-BUILD +204 -0
- package/LICENSE-HAPPY +21 -0
- package/README.md +60 -0
- package/THIRD-PARTY-NOTICES.md +34 -0
- package/dist/HappyTerminalUserError.d.ts +12 -0
- package/dist/app/AppTranscriptEntry.d.ts +33 -0
- package/dist/app/AppendOnlyStreamingRender.d.ts +12 -0
- package/dist/app/CodexFileDiff.d.ts +20 -0
- package/dist/app/CodexMcpToolCall.d.ts +25 -0
- package/dist/app/CodingAssistantAgentBackend.d.ts +81 -0
- package/dist/app/CodingAssistantApp.d.ts +128 -0
- package/dist/app/CompletedTurn.d.ts +11 -0
- package/dist/app/FileMentionAutocomplete.d.ts +30 -0
- package/dist/app/HappyTerminalProcessTerminal.d.ts +7 -0
- package/dist/app/NoticeChild.d.ts +4 -0
- package/dist/app/SecretMenuController.d.ts +31 -0
- package/dist/app/SelectionList.d.ts +24 -0
- package/dist/app/SessionDraftSync.d.ts +51 -0
- package/dist/app/StartupStatusApp.d.ts +34 -0
- package/dist/app/StartupStatusCardModel.d.ts +22 -0
- package/dist/app/TemporaryFullscreenController.d.ts +9 -0
- package/dist/app/TerminalColorLevel.d.ts +1 -0
- package/dist/app/TerminalOutputTrace.d.ts +12 -0
- package/dist/app/TerminalTheme.d.ts +11 -0
- package/dist/app/TranscriptEntryRenderCache.d.ts +10 -0
- package/dist/app/applyWorkflowRunUpdate.d.ts +2 -0
- package/dist/app/blendComposerBackground.d.ts +2 -0
- package/dist/app/boundedJsonStringify.d.ts +1 -0
- package/dist/app/calculateCacheHitPercent.d.ts +2 -0
- package/dist/app/codexDiffAnsi.d.ts +17 -0
- package/dist/app/compactCompletedTurnEntries.d.ts +2 -0
- package/dist/app/containsMarkdownTable.d.ts +1 -0
- package/dist/app/createAgentMarkdownTheme.d.ts +3 -0
- package/dist/app/createBackgroundTerminalViewer.d.ts +16 -0
- package/dist/app/createCompletedTurn.d.ts +6 -0
- package/dist/app/createEditorTheme.d.ts +3 -0
- package/dist/app/createFileMentionAutocompleteItems.d.ts +3 -0
- package/dist/app/createSecretInputPanel.d.ts +12 -0
- package/dist/app/createSelectionPanel.d.ts +14 -0
- package/dist/app/createSerialTaskQueue.d.ts +1 -0
- package/dist/app/createSessionPicker.d.ts +17 -0
- package/dist/app/createSlashCommands.d.ts +7 -0
- package/dist/app/createStopOnceHandler.d.ts +1 -0
- package/dist/app/createSubagentMonitor.d.ts +17 -0
- package/dist/app/createTerminalInputBurstHandler.d.ts +5 -0
- package/dist/app/createWorkflowMonitor.d.ts +15 -0
- package/dist/app/defaultTerminalTheme.d.ts +1 -0
- package/dist/app/describeModelChoice.d.ts +4 -0
- package/dist/app/describeProviderSignIn.d.ts +2 -0
- package/dist/app/describeReasoningLevel.d.ts +4 -0
- package/dist/app/detectCodexDiffLanguage.d.ts +1 -0
- package/dist/app/encodeModelChoice.d.ts +1 -0
- package/dist/app/endsAfterOpeningCodeFence.d.ts +1 -0
- package/dist/app/findFileMentionContext.d.ts +9 -0
- package/dist/app/findFileMentionPrefix.d.ts +5 -0
- package/dist/app/formatActivityElapsedTime.d.ts +1 -0
- package/dist/app/formatCodexMcpToolResult.d.ts +3 -0
- package/dist/app/formatCompactTokens.d.ts +1 -0
- package/dist/app/formatFileMention.d.ts +1 -0
- package/dist/app/formatProviderError.d.ts +6 -0
- package/dist/app/formatRelativeTime.d.ts +2 -0
- package/dist/app/formatResetDuration.d.ts +1 -0
- package/dist/app/formatSessionPickerEntry.d.ts +14 -0
- package/dist/app/formatSessionTokenStatus.d.ts +6 -0
- package/dist/app/formatSessionUsageSummary.d.ts +3 -0
- package/dist/app/formatStartupStatusUsageRemaining.d.ts +2 -0
- package/dist/app/formatStartupStatusUsageRows.d.ts +2 -0
- package/dist/app/formatSubagentToolCall.d.ts +10 -0
- package/dist/app/formatToolPermissionNotice.d.ts +3 -0
- package/dist/app/formatToolResultForDisplay.d.ts +2 -0
- package/dist/app/formatTurnUsageSummary.d.ts +2 -0
- package/dist/app/formatWorkUsageSummary.d.ts +14 -0
- package/dist/app/highlightAgentCode.d.ts +1 -0
- package/dist/app/highlightCodexDiffLine.d.ts +2 -0
- package/dist/app/highlightShellCommand.d.ts +1 -0
- package/dist/app/humanizeGoalStatus.d.ts +2 -0
- package/dist/app/humanizeMcpName.d.ts +1 -0
- package/dist/app/humanizePermissionMode.d.ts +2 -0
- package/dist/app/humanizePermissionReviewLevel.d.ts +2 -0
- package/dist/app/humanizeProviderId.d.ts +1 -0
- package/dist/app/humanizeReasoningLevel.d.ts +1 -0
- package/dist/app/humanizeSubagentStatus.d.ts +2 -0
- package/dist/app/humanizeToolName.d.ts +1 -0
- package/dist/app/humanizeWorkflowStatus.d.ts +2 -0
- package/dist/app/installResumeInstructions.d.ts +26 -0
- package/dist/app/installTerminalCrashCleanup.d.ts +26 -0
- package/dist/app/isLightTerminalBackground.d.ts +2 -0
- package/dist/app/layoutCodexFileDiff.d.ts +13 -0
- package/dist/app/looksLikeAuthenticationFailure.d.ts +5 -0
- package/dist/app/nearestXtermColorIndex.d.ts +2 -0
- package/dist/app/parseCodexMcpToolInvocation.d.ts +2 -0
- package/dist/app/parseSessionCommand.d.ts +6 -0
- package/dist/app/providerErrorResetAt.d.ts +2 -0
- package/dist/app/providerQuotaToStartupStatusUsage.d.ts +3 -0
- package/dist/app/readClipboardImage.d.ts +9 -0
- package/dist/app/renderActivityWave.d.ts +3 -0
- package/dist/app/renderAgentMarkdown.d.ts +8 -0
- package/dist/app/renderBackgroundTerminalCompletion.d.ts +1 -0
- package/dist/app/renderBackgroundTerminalInteraction.d.ts +2 -0
- package/dist/app/renderBackgroundTerminalSummary.d.ts +1 -0
- package/dist/app/renderChildRows.d.ts +13 -0
- package/dist/app/renderCodexDiffLine.d.ts +3 -0
- package/dist/app/renderCodexFileDiff.d.ts +7 -0
- package/dist/app/renderCodexMcpToolCall.d.ts +2 -0
- package/dist/app/renderCompletedTurnStats.d.ts +2 -0
- package/dist/app/renderExecCommand.d.ts +11 -0
- package/dist/app/renderExploration.d.ts +9 -0
- package/dist/app/renderFullscreenComponent.d.ts +8 -0
- package/dist/app/renderHappyTerminalBanner.d.ts +6 -0
- package/dist/app/renderHappyTerminalVersion.d.ts +1 -0
- package/dist/app/renderNoticeWithChildren.d.ts +7 -0
- package/dist/app/renderPendingSteeringMessages.d.ts +1 -0
- package/dist/app/renderStartupStatusCard.d.ts +7 -0
- package/dist/app/renderSubagentSummary.d.ts +6 -0
- package/dist/app/renderTurnCompletionSeparator.d.ts +1 -0
- package/dist/app/renderWorkflowSummary.d.ts +1 -0
- package/dist/app/resolveInputBackground.d.ts +3 -0
- package/dist/app/resolveStartupProviderQuota.d.ts +3 -0
- package/dist/app/resolveStartupSessionId.d.ts +18 -0
- package/dist/app/resolveTerminalColorLevel.d.ts +2 -0
- package/dist/app/resolveTerminalStyle.d.ts +1 -0
- package/dist/app/resolveTerminalTheme.d.ts +5 -0
- package/dist/app/runApp.d.ts +27 -0
- package/dist/app/runClipboardCommand.d.ts +8 -0
- package/dist/app/sanitizeTerminalText.d.ts +1 -0
- package/dist/app/shortenHomePath.d.ts +2 -0
- package/dist/app/sortSubagentsForDisplay.d.ts +2 -0
- package/dist/app/subagentElapsedMs.d.ts +2 -0
- package/dist/app/subagentLogMessageText.d.ts +2 -0
- package/dist/app/surfaceThemedLine.d.ts +3 -0
- package/dist/app/truncatePathToWidth.d.ts +1 -0
- package/dist/app/truncateTextForDisplay.d.ts +5 -0
- package/dist/app/truncateUtf8BytesForDisplay.d.ts +1 -0
- package/dist/app/upsertSubagentSummary.d.ts +2 -0
- package/dist/app/workflowDisplay.d.ts +2 -0
- package/dist/client/HappyAgentEventHub.d.ts +25 -0
- package/dist/client/RemoteAgent.d.ts +74 -0
- package/dist/client/RemoteAgentRunError.d.ts +4 -0
- package/dist/client/RemoteTerminalAttachment.d.ts +19 -0
- package/dist/client/RemoteTerminalClientReplica.d.ts +14 -0
- package/dist/client/ensureLocalProtocolServer.d.ts +10 -0
- package/dist/client/fetchProviderQuotas.d.ts +10 -0
- package/dist/client/index.d.ts +8 -0
- package/dist/client/loadAgentCatalog.d.ts +32 -0
- package/dist/config/createProjectConfigSecurityNotice.d.ts +10 -0
- package/dist/config/defaultConfig.d.ts +2 -0
- package/dist/config/getDefaultGlobalConfigPath.d.ts +1 -0
- package/dist/config/getDefaultLocalConfigPath.d.ts +1 -0
- package/dist/config/getDefaultRuntimeConfigPath.d.ts +1 -0
- package/dist/config/getHappyConfigDirectory.d.ts +1 -0
- package/dist/config/getHappyTerminalHome.d.ts +1 -0
- package/dist/config/index.d.ts +15 -0
- package/dist/config/loadConfig.d.ts +2 -0
- package/dist/config/mergeConfigValues.d.ts +2 -0
- package/dist/config/parseConfigToml.d.ts +7 -0
- package/dist/config/readConfigFile.d.ts +2 -0
- package/dist/config/readProjectConfigFile.d.ts +2 -0
- package/dist/config/resolveConfigPaths.d.ts +2 -0
- package/dist/config/runtimeConfigLock.d.ts +1 -0
- package/dist/config/types.d.ts +74 -0
- package/dist/config/updateRuntimeConfig.d.ts +2 -0
- package/dist/config/updateRuntimePreferences.d.ts +2 -0
- package/dist/config/withoutProjectMachineSettings.d.ts +7 -0
- package/dist/config/writeRuntimeConfig.d.ts +3 -0
- package/dist/config/writeRuntimeConfigDefaults.d.ts +2 -0
- package/dist/daemon/createUnixSocketFetch.d.ts +2 -0
- package/dist/daemon/daemonPid.d.ts +11 -0
- package/dist/daemon/ensureHappyAgentBinary.d.ts +15 -0
- package/dist/daemon/ensureLocalProtocolServer.d.ts +24 -0
- package/dist/daemon/getHappyDaemonPaths.d.ts +16 -0
- package/dist/daemon/happyAgentBinaryConfig.d.ts +15 -0
- package/dist/daemon/index.d.ts +5 -0
- package/dist/daemon/runDaemonCommand.d.ts +4 -0
- package/dist/debug/DebugLog.d.ts +11 -0
- package/dist/debug/closeNodeInspector.d.ts +1 -0
- package/dist/debug/createDebugJsonReplacer.d.ts +1 -0
- package/dist/debug/createRequestDebugDirectory.d.ts +1 -0
- package/dist/debug/getDebugRootDirectory.d.ts +1 -0
- package/dist/debug/getNodeInspectorUrl.d.ts +1 -0
- package/dist/debug/index.d.ts +8 -0
- package/dist/debug/openNodeInspector.d.ts +1 -0
- package/dist/debug/registerHappyTerminalDebugRoot.d.ts +5 -0
- package/dist/docs/DESIGN.md +2191 -0
- package/dist/docs/README.md +93 -0
- package/dist/docs/agents-and-collaboration.md +365 -0
- package/dist/docs/architecture.md +468 -0
- package/dist/docs/extending.md +561 -0
- package/dist/docs/happy.md +386 -0
- package/dist/docs/permissions-and-sandbox.md +342 -0
- package/dist/docs/workspaces.md +281 -0
- package/dist/errorToMessage.d.ts +1 -0
- package/dist/formatCliFailure.d.ts +10 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +17151 -0
- package/dist/main.js +18946 -0
- package/dist/observability/daemonContext.d.ts +16 -0
- package/dist/observability/index.d.ts +1 -0
- package/dist/processes/BoundedOutputBuffer.d.ts +20 -0
- package/dist/processes/NativeProcessManager.d.ts +66 -0
- package/dist/processes/ProcessGroupReaper.d.ts +32 -0
- package/dist/processes/index.d.ts +7 -0
- package/dist/processes/isProcessRunning.d.ts +1 -0
- package/dist/processes/isTargetProcessAlive.d.ts +1 -0
- package/dist/processes/killProcessTree.d.ts +2 -0
- package/dist/processes/resolveSystemShell.d.ts +1 -0
- package/dist/processes/startProcessTransport.d.ts +33 -0
- package/dist/processes/types.d.ts +41 -0
- package/dist/processes/waitForProcessExit.d.ts +2 -0
- package/dist/protocol/AppletProtocol.d.ts +101 -0
- package/dist/protocol/Attachment.d.ts +96 -0
- package/dist/protocol/ClientProtocolTypes.d.ts +481 -0
- package/dist/protocol/DocumentProtocol.d.ts +109 -0
- package/dist/protocol/EventId.d.ts +1 -0
- package/dist/protocol/FolderProtocol.d.ts +236 -0
- package/dist/protocol/FolderSharingProtocol.d.ts +97 -0
- package/dist/protocol/GitHubPluginProtocol.d.ts +43 -0
- package/dist/protocol/GlobalSecurityProtocol.d.ts +7 -0
- package/dist/protocol/HappyCloudProtocol.d.ts +178 -0
- package/dist/protocol/InstallationProtocol.d.ts +80 -0
- package/dist/protocol/OnboardingProtocol.d.ts +47 -0
- package/dist/protocol/P2pCredentialProtocol.d.ts +273 -0
- package/dist/protocol/P2pIdentityProtocol.d.ts +8 -0
- package/dist/protocol/P2pPairingProtocol.d.ts +68 -0
- package/dist/protocol/P2pProtocol.d.ts +185 -0
- package/dist/protocol/PluginWireTypes.d.ts +81 -0
- package/dist/protocol/ProfileProtocol.d.ts +127 -0
- package/dist/protocol/ProjectFileProtocol.d.ts +63 -0
- package/dist/protocol/ProjectProtocol.d.ts +646 -0
- package/dist/protocol/ProtocolVersion.d.ts +8 -0
- package/dist/protocol/ServiceNotice.d.ts +175 -0
- package/dist/protocol/SessionProtocol.d.ts +1156 -0
- package/dist/protocol/SharingProtocol.d.ts +159 -0
- package/dist/protocol/SlotProtocol.d.ts +270 -0
- package/dist/protocol/TimelineProtocol.d.ts +88 -0
- package/dist/protocol/WorkletProtocol.d.ts +159 -0
- package/dist/protocol/createEventIdFactory.d.ts +7 -0
- package/dist/protocol/index.d.ts +34 -0
- package/dist/readPackageVersion.d.ts +1 -0
- package/dist/readPackageVersion.js +16 -0
- package/dist/reportCliFailure.d.ts +2 -0
- package/dist/runHappyTerminal.d.ts +6 -0
- package/dist/terminal/index.d.ts +2 -0
- package/dist/terminal/types.d.ts +39 -0
- package/dist/types.d.ts +6 -0
- package/dist/writeStderrSync.d.ts +5 -0
- package/package.json +97 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# Permissions, sandboxing, and shell behavior
|
|
2
|
+
|
|
3
|
+
This document describes what an agent running inside Happy Agent is allowed to do, how a
|
|
4
|
+
restricted action can be escalated, and why a refused action must not be
|
|
5
|
+
retried by another route. It is written for coding agents, and it describes the
|
|
6
|
+
behavior that is actually implemented.
|
|
7
|
+
|
|
8
|
+
## One permission model for every provider
|
|
9
|
+
|
|
10
|
+
Happy Agent has a single permission model. Codex, Claude, Grok, and MCP tools all
|
|
11
|
+
execute through the same `AgentContext`, the same filesystem boundary, the same
|
|
12
|
+
shell sandbox, and the same `PermissionContext`. Provider differences exist only
|
|
13
|
+
in tool names, argument schemas, result formatting, and model-facing guidance.
|
|
14
|
+
There is no provider-specific security path in the agent loop.
|
|
15
|
+
|
|
16
|
+
Two consequences matter in practice:
|
|
17
|
+
|
|
18
|
+
- Running the "other provider's" tool never widens what you may do. `Bash`,
|
|
19
|
+
`exec_command`, and `run_terminal_command` are the same sandboxed execution
|
|
20
|
+
with different argument names.
|
|
21
|
+
- Every tool declares its own permission behavior on its definition. The loop
|
|
22
|
+
never decides anything from a tool name, a name prefix, a provider key, or a
|
|
23
|
+
guess about command contents.
|
|
24
|
+
|
|
25
|
+
The relevant fields on a tool definition are:
|
|
26
|
+
|
|
27
|
+
| Field | Meaning |
|
|
28
|
+
| --------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
29
|
+
| `shouldReviewInAutoMode` | Required. Whether this exact invocation must be reviewed in Auto. |
|
|
30
|
+
| `shouldRunInFullAccessInAutoMode` | Whether an allowed review also grants this one execution Full access. Review alone never implies elevation. |
|
|
31
|
+
| `requiresAutoOrFullAccess` | The tool acts outside Happy Agent's local sandbox and cannot run at all in Read only or Workspace write. |
|
|
32
|
+
| `describeAutoPermissionAction` | Human-readable description of the exact reviewed boundary. Required whenever a review can happen. |
|
|
33
|
+
| `autoPermissionInstructions` | Provider-specific Auto guidance injected into the system prompt only while the tool is active. |
|
|
34
|
+
| `availableToPermissionReviewer` | Whether the read-only reviewer agent may call the tool while investigating. |
|
|
35
|
+
|
|
36
|
+
## The four permission modes
|
|
37
|
+
|
|
38
|
+
The mode is `"read_only" | "workspace_write" | "auto" | "full_access"`, and Auto
|
|
39
|
+
is the default.
|
|
40
|
+
The mode lives in a `PermissionContext`, which exposes the current `mode`, a
|
|
41
|
+
`revision` counter, `setMode`, and `runWithMode` for a scoped temporary
|
|
42
|
+
override.
|
|
43
|
+
|
|
44
|
+
### Read only
|
|
45
|
+
|
|
46
|
+
- File tools may inspect files; any write fails with "File changes are disabled
|
|
47
|
+
in read-only mode."
|
|
48
|
+
- Shell commands run, but nothing on the host is writable except temporary
|
|
49
|
+
directories. The workspace itself is not writable.
|
|
50
|
+
- Shell network access is blocked; no managed proxy is created for this mode.
|
|
51
|
+
- On macOS and Linux, restricted reads follow Codex and may inspect the host
|
|
52
|
+
filesystem. `assertCanReadPath` only enforces its sensitive-path denial on
|
|
53
|
+
other platforms, where reading a private path outside the workspace fails with
|
|
54
|
+
a message pointing at Full access.
|
|
55
|
+
- On macOS, no local unix socket may be created in this mode.
|
|
56
|
+
|
|
57
|
+
### Workspace write
|
|
58
|
+
|
|
59
|
+
- File changes are allowed inside the working directory.
|
|
60
|
+
- Writes outside the working directory are refused: "Workspace write mode cannot
|
|
61
|
+
modify files outside the working directory."
|
|
62
|
+
- The project config file (`happy.toml`) is refused.
|
|
63
|
+
- Protected Git control paths are refused: "Workspace write mode cannot modify
|
|
64
|
+
Git control files without Full access."
|
|
65
|
+
- Shell writes are confined to the working directory, its Git control paths, and
|
|
66
|
+
temporary directories. Shell network access is blocked except for destinations
|
|
67
|
+
the user allowed in configuration, reached through Happy Agent's managed proxy.
|
|
68
|
+
|
|
69
|
+
### Auto
|
|
70
|
+
|
|
71
|
+
Auto is Workspace write plus automatic review.
|
|
72
|
+
|
|
73
|
+
- Routine reads and workspace edits run with no review at all.
|
|
74
|
+
- Every shell tool uses the Workspace write sandbox **by default**.
|
|
75
|
+
- A tool may request review for one exact action. Review is automatic and never
|
|
76
|
+
becomes a question to the user.
|
|
77
|
+
- Only a tool whose `shouldRunInFullAccessInAutoMode` returns true receives a
|
|
78
|
+
temporary Full access override, and only for that single execution. The loop
|
|
79
|
+
re-checks the mode immediately before starting the override and restores Auto
|
|
80
|
+
as soon as the call settles.
|
|
81
|
+
- Tools with `requiresAutoOrFullAccess` (all MCP operations) are available.
|
|
82
|
+
|
|
83
|
+
### Full access
|
|
84
|
+
|
|
85
|
+
Happy Agent's filesystem, shell, and network restrictions are removed.
|
|
86
|
+
`createSandboxedCommand` returns the raw command with no sandbox wrapper, and
|
|
87
|
+
`assertCanReadPath` / `assertCanWritePath` return immediately. Full access does
|
|
88
|
+
not claim to provide the restricted sandbox boundary — it is the absence of it.
|
|
89
|
+
Custom shells (`shell:` on `exec_command`) are available only in Full access.
|
|
90
|
+
|
|
91
|
+
## How Auto review works
|
|
92
|
+
|
|
93
|
+
The agent loop prepares a tool's permission before executing it:
|
|
94
|
+
|
|
95
|
+
1. If the mode is not Auto, nothing is reviewed.
|
|
96
|
+
2. The tool's `shouldReviewInAutoMode` is called with the real arguments. If it
|
|
97
|
+
returns false, the call executes normally inside the sandbox.
|
|
98
|
+
3. Otherwise `describeAutoPermissionAction` produces the exact action text. A
|
|
99
|
+
tool that requests review without defining it fails with an error instead of
|
|
100
|
+
executing.
|
|
101
|
+
4. A read-only side agent — Codex's Guardian policy, plus the user's global
|
|
102
|
+
`SECURITY.md` if present — reviews the action against the durable
|
|
103
|
+
conversation transcript. It may make read-only tool calls while deciding.
|
|
104
|
+
Its own permission mode is never Auto, so a review cannot recurse.
|
|
105
|
+
5. The verdict is `allow` or `deny`. There is no third outcome and no prompt to
|
|
106
|
+
the user.
|
|
107
|
+
|
|
108
|
+
### What counts as authorization
|
|
109
|
+
|
|
110
|
+
The reviewer sees a role-aware transcript built by
|
|
111
|
+
`createAutoPermissionTranscript`, not a compacted model-context suffix. Real
|
|
112
|
+
user messages and trusted answers to interactive questions are authorization
|
|
113
|
+
evidence and are preserved preferentially inside the review budget. Assistant
|
|
114
|
+
text, tool arguments, tool output, repository content, generated summaries, and
|
|
115
|
+
anything injected into a file are **not** authorization.
|
|
116
|
+
|
|
117
|
+
The verdict is re-derived from the reviewer's own classification
|
|
118
|
+
(`shouldAllowAutoPermissionReview`): a `critical` risk is never allowed
|
|
119
|
+
automatically, and a `high` risk is allowed only with medium or high user
|
|
120
|
+
authorization. If user evidence had to be dropped to fit the budget and the risk
|
|
121
|
+
is above `low`, the action is denied — the review fails closed.
|
|
122
|
+
|
|
123
|
+
### Denial semantics
|
|
124
|
+
|
|
125
|
+
`describeAutoPermissionDenial` produces the agent-facing result. There are three
|
|
126
|
+
distinct outcomes and they do not mean the same thing:
|
|
127
|
+
|
|
128
|
+
- **Rejected.** A real judgement was made. You must not pursue the same outcome
|
|
129
|
+
by another route, by splitting it into smaller steps, or by working around the
|
|
130
|
+
restriction. Continue only with a materially safer alternative; otherwise stop
|
|
131
|
+
and tell the user what you wanted to do and why it was refused, so they can
|
|
132
|
+
decide. If the user then approves it in their own words, that reply is real
|
|
133
|
+
authorization and the next review can allow it.
|
|
134
|
+
- **Timed out.** The review did not finish within its 90-second budget. The
|
|
135
|
+
action is unproven, not unsafe. You may try once more, or ask the user.
|
|
136
|
+
- **Unavailable.** No reviewer could run, or no reviewer exists for the session.
|
|
137
|
+
No judgement was made about the action. Continue with work that does not need
|
|
138
|
+
the permission, or ask the user. Auto without a reviewer never falls back to
|
|
139
|
+
silent execution.
|
|
140
|
+
|
|
141
|
+
### Refusal loops must end the turn
|
|
142
|
+
|
|
143
|
+
Because Auto never interrupts the user, nothing outside the agent can break a
|
|
144
|
+
loop of refusals. `AutoPermissionDenialCircuitBreaker` therefore stops the turn
|
|
145
|
+
itself: 3 refusals in a row, or 10 refusals within the last 50 decisions. A
|
|
146
|
+
successful action clears the consecutive counter but not the windowed one. When
|
|
147
|
+
it trips, the turn ends with an instruction to tell the user what you were
|
|
148
|
+
trying to do and why it kept being refused.
|
|
149
|
+
|
|
150
|
+
Treat that as the floor, not the target. An agent that reacts to a refusal by
|
|
151
|
+
trying a variation of the same action is doing the one thing the denial text
|
|
152
|
+
explicitly forbids.
|
|
153
|
+
|
|
154
|
+
## Escalation syntax per provider
|
|
155
|
+
|
|
156
|
+
The escalation field is provider-shaped, but every one of them requests the same
|
|
157
|
+
runtime behavior: in Auto, review the action first; if allowed, scope only that
|
|
158
|
+
one tool execution to `full_access`, then restore Auto immediately.
|
|
159
|
+
|
|
160
|
+
| Provider tool | Field | Where the reason goes |
|
|
161
|
+
| --------------------------- | ------------------------------------------ | --------------------- |
|
|
162
|
+
| Codex `exec_command` | `sandbox_permissions: "require_escalated"` | `justification` |
|
|
163
|
+
| Claude `Bash` | `dangerouslyDisableSandbox: true` | `description` |
|
|
164
|
+
| Grok `run_terminal_command` | `sandbox_permissions: "require_escalated"` | `description` |
|
|
165
|
+
|
|
166
|
+
A Pi `bash` tool with `sandbox_permissions: "require_escalated"` and a
|
|
167
|
+
`justification` is sometimes described alongside these. Happy Agent currently ships the
|
|
168
|
+
Codex, Claude, and Grok shell surfaces only; the Pi surface is part of the stated
|
|
169
|
+
model, not of what runs today.
|
|
170
|
+
|
|
171
|
+
Rules that apply to all of them:
|
|
172
|
+
|
|
173
|
+
- Omitting the field, or setting it to `use_default` / `false`, keeps the
|
|
174
|
+
command sandboxed and skips review entirely.
|
|
175
|
+
- In Read only or Workspace write the field does not bypass the selected mode.
|
|
176
|
+
- Reviewed actions that do not need host access stay in the current sandbox.
|
|
177
|
+
Sending input to an existing shell session (`write_stdin`, `TaskInput`) is
|
|
178
|
+
reviewed whenever the input is non-empty, but it is never elevated.
|
|
179
|
+
|
|
180
|
+
File tools follow the same rule from the other direction. `Read`, `Write`,
|
|
181
|
+
`Edit`, and `apply_patch` extract their real path argument and call the shared
|
|
182
|
+
boundary helpers. A path outside the workspace, a symlink escape, a protected
|
|
183
|
+
Git control path, or the project config file triggers review and, when allowed,
|
|
184
|
+
the temporary elevation for that one call. Paths under the user's skill roots
|
|
185
|
+
are readable without review.
|
|
186
|
+
|
|
187
|
+
## Sandbox limits
|
|
188
|
+
|
|
189
|
+
These limits apply to shell commands in Read only, Workspace write, and Auto.
|
|
190
|
+
They are enforced by the platform sandbox, not by inspecting your command text,
|
|
191
|
+
so rewriting the command does not change the outcome.
|
|
192
|
+
|
|
193
|
+
- **Writes** are confined to the working directory, its Git control paths, and
|
|
194
|
+
temporary directories. Everything else on the host is readable but not
|
|
195
|
+
writable. In Read only, only temporary directories are writable.
|
|
196
|
+
- **Unix sockets** must live inside the working directory. On macOS a socket
|
|
197
|
+
anywhere else is refused, including in a temporary directory, and the home
|
|
198
|
+
directory (or any ancestor of it) is never granted socket scope — that is
|
|
199
|
+
exactly where `~/.docker`, `~/.gnupg`, and agent sockets live. The host's own
|
|
200
|
+
sockets — the Docker daemon, the SSH agent, Happy Agent's control socket — are
|
|
201
|
+
unreachable by design.
|
|
202
|
+
- **Local port binding**: on macOS, binding a local TCP or UDP port is refused
|
|
203
|
+
unless the user enabled `network.allowLocalBinding` in configuration. On Linux
|
|
204
|
+
and inside Docker the command gets its own network namespace (`--unshare-net`),
|
|
205
|
+
so a listener it starts is reachable only from inside that command.
|
|
206
|
+
- **Outbound network** is blocked except for domains and ports the user allowed,
|
|
207
|
+
which are reached through Happy Agent's managed HTTP/SOCKS proxy. The allowlist comes
|
|
208
|
+
from `network.allowedDomains` with `network.allowedPorts` (default `[443]`);
|
|
209
|
+
`network.deniedDomains` overrides it. A blocked request explains itself: not in
|
|
210
|
+
the allowlist, in the denylist, DNS could not be resolved safely within two
|
|
211
|
+
seconds, or the destination resolves to a local or private address. Happy Agent owns
|
|
212
|
+
the proxy environment variables; unsetting them cannot grant direct access.
|
|
213
|
+
Only the user can change the policy, in the repository's `happy.toml` or the
|
|
214
|
+
global config.
|
|
215
|
+
- **Keychain**: on macOS the keychain is unavailable. `security`, and anything
|
|
216
|
+
backed by it, fails or reports nothing rather than returning a secret. Treat
|
|
217
|
+
every other system credential store the same way. Secrets reach a command only
|
|
218
|
+
through the `secrets` argument, which injects an attached session bundle.
|
|
219
|
+
- **Protected paths** are read-only even inside the workspace: `.agents`,
|
|
220
|
+
`.codex`, the project config files, and Happy Agent's own server directory, socket,
|
|
221
|
+
and token paths.
|
|
222
|
+
|
|
223
|
+
When a limit blocks necessary work: in Auto, request reviewed full-access
|
|
224
|
+
execution for that one command and explain why. In Read only or Workspace write,
|
|
225
|
+
stop and tell the user which limit it was. A sandbox refusal is not a bug in your
|
|
226
|
+
command and is not something to route around.
|
|
227
|
+
|
|
228
|
+
### Platform implementations
|
|
229
|
+
|
|
230
|
+
- **macOS** uses the system Seatbelt sandbox at `/usr/bin/sandbox-exec` with a
|
|
231
|
+
closed-by-default policy adapted from Codex: all reads allowed, writes only to
|
|
232
|
+
the computed writable roots, explicit denials for protected paths, PTY support,
|
|
233
|
+
and no network unless local binding or loopback ports were configured.
|
|
234
|
+
- **Linux** uses Bubblewrap: `/` bound read-only, `--dev /dev`, a private
|
|
235
|
+
`/tmp`, explicit `--bind` for writable roots, `--ro-bind` for protected paths,
|
|
236
|
+
and `--unshare-user --unshare-pid --unshare-net`. When nested procfs mounting
|
|
237
|
+
is unavailable, an empty private `/proc` is the fallback. `socat` bridges the
|
|
238
|
+
managed proxy into the namespace over unix sockets with a command-scoped
|
|
239
|
+
authentication token.
|
|
240
|
+
- **Other platforms** fall back to `@anthropic-ai/sandbox-runtime` with a
|
|
241
|
+
generated settings file.
|
|
242
|
+
- Restricted execution fails closed with a readable explanation when a sandbox
|
|
243
|
+
dependency is missing.
|
|
244
|
+
|
|
245
|
+
The repository's root `happy.toml` is part of the sandbox boundary because it
|
|
246
|
+
can grant managed network access to later commands. Restricted commands see an
|
|
247
|
+
existing file read-only. When it does not exist, it remains absent before,
|
|
248
|
+
during, and after the command; Happy Agent never creates a placeholder or other
|
|
249
|
+
synthetic file at that path.
|
|
250
|
+
|
|
251
|
+
## Shell and background processes
|
|
252
|
+
|
|
253
|
+
A shell command starts with a wait, not a life expectancy.
|
|
254
|
+
|
|
255
|
+
- The wait is a timeout on _you_, not on the command. When it expires the
|
|
256
|
+
command is not killed: it moves to the background and you get its output so
|
|
257
|
+
far plus a session ID to come back to.
|
|
258
|
+
- Claude `Bash`: `timeout` in milliseconds, default 120000, max 600000.
|
|
259
|
+
`run_in_background: true` starts it in the background immediately and waits
|
|
260
|
+
only about 3 seconds to confirm it did not fall over.
|
|
261
|
+
- Codex `exec_command`: `yield_time_ms`, default 10000 ms, effective range
|
|
262
|
+
250–30000 ms.
|
|
263
|
+
- Every provider can run in the background, read only what accumulated since the
|
|
264
|
+
last read, write to stdin, and stop a process.
|
|
265
|
+
- Stopping is graceful first and forceful about 2 seconds later, and it takes
|
|
266
|
+
the whole process tree.
|
|
267
|
+
- A background process that exits without having been read to the end produces a
|
|
268
|
+
developer message saying only that it ended. No output is attached; read it
|
|
269
|
+
with the normal read tool, which keeps answering for a while after the exit.
|
|
270
|
+
Do not poll for exit.
|
|
271
|
+
- Lifetime: a background process belongs to the session that started it and
|
|
272
|
+
lives as long as that session's runtime inside the daemon. Cancelling a turn
|
|
273
|
+
does not kill it. Archiving the session or exiting the daemon kills everything
|
|
274
|
+
it started.
|
|
275
|
+
- At most 64 active sessions per session runtime. Passing the cap evicts and
|
|
276
|
+
kills the oldest instead of failing your command; the evicted session stays
|
|
277
|
+
readable.
|
|
278
|
+
|
|
279
|
+
### TTY behavior
|
|
280
|
+
|
|
281
|
+
`tty` defaults to false, which uses pipes. Ask for a TTY only when a program
|
|
282
|
+
behaves differently without one. A PTY-backed command gets an 80×24 terminal and
|
|
283
|
+
an environment that discourages terminal output: `TERM=dumb`, `NO_COLOR=1`,
|
|
284
|
+
`COLORTERM=""`, and `PAGER`, `GIT_PAGER`, `GH_PAGER` forced to `cat`. Nothing is
|
|
285
|
+
done about full-screen applications beyond that. Interactive flags such as
|
|
286
|
+
`git rebase -i` are not supported.
|
|
287
|
+
|
|
288
|
+
## MCP
|
|
289
|
+
|
|
290
|
+
An MCP server executes outside Happy Agent's local filesystem sandbox, so Happy Agent cannot
|
|
291
|
+
enforce its boundary locally.
|
|
292
|
+
|
|
293
|
+
- Every MCP tool sets `requiresAutoOrFullAccess: true`. In Read only or
|
|
294
|
+
Workspace write the call fails with "This action requires Auto or Full access
|
|
295
|
+
because it can operate outside Happy Agent's local sandbox."
|
|
296
|
+
- Every direct and dynamic MCP tool invocation is reviewed in Auto:
|
|
297
|
+
`shouldReviewInAutoMode: () => true`, including `call_mcp_tool` and
|
|
298
|
+
`get_mcp_prompt`.
|
|
299
|
+
- Server-supplied annotations such as `readOnlyHint` are untrusted metadata.
|
|
300
|
+
They are never authorization evidence and never a reason to skip review.
|
|
301
|
+
- Happy Agent-owned protocol operations that are intrinsically read-only skip review:
|
|
302
|
+
`list_mcp_tools`, `list_mcp_resources`, `list_mcp_resource_templates`,
|
|
303
|
+
`read_mcp_resource`, `list_mcp_prompts`.
|
|
304
|
+
- The approval text discloses the external boundary explicitly: the server can
|
|
305
|
+
perform actions outside Happy Agent's filesystem sandbox.
|
|
306
|
+
|
|
307
|
+
## Docker sandbox environments
|
|
308
|
+
|
|
309
|
+
Happy Agent can run a session's commands inside a Docker container. The same permission
|
|
310
|
+
model applies; Docker is the outer isolation, not a replacement for the inner
|
|
311
|
+
sandbox.
|
|
312
|
+
|
|
313
|
+
An image or existing container used as a Happy Agent execution environment must contain
|
|
314
|
+
Bubblewrap and `socat`, and must allow the nested namespaces Bubblewrap needs.
|
|
315
|
+
`prepareDockerSandbox` probes for exactly that and fails with an actionable
|
|
316
|
+
message: install `bubblewrap` and `socat` in the image, and when connecting to an
|
|
317
|
+
existing container start it with `--security-opt seccomp=unconfined`.
|
|
318
|
+
|
|
319
|
+
Inside the container, a restricted command runs under Bubblewrap with `/` bound
|
|
320
|
+
read-only, a private `/tmp`, `--unshare-net`, the workspace bound writable (or
|
|
321
|
+
read-only in Read only mode), and `.agents`, `.codex`, and the project config
|
|
322
|
+
files bound read-only. Managed proxy access reaches the container through
|
|
323
|
+
temporary unix sockets shared by the working-directory bind mount, beneath a
|
|
324
|
+
root every restricted command sees read-only, with unguessable command-scoped
|
|
325
|
+
authentication. A restricted Docker command does not inherit the container's
|
|
326
|
+
parent process table or another command's temporary process-control state.
|
|
327
|
+
|
|
328
|
+
Proxy processes, socket bridges, and temporary directories are removed when a
|
|
329
|
+
command finishes or fails.
|
|
330
|
+
|
|
331
|
+
## Quick reference for agents
|
|
332
|
+
|
|
333
|
+
- Assume Auto. Read and edit inside the workspace freely; that path has no
|
|
334
|
+
review at all.
|
|
335
|
+
- Escalate only when the sandbox actually blocks necessary work, with one
|
|
336
|
+
command and a concrete reason. Do not escalate speculatively — an escalated
|
|
337
|
+
command is reviewed and may be refused.
|
|
338
|
+
- A refusal is a decision about the proposed action only. It is not a durable
|
|
339
|
+
rule, and an allow is not authorization for anything later.
|
|
340
|
+
- Never work around a denial. Take a materially safer route, or stop and explain.
|
|
341
|
+
- Never assume a background process needs polling, and never assume a timeout
|
|
342
|
+
killed a command.
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# Workspaces
|
|
2
|
+
|
|
3
|
+
A workspace is a folder an agent works in. In a Git project a workspace is a
|
|
4
|
+
worktree: its own checkout, on its own branch, cut from the project.
|
|
5
|
+
|
|
6
|
+
Every conversation already runs inside a workspace — the working directory. Most
|
|
7
|
+
work needs no workspace management at all. This document explains what a
|
|
8
|
+
workspace is, how one is created and archived, where it lives on disk, how to
|
|
9
|
+
start work inside another workspace, and when creating one is actually the right
|
|
10
|
+
call.
|
|
11
|
+
|
|
12
|
+
## What a workspace is
|
|
13
|
+
|
|
14
|
+
Happy Agent knows about _projects_ and _workspaces_:
|
|
15
|
+
|
|
16
|
+
- A **project** is a folder Happy Agent has been pointed at. It has an ID, a name, and a
|
|
17
|
+
storage key derived from the name.
|
|
18
|
+
- A **workspace** belongs to one project. It has an ID, a name, a path, a status,
|
|
19
|
+
and — when this session created it — an `owned` flag.
|
|
20
|
+
|
|
21
|
+
A workspace's status moves through `initializing` → `ready`, or ends at `failed`.
|
|
22
|
+
Archiving moves it to `archiving` and then `archived`. Only a `ready` workspace
|
|
23
|
+
can be worked in: starting an agent in one that is still initializing is
|
|
24
|
+
refused with a message saying so.
|
|
25
|
+
|
|
26
|
+
Managed workspaces require a Git repository project. `create_workspace` fails
|
|
27
|
+
with "Managed workspaces require a Git repository project." when the project
|
|
28
|
+
folder is not the top level of a repository. Copying the folder for non-Git
|
|
29
|
+
projects is planned, not implemented.
|
|
30
|
+
|
|
31
|
+
## Where workspaces live
|
|
32
|
+
|
|
33
|
+
Workspaces are created under a managed root, one directory per project:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
<workspaces root>/<project storage key>/<workspace storage key>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The root depends on the platform:
|
|
40
|
+
|
|
41
|
+
| Platform | Root |
|
|
42
|
+
| -------- | ----------------------------------------------------------------- |
|
|
43
|
+
| macOS | `~/Happy/Workspaces` |
|
|
44
|
+
| Linux | `~/happy/workspaces` |
|
|
45
|
+
| Any | `HAPPY_AGENT_WORKSPACES_DIRECTORY` (absolute path) overrides both |
|
|
46
|
+
|
|
47
|
+
Storage keys are slugs of the display name (lowercase, ASCII, dashes, at most 48
|
|
48
|
+
characters). A collision gets a numeric suffix: `workspace`, `workspace-2`,
|
|
49
|
+
`workspace-3`. The absolute path is persisted when the workspace is reserved and
|
|
50
|
+
stays authoritative afterwards, so changing the root only affects new
|
|
51
|
+
workspaces.
|
|
52
|
+
|
|
53
|
+
Never guess a workspace path. Use the `path` that `create_workspace`,
|
|
54
|
+
`list_workspaces`, or `delegate_to_workspace` returns.
|
|
55
|
+
|
|
56
|
+
## Naming
|
|
57
|
+
|
|
58
|
+
A workspace is created with a placeholder name, and takes the name of its first
|
|
59
|
+
chat once that chat is named. Its branch follows: renaming a workspace, whether
|
|
60
|
+
the first chat does it or a person does, renames the Git branch to match. The
|
|
61
|
+
folder never moves, because a chat is already working inside it.
|
|
62
|
+
|
|
63
|
+
A workspace a person has named is never renamed again by a chat.
|
|
64
|
+
|
|
65
|
+
## How a workspace is created
|
|
66
|
+
|
|
67
|
+
`create_workspace` takes a human-readable `name` and an optional `base_ref`:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{ "name": "Retry policy rewrite" }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
What happens, in order:
|
|
74
|
+
|
|
75
|
+
1. **The base commit is resolved.**
|
|
76
|
+
With no `base_ref`, Happy Agent fetches `origin` and forks `origin/<trunk>` — the
|
|
77
|
+
trunk as the remote has it, not whatever the project folder happens to be
|
|
78
|
+
checked out on. It falls back to the local branch when there is no reachable
|
|
79
|
+
`origin`. An explicit `base_ref` is used exactly as given, and an unknown ref
|
|
80
|
+
is an error. Fetching only writes remote-tracking refs and objects; the
|
|
81
|
+
project's own working tree, `HEAD`, and local branches are never touched.
|
|
82
|
+
2. **The workspace row is reserved** with a unique name, storage key, and path,
|
|
83
|
+
and the workspace is published as `initializing`. The tool returns at this
|
|
84
|
+
point.
|
|
85
|
+
3. **The worktree is materialized in the background**: `git worktree add -b
|
|
86
|
+
worktree/<branch key> <path> <commit>`, then Git's answer is verified — the
|
|
87
|
+
worktree must be at exactly the requested path and belong to the expected
|
|
88
|
+
repository.
|
|
89
|
+
4. **Configured sync files are replicated** — every path in `workspace.sync`
|
|
90
|
+
and `workspace.protected_sync` that exists in the project root is copied
|
|
91
|
+
into the workspace. This shares files Git cannot provide, such as gitignored
|
|
92
|
+
`.env` files. While the daemon runs, the project root's copies are watched
|
|
93
|
+
and re-copied to every ready workspace whenever they change. Sync is
|
|
94
|
+
one-way and best-effort: the root copy always wins, deletions in the root
|
|
95
|
+
are not replicated, and a missed event catches up on the next change. Paths
|
|
96
|
+
in `protected_sync` are additionally write-protected in workspaces, exactly
|
|
97
|
+
like `permissions.protected_paths`, so sessions cannot modify their copy
|
|
98
|
+
without Full access.
|
|
99
|
+
5. **Setup commands run** — `workspace.setup_commands` from the configuration
|
|
100
|
+
loaded inside the new workspace (for example `pnpm install --frozen-lockfile`).
|
|
101
|
+
6. The workspace is marked `ready`. A failure at any step marks it `failed`.
|
|
102
|
+
|
|
103
|
+
Two consequences worth remembering:
|
|
104
|
+
|
|
105
|
+
- A worktree is **always** a branch, created with the worktree, named
|
|
106
|
+
`worktree/<branch key>` — the workspace name in kebab-case, with a numeric
|
|
107
|
+
suffix when Git or another workspace already holds it. Other tooling keys off
|
|
108
|
+
branch names, so do not rename branches casually.
|
|
109
|
+
- Creation is not instant: the checkout and its setup commands take real time,
|
|
110
|
+
and a fresh workspace has no warm build cache or context.
|
|
111
|
+
|
|
112
|
+
Creation is idempotent by identity: repeating a create with the same requested
|
|
113
|
+
ID answers with the same workspace rather than making a second one. One create
|
|
114
|
+
never produces two entries.
|
|
115
|
+
|
|
116
|
+
## Ownership
|
|
117
|
+
|
|
118
|
+
A workspace records the session that created it. Only that session may:
|
|
119
|
+
|
|
120
|
+
- archive it with `archive_workspace`;
|
|
121
|
+
- start a hidden workspace agent in it with `spawn_workspace_agent`.
|
|
122
|
+
|
|
123
|
+
`list_workspaces` marks these with `owned: true`. A workspace created by another
|
|
124
|
+
session is not yours to move into. Reuse an existing workspace only to continue
|
|
125
|
+
the work already living in it, or when the user explicitly points you at it. If
|
|
126
|
+
coordinating across tasks seems to genuinely require somebody else's workspace,
|
|
127
|
+
ask the user first.
|
|
128
|
+
|
|
129
|
+
## Archiving
|
|
130
|
+
|
|
131
|
+
Archiving is an immediate, irreversible logical action. `archive_workspace` marks
|
|
132
|
+
the workspace `archiving`, stops its setup work, and removes it from the active
|
|
133
|
+
list; removing the worktree and the folder is background cleanup afterwards. If
|
|
134
|
+
cleanup fails the workspace still ends up archived and the failure is logged —
|
|
135
|
+
archival is never rolled back because a folder could not be deleted.
|
|
136
|
+
|
|
137
|
+
Archive an owned workspace when its work is finished or abandoned. Do not keep
|
|
138
|
+
workspaces around "for later": a later task gets a fresh one.
|
|
139
|
+
|
|
140
|
+
`archive_workspace` is reviewed in Auto mode, and the review text names the
|
|
141
|
+
workspace and says its managed worktree will be removed.
|
|
142
|
+
|
|
143
|
+
## Working inside a workspace
|
|
144
|
+
|
|
145
|
+
Work in a workspace runs _from inside_ it. There are two ways to start it, and
|
|
146
|
+
they differ in whether the user sees a conversation.
|
|
147
|
+
|
|
148
|
+
### `spawn_workspace_agent` — hidden subagent
|
|
149
|
+
|
|
150
|
+
Starts a managed subagent whose working directory is the workspace. It does not
|
|
151
|
+
appear in the user's session list; it appears under your session as a subagent
|
|
152
|
+
and reports its result back to you.
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"workspace_id": "ws_...",
|
|
157
|
+
"description": "Port the retry policy",
|
|
158
|
+
"prompt": "Full instructions...",
|
|
159
|
+
"provider": "codex",
|
|
160
|
+
"model": "openai/gpt-5.6-sol",
|
|
161
|
+
"reasoning_effort": "medium",
|
|
162
|
+
"background": true
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
It returns `{ sessionId, taskName, path, status, output }`. Background agents are
|
|
167
|
+
the default; read their output with `TaskOutput` (or your provider's equivalent),
|
|
168
|
+
stop them with `TaskStop`, and send follow-up work with `SendMessage`. `read_only:
|
|
169
|
+
true` restricts the child to Read only instead of inheriting your permission mode.
|
|
170
|
+
Model and reasoning effort are required. `provider` is optional and selects a
|
|
171
|
+
specific visible account when the user's request calls for one; otherwise Happy Agent
|
|
172
|
+
resolves and routes the provider. `context: "parent"` includes the delegator's
|
|
173
|
+
conversation; the default, `"task"`, starts with only the prompt.
|
|
174
|
+
`service_tier: "priority"` requests priority service when supported.
|
|
175
|
+
|
|
176
|
+
Use this when the workspace's work is _your_ work, delegated for isolation, and
|
|
177
|
+
the user only needs your final answer.
|
|
178
|
+
|
|
179
|
+
### `delegate_to_workspace` — visible session
|
|
180
|
+
|
|
181
|
+
Starts a full, user-visible conversation in the workspace. It gets its own place
|
|
182
|
+
in the session list, keeps your session as its parent, and can be reached
|
|
183
|
+
afterwards through `agent_info` + `agent_send` with the returned `agentId`.
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"workspace_id": "ws_...",
|
|
188
|
+
"title": "Retry policy rewrite",
|
|
189
|
+
"prompt": "Full instructions...",
|
|
190
|
+
"provider": "codex",
|
|
191
|
+
"model": "openai/gpt-5.6-sol",
|
|
192
|
+
"reasoning_effort": "medium"
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
It returns `{ agentId, sessionId, projectId, title, workspaceId, workspacePath }`.
|
|
197
|
+
Model and reasoning effort are required here too. `provider`, `read_only`, and
|
|
198
|
+
`service_tier` are optional and have the same meaning as for a hidden workspace
|
|
199
|
+
agent.
|
|
200
|
+
|
|
201
|
+
When the delegated run finishes, you receive a notification with its status and
|
|
202
|
+
result. Messages the user writes in that conversation stay there.
|
|
203
|
+
|
|
204
|
+
`delegate_to_workspace` is reviewed in Auto mode, because it starts a user-visible
|
|
205
|
+
agent working outside your own workspace. Only a primary session can delegate —
|
|
206
|
+
a subagent cannot — and you cannot delegate into the workspace you are already
|
|
207
|
+
working in.
|
|
208
|
+
|
|
209
|
+
### The rule that matters
|
|
210
|
+
|
|
211
|
+
Never start an agent in your own directory and have it reach into another
|
|
212
|
+
workspace's folder by path. An agent whose working directory is one workspace
|
|
213
|
+
must not edit files in another. Start it _in_ the workspace with
|
|
214
|
+
`spawn_workspace_agent` or `delegate_to_workspace`.
|
|
215
|
+
|
|
216
|
+
## Inspecting what exists
|
|
217
|
+
|
|
218
|
+
- `list_workspaces` — workspaces of your project, or of another project when you
|
|
219
|
+
pass `project_id` (that needs cross-workspace access). Listing is for
|
|
220
|
+
inspecting and following up on existing work, **not** for shopping for a
|
|
221
|
+
workspace to reuse.
|
|
222
|
+
- `list_workspace_sessions` — conversations of a project or of one workspace,
|
|
223
|
+
most recently active first. Each entry carries `id`, `agentId`, `title`,
|
|
224
|
+
`status`, `updatedAt`, and `delegatedBy` when an agent started it. The
|
|
225
|
+
`agentId` is what you pass to `agent_info` / `agent_send`.
|
|
226
|
+
- `list_projects` — every project on the machine. Requires cross-workspace access.
|
|
227
|
+
|
|
228
|
+
## When to create a workspace
|
|
229
|
+
|
|
230
|
+
A separate workspace exists to **isolate** work, not to organize it.
|
|
231
|
+
|
|
232
|
+
Create one when a piece of work will run alongside other work and their changes
|
|
233
|
+
could collide. That is the only criterion — not the number of tasks, not their
|
|
234
|
+
size.
|
|
235
|
+
|
|
236
|
+
**Create a workspace when:**
|
|
237
|
+
|
|
238
|
+
- you are starting two or more tasks that will edit the same repository at the
|
|
239
|
+
same time; each gets its own fresh workspace;
|
|
240
|
+
- a long-running piece of work must proceed without disturbing the branch the
|
|
241
|
+
user is looking at.
|
|
242
|
+
|
|
243
|
+
**Do not create a workspace when:**
|
|
244
|
+
|
|
245
|
+
- the work is a subtask of what you are already doing — that belongs in the
|
|
246
|
+
current workspace, however many subagents help with it;
|
|
247
|
+
- you just want tidier organization; a workspace is a full checkout with its own
|
|
248
|
+
dependencies and its own cold context, and that cost has to be bought by real
|
|
249
|
+
isolation;
|
|
250
|
+
- two parallel tasks could be squeezed into one workspace — they must not be.
|
|
251
|
+
|
|
252
|
+
One task, however many hands, is one workspace.
|
|
253
|
+
|
|
254
|
+
## Availability and configuration
|
|
255
|
+
|
|
256
|
+
The workspace tools appear only in a **primary session** (never in a subagent)
|
|
257
|
+
and only when `features.workspaces` is enabled — it is on by default.
|
|
258
|
+
`list_projects` additionally requires `features.cross_workspace`; when that is
|
|
259
|
+
off, the tool is not offered at all rather than failing when it is called.
|
|
260
|
+
|
|
261
|
+
Relevant configuration keys:
|
|
262
|
+
|
|
263
|
+
```toml
|
|
264
|
+
[features]
|
|
265
|
+
workspaces = true
|
|
266
|
+
cross_workspace = false
|
|
267
|
+
|
|
268
|
+
[workspace]
|
|
269
|
+
setup_commands = ["pnpm install --frozen-lockfile"]
|
|
270
|
+
# Project files copied into every workspace and re-copied whenever the project root
|
|
271
|
+
# copy changes, such as gitignored .env files. Sync is one-way: the root copy wins.
|
|
272
|
+
sync = [".env"]
|
|
273
|
+
# Synced like sync, and additionally protected from writing without Full access.
|
|
274
|
+
protected_sync = [".env.production"]
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Tracking changes
|
|
278
|
+
|
|
279
|
+
For a workspace branch, the baseline for "what changed" is the branch's merge
|
|
280
|
+
base with `origin/main`; local `main` is never used. Happy Agent tracks changes
|
|
281
|
+
line by line, detects binary files, and handles large files separately.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function errorToMessage(error: unknown): string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface FormatCliFailureOptions {
|
|
2
|
+
/** Prints the untouched stack instead of the trimmed frame list. */
|
|
3
|
+
debug?: boolean;
|
|
4
|
+
color?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Renders a failure the way the rest of Happy Terminal looks: a marked summary line, an actionable hint,
|
|
8
|
+
* and — only for unexpected crashes — a few trimmed frames instead of a raw Node stack.
|
|
9
|
+
*/
|
|
10
|
+
export declare function formatCliFailure(error: unknown, options?: FormatCliFailureOptions): string;
|
package/dist/index.d.ts
ADDED