@humwick/runner 0.130.5 → 0.130.6
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/agent-bin/humwick +2 -2
- package/agent-bin/humwick-browser +4 -0
- package/hooks/fff-search-redirect/fff-search-redirect.cjs +1 -1
- package/hooks/web-tools-redirect/web-tools-redirect.cjs +2 -2
- package/package.json +7 -10
- package/plugins/browser/skills/playwright-cli/SKILL.md +8 -6
- package/plugins/claude/.claude-plugin/plugin.json +3 -3
- package/plugins/claude/claude-statusline-probe.cjs +26 -16
- package/plugins/claude/hooks/gate-run-guard.provisioned.mjs +47 -17
- package/plugins/claude/hooks/gate-run-guard.sh +3 -3
- package/plugins/claude/hooks/gate-run-guard.ts +5 -5
- package/plugins/claude/hooks/kill-guard.sh +4 -4
- package/plugins/claude/hooks/kill-guard.ts +1 -1
- package/plugins/claude/hooks/permission-request.sh +1 -1
- package/plugins/claude/hooks/post-compact.sh +1 -1
- package/plugins/claude/hooks/post-tool-use.sh +3 -3
- package/plugins/claude/hooks/pre-compact.sh +2 -2
- package/plugins/claude/hooks/read-only-bash-guard.sh +4 -4
- package/plugins/claude/hooks/relay-status.sh +32 -32
- package/plugins/claude/hooks/session-end.sh +2 -2
- package/plugins/claude/hooks/session-start.sh +3 -3
- package/plugins/claude/hooks/shell-trust.ts +1 -1
- package/plugins/claude/hooks/stop-failure.sh +2 -2
- package/plugins/claude/hooks/stop.sh +6 -6
- package/plugins/claude/hooks/subagent-skill-policy.cjs +8 -6
- package/plugins/claude/hooks/subagent-stop.sh +3 -3
- package/plugins/claude/hooks/user-prompt-submit.sh +1 -1
- package/plugins/claude/hooks/wait-loop-guard.ts +3 -3
- package/plugins/claude/monitors/monitors.json +2 -2
- package/plugins/claude/monitors/relay-monitor.provisioned.mjs +96 -62
- package/plugins/claude/monitors/relay-monitor.ts +5 -5
- package/plugins/claude/skills/label/SKILL.md +3 -3
- package/plugins/claude/skills/message/SKILL.md +5 -5
- package/plugins/claude/skills/react/SKILL.md +1 -1
- package/plugins/claude/skills/read-message/SKILL.md +4 -4
- package/plugins/claude/skills/reply/SKILL.md +5 -5
- package/plugins/claude/skills/send-claimable/SKILL.md +5 -5
- package/plugins/claude/skills/slice-first/SKILL.md +5 -3
- package/plugins/claude/skills/slice-first/references/plan-recipe.md +30 -29
- package/plugins/claude/skills/status/SKILL.md +1 -1
- package/plugins/claude/skills/tags/SKILL.md +5 -5
- package/plugins/claude/skills/task/SKILL.md +10 -10
- package/plugins/claude-output-style/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-output-style/output-styles/{agent-relay.md → humwick.md} +1 -1
- package/plugins/codex/skills/guide/SKILL.md +1 -1
- package/plugins/codex/skills/label/SKILL.md +3 -3
- package/plugins/codex/skills/message/SKILL.md +5 -5
- package/plugins/codex/skills/react/SKILL.md +1 -1
- package/plugins/codex/skills/read-message/SKILL.md +4 -4
- package/plugins/codex/skills/reply/SKILL.md +5 -5
- package/plugins/codex/skills/send-claimable/SKILL.md +5 -5
- package/plugins/codex/skills/slice-first/SKILL.md +5 -3
- package/plugins/codex/skills/slice-first/references/plan-recipe.md +30 -29
- package/plugins/codex/skills/status/SKILL.md +1 -1
- package/plugins/codex/skills/tags/SKILL.md +5 -5
- package/plugins/codex/skills/task/SKILL.md +11 -10
- package/src/adapter.ts +95 -92
- package/src/adapters/claude-api.ts +8 -11
- package/src/adapters/claude-context-command.ts +1 -1
- package/src/adapters/claude-credentials.ts +7 -3
- package/src/adapters/claude-delivery-lanes.ts +2 -2
- package/src/adapters/claude-delivery.ts +16 -16
- package/src/adapters/claude-effort.ts +2 -2
- package/src/adapters/claude-exit.ts +2 -2
- package/src/adapters/claude-headless-mirror.ts +18 -2
- package/src/adapters/claude-headless-refusal.ts +3 -3
- package/src/adapters/claude-headless.ts +11 -10
- package/src/adapters/claude-launch-exit.ts +1 -1
- package/src/adapters/claude-monitor-delivery-plan.ts +36 -16
- package/src/adapters/claude-permission.ts +5 -5
- package/src/adapters/claude-prompt-gates.ts +9 -9
- package/src/adapters/claude-prompt-hook.ts +2 -2
- package/src/adapters/claude-session-capture.ts +6 -6
- package/src/adapters/claude-session-probe.ts +25 -25
- package/src/adapters/claude-shutdown.ts +11 -11
- package/src/adapters/claude-status-detectors.ts +31 -31
- package/src/adapters/claude-subagents.ts +7 -7
- package/src/adapters/claude-submit-confirmation.ts +6 -6
- package/src/adapters/claude-tmux.ts +43 -43
- package/src/adapters/claude-token-usage-capture.ts +5 -5
- package/src/adapters/claude-tool-policy.ts +7 -7
- package/src/adapters/claude-transcript.ts +24 -24
- package/src/adapters/claude.ts +100 -100
- package/src/adapters/codex-agent-message-capture.ts +26 -63
- package/src/adapters/codex-app-server.ts +1 -1
- package/src/adapters/codex-client.ts +9 -9
- package/src/adapters/codex-effective-config.ts +1 -1
- package/src/adapters/codex-launch-config.ts +5 -5
- package/src/adapters/codex-rollout-context.ts +2 -2
- package/src/adapters/codex-skill-discovery.ts +1 -1
- package/src/adapters/codex-token-usage-capture.ts +16 -16
- package/src/adapters/codex-tool-output.ts +4 -4
- package/src/adapters/codex-version.ts +3 -3
- package/src/adapters/codex.ts +64 -64
- package/src/adapters/opencode-api.ts +1 -1
- package/src/adapters/opencode-context.ts +20 -22
- package/src/adapters/opencode-model-registry.ts +7 -7
- package/src/adapters/opencode-provider.ts +8 -8
- package/src/adapters/opencode-provisioning.ts +30 -0
- package/src/adapters/opencode-server-retry.ts +1 -1
- package/src/adapters/opencode-soft-steer.ts +5 -5
- package/src/adapters/opencode-spawn.ts +18 -14
- package/src/adapters/opencode-token-usage-capture.ts +12 -12
- package/src/adapters/opencode-tool-events.ts +15 -15
- package/src/adapters/opencode.ts +232 -232
- package/src/adapters/project-shell.ts +11 -11
- package/src/adapters/service-access.ts +6 -6
- package/src/adapters/tmux-offload.ts +9 -9
- package/src/adapters/tool-summary.ts +5 -5
- package/src/agent-redact.ts +1 -1
- package/src/agent-runner/commands/dispatch.ts +10 -10
- package/src/agent-runner/commands/lifecycle.ts +2 -2
- package/src/agent-runner/commands/prompt.ts +19 -19
- package/src/agent-runner/credentials.ts +5 -5
- package/src/agent-runner/inbox.ts +1 -1
- package/src/agent-runner/lifecycle-milestones.ts +3 -3
- package/src/agent-runner/lifecycle-phase.ts +2 -2
- package/src/agent-runner/liveness-exchange.ts +4 -10
- package/src/agent-runner/registration.ts +8 -8
- package/src/agent-runner/runner-context.ts +9 -14
- package/src/agent-runner/session-mirror.ts +12 -12
- package/src/agent-runner/task-claims.ts +2 -2
- package/src/agent-runner/token-usage-coverage.ts +1 -1
- package/src/agent-runner/turn-report.ts +12 -12
- package/src/api-budget-accounting.ts +3 -3
- package/src/area-memory.ts +6 -6
- package/src/artifact-path-materialize.ts +1 -1
- package/src/async-guard.ts +5 -5
- package/src/attachment-cache.ts +17 -22
- package/src/background-script-reconcile.ts +6 -6
- package/src/bounded-log.ts +1 -1
- package/src/busy-reconciler.ts +22 -22
- package/src/claim-tracker.ts +3 -3
- package/src/claude-settings.ts +8 -8
- package/src/claude-system-prompt.ts +1 -1
- package/src/claude-workspace-trust.ts +1 -1
- package/src/config.ts +62 -61
- package/src/context-boundary.ts +5 -5
- package/src/context-command.ts +3 -3
- package/src/control-server.ts +32 -32
- package/src/delivery-result.ts +2 -2
- package/src/effort-command.ts +2 -2
- package/src/empty-turn.ts +22 -14
- package/src/free-port.ts +1 -1
- package/src/gate-run-config.ts +3 -3
- package/src/host-asset-plan.ts +24 -17
- package/src/host-claude-config-worker.ts +2 -2
- package/src/host-sources.ts +2 -2
- package/src/index.ts +16 -16
- package/src/launch-assembly.ts +94 -96
- package/src/liveness.ts +15 -15
- package/src/logger.ts +3 -3
- package/src/mcp-outbox.ts +3 -3
- package/src/mcp-preflight.ts +2 -2
- package/src/mcp-source-selection.ts +2 -2
- package/src/mcp-tool-deferral.ts +1 -1
- package/src/mcp-vault-env.ts +2 -2
- package/src/message-body-config/config.ts +4 -4
- package/src/mid-turn-delivery.ts +2 -2
- package/src/native-memory-lever.ts +8 -8
- package/src/outbox.ts +15 -15
- package/src/pending-permission-store.ts +1 -1
- package/src/profile-home.ts +19 -19
- package/src/profile-projection.ts +26 -24
- package/src/project-instructions.ts +5 -5
- package/src/provider-auth-warning.ts +17 -0
- package/src/provider-double.ts +1 -1
- package/src/provider-hold.ts +3 -3
- package/src/provider-home-marker.ts +1 -1
- package/src/provider-home-tombstone.ts +1 -1
- package/src/provider-input-queue.ts +2 -2
- package/src/provider-integration.ts +15 -13
- package/src/provider-setup.ts +15 -3
- package/src/providers.ts +1 -1
- package/src/provisioning.ts +37 -40
- package/src/question-decision.ts +2 -2
- package/src/quota.ts +0 -6
- package/src/rate-limit.ts +24 -13
- package/src/refused-prompt-store.ts +2 -2
- package/src/relay-injection-events.ts +2 -2
- package/src/relay-instructions.ts +5 -5
- package/src/relay-mcp-proxy.long-turn-e2e.ts +13 -13
- package/src/relay-mcp-proxy.ts +57 -58
- package/src/relay-mcp.ts +55 -69
- package/src/reply-obligation-cache.ts +9 -9
- package/src/response-capture-report.ts +9 -9
- package/src/response-routing.ts +1 -1
- package/src/runner-core.ts +209 -222
- package/src/runner-exit.ts +1 -1
- package/src/runner-helpers.ts +10 -10
- package/src/runner-insights.ts +4 -2
- package/src/runner-mcp-proxy.ts +3 -3
- package/src/runner-spawn-config.ts +3 -3
- package/src/runtime-token-file.ts +1 -1
- package/src/runtime-token-remint.ts +1 -1
- package/src/service-access.ts +2 -2
- package/src/session-destroy.ts +1 -1
- package/src/session-insights.ts +1 -1
- package/src/session-scratch.ts +24 -37
- package/src/spawn-input.ts +6 -6
- package/src/startup-config-fetch.ts +4 -4
- package/src/subreaper.ts +3 -5
- package/src/test-run-dedup.ts +13 -13
- package/src/transcript-context.ts +2 -2
- package/src/transport-errors.ts +1 -1
- package/src/turn-opener.ts +1 -1
- package/src/turn-reconcile-event.ts +2 -2
- package/src/vault-proxy-hop.ts +2 -2
- package/src/web-tools.ts +2 -2
- package/agent-bin/agent-relay-browser +0 -10
- package/src/adapters/provider-quota-harvest.ts +0 -76
|
@@ -6,7 +6,8 @@ import type {
|
|
|
6
6
|
AgentProfileProjectionTrust,
|
|
7
7
|
SpawnProvider,
|
|
8
8
|
} from "@humwick/sdk";
|
|
9
|
-
import { isRecord,
|
|
9
|
+
import { isRecord, MCP_SERVER_INFO } from "@humwick/sdk";
|
|
10
|
+
import { SHARED_PROFILE_CAPABILITIES, STORED_NAMES } from "@humwick/sdk/stored-names";
|
|
10
11
|
import { getManifest } from "@humwick/providers";
|
|
11
12
|
import type { AssembledLaunch } from "./launch-assembly";
|
|
12
13
|
import { projectSourceEnabled, type ResolvedSourceShadow } from "./provisioning";
|
|
@@ -23,10 +24,10 @@ interface AgentProfileProjectionInput {
|
|
|
23
24
|
generatedAt?: number;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
type MatrixSource = "host" | "project" |
|
|
27
|
+
type MatrixSource = "host" | "project" | typeof STORED_NAMES.source;
|
|
27
28
|
type MatrixCapability = "instructions" | "mcp" | "hooks" | "skills" | "plugins";
|
|
28
29
|
|
|
29
|
-
const MATRIX_SOURCES: readonly MatrixSource[] = ["host", "project",
|
|
30
|
+
const MATRIX_SOURCES: readonly MatrixSource[] = ["host", "project", STORED_NAMES.source];
|
|
30
31
|
const MATRIX_CAPABILITIES: readonly MatrixCapability[] = ["instructions", "mcp", "hooks", "skills", "plugins"];
|
|
31
32
|
|
|
32
33
|
export function agentProfileProjectionReport(input: AgentProfileProjectionInput): AgentProfileProjectionReport {
|
|
@@ -72,11 +73,11 @@ function matrixEntries(a: AssembledLaunch): AgentProfileProjectionEntry[] {
|
|
|
72
73
|
"project.hooks": projectHooks(a),
|
|
73
74
|
"project.skills": projectSkills(a),
|
|
74
75
|
"project.plugins": projectPlugins(a),
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
[SHARED_PROFILE_CAPABILITIES.instructions]: relayInstructions(a),
|
|
77
|
+
[SHARED_PROFILE_CAPABILITIES.mcp]: relayMcp(a),
|
|
78
|
+
[SHARED_PROFILE_CAPABILITIES.hooks]: relayHooks(a),
|
|
79
|
+
[SHARED_PROFILE_CAPABILITIES.skills]: relaySkills(a),
|
|
80
|
+
[SHARED_PROFILE_CAPABILITIES.plugins]: relayPlugins(a),
|
|
80
81
|
};
|
|
81
82
|
addCrossSourceShadows(cells, a);
|
|
82
83
|
return MATRIX_SOURCES.flatMap((source) => MATRIX_CAPABILITIES.map((capability) => cells[`${source}.${capability}`]));
|
|
@@ -93,10 +94,11 @@ function addCrossSourceShadows(
|
|
|
93
94
|
a: AssembledLaunch,
|
|
94
95
|
): void {
|
|
95
96
|
for (const shadow of a.sourceShadows ?? []) {
|
|
96
|
-
const
|
|
97
|
-
const
|
|
97
|
+
const source = shadow.source;
|
|
98
|
+
const entry = cells[`${source}.${shadow.capability}`];
|
|
99
|
+
const provenance = source === STORED_NAMES.source
|
|
98
100
|
? relayProvenance(a, sourceKind(shadow.capability), shadow.name)
|
|
99
|
-
:
|
|
101
|
+
: source;
|
|
100
102
|
entry.shadowed = dedupeAssets([...(entry.shadowed ?? []), asset(shadow.name, provenance)]);
|
|
101
103
|
}
|
|
102
104
|
}
|
|
@@ -159,7 +161,7 @@ function hostPlugins(a: AssembledLaunch): AgentProfileProjectionEntry {
|
|
|
159
161
|
|
|
160
162
|
function projectInstructions(a: AssembledLaunch): AgentProfileProjectionEntry {
|
|
161
163
|
if (a.instructions.repo === "allow") return scalarCell("project.instructions", "allow", "applied", "allow", projectInstructionMechanism(a), "Project/repo instructions remain available.");
|
|
162
|
-
//
|
|
164
|
+
// the native project scope was dropped to gate ambient hooks, but P.instructions
|
|
163
165
|
// re-injected the repo CLAUDE.md/AGENTS.md through the controlled append-system-prompt channel.
|
|
164
166
|
// Report it as applied+honored (trusted), NOT suppressed, so the launch shape is not misreported.
|
|
165
167
|
if (a.instructions.repo === "reinjected") return scalarCell("project.instructions", "allow", "applied", "reinjected", "controlled append-system-prompt re-injection", "Repo CLAUDE.md/AGENTS.md are re-injected through Relay's controlled channel because the native project scope is dropped to gate ambient hooks.", "trusted");
|
|
@@ -204,40 +206,40 @@ function projectPlugins(a: AssembledLaunch): AgentProfileProjectionEntry {
|
|
|
204
206
|
|
|
205
207
|
function relayInstructions(a: AssembledLaunch): AgentProfileProjectionEntry {
|
|
206
208
|
if (a.relayContextPending || a.launchContextPending) {
|
|
207
|
-
return scalarCell(
|
|
209
|
+
return scalarCell(SHARED_PROFILE_CAPABILITIES.instructions, "enabled", "applied", "pending first provider turn", "runner provider-delivery injection", "Relay-authored instructions are available and will be prepended to the first provider turn; no provider turn has been emitted at launch.");
|
|
208
210
|
}
|
|
209
211
|
const active = a.relayContext || a.instructions.systemPromptAppend;
|
|
210
|
-
return scalarCell(
|
|
212
|
+
return scalarCell(SHARED_PROFILE_CAPABILITIES.instructions, active ? "enabled" : "disabled", active ? "applied" : "disabled", active ? "injected" : "off", "runner context/prompt injection", active ? "Relay-authored instructions reached the provider turn." : "No Relay-authored instruction material was emitted.");
|
|
211
213
|
}
|
|
212
214
|
|
|
213
215
|
function relayMcp(a: AssembledLaunch): AgentProfileProjectionEntry {
|
|
214
216
|
const assets = [
|
|
215
|
-
...(a.mcp.relayEndpoint ? [asset(
|
|
217
|
+
...(a.mcp.relayEndpoint ? [asset(MCP_SERVER_INFO.name, "reserved")] : []),
|
|
216
218
|
...a.mcp.servers.map((name) => asset(name, relayProvenance(a, "mcp", name))),
|
|
217
219
|
];
|
|
218
|
-
return assetCell(
|
|
220
|
+
return assetCell(SHARED_PROFILE_CAPABILITIES.mcp, assets.length ? "enabled" : "disabled", assets.length ? "applied" : "disabled", assets, "Relay MCP endpoint / controlled MCP config", assets.length ? `Relay MCP assets reached the launch (${assets.map((item) => item.name).join(", ")}).` : "No Relay MCP asset reached this launch.", relayShadows(a, "mcp", assets), "trusted");
|
|
219
221
|
}
|
|
220
222
|
|
|
221
223
|
function relayHooks(a: AssembledLaunch): AgentProfileProjectionEntry {
|
|
222
|
-
if (a.hooksSuppressed?.names.length) return assetCell(
|
|
224
|
+
if (a.hooksSuppressed?.names.length) return assetCell(SHARED_PROFILE_CAPABILITIES.hooks, "enabled", "partial", [], "hook delivery withheld", `Relay hooks were resolved but suppressed: ${a.hooksSuppressed.reason}.`, [], "trusted");
|
|
223
225
|
const assets = a.hooks.map((item) => asset(item.name, relayProvenance(a, "hook", item.name)));
|
|
224
|
-
return assetCell(
|
|
226
|
+
return assetCell(SHARED_PROFILE_CAPABILITIES.hooks, assets.length ? "enabled" : "disabled", assets.length ? "applied" : "disabled", assets, "provider hook settings/hooks.json", assets.length ? `Relay hooks reached the launch (${assets.map((item) => item.name).join(", ")}).` : "No Relay hook reached this launch.", relayShadows(a, "hook", assets), "trusted");
|
|
225
227
|
}
|
|
226
228
|
|
|
227
229
|
function relaySkills(a: AssembledLaunch): AgentProfileProjectionEntry {
|
|
228
230
|
const assets = [
|
|
229
|
-
...(a.relaySkills ? [asset("
|
|
231
|
+
...(a.relaySkills ? [asset("humwick", "bundled")] : []),
|
|
230
232
|
...a.skills.map((item) => asset(item.name, relayProvenance(a, "skill", item.name))),
|
|
231
233
|
];
|
|
232
|
-
return assetCell(
|
|
234
|
+
return assetCell(SHARED_PROFILE_CAPABILITIES.skills, assets.length ? "enabled" : "disabled", assets.length ? "applied" : "disabled", assets, "bundled skills config / Relay plugin", assets.length ? `Relay skill assets reached the launch (${assets.map((item) => item.name).join(", ")}).` : "No Relay skill asset reached this launch.", relayShadows(a, "skill", assets), "trusted");
|
|
233
235
|
}
|
|
234
236
|
|
|
235
237
|
function relayPlugins(a: AssembledLaunch): AgentProfileProjectionEntry {
|
|
236
238
|
const assets = [
|
|
237
|
-
...(a.relayPlugin ? [asset(getManifest(a.provider)?.provisioning?.bundledPlugin?.ref ?? "
|
|
239
|
+
...(a.relayPlugin ? [asset(getManifest(a.provider)?.provisioning?.bundledPlugin?.ref ?? "humwick", "bundled")] : []),
|
|
238
240
|
...a.plugins.map((item) => asset(item.name, relayProvenance(a, "plugin", item.name))),
|
|
239
241
|
];
|
|
240
|
-
return assetCell(
|
|
242
|
+
return assetCell(SHARED_PROFILE_CAPABILITIES.plugins, assets.length ? "enabled" : "disabled", assets.length ? "applied" : "disabled", assets, "provider plugin-dir launch args", assets.length ? `Relay plugin assets reached the launch (${assets.map((item) => item.name).join(", ")}).` : "No Relay plugin asset reached this launch.", relayShadows(a, "plugin", assets), "trusted");
|
|
241
243
|
}
|
|
242
244
|
|
|
243
245
|
function relayStatusLineEntry(a: AssembledLaunch): AgentProfileProjectionEntry {
|
|
@@ -249,7 +251,7 @@ function configHomeEntry(a: AssembledLaunch): AgentProfileProjectionEntry {
|
|
|
249
251
|
}
|
|
250
252
|
|
|
251
253
|
function launchShapeEntry(a: AssembledLaunch): AgentProfileProjectionEntry {
|
|
252
|
-
//
|
|
254
|
+
// argv can contain the full system prompt and inlined config. The old cell copied that
|
|
253
255
|
// string into requested, effective, and detail, turning a ~68 KiB launch into ~203 KiB of static
|
|
254
256
|
// agent meta that every status frame then carried through a read/merge/stringify/write cycle.
|
|
255
257
|
// The projection is diagnostic, not the launch authority: retain its useful shape/size facts and
|
|
@@ -356,7 +358,7 @@ function relaySourceKindTiers(a: AssembledLaunch): Record<"skill" | "plugin" | "
|
|
|
356
358
|
.filter((value): value is { source: string; ref: string } => isRecord(value) && value.source === "relay" && typeof value.ref === "string" && !skippedMcpRefs.has(value.ref))
|
|
357
359
|
.map((value) => value.ref);
|
|
358
360
|
return {
|
|
359
|
-
skill: { reserved: [], registry: relayRegistryRefs(profile?.skills), bundled: a.relaySkills ? ["
|
|
361
|
+
skill: { reserved: [], registry: relayRegistryRefs(profile?.skills), bundled: a.relaySkills ? ["humwick"] : [] },
|
|
360
362
|
plugin: { reserved: [], registry: relayRegistryRefs(profile?.plugins), bundled: a.relayPlugin && provisioning?.bundledPlugin ? [provisioning.bundledPlugin.ref] : [] },
|
|
361
363
|
hook: { reserved: bundledHooks.filter((hook) => hook.baseline).map((hook) => hook.ref), registry: relayRegistryRefs(profile?.hooks?.refs), bundled: bundledHooks.filter((hook) => !hook.baseline).map((hook) => hook.ref) },
|
|
362
364
|
mcp: { reserved: a.mcp.relayEndpoint ? [RELAY_MCP_SERVER_NAME] : [], registry: mcpRegistry, bundled: [] },
|
|
@@ -12,7 +12,7 @@ import { projectSettingSourcesOn } from "./profile-home";
|
|
|
12
12
|
const REPO_INSTRUCTION_FILES = ["CLAUDE.md", "AGENTS.md"] as const;
|
|
13
13
|
|
|
14
14
|
// Bound each re-injected file so a pathological repo doc cannot balloon the context.
|
|
15
|
-
// The composed append travels by private file
|
|
15
|
+
// The composed append travels by private file. Mirrors Codex's
|
|
16
16
|
// project_doc_max_bytes default; oversized content is truncated with a loud marker
|
|
17
17
|
// rather than silently dropped.
|
|
18
18
|
const REPO_INSTRUCTIONS_MAX_BYTES = 64 * 1024;
|
|
@@ -32,7 +32,7 @@ export interface ComposedSystemPromptAppend {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Fold the caller's `systemPromptAppend` together with any
|
|
35
|
+
* Fold the caller's `systemPromptAppend` together with any repo re-injection into the single
|
|
36
36
|
* `--append-system-prompt` value the launch emits. The two channels are distinct in reporting
|
|
37
37
|
* (relay-authored append vs. repo-doc re-injection), so `reinjected` is returned separately.
|
|
38
38
|
*/
|
|
@@ -45,7 +45,7 @@ export function composeSystemPromptAppend(config: Pick<RunnerSpawnConfig, "agent
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
*
|
|
48
|
+
* the instructions sibling of {@link resolveProjectHooks}. When `P.hooks` is
|
|
49
49
|
* named, the runner drops the ENTIRE native project setting-source scope so ambient repo
|
|
50
50
|
* hooks can never bypass the trust gate (profile-home `projectSettingSourcesOn`). That drop
|
|
51
51
|
* also strips the repo CLAUDE.md/AGENTS.md cascade even when `P.instructions` is on, silently
|
|
@@ -106,14 +106,14 @@ export function projectInstructionsReinjected(config: Pick<RunnerSpawnConfig, "a
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
const REPO_INSTRUCTIONS_PREAMBLE =
|
|
109
|
-
"[
|
|
109
|
+
"[humwick] The repository instruction docs below are re-injected by Relay because the " +
|
|
110
110
|
"native project setting-source scope was dropped to gate ambient project hooks. Follow them " +
|
|
111
111
|
"as the repository's CLAUDE.md/AGENTS.md guidance.";
|
|
112
112
|
|
|
113
113
|
function renderRepoInstructionBlock(name: string, content: string): string {
|
|
114
114
|
const bytes = Buffer.byteLength(content, "utf8");
|
|
115
115
|
const body = bytes > REPO_INSTRUCTIONS_MAX_BYTES
|
|
116
|
-
? `${Buffer.from(content, "utf8").subarray(0, REPO_INSTRUCTIONS_MAX_BYTES).toString("utf8")}\n[
|
|
116
|
+
? `${Buffer.from(content, "utf8").subarray(0, REPO_INSTRUCTIONS_MAX_BYTES).toString("utf8")}\n[humwick: ${name} truncated at ${REPO_INSTRUCTIONS_MAX_BYTES} bytes]`
|
|
117
117
|
: content.replace(/\s+$/, "");
|
|
118
118
|
return `<repo-instructions source="${name}">\n${body}\n</repo-instructions>`;
|
|
119
119
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { hostname } from "node:os";
|
|
2
|
+
import type { RunnerSpawnConfig } from "./adapter";
|
|
3
|
+
import type { RunnerTimelineEvent } from "./turn-reconcile-event";
|
|
4
|
+
|
|
5
|
+
/** Claude's status command resolves file/keychain auth without Relay inspecting credentials. */
|
|
6
|
+
export function localClaudeOAuthWarning(config: Pick<RunnerSpawnConfig, "provider" | "agentId" | "instanceId">): RunnerTimelineEvent {
|
|
7
|
+
const host = hostname();
|
|
8
|
+
return {
|
|
9
|
+
status: "provider.auth.local_oauth_fallback",
|
|
10
|
+
id: `local-oauth-${config.instanceId}-${crypto.randomUUID()}`,
|
|
11
|
+
timestamp: Date.now(),
|
|
12
|
+
title: "Claude is using the host's local OAuth login",
|
|
13
|
+
body: `Host ${host}, agent ${config.agentId}, provider ${config.provider}: no effective CLAUDE_CODE_OAUTH_TOKEN. Claude reports a local OAuth login (Claude config home or platform keychain). This agent will use the host login's account and quota. Restore the provider's injected CLAUDE_CODE_OAUTH_TOKEN and restart the agent to use the intended account.`,
|
|
14
|
+
icon: "ti-alert-triangle",
|
|
15
|
+
metadata: { eventType: "provider.auth.local_oauth_fallback", severity: "warning", host, agentId: config.agentId, provider: config.provider, authSource: "local-oauth" },
|
|
16
|
+
};
|
|
17
|
+
}
|
package/src/provider-double.ts
CHANGED
|
@@ -54,6 +54,6 @@ export function primaryProviderInvocation(provider: SpawnProvider, spawnArgs: Sp
|
|
|
54
54
|
logicalCommand: spawnArgs.command,
|
|
55
55
|
argv: [...spawnArgs.args],
|
|
56
56
|
cwd: spawnArgs.cwd,
|
|
57
|
-
env: writeChildEnv({}, { ...spawnArgs.env }),
|
|
57
|
+
env: writeChildEnv({}, { ...spawnArgs.env }, []),
|
|
58
58
|
};
|
|
59
59
|
}
|
package/src/provider-hold.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import type { Message } from "@humwick/sdk";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* the provider hold/resume state machine: ONE owner for "the provider refused input, hold,
|
|
6
6
|
* resume exactly once". Pure (no I/O); runner-core drives it and does the delivery.
|
|
7
7
|
*
|
|
8
8
|
* State: the active hold EPISODE (id, or none) and ONE parked queue of refused input. Every
|
|
@@ -53,10 +53,10 @@ export function resumeWakeEpisode(message: Message): string | undefined {
|
|
|
53
53
|
return typeof id === "string" && id ? id : undefined;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
export const PROVIDER_HELD_REDELIVERY_NOTE = "[
|
|
56
|
+
export const PROVIDER_HELD_REDELIVERY_NOTE = "[humwick] Redelivered after a provider usage-limit hold: the provider refused this message before you could act on it, so it may already appear earlier in your conversation unanswered. Handle it now, exactly once.";
|
|
57
57
|
|
|
58
58
|
/** A late refusal (the turn started, then the provider refused a later call) does not prove the input never ran. */
|
|
59
|
-
export const PROVIDER_HELD_LATE_REDELIVERY_NOTE = "[
|
|
59
|
+
export const PROVIDER_HELD_LATE_REDELIVERY_NOTE = "[humwick] Redelivered after a provider usage-limit hold: the provider cut off the turn for this message partway, so you may already have acted on part of it. Check what is already done and continue only the unfinished work, then answer once.";
|
|
60
60
|
|
|
61
61
|
export type ResumeDecision = { action: "settle" } | { action: "continue" } | { action: "replay"; messages: Message[] };
|
|
62
62
|
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
// Re-export shim
|
|
1
|
+
// Re-export shim — the marker moved to the SDK so the runner (writer), the server
|
|
2
2
|
// reaper and the per-orchestrator-host reaper script share one implementation.
|
|
3
3
|
export * from "@humwick/sdk/provider-home-marker";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Re-export shim
|
|
1
|
+
// Re-export shim — tombstone naming + spawn-side reclaim moved to the SDK so the
|
|
2
2
|
// runner, the server reaper and the per-orchestrator-host reaper script share one
|
|
3
3
|
// implementation of the quarantine name-spaces.
|
|
4
4
|
export * from "@humwick/sdk/provider-home-tombstone";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// One agent, ONE provider input stream
|
|
1
|
+
// One agent, ONE provider input stream.
|
|
2
2
|
//
|
|
3
3
|
// Every payload the runner hands to a provider — a relay message batch, an injected prompt, a
|
|
4
4
|
// self-resume continuation, a memory preamble — ends up in the same place: the provider's single
|
|
@@ -60,7 +60,7 @@ export class ProviderInputQueue {
|
|
|
60
60
|
this.stuckAfterMs = options.stuckAfterMs ?? DEFAULT_STUCK_AFTER_MS;
|
|
61
61
|
if (options.onStuck) this.onStuck = options.onStuck;
|
|
62
62
|
// Capture native timers: another subsystem's synchronous timer shim must not expire
|
|
63
|
-
// a freshly granted slot and feed an immediate delivery/re-arm loop
|
|
63
|
+
// a freshly granted slot and feed an immediate delivery/re-arm loop.
|
|
64
64
|
this.schedule = options.schedule ?? ((fn, ms) => {
|
|
65
65
|
const timer = setTimeout(fn, ms);
|
|
66
66
|
timer.unref();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// bounded provider bridge; project owned metadata, never user assets or physical paths.
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { basename, join, resolve } from "node:path";
|
|
4
4
|
import { getManifest } from "@humwick/providers";
|
|
@@ -6,8 +6,7 @@ import { sanitizeFsName } from "@humwick/sdk/fs-name";
|
|
|
6
6
|
import type { RunnerSpawnConfig } from "./adapter";
|
|
7
7
|
import type { ResolvedAsset } from "./provisioning";
|
|
8
8
|
import { providerHomeRootFromEnv } from "./config";
|
|
9
|
-
import {
|
|
10
|
-
import { isRecord, mcpServerInfo, wireToolName, wireToolText, type ProvisioningAssetFile, type ToolGeneration } from "@humwick/sdk";
|
|
9
|
+
import { isRecord, MCP_SERVER_INFO, registryToolName, type ProvisioningAssetFile } from "@humwick/sdk";
|
|
11
10
|
|
|
12
11
|
export const CLAUDE_OWNED_PLUGIN_IDS = ["agent-relay@agent-relay", "humwick@humwick"];
|
|
13
12
|
const OWNED_PLUGIN_NAMES = new Set(["agent-relay", "agent-relay-runner", "humwick", "humwick-runner"]);
|
|
@@ -17,14 +16,19 @@ function ownedClaudePluginName(dir: string): string | undefined {
|
|
|
17
16
|
catch { return undefined; }
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
export function selectClaudePluginDirs(dirs: string[],
|
|
19
|
+
export function selectClaudePluginDirs(dirs: string[], ownedProvided: boolean): string[] {
|
|
21
20
|
const entries = [...new Set(dirs)].map(dir => ({ dir, name: ownedClaudePluginName(dir) }));
|
|
22
|
-
const owned = ownedProvided ? undefined : entries.find(entry => entry.name ===
|
|
21
|
+
const owned = ownedProvided ? undefined : entries.find(entry => entry.name === MCP_SERVER_INFO.name || entry.name === `${MCP_SERVER_INFO.name}-runner`) ?? entries.find(entry => entry.name);
|
|
23
22
|
return entries.filter(entry => !entry.name || entry === owned).map(entry => entry.dir);
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
export function
|
|
27
|
-
|
|
25
|
+
export function canonicalIntegrationAssetName(name: string): string {
|
|
26
|
+
return name.replace(/^agent-relay-/, "humwick-");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function providerIntegrationFiles(name: string, files: ProvisioningAssetFile[]): ProvisioningAssetFile[] {
|
|
30
|
+
const canonicalName = canonicalIntegrationAssetName(name);
|
|
31
|
+
if (!(canonicalName === "humwick-claude-plugin" || canonicalName.startsWith("humwick-codex-") || canonicalName.startsWith("humwick-opencode-"))) return files;
|
|
28
32
|
return files.map(file => {
|
|
29
33
|
if (file.path === ".claude-plugin/plugin.json") {
|
|
30
34
|
const manifest = JSON.parse(file.content);
|
|
@@ -33,7 +37,7 @@ export function providerIntegrationFiles(name: string, files: ProvisioningAssetF
|
|
|
33
37
|
return { ...file, content: JSON.stringify(manifest, null, 2) };
|
|
34
38
|
}
|
|
35
39
|
if (!file.path.endsWith(".md")) return file;
|
|
36
|
-
return { ...file, content:
|
|
40
|
+
return { ...file, content: file.content
|
|
37
41
|
.replace(/\bAgent Relay\b/g, "Humwick")
|
|
38
42
|
.replace(/(?<![\w/.-])agent-relay(?![\w/.-])/g, "humwick") };
|
|
39
43
|
});
|
|
@@ -43,8 +47,6 @@ const bundleCache = new Map<string, ProvisioningAssetFile[]>();
|
|
|
43
47
|
|
|
44
48
|
// The same manifest overrides as registry seeding make relocated monitors self-contained.
|
|
45
49
|
export function bundledIntegrationAsset(config: RunnerSpawnConfig, source: string, name: string): ResolvedAsset {
|
|
46
|
-
const generation = runnerMcpGeneration(config.env);
|
|
47
|
-
if (generation === "relay") return { name, dir: source, source: { kind: "inPlace" } };
|
|
48
50
|
let files = bundleCache.get(source);
|
|
49
51
|
if (!files) {
|
|
50
52
|
const descriptor = getManifest(config.provider)?.provisioning?.bundledPlugin;
|
|
@@ -55,7 +57,7 @@ export function bundledIntegrationAsset(config: RunnerSpawnConfig, source: strin
|
|
|
55
57
|
bundleCache.set(source, files);
|
|
56
58
|
}
|
|
57
59
|
return { name, dir: join(bundledIntegrationHome(config), basename(source)),
|
|
58
|
-
source: { kind: "files", files: providerIntegrationFiles(name, files
|
|
60
|
+
source: { kind: "files", files: providerIntegrationFiles(name, files) } };
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
export function bundledIntegrationAssets(config: RunnerSpawnConfig): ResolvedAsset[] {
|
|
@@ -82,12 +84,12 @@ export function bundledIntegrationHome(config: RunnerSpawnConfig): string {
|
|
|
82
84
|
return join(providerHomeRootFromEnv(), config.provider, "integrations", sanitizeFsName(config.instanceId || config.runnerId));
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
export function projectClaudePermissionRules(settings: Record<string, unknown
|
|
87
|
+
export function projectClaudePermissionRules(settings: Record<string, unknown>): Record<string, unknown> {
|
|
86
88
|
if (!isRecord(settings.permissions)) return settings;
|
|
87
89
|
const permissions = { ...settings.permissions };
|
|
88
90
|
for (const kind of ["allow", "deny", "ask"]) {
|
|
89
91
|
if (Array.isArray(permissions[kind])) permissions[kind] = permissions[kind].map(rule => typeof rule === "string"
|
|
90
|
-
? rule.replace(/^mcp__(?:agent-relay|humwick)__(.*)$/, (_match, tool) => `mcp__${
|
|
92
|
+
? rule.replace(/^mcp__(?:agent-relay|humwick)__(.*)$/, (_match, tool) => `mcp__${MCP_SERVER_INFO.name}__${registryToolName(tool)}`) : rule);
|
|
91
93
|
}
|
|
92
94
|
return { ...settings, permissions };
|
|
93
95
|
}
|
package/src/provider-setup.ts
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
import { errMessage } from "@humwick/sdk";
|
|
2
2
|
import { checkProviderAuth } from "@humwick/sdk/provider-probe";
|
|
3
3
|
import { selectProviderConfigEnv } from "@humwick/sdk/child-env";
|
|
4
|
+
import { getManifest } from "@humwick/providers";
|
|
4
5
|
import type { ProviderAdapter, ProviderStatusEvent } from "./adapter";
|
|
5
6
|
import { logger } from "./logger";
|
|
6
7
|
import { hostSourcesFor } from "./host-asset-plan";
|
|
7
8
|
import { resolveLaunchCommand } from "./provider-command";
|
|
9
|
+
import { localClaudeOAuthWarning } from "./provider-auth-warning";
|
|
8
10
|
|
|
9
|
-
/** All real adapters share the managed-spawn login check, including replacements
|
|
11
|
+
/** All real adapters share the managed-spawn login check, including replacements. */
|
|
10
12
|
export function withProviderSetup(adapter: ProviderAdapter): ProviderAdapter {
|
|
11
13
|
const spawn = adapter.spawn.bind(adapter);
|
|
12
14
|
adapter.spawn = async (config) => {
|
|
15
|
+
const manifest = getManifest(config.provider);
|
|
16
|
+
const claudeOAuth = manifest?.probe?.authCheck?.command === "claude" && !manifest.apiAuth;
|
|
17
|
+
// probe the artifact we will actually launch, after home and credential isolation.
|
|
18
|
+
if (claudeOAuth) config = await adapter.hydrateSpawnConfig?.(config) ?? config;
|
|
19
|
+
const prepared = claudeOAuth ? adapter.buildSpawnArgs(config, config.providerConfig) : undefined;
|
|
13
20
|
const env = selectProviderConfigEnv(config.providerConfig.env, hostSourcesFor(config).settings);
|
|
14
|
-
const auth = await checkProviderAuth(config.provider, undefined, env, resolveLaunchCommand(config.providerConfig.command), config.env);
|
|
21
|
+
const auth = await checkProviderAuth(config.provider, undefined, env, prepared?.command ?? resolveLaunchCommand(config.providerConfig.command), config.env, prepared?.env);
|
|
15
22
|
if (!auth.ok) throw new Error(auth.reason);
|
|
16
|
-
|
|
23
|
+
if (prepared && !prepared.env.CLAUDE_CODE_OAUTH_TOKEN?.trim() && auth.authSource === "local-oauth") {
|
|
24
|
+
const warning = localClaudeOAuthWarning(config);
|
|
25
|
+
console.warn(`${warning.title}: ${warning.body}`);
|
|
26
|
+
config.onLaunchWarning?.(warning);
|
|
27
|
+
}
|
|
28
|
+
return spawn(config, prepared);
|
|
17
29
|
};
|
|
18
30
|
return adapter;
|
|
19
31
|
}
|
package/src/providers.ts
CHANGED
|
@@ -14,7 +14,7 @@ interface RunnerProviderRegistration {
|
|
|
14
14
|
|
|
15
15
|
const RUNNER_PROVIDERS: Record<string, RunnerProviderRegistration> = {
|
|
16
16
|
claude: { provider: "claude", createAdapter: (args, mode) => createClaudeTransportAdapter("claude", args, mode) },
|
|
17
|
-
//
|
|
17
|
+
// claude-b reuses the Claude adapter under its own id (the OpenCodeAdapter/zai
|
|
18
18
|
// pattern): same binary and launch wiring, its own manifest home/credential.
|
|
19
19
|
"claude-b": { provider: "claude-b", createAdapter: (args, mode) => createClaudeTransportAdapter("claude-b", args, mode) },
|
|
20
20
|
"claude-api": { provider: "claude-api", createAdapter: () => new ClaudeApiAdapter() },
|
package/src/provisioning.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { providerIntegrationFiles, projectClaudePermissionRules } from "./provider-integration";
|
|
2
|
-
import { runnerMcpGeneration } from "./relay-mcp";
|
|
3
2
|
import { writeChildEnv } from "@humwick/sdk/env";
|
|
4
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
5
4
|
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
6
5
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
7
6
|
import { homedir } from "node:os";
|
|
8
7
|
import { sanitizeFsName } from "@humwick/sdk/fs-name";
|
|
9
|
-
import { RETIRED_SHARED_MCP_SERVER_NAMES, isRelayMcpServerName,
|
|
8
|
+
import { RETIRED_SHARED_MCP_SERVER_NAMES, isRelayMcpServerName, MCP_SERVER_INFO, coerceProjectMcpServer, fffMcpBinaryPath, PROJECT_MCP_FILE, profileProjectSourceEnabled } from "@humwick/sdk";
|
|
10
9
|
import { getManifest } from "@humwick/providers";
|
|
11
10
|
import type { ProvisioningAssetFile, ProvisioningHookSet, ProvisioningMcpServer, ResolvedProvisioning } from "@humwick/sdk";
|
|
12
11
|
import type { RunnerSpawnConfig } from "./adapter";
|
|
@@ -17,11 +16,11 @@ import { profileAllowsHostGlobalInstructions, providerSourceHome } from "./profi
|
|
|
17
16
|
import { hostSourcesFor, skillDirsUnder } from "./host-asset-plan";
|
|
18
17
|
|
|
19
18
|
// Runner-side materialization of the relay-resolved provisioning bundle
|
|
20
|
-
//
|
|
19
|
+
// The relay resolves a profile's refs against the DB registry and
|
|
21
20
|
// attaches the concrete definitions to profile.provisioning; here we turn those into
|
|
22
21
|
// real on-disk dirs (skills/plugins). MCP is handled in relay-mcp.ts.
|
|
23
22
|
//
|
|
24
|
-
//
|
|
23
|
+
// split into PURE RESOLUTION (resolve* → where each asset lands, no writes) and
|
|
25
24
|
// MATERIALIZATION (materialize* → the disk writes). The single launch assembler
|
|
26
25
|
// (launch-assembly.ts) calls the resolvers to build BOTH the launch args and the
|
|
27
26
|
// projection report, so what's reported can never drift from what's written; the
|
|
@@ -38,11 +37,11 @@ export function profileProvisioning(config: Pick<RunnerSpawnConfig, "agentProfil
|
|
|
38
37
|
|
|
39
38
|
// The fff search-redirect hook is bundled per provider manifest; it only needs the binary path in env.
|
|
40
39
|
function providerBundlesFffRedirect(provider: RunnerSpawnConfig["provider"]): boolean {
|
|
41
|
-
return Boolean(getManifest(provider)?.provisioning?.bundledHooks?.some((hook) => hook.ref === "
|
|
40
|
+
return Boolean(getManifest(provider)?.provisioning?.bundledHooks?.some((hook) => hook.ref === "humwick-fff-search-redirect"));
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
//
|
|
45
|
-
// provisioned descriptor or dropped here, never emitted broken.
|
|
43
|
+
// provisioned stdio commands authored on another host are swapped for this host's
|
|
44
|
+
// provisioned descriptor or dropped here, never emitted broken. The fff path rewrite
|
|
46
45
|
// lives here too so every provider launch gets it before the preflight judges the path.
|
|
47
46
|
export function launchProvisionedMcpServers(
|
|
48
47
|
config: Pick<RunnerSpawnConfig, "agentProfile">,
|
|
@@ -59,8 +58,8 @@ export function launchProvisionedMcpServers(
|
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
export function fffMcpHookEnv(config: Pick<RunnerSpawnConfig, "agentProfile" | "provider">): Record<string, string> {
|
|
62
|
-
return writeChildEnv({}, {
|
|
63
|
-
? launchProvisionedMcpServers(config).fff?.command ?? "" : "" });
|
|
61
|
+
return writeChildEnv({}, { HUMWICK_FFF_MCP_BINARY: providerBundlesFffRedirect(config.provider)
|
|
62
|
+
? launchProvisionedMcpServers(config).fff?.command ?? "" : "" }, []);
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
/** The resolved source matrix decides project MCP/skill projection (see profileProjectSourceEnabled). */
|
|
@@ -80,13 +79,13 @@ export function projectSettingsEnabled(config: Pick<RunnerSpawnConfig, "agentPro
|
|
|
80
79
|
return Boolean(config.agentProfile?.projectSettings);
|
|
81
80
|
}
|
|
82
81
|
|
|
83
|
-
//
|
|
82
|
+
// project `.mcp.json` MCP projection. OPT-IN via the resolved profile's `projectMcp`
|
|
84
83
|
// (set per-spawn or by profile, relay-side). Reads the spawn cwd's COMMITTED `.mcp.json` and
|
|
85
84
|
// returns its `mcpServers` as a provider-neutral record, so the launch assembler injects them
|
|
86
85
|
// through the CONTROLLED `--mcp-config` (Claude) / `-c mcp_servers.*` (Codex) path that
|
|
87
86
|
// survives `--strict-mcp-config` — the project's own servers travel with the repo like they do
|
|
88
87
|
// for a human who clones it, WITHOUT enabling Claude's ambient project-discovery trust modal
|
|
89
|
-
// (which wedges headless agents, the reason
|
|
88
|
+
// (which wedges headless agents, the reason made every managed launch strict). The runner
|
|
90
89
|
// is the filesystem authority for the spawn cwd (relay may run on another host), so the read
|
|
91
90
|
// lands here. Best-effort: a missing/malformed file (or one with no `mcpServers`) yields {} and
|
|
92
91
|
// never aborts the launch. Returns {} when projection is OFF.
|
|
@@ -99,7 +98,7 @@ export function resolveProjectMcpServers(
|
|
|
99
98
|
// server decides roots/pinned/any against its registry and carries the result
|
|
100
99
|
// through the spawn-scoped profile. Missing/denied decisions fail closed.
|
|
101
100
|
if (profile?.sources?.project && !profile.projectGates?.mcp?.allowed) return {};
|
|
102
|
-
//
|
|
101
|
+
// a project role's entries were read at one commit; the working tree never applies.
|
|
103
102
|
if (profile?.pinnedProjectMcp) return { ...profile.pinnedProjectMcp.servers };
|
|
104
103
|
const file = join(config.cwd, PROJECT_MCP_FILE);
|
|
105
104
|
if (!existsSync(file)) return {};
|
|
@@ -146,13 +145,13 @@ export function resolveClaudeHostSkills(
|
|
|
146
145
|
provider: string = "claude",
|
|
147
146
|
): ResolvedAsset[] {
|
|
148
147
|
if (!hostSourcesFor(config).skills) return [];
|
|
149
|
-
//
|
|
148
|
+
// keyed by provider id so a dedicatedHome claude-family provider (claude-b)
|
|
150
149
|
// resolves host skills from ITS OWN source home, never ~/.claude's.
|
|
151
150
|
return linkedSkillAssets(join(providerSourceHome(provider), "skills"), claudeHome, reservedNames);
|
|
152
151
|
}
|
|
153
152
|
|
|
154
153
|
export interface ResolvedSourceShadow {
|
|
155
|
-
source: "
|
|
154
|
+
source: import("@humwick/sdk").ProfileSourceKind;
|
|
156
155
|
capability: "mcp" | "hooks" | "skills" | "plugins";
|
|
157
156
|
name: string;
|
|
158
157
|
}
|
|
@@ -328,7 +327,7 @@ function materializeAsset(asset: ResolvedAsset): boolean {
|
|
|
328
327
|
return existsSync(asset.dir);
|
|
329
328
|
}
|
|
330
329
|
|
|
331
|
-
//
|
|
330
|
+
// (Claude) — each provisioned skill lands at <claudeHome>/skills/<name> so it's
|
|
332
331
|
// auto-discovered under the launch's `--setting-sources "user"` scope (host skills
|
|
333
332
|
// never bleed). Inline files are written there; a pre-existing dir is symlinked in.
|
|
334
333
|
export function resolveClaudeProvisionedSkills(claudeHome: string, config: RunnerSpawnConfig): ResolvedAsset[] {
|
|
@@ -338,7 +337,7 @@ export function resolveClaudeProvisionedSkills(claudeHome: string, config: Runne
|
|
|
338
337
|
for (const skill of skills) {
|
|
339
338
|
const dir = join(skillsRoot, assetDirName(skill.name));
|
|
340
339
|
if (skill.files?.length) {
|
|
341
|
-
resolved.push({ name: skill.name, dir, source: { kind: "files", files: providerIntegrationFiles(skill.name, skill.files
|
|
340
|
+
resolved.push({ name: skill.name, dir, source: { kind: "files", files: providerIntegrationFiles(skill.name, skill.files) } });
|
|
342
341
|
continue;
|
|
343
342
|
}
|
|
344
343
|
if (skill.dir) {
|
|
@@ -349,7 +348,7 @@ export function resolveClaudeProvisionedSkills(claudeHome: string, config: Runne
|
|
|
349
348
|
return resolved;
|
|
350
349
|
}
|
|
351
350
|
|
|
352
|
-
//
|
|
351
|
+
// (Claude) — each provisioned plugin resolves to a --plugin-dir. Inline plugins
|
|
353
352
|
// materialize under <claudeHome>/relay-provisioned-plugins/<name>; declared dirs are
|
|
354
353
|
// used in place. The resolved `dir` is appended to the launch's --plugin-dir set.
|
|
355
354
|
export function resolveClaudeProvisionedPlugins(claudeHome: string, config: RunnerSpawnConfig): ResolvedAsset[] {
|
|
@@ -358,7 +357,7 @@ export function resolveClaudeProvisionedPlugins(claudeHome: string, config: Runn
|
|
|
358
357
|
const resolved: ResolvedAsset[] = [];
|
|
359
358
|
for (const plugin of plugins) {
|
|
360
359
|
if (plugin.files?.length) {
|
|
361
|
-
resolved.push({ name: plugin.name, dir: join(parent, assetDirName(plugin.name)), source: { kind: "files", files: providerIntegrationFiles(plugin.name, plugin.files
|
|
360
|
+
resolved.push({ name: plugin.name, dir: join(parent, assetDirName(plugin.name)), source: { kind: "files", files: providerIntegrationFiles(plugin.name, plugin.files) } });
|
|
362
361
|
continue;
|
|
363
362
|
}
|
|
364
363
|
if (plugin.dir) {
|
|
@@ -369,18 +368,18 @@ export function resolveClaudeProvisionedPlugins(claudeHome: string, config: Runn
|
|
|
369
368
|
return resolved;
|
|
370
369
|
}
|
|
371
370
|
|
|
372
|
-
//
|
|
371
|
+
// (Codex) — each provisioned skill resolves to a dir for `-c skills.config`.
|
|
373
372
|
// Inline skills materialize under <codexHome>/relay-provisioned-skills/<name>; declared
|
|
374
373
|
// dirs are used in place. Codex requires a SKILL.md at the dir root to register it, so
|
|
375
374
|
// inline sets without one and dirs missing it are dropped at resolution time.
|
|
376
|
-
export function
|
|
375
|
+
export function resolveProvisionedSkillPaths(home: string, config: RunnerSpawnConfig): ResolvedAsset[] {
|
|
377
376
|
const { skills } = profileProvisioning(config);
|
|
378
|
-
const parent = join(
|
|
377
|
+
const parent = join(home, "relay-provisioned-skills");
|
|
379
378
|
const resolved: ResolvedAsset[] = [];
|
|
380
379
|
for (const skill of skills) {
|
|
381
380
|
if (skill.files?.length) {
|
|
382
381
|
if (!hasRootSkillManifest(skill.files)) continue;
|
|
383
|
-
resolved.push({ name: skill.name, dir: join(parent, assetDirName(skill.name)), source: { kind: "files", files: providerIntegrationFiles(skill.name, skill.files
|
|
382
|
+
resolved.push({ name: skill.name, dir: join(parent, assetDirName(skill.name)), source: { kind: "files", files: providerIntegrationFiles(skill.name, skill.files) } });
|
|
384
383
|
continue;
|
|
385
384
|
}
|
|
386
385
|
if (skill.dir) {
|
|
@@ -391,6 +390,8 @@ export function resolveCodexProvisionedSkills(codexHome: string, config: RunnerS
|
|
|
391
390
|
return resolved;
|
|
392
391
|
}
|
|
393
392
|
|
|
393
|
+
export { resolveProvisionedSkillPaths as resolveCodexProvisionedSkills };
|
|
394
|
+
|
|
394
395
|
export function resolveProvisionedHooks(home: string, config: RunnerSpawnConfig): ResolvedHookAsset[] {
|
|
395
396
|
const { hooks } = profileProvisioning(config);
|
|
396
397
|
const parent = join(home, "relay-provisioned-hooks");
|
|
@@ -464,7 +465,7 @@ export function codexHookTrustEntries(codexHome: string, hooks: ProvisioningHook
|
|
|
464
465
|
return hookTrustEntries([...new Set([codexHome, realpathOr(codexHome)])].map((home) => join(home, "hooks.json")), hooks);
|
|
465
466
|
}
|
|
466
467
|
|
|
467
|
-
//
|
|
468
|
+
// Codex also reads TOML hooks and their trust state from the `-c` session-flags layer,
|
|
468
469
|
// keyed on this synthetic source, so a launch can carry hooks while keeping any CODEX_HOME.
|
|
469
470
|
const CODEX_SESSION_FLAGS_SOURCE = "/<session-flags>/config.toml";
|
|
470
471
|
|
|
@@ -577,7 +578,7 @@ function canonicalJson(value: unknown): unknown {
|
|
|
577
578
|
return value;
|
|
578
579
|
}
|
|
579
580
|
|
|
580
|
-
//
|
|
581
|
+
// write relay-owned settings into an ISOLATED Claude managed home's `settings.json`
|
|
581
582
|
// (the DOCUMENTED hook source Claude honors under `--setting-sources "user"` or the default,
|
|
582
583
|
// unlike the inline `--settings` CLI flag). The managed home is relay-owned and freshly keyed
|
|
583
584
|
// per spawn, so an unconditional write is safe (profile-home seeds only `.claude.json`/`CLAUDE.md`,
|
|
@@ -601,7 +602,7 @@ export function writeClaudeSettingsJson(claudeHome: string, settings: Record<str
|
|
|
601
602
|
}
|
|
602
603
|
}
|
|
603
604
|
|
|
604
|
-
//
|
|
605
|
+
// merge a permissions.deny entry into a managed Claude settings payload WITHOUT
|
|
605
606
|
// clobbering permissions the payload already carries (deny lists union, everything else passes
|
|
606
607
|
// through). Pure — the caller composes and writes.
|
|
607
608
|
export function mergeClaudeSettingsDeny(settings: Record<string, unknown>, deny: string[]): Record<string, unknown> {
|
|
@@ -622,22 +623,18 @@ export function mergeClaudeSettingsAllow(settings: Record<string, unknown>, allo
|
|
|
622
623
|
}
|
|
623
624
|
|
|
624
625
|
export const CLAUDE_GUARDED_RELAY_TOOLS = [
|
|
625
|
-
"
|
|
626
|
-
"
|
|
627
|
-
"
|
|
628
|
-
"
|
|
629
|
-
"
|
|
626
|
+
"mcp__humwick__humwick_send_message",
|
|
627
|
+
"mcp__humwick__humwick_reply",
|
|
628
|
+
"mcp__humwick__humwick_task_set_status",
|
|
629
|
+
"mcp__humwick__humwick_task_comment",
|
|
630
|
+
"mcp__humwick__humwick_task_deliver",
|
|
630
631
|
];
|
|
631
632
|
|
|
632
|
-
function
|
|
633
|
-
return
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
export function claudeGuardedRelaySettings(approvalMode: string | undefined, generation: ToolGeneration = "relay"): Record<string, unknown> {
|
|
637
|
-
return approvalMode === "guarded" ? { permissions: { allow: guardedRelayTools(generation) } } : {};
|
|
633
|
+
export function claudeGuardedRelaySettings(approvalMode: string | undefined): Record<string, unknown> {
|
|
634
|
+
return approvalMode === "guarded" ? { permissions: { allow: [...CLAUDE_GUARDED_RELAY_TOOLS] } } : {};
|
|
638
635
|
}
|
|
639
636
|
|
|
640
|
-
//
|
|
637
|
+
// Claude's project scan loads CLAUDE.md, CLAUDE.local.md, .claude/CLAUDE.md and .claude/rules/**
|
|
641
638
|
// from EVERY ancestor of the cwd, so host files above the repo (~/.claude/CLAUDE.md, ~/CLAUDE.md,
|
|
642
639
|
// ~/projects/CLAUDE.md) reach an agent despite the isolated CLAUDE_CONFIG_DIR. Host instructions
|
|
643
640
|
// `ignore` excludes them through the supported `claudeMdExcludes` setting. Rule: the repo root
|
|
@@ -687,9 +684,9 @@ export function mergeClaudeRelayPermissions(settings: Record<string, unknown>, r
|
|
|
687
684
|
return mergeClaudeSettingsAllow(settings, allow);
|
|
688
685
|
}
|
|
689
686
|
|
|
690
|
-
export function withManagedClaudePermissions(settings: Record<string, unknown>, approvalMode: string | undefined
|
|
691
|
-
const guarded = approvalMode === "guarded" ? mergeClaudeSettingsAllow(settings,
|
|
692
|
-
return projectClaudePermissionRules(mergeClaudeSettingsDeny({ ...guarded, ...claudeBypassPermissionSettings(approvalMode), idleCompaction: false }, ["SendFeedback"])
|
|
687
|
+
export function withManagedClaudePermissions(settings: Record<string, unknown>, approvalMode: string | undefined): Record<string, unknown> {
|
|
688
|
+
const guarded = approvalMode === "guarded" ? mergeClaudeSettingsAllow(settings, [...CLAUDE_GUARDED_RELAY_TOOLS]) : settings;
|
|
689
|
+
return projectClaudePermissionRules(mergeClaudeSettingsDeny({ ...guarded, ...claudeBypassPermissionSettings(approvalMode), idleCompaction: false }, ["SendFeedback"]));
|
|
693
690
|
}
|
|
694
691
|
|
|
695
692
|
// Write the resolved assets to disk; returns the dirs that materialized successfully,
|