@cabane/companion 0.6.186 → 0.6.188
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/README.md +16 -10
- package/dist/cli.js +599 -628
- package/dist/launch.js +1 -1
- package/dist/pairing-config.js +1 -1
- package/dist/runtime.js +408 -522
- package/dist/static/app.js +1 -2
- package/dist/static/styles.css +0 -7
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -369,7 +369,6 @@ var init_prepare_hook = __esm({
|
|
|
369
369
|
CABANE_AGENT_USERNAME: input.agentUsername,
|
|
370
370
|
CABANE_DEFAULT_CWD: input.defaultCwd,
|
|
371
371
|
CABANE_RUNTIME: input.runtime ?? "",
|
|
372
|
-
CABANE_HOST_ACCESS: input.hostAccess ? "1" : "0",
|
|
373
372
|
// Newline-joined — workspace paths never contain a newline, so a
|
|
374
373
|
// hook splits on `\n` unambiguously. Empty string when none.
|
|
375
374
|
CABANE_TRIGGER_ENTRY_PATHS: input.triggerEntryPaths.join("\n"),
|
|
@@ -727,7 +726,7 @@ var init_config = __esm({
|
|
|
727
726
|
// yourself, `claudeCode: { autoMemory: true }` hands auto-memory back to your
|
|
728
727
|
// own `~/.claude/settings.json` — Cabane then stops
|
|
729
728
|
// injecting the off switch and your normal Claude Code memory workflow applies
|
|
730
|
-
// (
|
|
729
|
+
// (the user and project settings load on every Claude Code turn).
|
|
731
730
|
claudeCode: z2.object({ autoMemory: z2.boolean().optional() }).strict().optional()
|
|
732
731
|
}).strict();
|
|
733
732
|
agentsConfigSchema = z2.record(z2.string(), localAgentConfigSchema);
|
|
@@ -1722,45 +1721,13 @@ var init_version = __esm({
|
|
|
1722
1721
|
}
|
|
1723
1722
|
});
|
|
1724
1723
|
|
|
1725
|
-
// packages/agent-runtime/src/host-policy.ts
|
|
1726
|
-
import { z as z6 } from "zod";
|
|
1727
|
-
var hostPolicySchema;
|
|
1728
|
-
var init_host_policy = __esm({
|
|
1729
|
-
"packages/agent-runtime/src/host-policy.ts"() {
|
|
1730
|
-
"use strict";
|
|
1731
|
-
hostPolicySchema = z6.object({
|
|
1732
|
-
// Host filesystem and shell: `Bash`/`Read`/`Write`/`Edit`/`Glob`/`Grep`, the notebook pair,
|
|
1733
|
-
// git-worktree tools and `REPL`. Off on the locked assistant surface; on in the coding posture.
|
|
1734
|
-
hostFs: z6.boolean(),
|
|
1735
|
-
// Public web (`WebSearch` / `WebFetch`). Not host reach; a grant like the rest.
|
|
1736
|
-
web: z6.boolean(),
|
|
1737
|
-
// Browser automation (the Playwright MCP surface). Varies by host.
|
|
1738
|
-
browser: z6.boolean(),
|
|
1739
|
-
// User-configured MCP servers permitted.
|
|
1740
|
-
userMcp: z6.boolean(),
|
|
1741
|
-
// Subagent dispatch (`Task` / `Agent`) — a grant: `false` on the locked surface, `true` in the
|
|
1742
|
-
// coding posture, where the coding allow-hook reaches sub-agent tool calls. A subagent completes
|
|
1743
|
-
// within the turn, so it's not an invariant like `scheduling`.
|
|
1744
|
-
subagents: z6.boolean(),
|
|
1745
|
-
// ── Hard platform invariants — always denied, never granted ────────────────
|
|
1746
|
-
// Deferred re-invocation / scheduling (`ScheduleWakeup`, the `Cron*` / `Task*` families,
|
|
1747
|
-
// `Monitor`, `Workflow`, …). A turn is one query that resolves on `result`; a scheduled callback
|
|
1748
|
-
// fires after the reply window has closed and strands the agent.
|
|
1749
|
-
scheduling: z6.literal("never"),
|
|
1750
|
-
// Human-facing UI prompts (`AskUserQuestion`). Nothing in a Cabane conversation can answer a
|
|
1751
|
-
// structured prompt, so the call hangs the turn. The agent asks in its reply instead.
|
|
1752
|
-
uiPrompts: z6.literal("never")
|
|
1753
|
-
});
|
|
1754
|
-
}
|
|
1755
|
-
});
|
|
1756
|
-
|
|
1757
1724
|
// packages/agent-runtime/src/turn-event.ts
|
|
1758
|
-
import { z as
|
|
1725
|
+
import { z as z6 } from "zod";
|
|
1759
1726
|
var turnEventSchema;
|
|
1760
1727
|
var init_turn_event = __esm({
|
|
1761
1728
|
"packages/agent-runtime/src/turn-event.ts"() {
|
|
1762
1729
|
"use strict";
|
|
1763
|
-
turnEventSchema =
|
|
1730
|
+
turnEventSchema = z6.discriminatedUnion("type", [
|
|
1764
1731
|
// The runtime's opaque session state, emitted when the adapter learns it (e.g. the SDK
|
|
1765
1732
|
// `system/init` frame). The platform stores `state` verbatim per (conversation, agent) and
|
|
1766
1733
|
// hands it back next turn; only the adapter knows what it means.
|
|
@@ -1772,21 +1739,21 @@ var init_turn_event = __esm({
|
|
|
1772
1739
|
// server rewinds the mark, so the next turn rebuilds a full catch-up (this turn is unavoidably
|
|
1773
1740
|
// lossy — the degrade is known only after the manifest was committed). A plain boolean; the
|
|
1774
1741
|
// runtime-specific reason stays in the adapter's `onWarn` log. Optional.
|
|
1775
|
-
|
|
1776
|
-
type:
|
|
1777
|
-
state:
|
|
1778
|
-
degraded:
|
|
1742
|
+
z6.object({
|
|
1743
|
+
type: z6.literal("session"),
|
|
1744
|
+
state: z6.string(),
|
|
1745
|
+
degraded: z6.boolean().optional()
|
|
1779
1746
|
}),
|
|
1780
1747
|
// One readable thinking summary. Transient — surfaced live, never persisted as content.
|
|
1781
|
-
|
|
1748
|
+
z6.object({ type: z6.literal("thinking"), text: z6.string() }),
|
|
1782
1749
|
// Assistant text. `terminal: false` is interim narration (commits as `progress`);
|
|
1783
1750
|
// `terminal: true` is the turn's closing reply (commits as `final`).
|
|
1784
|
-
|
|
1751
|
+
z6.object({ type: z6.literal("text"), body: z6.string(), terminal: z6.boolean() }),
|
|
1785
1752
|
// Runtime/provider-authored prose about the turn — a failure's explanation, or a line the
|
|
1786
1753
|
// harness wrote on a turn it recovered. Not the agent's words: the pump persists it as
|
|
1787
1754
|
// `runtime_notice`, drawn in the SystemNote voice. Adapters emit it only from positive runtime
|
|
1788
1755
|
// evidence; the web never classifies English strings.
|
|
1789
|
-
|
|
1756
|
+
z6.object({ type: z6.literal("runtime_notice"), body: z6.string() }),
|
|
1790
1757
|
// A tool-activity transition. `name` is prefix-stripped (`cabane_read`, not
|
|
1791
1758
|
// `mcp__cabane__cabane_read`); `summary` is the short card label; `phase` pairs `start` with a
|
|
1792
1759
|
// later `done`/`error` on the same `id`.
|
|
@@ -1795,15 +1762,15 @@ var init_turn_event = __esm({
|
|
|
1795
1762
|
// log are a consumer concern (docs/turn-protocol.md). `mcpServer` is the MCP server the call
|
|
1796
1763
|
// came from (`cabane`, `playwright`, …), derived before `name` dropped the prefix; null for a
|
|
1797
1764
|
// host tool. Optional, read as null when absent.
|
|
1798
|
-
|
|
1799
|
-
type:
|
|
1800
|
-
id:
|
|
1801
|
-
name:
|
|
1802
|
-
phase:
|
|
1803
|
-
summary:
|
|
1804
|
-
input:
|
|
1805
|
-
result:
|
|
1806
|
-
mcpServer:
|
|
1765
|
+
z6.object({
|
|
1766
|
+
type: z6.literal("tool"),
|
|
1767
|
+
id: z6.string(),
|
|
1768
|
+
name: z6.string(),
|
|
1769
|
+
phase: z6.enum(["start", "done", "error"]),
|
|
1770
|
+
summary: z6.string(),
|
|
1771
|
+
input: z6.unknown().optional(),
|
|
1772
|
+
result: z6.unknown().optional(),
|
|
1773
|
+
mcpServer: z6.string().nullable().optional()
|
|
1807
1774
|
}),
|
|
1808
1775
|
// The turn's terminal outcome. `ok:false` carries a machine reason (`no_session`, an error
|
|
1809
1776
|
// code), surfaced by the host's clear-PATCH as `errorReason`.
|
|
@@ -1824,48 +1791,48 @@ var init_turn_event = __esm({
|
|
|
1824
1791
|
// started with); `resolvedConfig` the knobs the harness reported (`{ reasoningEffort }` for
|
|
1825
1792
|
// codex). Known only after the run streams, so they ride this event home and the host relays
|
|
1826
1793
|
// them on settle. A cancel has no result event at all.
|
|
1827
|
-
|
|
1828
|
-
type:
|
|
1829
|
-
ok:
|
|
1830
|
-
reason:
|
|
1831
|
-
usage:
|
|
1832
|
-
inputTokens:
|
|
1833
|
-
outputTokens:
|
|
1834
|
-
cacheReadTokens:
|
|
1835
|
-
cacheCreationTokens:
|
|
1836
|
-
contextTokens:
|
|
1837
|
-
contextWindow:
|
|
1794
|
+
z6.object({
|
|
1795
|
+
type: z6.literal("result"),
|
|
1796
|
+
ok: z6.boolean(),
|
|
1797
|
+
reason: z6.string().optional(),
|
|
1798
|
+
usage: z6.object({
|
|
1799
|
+
inputTokens: z6.number(),
|
|
1800
|
+
outputTokens: z6.number(),
|
|
1801
|
+
cacheReadTokens: z6.number().optional(),
|
|
1802
|
+
cacheCreationTokens: z6.number().optional(),
|
|
1803
|
+
contextTokens: z6.number().optional(),
|
|
1804
|
+
contextWindow: z6.number().optional(),
|
|
1838
1805
|
// The rest of the harness's context reading, when it gave one: where it will compact, its
|
|
1839
1806
|
// `/context` breakdown, and every compaction it reported. Claude Code only.
|
|
1840
|
-
contextCompactAt:
|
|
1841
|
-
contextCategories:
|
|
1842
|
-
|
|
1843
|
-
name:
|
|
1844
|
-
tokens:
|
|
1845
|
-
kind:
|
|
1807
|
+
contextCompactAt: z6.number().optional(),
|
|
1808
|
+
contextCategories: z6.array(
|
|
1809
|
+
z6.object({
|
|
1810
|
+
name: z6.string(),
|
|
1811
|
+
tokens: z6.number(),
|
|
1812
|
+
kind: z6.enum(["used", "free", "buffer", "deferred"])
|
|
1846
1813
|
})
|
|
1847
1814
|
).optional(),
|
|
1848
|
-
contextCompactions:
|
|
1849
|
-
|
|
1850
|
-
trigger:
|
|
1851
|
-
preTokens:
|
|
1852
|
-
postTokens:
|
|
1815
|
+
contextCompactions: z6.array(
|
|
1816
|
+
z6.object({
|
|
1817
|
+
trigger: z6.enum(["manual", "auto"]),
|
|
1818
|
+
preTokens: z6.number(),
|
|
1819
|
+
postTokens: z6.number().optional()
|
|
1853
1820
|
})
|
|
1854
1821
|
).optional()
|
|
1855
1822
|
}).optional(),
|
|
1856
|
-
resolvedModel:
|
|
1857
|
-
resolvedConfig:
|
|
1858
|
-
effort:
|
|
1859
|
-
thinking:
|
|
1860
|
-
reasoningEffort:
|
|
1823
|
+
resolvedModel: z6.string().optional(),
|
|
1824
|
+
resolvedConfig: z6.object({
|
|
1825
|
+
effort: z6.string().optional(),
|
|
1826
|
+
thinking: z6.string().optional(),
|
|
1827
|
+
reasoningEffort: z6.string().optional()
|
|
1861
1828
|
}).optional(),
|
|
1862
1829
|
// The harness-reported MCP inventory from this turn's init frame. Diagnostic only: names,
|
|
1863
1830
|
// statuses and a count — never server definitions, credentials or session ids.
|
|
1864
1831
|
// `initReceived:false` tells a missing init frame from a real empty inventory.
|
|
1865
|
-
mcpInventory:
|
|
1866
|
-
initReceived:
|
|
1867
|
-
servers:
|
|
1868
|
-
toolCount:
|
|
1832
|
+
mcpInventory: z6.object({
|
|
1833
|
+
initReceived: z6.boolean(),
|
|
1834
|
+
servers: z6.array(z6.object({ name: z6.string(), status: z6.string() })),
|
|
1835
|
+
toolCount: z6.number().int().nonnegative()
|
|
1869
1836
|
}).optional()
|
|
1870
1837
|
})
|
|
1871
1838
|
]);
|
|
@@ -1873,27 +1840,27 @@ var init_turn_event = __esm({
|
|
|
1873
1840
|
});
|
|
1874
1841
|
|
|
1875
1842
|
// packages/agent-runtime/src/turn-diagnostics.ts
|
|
1876
|
-
import { z as
|
|
1843
|
+
import { z as z7 } from "zod";
|
|
1877
1844
|
var turnResultReasonSchema, turnOutcomes, turnSessionModes, turnRuntimeResultKinds, turnFinalSources, turnDiagnosticsSchema;
|
|
1878
1845
|
var init_turn_diagnostics = __esm({
|
|
1879
1846
|
"packages/agent-runtime/src/turn-diagnostics.ts"() {
|
|
1880
1847
|
"use strict";
|
|
1881
|
-
turnResultReasonSchema =
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1848
|
+
turnResultReasonSchema = z7.discriminatedUnion("kind", [
|
|
1849
|
+
z7.object({ kind: z7.literal("usage_capped"), resetsAt: z7.string().optional() }),
|
|
1850
|
+
z7.object({ kind: z7.literal("rate_limited") }),
|
|
1851
|
+
z7.object({ kind: z7.literal("server_error") }),
|
|
1852
|
+
z7.object({ kind: z7.literal("auth_expired") }),
|
|
1853
|
+
z7.object({ kind: z7.literal("no_result") }),
|
|
1854
|
+
z7.object({ kind: z7.literal("empty_result") }),
|
|
1855
|
+
z7.object({ kind: z7.literal("empty_result_unverified") }),
|
|
1856
|
+
z7.object({ kind: z7.literal("timeout_idle") }),
|
|
1857
|
+
z7.object({ kind: z7.literal("timeout_total") }),
|
|
1858
|
+
z7.object({ kind: z7.literal("cancelled") }),
|
|
1859
|
+
z7.object({ kind: z7.literal("lease_lost") }),
|
|
1860
|
+
z7.object({ kind: z7.literal("skipped") }),
|
|
1861
|
+
z7.object({ kind: z7.literal("session_start_failed") }),
|
|
1862
|
+
z7.object({ kind: z7.literal("workspace_tools_missing") }),
|
|
1863
|
+
z7.object({ kind: z7.literal("runtime_error") })
|
|
1897
1864
|
]);
|
|
1898
1865
|
turnOutcomes = ["success", "failure", "cancelled", "skipped"];
|
|
1899
1866
|
turnSessionModes = ["fresh", "resumed", "degraded"];
|
|
@@ -1905,25 +1872,25 @@ var init_turn_diagnostics = __esm({
|
|
|
1905
1872
|
"marker",
|
|
1906
1873
|
"none"
|
|
1907
1874
|
];
|
|
1908
|
-
turnDiagnosticsSchema =
|
|
1909
|
-
outcome:
|
|
1875
|
+
turnDiagnosticsSchema = z7.object({
|
|
1876
|
+
outcome: z7.enum(turnOutcomes),
|
|
1910
1877
|
resultReason: turnResultReasonSchema.nullable(),
|
|
1911
|
-
sessionMode:
|
|
1912
|
-
sessionFingerprint:
|
|
1913
|
-
eventCounts:
|
|
1914
|
-
session:
|
|
1915
|
-
text:
|
|
1916
|
-
runtime_notice:
|
|
1917
|
-
thinking:
|
|
1918
|
-
tool:
|
|
1919
|
-
result:
|
|
1878
|
+
sessionMode: z7.enum(turnSessionModes),
|
|
1879
|
+
sessionFingerprint: z7.string().regex(/^[a-f0-9]{16}$/).nullable(),
|
|
1880
|
+
eventCounts: z7.object({
|
|
1881
|
+
session: z7.number().int().nonnegative(),
|
|
1882
|
+
text: z7.number().int().nonnegative(),
|
|
1883
|
+
runtime_notice: z7.number().int().nonnegative(),
|
|
1884
|
+
thinking: z7.number().int().nonnegative(),
|
|
1885
|
+
tool: z7.number().int().nonnegative(),
|
|
1886
|
+
result: z7.number().int().nonnegative()
|
|
1920
1887
|
}),
|
|
1921
|
-
runtimeResultKind:
|
|
1922
|
-
finalSource:
|
|
1923
|
-
mcpInventory:
|
|
1924
|
-
initReceived:
|
|
1925
|
-
servers:
|
|
1926
|
-
toolCount:
|
|
1888
|
+
runtimeResultKind: z7.enum(turnRuntimeResultKinds).nullable(),
|
|
1889
|
+
finalSource: z7.enum(turnFinalSources),
|
|
1890
|
+
mcpInventory: z7.object({
|
|
1891
|
+
initReceived: z7.boolean(),
|
|
1892
|
+
servers: z7.array(z7.object({ name: z7.string(), status: z7.string() })),
|
|
1893
|
+
toolCount: z7.number().int().nonnegative()
|
|
1927
1894
|
}).optional()
|
|
1928
1895
|
});
|
|
1929
1896
|
}
|
|
@@ -1997,105 +1964,106 @@ var init_empty_result = __esm({
|
|
|
1997
1964
|
});
|
|
1998
1965
|
|
|
1999
1966
|
// packages/agent-runtime/src/turn-request.ts
|
|
2000
|
-
import { z as
|
|
1967
|
+
import { z as z8 } from "zod";
|
|
2001
1968
|
var contentBlockSchema, effortLevelSchema, resolvedRunConfigSchema, resolvedMcpServerSchema, resolvedMcpServersSchema, hostInjectedServersSchema, turnRequestSchema;
|
|
2002
1969
|
var init_turn_request = __esm({
|
|
2003
1970
|
"packages/agent-runtime/src/turn-request.ts"() {
|
|
2004
1971
|
"use strict";
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
type:
|
|
2010
|
-
source: z9.object({ type: z9.literal("url"), url: z9.string() })
|
|
1972
|
+
contentBlockSchema = z8.discriminatedUnion("type", [
|
|
1973
|
+
z8.object({ type: z8.literal("text"), text: z8.string() }),
|
|
1974
|
+
z8.object({
|
|
1975
|
+
type: z8.literal("image"),
|
|
1976
|
+
source: z8.object({ type: z8.literal("url"), url: z8.string() })
|
|
2011
1977
|
}),
|
|
2012
|
-
|
|
2013
|
-
type:
|
|
2014
|
-
source:
|
|
1978
|
+
z8.object({
|
|
1979
|
+
type: z8.literal("document"),
|
|
1980
|
+
source: z8.object({ type: z8.literal("url"), url: z8.string() })
|
|
2015
1981
|
})
|
|
2016
1982
|
]);
|
|
2017
|
-
effortLevelSchema =
|
|
2018
|
-
resolvedRunConfigSchema =
|
|
2019
|
-
model:
|
|
1983
|
+
effortLevelSchema = z8.enum(["low", "medium", "high", "xhigh", "max"]);
|
|
1984
|
+
resolvedRunConfigSchema = z8.object({
|
|
1985
|
+
model: z8.string().nullable(),
|
|
2020
1986
|
effort: effortLevelSchema.optional(),
|
|
2021
|
-
runtimeOptions:
|
|
1987
|
+
runtimeOptions: z8.record(z8.string(), z8.unknown()).optional()
|
|
2022
1988
|
});
|
|
2023
|
-
resolvedMcpServerSchema =
|
|
2024
|
-
|
|
2025
|
-
type:
|
|
2026
|
-
command:
|
|
2027
|
-
args:
|
|
2028
|
-
env:
|
|
2029
|
-
envFromEnv:
|
|
1989
|
+
resolvedMcpServerSchema = z8.union([
|
|
1990
|
+
z8.object({
|
|
1991
|
+
type: z8.literal("stdio").optional(),
|
|
1992
|
+
command: z8.string(),
|
|
1993
|
+
args: z8.array(z8.string()).optional(),
|
|
1994
|
+
env: z8.record(z8.string(), z8.string()).optional(),
|
|
1995
|
+
envFromEnv: z8.record(z8.string(), z8.string()).optional()
|
|
2030
1996
|
}),
|
|
2031
|
-
|
|
2032
|
-
type:
|
|
2033
|
-
url:
|
|
2034
|
-
headers:
|
|
2035
|
-
headersFromEnv:
|
|
1997
|
+
z8.object({
|
|
1998
|
+
type: z8.literal("http"),
|
|
1999
|
+
url: z8.string(),
|
|
2000
|
+
headers: z8.record(z8.string(), z8.string()).optional(),
|
|
2001
|
+
headersFromEnv: z8.record(z8.string(), z8.string()).optional()
|
|
2036
2002
|
}),
|
|
2037
|
-
|
|
2038
|
-
type:
|
|
2039
|
-
url:
|
|
2040
|
-
headers:
|
|
2041
|
-
headersFromEnv:
|
|
2003
|
+
z8.object({
|
|
2004
|
+
type: z8.literal("sse"),
|
|
2005
|
+
url: z8.string(),
|
|
2006
|
+
headers: z8.record(z8.string(), z8.string()).optional(),
|
|
2007
|
+
headersFromEnv: z8.record(z8.string(), z8.string()).optional()
|
|
2042
2008
|
})
|
|
2043
2009
|
]);
|
|
2044
|
-
resolvedMcpServersSchema =
|
|
2045
|
-
hostInjectedServersSchema =
|
|
2046
|
-
turnRequestSchema =
|
|
2010
|
+
resolvedMcpServersSchema = z8.record(z8.string(), resolvedMcpServerSchema);
|
|
2011
|
+
hostInjectedServersSchema = z8.record(z8.string(), z8.unknown());
|
|
2012
|
+
turnRequestSchema = z8.object({
|
|
2047
2013
|
// Server-composed standing instructions: the core segment (body, tool naming, identity, agent
|
|
2048
2014
|
// charter) and, after it, the conversation segment (workspace and room charters, skill indexes,
|
|
2049
2015
|
// roster). One string to the adapter, current as of this dispatch.
|
|
2050
|
-
systemPrompt:
|
|
2016
|
+
systemPrompt: z8.string(),
|
|
2051
2017
|
// The same text as its two ordered segments, so an adapter that writes standing text into the
|
|
2052
2018
|
// conversation (Codex's input preamble) can tell which half changed. `systemPrompt` is
|
|
2053
2019
|
// `joinStanding(standing)`. Optional: absent from an API older than the split, where
|
|
2054
2020
|
// `systemPrompt` is the core alone and the conversation material rides `prompt`.
|
|
2055
|
-
standing:
|
|
2021
|
+
standing: z8.object({ core: z8.string(), conversation: z8.string() }).optional(),
|
|
2056
2022
|
// Server-composed per-turn user text (anchor reminder + the triggering message).
|
|
2057
|
-
prompt:
|
|
2023
|
+
prompt: z8.string(),
|
|
2058
2024
|
// The multi-block user-message body (text + vision).
|
|
2059
|
-
content:
|
|
2025
|
+
content: z8.array(contentBlockSchema),
|
|
2060
2026
|
// Portable-or-dialect run-config (above).
|
|
2061
2027
|
config: resolvedRunConfigSchema,
|
|
2062
|
-
// Abstract capability grants; the adapter maps them to tool names.
|
|
2063
|
-
policy: hostPolicySchema,
|
|
2064
2028
|
// Prior opaque session state, or null for a fresh session.
|
|
2065
|
-
session:
|
|
2029
|
+
session: z8.string().nullable(),
|
|
2066
2030
|
// The cabane control-plane coordinates for this turn's MCP + post-back.
|
|
2067
|
-
cabane:
|
|
2068
|
-
mcpUrl:
|
|
2069
|
-
bearer:
|
|
2070
|
-
activeConversationId:
|
|
2031
|
+
cabane: z8.object({
|
|
2032
|
+
mcpUrl: z8.string(),
|
|
2033
|
+
bearer: z8.string(),
|
|
2034
|
+
activeConversationId: z8.string(),
|
|
2071
2035
|
// Scoped turn-control MCP URL. Codex and opencode mount it as `cabane_companion` with the bearer
|
|
2072
2036
|
// and active-conversation header, for ask, withdraw_ask, wake_me, cancel_wake and skip_turn.
|
|
2073
2037
|
// Claude Code mounts the companion's in-process instance instead. Optional; the companion
|
|
2074
2038
|
// populates it.
|
|
2075
|
-
turnControlUrl:
|
|
2039
|
+
turnControlUrl: z8.string().optional(),
|
|
2076
2040
|
// Workspace coordinate retained for envelope compatibility. Current
|
|
2077
2041
|
// adapters use MCP, whose workspace operations take workspaceId explicitly.
|
|
2078
2042
|
// The companion supplies this field; it is not required by those adapters.
|
|
2079
|
-
workspaceId:
|
|
2043
|
+
workspaceId: z8.string().optional()
|
|
2080
2044
|
}),
|
|
2081
2045
|
// Machine-local resolution (host-filled): the checkout cwd, extra env from a
|
|
2082
2046
|
// prepare hook, and the resolved user MCP servers.
|
|
2083
|
-
local:
|
|
2084
|
-
cwd:
|
|
2085
|
-
env:
|
|
2047
|
+
local: z8.object({
|
|
2048
|
+
cwd: z8.string().optional(),
|
|
2049
|
+
env: z8.record(z8.string(), z8.string()).optional(),
|
|
2086
2050
|
mcpServers: resolvedMcpServersSchema.optional(),
|
|
2087
2051
|
// The values `headersFromEnv` names, per turn (`CABANE_MCP_<n>` → complete header value). The
|
|
2088
2052
|
// adapter merges them into the harness process's environment and nowhere else; the host never
|
|
2089
2053
|
// merges them into its own. Absent when no server references one.
|
|
2090
|
-
secretEnv:
|
|
2054
|
+
secretEnv: z8.record(z8.string(), z8.string()).optional(),
|
|
2091
2055
|
// Machine-local claude-code knobs for an operator running their own companion — the auto-memory
|
|
2092
2056
|
// escape hatch. `autoMemory: true` opts back into Claude Code's auto-memory (governed by the
|
|
2093
2057
|
// operator's `.claude/settings.json`); absent/false keeps the adapter's force-off default
|
|
2094
2058
|
// (`buildClaudeCodeOptions`).
|
|
2095
|
-
claudeCode:
|
|
2059
|
+
claudeCode: z8.object({ autoMemory: z8.boolean().optional() }).optional(),
|
|
2060
|
+
// Machine-local Codex facts the host read before the turn. `configSetsSandboxMode`: the
|
|
2061
|
+
// person's own Codex `config.toml` names a `sandbox_mode`, so the adapter passes no sandbox
|
|
2062
|
+
// flag and the file governs (`codexSandboxMode`). Absent/false → `danger-full-access`.
|
|
2063
|
+
codex: z8.object({ configSetsSandboxMode: z8.boolean().optional() }).optional()
|
|
2096
2064
|
}),
|
|
2097
2065
|
// Host-owned injected servers (host-filled).
|
|
2098
|
-
extra:
|
|
2066
|
+
extra: z8.object({
|
|
2099
2067
|
mcpServers: hostInjectedServersSchema
|
|
2100
2068
|
})
|
|
2101
2069
|
});
|
|
@@ -2119,18 +2087,18 @@ var init_standing = __esm({
|
|
|
2119
2087
|
});
|
|
2120
2088
|
|
|
2121
2089
|
// packages/agent-runtime/src/conformance.ts
|
|
2122
|
-
import { z as
|
|
2090
|
+
import { z as z9 } from "zod";
|
|
2123
2091
|
var conformanceFixtureSchema;
|
|
2124
2092
|
var init_conformance = __esm({
|
|
2125
2093
|
"packages/agent-runtime/src/conformance.ts"() {
|
|
2126
2094
|
"use strict";
|
|
2127
2095
|
init_turn_event();
|
|
2128
2096
|
init_turn_request();
|
|
2129
|
-
conformanceFixtureSchema =
|
|
2130
|
-
name:
|
|
2097
|
+
conformanceFixtureSchema = z9.object({
|
|
2098
|
+
name: z9.string(),
|
|
2131
2099
|
request: turnRequestSchema,
|
|
2132
|
-
nativeStream:
|
|
2133
|
-
expected:
|
|
2100
|
+
nativeStream: z9.array(z9.unknown()),
|
|
2101
|
+
expected: z9.array(turnEventSchema)
|
|
2134
2102
|
});
|
|
2135
2103
|
}
|
|
2136
2104
|
});
|
|
@@ -2627,13 +2595,7 @@ var init_mcp_secret_env = __esm({
|
|
|
2627
2595
|
});
|
|
2628
2596
|
|
|
2629
2597
|
// packages/agent-runtime/src/claude-code/policy.ts
|
|
2630
|
-
import { z as
|
|
2631
|
-
function disallowedToolsFor(policy) {
|
|
2632
|
-
const tools = [...UNSUPPORTED_TOOLS, ...STRANDING_TOOLS];
|
|
2633
|
-
if (!policy.subagents) tools.unshift(...SUBAGENT_TOOLS);
|
|
2634
|
-
if (!policy.hostFs) tools.unshift(...HOST_FS_TOOLS);
|
|
2635
|
-
return tools;
|
|
2636
|
-
}
|
|
2598
|
+
import { z as z10 } from "zod";
|
|
2637
2599
|
function withThinkingSummaries(thinking) {
|
|
2638
2600
|
if (!thinking) return { type: "adaptive", display: "summarized" };
|
|
2639
2601
|
if (thinking.type === "disabled") return thinking;
|
|
@@ -2645,27 +2607,10 @@ function readThinking(runtimeOptions) {
|
|
|
2645
2607
|
if (!parsed.success) return void 0;
|
|
2646
2608
|
return parsed.data.thinking;
|
|
2647
2609
|
}
|
|
2648
|
-
var
|
|
2610
|
+
var SUBAGENT_TOOLS, UNSUPPORTED_TOOLS, STRANDING_TOOLS, DEFAULT_WEB_TOOLS, claudeCodeDialectSchema;
|
|
2649
2611
|
var init_policy = __esm({
|
|
2650
2612
|
"packages/agent-runtime/src/claude-code/policy.ts"() {
|
|
2651
2613
|
"use strict";
|
|
2652
|
-
HOST_FS_TOOLS = [
|
|
2653
|
-
// shell + local filesystem
|
|
2654
|
-
"Bash",
|
|
2655
|
-
"Read",
|
|
2656
|
-
"Write",
|
|
2657
|
-
"Edit",
|
|
2658
|
-
"Glob",
|
|
2659
|
-
"Grep",
|
|
2660
|
-
// notebook tools (host-fs, Jupyter) — same host reach as Read/Write/Edit
|
|
2661
|
-
"NotebookEdit",
|
|
2662
|
-
"NotebookRead",
|
|
2663
|
-
// git-worktree manipulation on the host checkout
|
|
2664
|
-
"EnterWorktree",
|
|
2665
|
-
"ExitWorktree",
|
|
2666
|
-
// host code execution
|
|
2667
|
-
"REPL"
|
|
2668
|
-
];
|
|
2669
2614
|
SUBAGENT_TOOLS = ["Task", "Agent"];
|
|
2670
2615
|
UNSUPPORTED_TOOLS = ["AskUserQuestion"];
|
|
2671
2616
|
STRANDING_TOOLS = [
|
|
@@ -2687,18 +2632,18 @@ var init_policy = __esm({
|
|
|
2687
2632
|
"Workflow"
|
|
2688
2633
|
];
|
|
2689
2634
|
DEFAULT_WEB_TOOLS = ["WebSearch", "WebFetch"];
|
|
2690
|
-
claudeCodeDialectSchema =
|
|
2691
|
-
thinking:
|
|
2692
|
-
|
|
2693
|
-
type:
|
|
2694
|
-
display:
|
|
2635
|
+
claudeCodeDialectSchema = z10.object({
|
|
2636
|
+
thinking: z10.discriminatedUnion("type", [
|
|
2637
|
+
z10.object({
|
|
2638
|
+
type: z10.literal("adaptive"),
|
|
2639
|
+
display: z10.enum(["summarized", "omitted"]).optional()
|
|
2695
2640
|
}),
|
|
2696
|
-
|
|
2697
|
-
type:
|
|
2698
|
-
budgetTokens:
|
|
2699
|
-
display:
|
|
2641
|
+
z10.object({
|
|
2642
|
+
type: z10.literal("enabled"),
|
|
2643
|
+
budgetTokens: z10.number().int().positive().optional(),
|
|
2644
|
+
display: z10.enum(["summarized", "omitted"]).optional()
|
|
2700
2645
|
}),
|
|
2701
|
-
|
|
2646
|
+
z10.object({ type: z10.literal("disabled") })
|
|
2702
2647
|
]).optional()
|
|
2703
2648
|
}).loose();
|
|
2704
2649
|
}
|
|
@@ -2766,13 +2711,13 @@ async function preToolUseHook(input) {
|
|
|
2766
2711
|
hookSpecificOutput: {
|
|
2767
2712
|
hookEventName: "PreToolUse",
|
|
2768
2713
|
permissionDecision: "allow",
|
|
2769
|
-
permissionDecisionReason: "
|
|
2714
|
+
permissionDecisionReason: "headless never-prompt (CT680)",
|
|
2770
2715
|
updatedInput: toolInput
|
|
2771
2716
|
}
|
|
2772
2717
|
};
|
|
2773
2718
|
}
|
|
2774
2719
|
function buildClaudeCodeOptions(req, augment) {
|
|
2775
|
-
const {
|
|
2720
|
+
const { config } = req;
|
|
2776
2721
|
const cwd = req.local.cwd;
|
|
2777
2722
|
const userServers = {};
|
|
2778
2723
|
for (const [name, server] of Object.entries(req.local.mcpServers ?? {})) {
|
|
@@ -2805,15 +2750,10 @@ function buildClaudeCodeOptions(req, augment) {
|
|
|
2805
2750
|
alwaysLoad: true
|
|
2806
2751
|
};
|
|
2807
2752
|
}
|
|
2808
|
-
const useCodingPreset = policy.hostFs;
|
|
2809
2753
|
const cabaneGlob = `mcp__${CABANE_MCP_SERVER}__*`;
|
|
2810
2754
|
const extraServerGlobs = Object.keys(req.extra.mcpServers).map((name) => `mcp__${name}__*`);
|
|
2811
|
-
const allowedTools = dedupe([
|
|
2812
|
-
|
|
2813
|
-
...extraServerGlobs,
|
|
2814
|
-
...policy.web ? DEFAULT_WEB_TOOLS : []
|
|
2815
|
-
]);
|
|
2816
|
-
const disallowedTools = dedupe([...disallowedToolsFor(policy)]);
|
|
2755
|
+
const allowedTools = dedupe([cabaneGlob, ...extraServerGlobs, ...DEFAULT_WEB_TOOLS]);
|
|
2756
|
+
const disallowedTools = dedupe([...UNSUPPORTED_TOOLS, ...STRANDING_TOOLS]);
|
|
2817
2757
|
const resumeDecision = decideResume(req.session, cwd);
|
|
2818
2758
|
const resume = "resume" in resumeDecision ? resumeDecision.resume : null;
|
|
2819
2759
|
const freshReason = "fresh" in resumeDecision ? resumeDecision.reason : void 0;
|
|
@@ -2831,9 +2771,7 @@ function buildClaudeCodeOptions(req, augment) {
|
|
|
2831
2771
|
// (`runtimeOptions['claude-code']`).
|
|
2832
2772
|
thinking: withThinkingSummaries(readThinking(config.runtimeOptions)),
|
|
2833
2773
|
...config.effort ? { effort: config.effort } : {},
|
|
2834
|
-
//
|
|
2835
|
-
// user's existing `cabane` MCP registration could shadow ours.
|
|
2836
|
-
settingSources: [],
|
|
2774
|
+
// Without `strictMcpConfig` the user's existing `cabane` MCP registration could shadow ours.
|
|
2837
2775
|
strictMcpConfig: true,
|
|
2838
2776
|
// Auto-memory off unless the operator opted back in (see above).
|
|
2839
2777
|
...devControlsAutoMemory ? {} : { settings: { autoMemoryEnabled: false } },
|
|
@@ -2869,31 +2807,19 @@ function buildClaudeCodeOptions(req, augment) {
|
|
|
2869
2807
|
},
|
|
2870
2808
|
...resume ? { resume } : {}
|
|
2871
2809
|
};
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
hooks: { PreToolUse: [{ hooks: [preToolUseHook] }] }
|
|
2886
|
-
};
|
|
2887
|
-
} else {
|
|
2888
|
-
options = {
|
|
2889
|
-
...base,
|
|
2890
|
-
systemPrompt: { type: "custom", prompt: req.systemPrompt, snapshot: false },
|
|
2891
|
-
allowedTools,
|
|
2892
|
-
disallowedTools,
|
|
2893
|
-
permissionMode: "bypassPermissions",
|
|
2894
|
-
allowDangerouslySkipPermissions: true
|
|
2895
|
-
};
|
|
2896
|
-
}
|
|
2810
|
+
const options = {
|
|
2811
|
+
...base,
|
|
2812
|
+
systemPrompt: {
|
|
2813
|
+
type: "preset",
|
|
2814
|
+
preset: "claude_code",
|
|
2815
|
+
append: req.systemPrompt,
|
|
2816
|
+
snapshot: false
|
|
2817
|
+
},
|
|
2818
|
+
settingSources: ["user", "project"],
|
|
2819
|
+
allowedTools,
|
|
2820
|
+
disallowedTools,
|
|
2821
|
+
hooks: { PreToolUse: [{ hooks: [preToolUseHook] }] }
|
|
2822
|
+
};
|
|
2897
2823
|
return { options: augment ? augment(options, req) : options, resume, freshReason };
|
|
2898
2824
|
}
|
|
2899
2825
|
function dedupe(xs) {
|
|
@@ -3440,7 +3366,6 @@ function makeRequest(overrides2 = {}) {
|
|
|
3440
3366
|
content: [{ type: "text", text: "hi there" }],
|
|
3441
3367
|
// The adapter rejects a null model, so the fixture carries a real bare SDK id.
|
|
3442
3368
|
config: { model: "claude-opus-4-8" },
|
|
3443
|
-
policy: CABANE_POLICY,
|
|
3444
3369
|
session: null,
|
|
3445
3370
|
cabane: { mcpUrl: "https://cabane.test/api/mcp", bearer: "tok", activeConversationId: "conv" },
|
|
3446
3371
|
local: { cwd: CWD },
|
|
@@ -3448,22 +3373,13 @@ function makeRequest(overrides2 = {}) {
|
|
|
3448
3373
|
...overrides2
|
|
3449
3374
|
};
|
|
3450
3375
|
}
|
|
3451
|
-
var ABORT_SENTINEL,
|
|
3376
|
+
var ABORT_SENTINEL, CWD, HEALTHY_MCP_INVENTORY, init, assistantText, assistantTextTool, toolResult, resultSuccess, resultSuccessFull, resultError, resultErrorFull, rateLimitEvent, authStatus, sessionEvent, BASE_CLAUDE_CODE_CONFORMANCE_FIXTURES, CLAUDE_CODE_CONFORMANCE_FIXTURES;
|
|
3452
3377
|
var init_conformance2 = __esm({
|
|
3453
3378
|
"packages/agent-runtime/src/claude-code/conformance.ts"() {
|
|
3454
3379
|
"use strict";
|
|
3455
3380
|
init_claude_code();
|
|
3456
3381
|
init_session();
|
|
3457
3382
|
ABORT_SENTINEL = { __abortHere: true };
|
|
3458
|
-
CABANE_POLICY = {
|
|
3459
|
-
hostFs: false,
|
|
3460
|
-
web: true,
|
|
3461
|
-
browser: false,
|
|
3462
|
-
userMcp: false,
|
|
3463
|
-
subagents: false,
|
|
3464
|
-
scheduling: "never",
|
|
3465
|
-
uiPrompts: "never"
|
|
3466
|
-
};
|
|
3467
3383
|
CWD = "/env/here";
|
|
3468
3384
|
HEALTHY_MCP_INVENTORY = {
|
|
3469
3385
|
initReceived: true,
|
|
@@ -4266,43 +4182,25 @@ var init_loop2 = __esm({
|
|
|
4266
4182
|
});
|
|
4267
4183
|
|
|
4268
4184
|
// packages/agent-runtime/src/opencode/policy.ts
|
|
4269
|
-
import { z as
|
|
4270
|
-
function
|
|
4185
|
+
import { z as z11 } from "zod";
|
|
4186
|
+
function opencodeTools() {
|
|
4271
4187
|
const tools = {};
|
|
4272
|
-
const
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
if (!policy.hostFs) deny(OPENCODE_HOST_TOOLS);
|
|
4276
|
-
if (!policy.subagents) deny(OPENCODE_SUBAGENT_TOOLS);
|
|
4277
|
-
if (!policy.web) deny(OPENCODE_WEB_TOOLS);
|
|
4278
|
-
deny(OPENCODE_SCHEDULING_TOOLS);
|
|
4279
|
-
deny(OPENCODE_UI_PROMPT_TOOLS);
|
|
4280
|
-
return { tools, allowAllHostTools: policy.hostFs };
|
|
4188
|
+
for (const name of [...OPENCODE_SCHEDULING_TOOLS, ...OPENCODE_UI_PROMPT_TOOLS])
|
|
4189
|
+
tools[name] = false;
|
|
4190
|
+
return tools;
|
|
4281
4191
|
}
|
|
4282
4192
|
function readOpencodeDialect(runtimeOptions) {
|
|
4283
4193
|
const parsed = opencodeDialectSchema.safeParse(runtimeOptions?.["opencode"] ?? {});
|
|
4284
4194
|
return parsed.success ? parsed.data : {};
|
|
4285
4195
|
}
|
|
4286
|
-
var
|
|
4196
|
+
var OPENCODE_SCHEDULING_TOOLS, OPENCODE_UI_PROMPT_TOOLS, opencodeDialectSchema;
|
|
4287
4197
|
var init_policy2 = __esm({
|
|
4288
4198
|
"packages/agent-runtime/src/opencode/policy.ts"() {
|
|
4289
4199
|
"use strict";
|
|
4290
|
-
OPENCODE_HOST_TOOLS = [
|
|
4291
|
-
"bash",
|
|
4292
|
-
"edit",
|
|
4293
|
-
"write",
|
|
4294
|
-
"read",
|
|
4295
|
-
"grep",
|
|
4296
|
-
"glob",
|
|
4297
|
-
"list",
|
|
4298
|
-
"patch"
|
|
4299
|
-
];
|
|
4300
|
-
OPENCODE_WEB_TOOLS = ["webfetch"];
|
|
4301
|
-
OPENCODE_SUBAGENT_TOOLS = ["task"];
|
|
4302
4200
|
OPENCODE_SCHEDULING_TOOLS = [];
|
|
4303
4201
|
OPENCODE_UI_PROMPT_TOOLS = ["question"];
|
|
4304
|
-
opencodeDialectSchema =
|
|
4305
|
-
agent:
|
|
4202
|
+
opencodeDialectSchema = z11.object({
|
|
4203
|
+
agent: z11.string().min(1).optional()
|
|
4306
4204
|
}).loose();
|
|
4307
4205
|
}
|
|
4308
4206
|
});
|
|
@@ -4321,18 +4219,16 @@ var init_model2 = __esm({
|
|
|
4321
4219
|
|
|
4322
4220
|
// packages/agent-runtime/src/opencode/run-spec.ts
|
|
4323
4221
|
function buildRunSpec(req, resumeSessionId) {
|
|
4324
|
-
const {
|
|
4222
|
+
const { config } = req;
|
|
4325
4223
|
const directory = req.local.cwd ?? "";
|
|
4326
4224
|
const dialect = readOpencodeDialect(config.runtimeOptions);
|
|
4327
|
-
const { tools, allowAllHostTools } = opencodeToolPolicy(policy);
|
|
4328
4225
|
return {
|
|
4329
4226
|
resumeSessionId,
|
|
4330
4227
|
directory,
|
|
4331
4228
|
model: config.model ? parseOpencodeModel(config.model) : null,
|
|
4332
4229
|
systemPrompt: req.systemPrompt,
|
|
4333
4230
|
parts: buildParts(req),
|
|
4334
|
-
tools,
|
|
4335
|
-
allowAllHostTools,
|
|
4231
|
+
tools: opencodeTools(),
|
|
4336
4232
|
...dialect.agent ? { agent: dialect.agent } : {},
|
|
4337
4233
|
mcp: buildMcp(req),
|
|
4338
4234
|
// Every server this turn installs with a credential resolved into it: a remote one's
|
|
@@ -4480,11 +4376,12 @@ function createHttpOpencodeTransport(opts) {
|
|
|
4480
4376
|
const doFetch = opts.fetchImpl ?? fetch;
|
|
4481
4377
|
return {
|
|
4482
4378
|
async run(spec, signal) {
|
|
4379
|
+
const url = scopedUrl(base, spec.directory);
|
|
4483
4380
|
const release = await acquireServerTurnLock(base);
|
|
4484
4381
|
if (signal.aborted) release();
|
|
4485
4382
|
else signal.addEventListener("abort", release, { once: true });
|
|
4486
4383
|
try {
|
|
4487
|
-
const eventRes = await doFetch(
|
|
4384
|
+
const eventRes = await doFetch(url("/event"), {
|
|
4488
4385
|
headers: { accept: "text/event-stream" },
|
|
4489
4386
|
signal
|
|
4490
4387
|
});
|
|
@@ -4494,7 +4391,7 @@ function createHttpOpencodeTransport(opts) {
|
|
|
4494
4391
|
let sessionId = spec.resumeSessionId;
|
|
4495
4392
|
let created = false;
|
|
4496
4393
|
if (!sessionId) {
|
|
4497
|
-
const res = await doFetch(
|
|
4394
|
+
const res = await doFetch(url("/session"), {
|
|
4498
4395
|
method: "POST",
|
|
4499
4396
|
headers: { "content-type": "application/json" },
|
|
4500
4397
|
body: JSON.stringify({ body: {} }),
|
|
@@ -4506,9 +4403,9 @@ function createHttpOpencodeTransport(opts) {
|
|
|
4506
4403
|
sessionId = json.id;
|
|
4507
4404
|
created = true;
|
|
4508
4405
|
}
|
|
4509
|
-
await installMcp(doFetch,
|
|
4406
|
+
await installMcp(doFetch, url, spec, signal);
|
|
4510
4407
|
const promptRes = await doFetch(
|
|
4511
|
-
|
|
4408
|
+
url(`/session/${encodeURIComponent(sessionId)}/prompt_async`),
|
|
4512
4409
|
{
|
|
4513
4410
|
method: "POST",
|
|
4514
4411
|
headers: { "content-type": "application/json" },
|
|
@@ -4527,7 +4424,7 @@ function createHttpOpencodeTransport(opts) {
|
|
|
4527
4424
|
}
|
|
4528
4425
|
const sid = sessionId;
|
|
4529
4426
|
const onAbort = () => {
|
|
4530
|
-
void doFetch(
|
|
4427
|
+
void doFetch(url(`/session/${encodeURIComponent(sid)}/abort`), {
|
|
4531
4428
|
method: "POST"
|
|
4532
4429
|
}).catch(() => {
|
|
4533
4430
|
});
|
|
@@ -4539,7 +4436,13 @@ function createHttpOpencodeTransport(opts) {
|
|
|
4539
4436
|
created,
|
|
4540
4437
|
directory: spec.directory,
|
|
4541
4438
|
// The lock is released when this stream finishes draining.
|
|
4542
|
-
events: releaseAfter(
|
|
4439
|
+
events: releaseAfter(
|
|
4440
|
+
approvePermissions(
|
|
4441
|
+
parseSseStream(eventRes.body, sessionId, signal),
|
|
4442
|
+
(ev) => replyToPermission(doFetch, url, ev, signal)
|
|
4443
|
+
),
|
|
4444
|
+
release
|
|
4445
|
+
)
|
|
4543
4446
|
};
|
|
4544
4447
|
} catch (err) {
|
|
4545
4448
|
release();
|
|
@@ -4555,16 +4458,16 @@ async function* releaseAfter(events, release) {
|
|
|
4555
4458
|
release();
|
|
4556
4459
|
}
|
|
4557
4460
|
}
|
|
4558
|
-
async function installMcp(doFetch,
|
|
4461
|
+
async function installMcp(doFetch, url, spec, signal) {
|
|
4559
4462
|
const [live, configured] = await Promise.all([
|
|
4560
|
-
getJson(doFetch,
|
|
4561
|
-
getJson(doFetch,
|
|
4463
|
+
getJson(doFetch, url("/mcp"), signal),
|
|
4464
|
+
getJson(doFetch, url("/config"), signal)
|
|
4562
4465
|
]);
|
|
4563
4466
|
const operatorOwned = new Set(Object.keys(asRecord2(asRecord2(configured).mcp)));
|
|
4564
4467
|
const stale = Object.keys(asRecord2(live)).filter(
|
|
4565
4468
|
(name) => !(name in spec.mcp) && !operatorOwned.has(name)
|
|
4566
4469
|
);
|
|
4567
|
-
await Promise.all(stale.map((name) => postMcp(doFetch,
|
|
4470
|
+
await Promise.all(stale.map((name) => postMcp(doFetch, url, name, SCRUBBED_MCP_CONFIG, signal)));
|
|
4568
4471
|
const shadowing = (spec.credentialServers ?? []).filter((name) => operatorOwned.has(name));
|
|
4569
4472
|
if (shadowing.length > 0) {
|
|
4570
4473
|
throw new Error(
|
|
@@ -4572,11 +4475,11 @@ async function installMcp(doFetch, base, spec, signal) {
|
|
|
4572
4475
|
);
|
|
4573
4476
|
}
|
|
4574
4477
|
await Promise.all(
|
|
4575
|
-
Object.entries(spec.mcp).map(([name, config]) => postMcp(doFetch,
|
|
4478
|
+
Object.entries(spec.mcp).map(([name, config]) => postMcp(doFetch, url, name, config, signal))
|
|
4576
4479
|
);
|
|
4577
4480
|
}
|
|
4578
|
-
async function postMcp(doFetch,
|
|
4579
|
-
const res = await doFetch(
|
|
4481
|
+
async function postMcp(doFetch, url, name, config, signal) {
|
|
4482
|
+
const res = await doFetch(url("/mcp"), {
|
|
4580
4483
|
method: "POST",
|
|
4581
4484
|
headers: { "content-type": "application/json" },
|
|
4582
4485
|
body: JSON.stringify({ name, config }),
|
|
@@ -4640,6 +4543,33 @@ function belongsToSession(ev, sessionId) {
|
|
|
4640
4543
|
if (typeof infoSid === "string") return infoSid === sessionId;
|
|
4641
4544
|
return true;
|
|
4642
4545
|
}
|
|
4546
|
+
async function* approvePermissions(events, reply2) {
|
|
4547
|
+
for await (const ev of events) {
|
|
4548
|
+
if (ev.type === "permission.asked" || ev.type === "permission.v2.asked") await reply2(ev);
|
|
4549
|
+
yield ev;
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
async function replyToPermission(doFetch, url, ev, signal) {
|
|
4553
|
+
const props = asRecord2(ev.properties);
|
|
4554
|
+
const id = typeof props.id === "string" ? props.id : null;
|
|
4555
|
+
const sessionId = typeof props.sessionID === "string" ? props.sessionID : null;
|
|
4556
|
+
if (!id) throw new Error(`opencode: ${ev.type} carried no request id`);
|
|
4557
|
+
const target = ev.type === "permission.v2.asked" && sessionId ? url(
|
|
4558
|
+
`/api/session/${encodeURIComponent(sessionId)}/permission/${encodeURIComponent(id)}/reply`,
|
|
4559
|
+
{ unscoped: true }
|
|
4560
|
+
) : url(`/permission/${encodeURIComponent(id)}/reply`);
|
|
4561
|
+
const res = await doFetch(target, {
|
|
4562
|
+
method: "POST",
|
|
4563
|
+
headers: { "content-type": "application/json" },
|
|
4564
|
+
body: JSON.stringify({ reply: "once" }),
|
|
4565
|
+
signal
|
|
4566
|
+
});
|
|
4567
|
+
if (!res.ok) throw new Error(`opencode: permission reply failed (HTTP ${res.status})`);
|
|
4568
|
+
}
|
|
4569
|
+
function scopedUrl(base, directory) {
|
|
4570
|
+
const scope = directory ? `directory=${encodeURIComponent(directory)}` : "";
|
|
4571
|
+
return (path, opts) => scope && !opts?.unscoped ? `${base}${path}${path.includes("?") ? "&" : "?"}${scope}` : base + path;
|
|
4572
|
+
}
|
|
4643
4573
|
function trimSlash(s) {
|
|
4644
4574
|
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
4645
4575
|
}
|
|
@@ -4737,7 +4667,6 @@ function makeRequest2(overrides2 = {}) {
|
|
|
4737
4667
|
prompt: "hi there",
|
|
4738
4668
|
content: [{ type: "text", text: "hi there" }],
|
|
4739
4669
|
config: { model: "deepseek/deepseek-chat" },
|
|
4740
|
-
policy: COMPANION_POLICY,
|
|
4741
4670
|
session: null,
|
|
4742
4671
|
cabane: { mcpUrl: "https://cabane.test/api/mcp", bearer: "tok", activeConversationId: "conv" },
|
|
4743
4672
|
local: { cwd: DIR },
|
|
@@ -4745,7 +4674,7 @@ function makeRequest2(overrides2 = {}) {
|
|
|
4745
4674
|
...overrides2
|
|
4746
4675
|
};
|
|
4747
4676
|
}
|
|
4748
|
-
var ABORT_SENTINEL2, NEW_SESSION_ID, BOUNDED_NOTICE,
|
|
4677
|
+
var ABORT_SENTINEL2, NEW_SESSION_ID, BOUNDED_NOTICE, DIR, textPart, reasoningPart, toolPart, messageUpdated, echoedUserPart, idle, errored, sessionEvent2, OPENCODE_CONFORMANCE_FIXTURES;
|
|
4749
4678
|
var init_conformance3 = __esm({
|
|
4750
4679
|
"packages/agent-runtime/src/opencode/conformance.ts"() {
|
|
4751
4680
|
"use strict";
|
|
@@ -4754,15 +4683,6 @@ var init_conformance3 = __esm({
|
|
|
4754
4683
|
ABORT_SENTINEL2 = { __abortHere: true };
|
|
4755
4684
|
NEW_SESSION_ID = "sess_new";
|
|
4756
4685
|
BOUNDED_NOTICE = "You've hit your usage limit \xB7 ".padEnd(200, "x");
|
|
4757
|
-
COMPANION_POLICY = {
|
|
4758
|
-
hostFs: false,
|
|
4759
|
-
web: true,
|
|
4760
|
-
browser: true,
|
|
4761
|
-
userMcp: true,
|
|
4762
|
-
subagents: false,
|
|
4763
|
-
scheduling: "never",
|
|
4764
|
-
uiPrompts: "never"
|
|
4765
|
-
};
|
|
4766
4686
|
DIR = "/env/here";
|
|
4767
4687
|
textPart = (id, text) => ({
|
|
4768
4688
|
type: "message.part.updated",
|
|
@@ -5590,23 +5510,9 @@ var init_loop3 = __esm({
|
|
|
5590
5510
|
});
|
|
5591
5511
|
|
|
5592
5512
|
// packages/agent-runtime/src/codex/policy.ts
|
|
5593
|
-
import { z as
|
|
5594
|
-
function
|
|
5595
|
-
return
|
|
5596
|
-
sandboxMode: "danger-full-access",
|
|
5597
|
-
// Headless: nothing to approve, because nothing is denied.
|
|
5598
|
-
approvalPolicy: "never",
|
|
5599
|
-
// Full access means the command network is open regardless of the grant —
|
|
5600
|
-
// recorded as `true` so the value never claims a restriction that isn't
|
|
5601
|
-
// enforced.
|
|
5602
|
-
networkAccessEnabled: true
|
|
5603
|
-
} : {
|
|
5604
|
-
sandboxMode: "read-only",
|
|
5605
|
-
// Headless: the sandbox is the boundary; never pause for a human.
|
|
5606
|
-
approvalPolicy: "never",
|
|
5607
|
-
// Retained in the policy value for symmetry; read-only ignores it.
|
|
5608
|
-
networkAccessEnabled: policy.web
|
|
5609
|
-
};
|
|
5513
|
+
import { z as z12 } from "zod";
|
|
5514
|
+
function codexSandboxMode(local) {
|
|
5515
|
+
return local.codex?.configSetsSandboxMode ? null : "danger-full-access";
|
|
5610
5516
|
}
|
|
5611
5517
|
function readCodexDialect(runtimeOptions) {
|
|
5612
5518
|
const parsed = codexDialectSchema.safeParse(runtimeOptions?.["codex"] ?? {});
|
|
@@ -5617,8 +5523,8 @@ var init_policy3 = __esm({
|
|
|
5617
5523
|
"packages/agent-runtime/src/codex/policy.ts"() {
|
|
5618
5524
|
"use strict";
|
|
5619
5525
|
CODEX_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max", "ultra"];
|
|
5620
|
-
codexDialectSchema =
|
|
5621
|
-
modelReasoningEffort:
|
|
5526
|
+
codexDialectSchema = z12.object({
|
|
5527
|
+
modelReasoningEffort: z12.enum(CODEX_REASONING_EFFORTS).optional()
|
|
5622
5528
|
}).loose();
|
|
5623
5529
|
}
|
|
5624
5530
|
});
|
|
@@ -5638,11 +5544,10 @@ var init_model3 = __esm({
|
|
|
5638
5544
|
});
|
|
5639
5545
|
|
|
5640
5546
|
// packages/agent-runtime/src/codex/run-spec.ts
|
|
5641
|
-
function buildRunSpec2(req, resumeThreadId,
|
|
5642
|
-
const {
|
|
5547
|
+
function buildRunSpec2(req, resumeThreadId, threadStanding = null, imagePaths = []) {
|
|
5548
|
+
const { config } = req;
|
|
5643
5549
|
const directory = req.local.cwd ?? "";
|
|
5644
5550
|
const dialect = readCodexDialect(config.runtimeOptions);
|
|
5645
|
-
const baseInstructionsFile = !policy.hostFs && instructionsFile ? instructionsFile : null;
|
|
5646
5551
|
const model = config.model ? parseCodexModel(config.model) : null;
|
|
5647
5552
|
const segments = standingSegments(req);
|
|
5648
5553
|
const conversationText = segments.conversation.trim();
|
|
@@ -5652,25 +5557,24 @@ function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadStand
|
|
|
5652
5557
|
};
|
|
5653
5558
|
const resumed = resumeThreadId !== null;
|
|
5654
5559
|
const held = resumed ? threadStanding : null;
|
|
5655
|
-
const viaInput = baseInstructionsFile === null;
|
|
5656
5560
|
const preamble = {
|
|
5657
|
-
core:
|
|
5561
|
+
core: (held?.core ?? null) !== fingerprints.core,
|
|
5658
5562
|
// Compared both ways: a held segment that is now empty differs from it, and nothing held
|
|
5659
5563
|
// equals nothing to send.
|
|
5660
|
-
conversation:
|
|
5564
|
+
conversation: (held?.conversation ?? null) !== fingerprints.conversation
|
|
5661
5565
|
};
|
|
5662
5566
|
return {
|
|
5663
5567
|
resumeThreadId,
|
|
5664
5568
|
directory,
|
|
5665
5569
|
model,
|
|
5666
|
-
|
|
5570
|
+
sandboxMode: codexSandboxMode(req.local),
|
|
5571
|
+
approvalPolicy: "never",
|
|
5667
5572
|
skipGitRepoCheck: true,
|
|
5668
5573
|
...dialect.modelReasoningEffort ? { modelReasoningEffort: dialect.modelReasoningEffort } : {},
|
|
5669
|
-
baseInstructionsFile,
|
|
5670
5574
|
input: withImages(buildInput(req, segments, preamble, resumed), imagePaths),
|
|
5671
5575
|
preamble,
|
|
5672
5576
|
fingerprints,
|
|
5673
|
-
config: buildConfig(req
|
|
5577
|
+
config: buildConfig(req),
|
|
5674
5578
|
secretEnv: codexSecretEnv(req)
|
|
5675
5579
|
};
|
|
5676
5580
|
}
|
|
@@ -5713,7 +5617,7 @@ function withImages(text, imagePaths) {
|
|
|
5713
5617
|
...imagePaths.map((path) => ({ type: "local_image", path }))
|
|
5714
5618
|
];
|
|
5715
5619
|
}
|
|
5716
|
-
function buildConfig(req
|
|
5620
|
+
function buildConfig(req) {
|
|
5717
5621
|
const mcp_servers = {};
|
|
5718
5622
|
for (const [name, server] of Object.entries(req.local.mcpServers ?? {})) {
|
|
5719
5623
|
if ("url" in server) {
|
|
@@ -5779,9 +5683,6 @@ function buildConfig(req, baseInstructionsFile) {
|
|
|
5779
5683
|
return {
|
|
5780
5684
|
mcp_servers,
|
|
5781
5685
|
experimental_use_rmcp_client: true,
|
|
5782
|
-
// The no-host-access prompt replacement — Cabane's composed prompt becomes Codex's base
|
|
5783
|
-
// instructions, and Codex's environment preamble goes with the harness prompt it belonged to.
|
|
5784
|
-
...baseInstructionsFile ? { model_instructions_file: baseInstructionsFile, include_environment_context: false } : {},
|
|
5785
5686
|
...Object.keys(shellEnv).length ? { shell_environment_policy: { set: shellEnv } } : {}
|
|
5786
5687
|
};
|
|
5787
5688
|
}
|
|
@@ -5804,8 +5705,7 @@ var init_run_spec2 = __esm({
|
|
|
5804
5705
|
"CABANE_PLAYGROUND_BIN",
|
|
5805
5706
|
"CABANE_CONVERSATION_ID",
|
|
5806
5707
|
"CABANE_AGENT_ID",
|
|
5807
|
-
"CABANE_CONVERSATION_TITLE"
|
|
5808
|
-
"CABANE_HOST_ACCESS"
|
|
5708
|
+
"CABANE_CONVERSATION_TITLE"
|
|
5809
5709
|
];
|
|
5810
5710
|
CORE_REPLACES = "Your standing instructions changed since an earlier copy in this conversation, or that copy may have been compacted away. This copy replaces every earlier one: where they differ, this one holds, and anything an earlier copy said that this one does not no longer applies.";
|
|
5811
5711
|
CONVERSATION_REPLACES = "This conversation's standing context \u2014 its charters, skill indexes and roster \u2014 changed since an earlier copy in this conversation, or that copy may have been compacted away. This copy replaces every earlier one, including charters and skills an earlier turn carried in its own text: where they differ, this one holds, and anything an earlier copy said that this one does not no longer applies.";
|
|
@@ -5884,10 +5784,10 @@ import {
|
|
|
5884
5784
|
function buildSdkThreadOptions(spec) {
|
|
5885
5785
|
return {
|
|
5886
5786
|
...spec.model ? { model: spec.model } : {},
|
|
5887
|
-
sandboxMode: spec.
|
|
5787
|
+
...spec.sandboxMode ? { sandboxMode: spec.sandboxMode } : {},
|
|
5888
5788
|
workingDirectory: spec.directory,
|
|
5889
5789
|
skipGitRepoCheck: spec.skipGitRepoCheck,
|
|
5890
|
-
approvalPolicy: spec.
|
|
5790
|
+
approvalPolicy: spec.approvalPolicy,
|
|
5891
5791
|
...spec.modelReasoningEffort ? { modelReasoningEffort: spec.modelReasoningEffort } : {}
|
|
5892
5792
|
};
|
|
5893
5793
|
}
|
|
@@ -5989,23 +5889,6 @@ function createCodexAdapter(deps = {}) {
|
|
|
5989
5889
|
);
|
|
5990
5890
|
}
|
|
5991
5891
|
}
|
|
5992
|
-
let instructions = null;
|
|
5993
|
-
if (!req.policy.hostFs) {
|
|
5994
|
-
if (!deps.writeInstructionsFile) {
|
|
5995
|
-
deps.onWarn?.(
|
|
5996
|
-
"codex adapter: no instructions-file writer wired on this host \u2014 the composed prompt rides as the input preamble, so Codex keeps its own base instructions (CT871)"
|
|
5997
|
-
);
|
|
5998
|
-
} else {
|
|
5999
|
-
try {
|
|
6000
|
-
instructions = await deps.writeInstructionsFile(req.systemPrompt);
|
|
6001
|
-
} catch (err) {
|
|
6002
|
-
deps.onWarn?.(
|
|
6003
|
-
"codex adapter: failed to write the per-turn instructions file \u2014 falling back to the input preamble (CT871)",
|
|
6004
|
-
{ err: err instanceof Error ? err.message : String(err) }
|
|
6005
|
-
);
|
|
6006
|
-
}
|
|
6007
|
-
}
|
|
6008
|
-
}
|
|
6009
5892
|
let images = null;
|
|
6010
5893
|
try {
|
|
6011
5894
|
const imageUrls = req.content.flatMap((b) => b.type === "image" ? [b.source.url] : []);
|
|
@@ -6029,13 +5912,7 @@ function createCodexAdapter(deps = {}) {
|
|
|
6029
5912
|
}
|
|
6030
5913
|
if (signal.aborted) return;
|
|
6031
5914
|
}
|
|
6032
|
-
const spec = buildRunSpec2(
|
|
6033
|
-
req,
|
|
6034
|
-
resumeThreadId,
|
|
6035
|
-
instructions?.path ?? null,
|
|
6036
|
-
threadStanding,
|
|
6037
|
-
images?.paths ?? []
|
|
6038
|
-
);
|
|
5915
|
+
const spec = buildRunSpec2(req, resumeThreadId, threadStanding, images?.paths ?? []);
|
|
6039
5916
|
if (spec.model === null) {
|
|
6040
5917
|
deps.onWarn?.("Codex is using its default model because no model was resolved", {
|
|
6041
5918
|
model: req.config.model
|
|
@@ -6077,15 +5954,6 @@ function createCodexAdapter(deps = {}) {
|
|
|
6077
5954
|
});
|
|
6078
5955
|
}
|
|
6079
5956
|
}
|
|
6080
|
-
if (instructions) {
|
|
6081
|
-
try {
|
|
6082
|
-
await instructions.cleanup();
|
|
6083
|
-
} catch (err) {
|
|
6084
|
-
deps.onWarn?.("codex adapter: instructions-file cleanup failed (CT871)", {
|
|
6085
|
-
err: err instanceof Error ? err.message : String(err)
|
|
6086
|
-
});
|
|
6087
|
-
}
|
|
6088
|
-
}
|
|
6089
5957
|
}
|
|
6090
5958
|
}
|
|
6091
5959
|
};
|
|
@@ -6122,7 +5990,6 @@ function makeRequest3(overrides2 = {}) {
|
|
|
6122
5990
|
// Behavioural fixtures use the model-less token (no `resolvedModel` emitted); a dedicated capture
|
|
6123
5991
|
// fixture sets a real model and effort.
|
|
6124
5992
|
config: { model: null },
|
|
6125
|
-
policy: COMPANION_POLICY2,
|
|
6126
5993
|
session: null,
|
|
6127
5994
|
cabane: { mcpUrl: "https://cabane.test/api/mcp", bearer: "tok", activeConversationId: "conv" },
|
|
6128
5995
|
local: { cwd: DIR2 },
|
|
@@ -6130,7 +5997,7 @@ function makeRequest3(overrides2 = {}) {
|
|
|
6130
5997
|
...overrides2
|
|
6131
5998
|
};
|
|
6132
5999
|
}
|
|
6133
|
-
var ABORT_SENTINEL3, NEW_THREAD_ID, BOUNDED_NOTICE2,
|
|
6000
|
+
var ABORT_SENTINEL3, NEW_THREAD_ID, BOUNDED_NOTICE2, DIR2, PROMPT, threadStarted, agentMessage, reasoning, toolFrame, turnCompleted, turnFailed, fatalError, errorItem, OP_SUCCESS, OP_FAILURE, startedSession, closingSession, CODEX_CONFORMANCE_FIXTURES;
|
|
6134
6001
|
var init_conformance4 = __esm({
|
|
6135
6002
|
"packages/agent-runtime/src/codex/conformance.ts"() {
|
|
6136
6003
|
"use strict";
|
|
@@ -6139,15 +6006,6 @@ var init_conformance4 = __esm({
|
|
|
6139
6006
|
ABORT_SENTINEL3 = { __abortHere: true };
|
|
6140
6007
|
NEW_THREAD_ID = "th_new";
|
|
6141
6008
|
BOUNDED_NOTICE2 = "You've hit your usage limit \xB7 ".padEnd(200, "x");
|
|
6142
|
-
COMPANION_POLICY2 = {
|
|
6143
|
-
hostFs: false,
|
|
6144
|
-
web: true,
|
|
6145
|
-
browser: true,
|
|
6146
|
-
userMcp: true,
|
|
6147
|
-
subagents: false,
|
|
6148
|
-
scheduling: "never",
|
|
6149
|
-
uiPrompts: "never"
|
|
6150
|
-
};
|
|
6151
6009
|
DIR2 = "/env/here";
|
|
6152
6010
|
PROMPT = "system";
|
|
6153
6011
|
threadStarted = (threadId) => ({
|
|
@@ -7063,7 +6921,6 @@ var init_src = __esm({
|
|
|
7063
6921
|
"packages/agent-runtime/src/index.ts"() {
|
|
7064
6922
|
"use strict";
|
|
7065
6923
|
init_version();
|
|
7066
|
-
init_host_policy();
|
|
7067
6924
|
init_turn_event();
|
|
7068
6925
|
init_turn_diagnostics();
|
|
7069
6926
|
init_turn_diagnostics_normalize();
|
|
@@ -7444,7 +7301,7 @@ var init_turn_containment = __esm({
|
|
|
7444
7301
|
});
|
|
7445
7302
|
|
|
7446
7303
|
// src/run-config.ts
|
|
7447
|
-
import { z as
|
|
7304
|
+
import { z as z13 } from "zod";
|
|
7448
7305
|
function parseRunConfig(raw, onWarn) {
|
|
7449
7306
|
const result = runConfigSchema.safeParse(raw ?? {});
|
|
7450
7307
|
if (result.success) return result.data;
|
|
@@ -7457,48 +7314,43 @@ var mcpStdioServerSchema, mcpHttpServerSchema, mcpSseServerSchema, mcpServerDefS
|
|
|
7457
7314
|
var init_run_config = __esm({
|
|
7458
7315
|
"src/run-config.ts"() {
|
|
7459
7316
|
"use strict";
|
|
7460
|
-
mcpStdioServerSchema =
|
|
7461
|
-
type:
|
|
7462
|
-
command:
|
|
7463
|
-
args:
|
|
7464
|
-
env:
|
|
7317
|
+
mcpStdioServerSchema = z13.object({
|
|
7318
|
+
type: z13.literal("stdio").optional(),
|
|
7319
|
+
command: z13.string().min(1),
|
|
7320
|
+
args: z13.array(z13.string()).optional(),
|
|
7321
|
+
env: z13.record(z13.string(), z13.string()).optional(),
|
|
7465
7322
|
// Variable → the name of a `secretEnv` entry holding its value. Set by the server on a
|
|
7466
7323
|
// room's shared browser, whose connect URL is a credential.
|
|
7467
|
-
envFromEnv:
|
|
7324
|
+
envFromEnv: z13.record(z13.string(), z13.string()).optional()
|
|
7468
7325
|
});
|
|
7469
|
-
mcpHttpServerSchema =
|
|
7470
|
-
type:
|
|
7471
|
-
url:
|
|
7472
|
-
headers:
|
|
7326
|
+
mcpHttpServerSchema = z13.object({
|
|
7327
|
+
type: z13.literal("http"),
|
|
7328
|
+
url: z13.string().url(),
|
|
7329
|
+
headers: z13.record(z13.string(), z13.string()).optional(),
|
|
7473
7330
|
// Header → the name of a `secretEnv` entry holding its value. Set by
|
|
7474
7331
|
// the server on a room connection's entry, and by `resolveMcpSecrets` on a
|
|
7475
7332
|
// header whose `${VAR}` it resolved.
|
|
7476
|
-
headersFromEnv:
|
|
7333
|
+
headersFromEnv: z13.record(z13.string(), z13.string()).optional()
|
|
7477
7334
|
});
|
|
7478
|
-
mcpSseServerSchema =
|
|
7479
|
-
type:
|
|
7480
|
-
url:
|
|
7481
|
-
headers:
|
|
7335
|
+
mcpSseServerSchema = z13.object({
|
|
7336
|
+
type: z13.literal("sse"),
|
|
7337
|
+
url: z13.string().url(),
|
|
7338
|
+
headers: z13.record(z13.string(), z13.string()).optional(),
|
|
7482
7339
|
// Header → the name of a `secretEnv` entry holding its value. Set by
|
|
7483
7340
|
// the server on a room connection's entry, and by `resolveMcpSecrets` on a
|
|
7484
7341
|
// header whose `${VAR}` it resolved.
|
|
7485
|
-
headersFromEnv:
|
|
7342
|
+
headersFromEnv: z13.record(z13.string(), z13.string()).optional()
|
|
7486
7343
|
});
|
|
7487
|
-
mcpServerDefSchema =
|
|
7488
|
-
thinkingConfigSchema =
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7344
|
+
mcpServerDefSchema = z13.union([mcpHttpServerSchema, mcpSseServerSchema, mcpStdioServerSchema]);
|
|
7345
|
+
thinkingConfigSchema = z13.discriminatedUnion("type", [
|
|
7346
|
+
z13.object({ type: z13.literal("adaptive") }),
|
|
7347
|
+
z13.object({ type: z13.literal("enabled"), budgetTokens: z13.number().int().positive().optional() }),
|
|
7348
|
+
z13.object({ type: z13.literal("disabled") })
|
|
7492
7349
|
]);
|
|
7493
|
-
effortSchema =
|
|
7494
|
-
runConfigSchema =
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
// `agentRunConfigSchema` (independent zod, forward-compatible: unknown keys are
|
|
7498
|
-
// stripped, so an older companion riding a newer server never rejects the config).
|
|
7499
|
-
hostAccess: z14.boolean().optional(),
|
|
7500
|
-
mcpServers: z14.record(z14.string(), mcpServerDefSchema).optional(),
|
|
7501
|
-
model: z14.string().min(1).optional(),
|
|
7350
|
+
effortSchema = z13.enum(["low", "medium", "high", "xhigh", "max"]);
|
|
7351
|
+
runConfigSchema = z13.object({
|
|
7352
|
+
mcpServers: z13.record(z13.string(), mcpServerDefSchema).optional(),
|
|
7353
|
+
model: z13.string().min(1).optional(),
|
|
7502
7354
|
thinking: thinkingConfigSchema.optional(),
|
|
7503
7355
|
effort: effortSchema.optional()
|
|
7504
7356
|
});
|
|
@@ -7507,7 +7359,7 @@ var init_run_config = __esm({
|
|
|
7507
7359
|
});
|
|
7508
7360
|
|
|
7509
7361
|
// src/runner/protocol.ts
|
|
7510
|
-
import { z as
|
|
7362
|
+
import { z as z14 } from "zod";
|
|
7511
7363
|
function required(env, name) {
|
|
7512
7364
|
const value = env[name];
|
|
7513
7365
|
if (!value) throw new Error(`runner: ${name} is not set`);
|
|
@@ -7585,56 +7437,56 @@ var init_protocol = __esm({
|
|
|
7585
7437
|
// Everything else the turn is constructed from, as JSON: see `RunnerSpec`.
|
|
7586
7438
|
spec: "CABANE_RUNNER_SPEC"
|
|
7587
7439
|
};
|
|
7588
|
-
runtimeHealthSchema =
|
|
7589
|
-
quotaState:
|
|
7590
|
-
limitedUntil:
|
|
7591
|
-
lastError:
|
|
7440
|
+
runtimeHealthSchema = z14.object({
|
|
7441
|
+
quotaState: z14.enum(["ok", "limited", "auth_expired", "unknown"]),
|
|
7442
|
+
limitedUntil: z14.string().nullable(),
|
|
7443
|
+
lastError: z14.string().nullable()
|
|
7592
7444
|
});
|
|
7593
|
-
runnerSpecSchema =
|
|
7594
|
-
deviceId:
|
|
7445
|
+
runnerSpecSchema = z14.object({
|
|
7446
|
+
deviceId: z14.string().optional(),
|
|
7595
7447
|
// The launcher's boot id: the one its heartbeat carries, so a runner's lease
|
|
7596
7448
|
// matches the device that holds it.
|
|
7597
|
-
bootId:
|
|
7598
|
-
workspaceId:
|
|
7599
|
-
workspaceSlug:
|
|
7600
|
-
agentId:
|
|
7601
|
-
agentUsername:
|
|
7602
|
-
agentName:
|
|
7449
|
+
bootId: z14.string(),
|
|
7450
|
+
workspaceId: z14.string(),
|
|
7451
|
+
workspaceSlug: z14.string(),
|
|
7452
|
+
agentId: z14.string(),
|
|
7453
|
+
agentUsername: z14.string(),
|
|
7454
|
+
agentName: z14.string(),
|
|
7603
7455
|
local: localAgentConfigSchema,
|
|
7604
7456
|
runConfig: runConfigSchema,
|
|
7605
7457
|
// The device's `root`, resolved by the launcher: the pair directory the turn
|
|
7606
7458
|
// stands in when nothing else names one lives under it (`work-dir.ts`).
|
|
7607
|
-
workRoot:
|
|
7608
|
-
opencodeServerUrl:
|
|
7609
|
-
claudeCodeAvailable:
|
|
7610
|
-
codexAvailable:
|
|
7611
|
-
idleTimeoutMs:
|
|
7612
|
-
totalTimeoutMs:
|
|
7613
|
-
leaseRenewalMs:
|
|
7614
|
-
preparingRowDelayMs:
|
|
7459
|
+
workRoot: z14.string(),
|
|
7460
|
+
opencodeServerUrl: z14.string().optional(),
|
|
7461
|
+
claudeCodeAvailable: z14.boolean(),
|
|
7462
|
+
codexAvailable: z14.boolean(),
|
|
7463
|
+
idleTimeoutMs: z14.number().int().positive().optional(),
|
|
7464
|
+
totalTimeoutMs: z14.number().int().positive().optional(),
|
|
7465
|
+
leaseRenewalMs: z14.number().int().positive().optional(),
|
|
7466
|
+
preparingRowDelayMs: z14.number().int().nonnegative().optional(),
|
|
7615
7467
|
// Whether the turn writes a transcript, into the device's own directory
|
|
7616
7468
|
// (`transcriptsDir()`).
|
|
7617
|
-
transcripts:
|
|
7618
|
-
logLevel:
|
|
7469
|
+
transcripts: z14.boolean(),
|
|
7470
|
+
logLevel: z14.string(),
|
|
7619
7471
|
// The launcher's connector-health observations, by runtime, as of the start.
|
|
7620
|
-
connectorHealth:
|
|
7472
|
+
connectorHealth: z14.record(z14.string(), runtimeHealthSchema),
|
|
7621
7473
|
// True when this (conversation, agent) pair was already told its session
|
|
7622
7474
|
// could not be saved: the notice is once per pair, not once per process.
|
|
7623
|
-
sessionWriteNoticeClaimed:
|
|
7475
|
+
sessionWriteNoticeClaimed: z14.boolean(),
|
|
7624
7476
|
// The runner's identity, minted with its credential. The start
|
|
7625
7477
|
// PATCH carries it so the lease knows which runner holds the span.
|
|
7626
|
-
runnerId:
|
|
7478
|
+
runnerId: z14.string()
|
|
7627
7479
|
});
|
|
7628
|
-
dispatchSchema =
|
|
7629
|
-
type:
|
|
7630
|
-
agentId:
|
|
7631
|
-
deviceId:
|
|
7632
|
-
conversationId:
|
|
7633
|
-
messageId:
|
|
7634
|
-
episode:
|
|
7480
|
+
dispatchSchema = z14.object({
|
|
7481
|
+
type: z14.literal("device:dispatch_requested"),
|
|
7482
|
+
agentId: z14.string(),
|
|
7483
|
+
deviceId: z14.string(),
|
|
7484
|
+
conversationId: z14.string(),
|
|
7485
|
+
messageId: z14.string(),
|
|
7486
|
+
episode: z14.number().int().optional()
|
|
7635
7487
|
});
|
|
7636
|
-
credentialsSchema =
|
|
7637
|
-
credential:
|
|
7488
|
+
credentialsSchema = z14.object({
|
|
7489
|
+
credential: z14.string().startsWith("cabrn_")
|
|
7638
7490
|
});
|
|
7639
7491
|
}
|
|
7640
7492
|
});
|
|
@@ -8030,7 +7882,7 @@ var init_work_dir = __esm({
|
|
|
8030
7882
|
|
|
8031
7883
|
// src/turn-control-tools.ts
|
|
8032
7884
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
8033
|
-
import { z as
|
|
7885
|
+
import { z as z15 } from "zod";
|
|
8034
7886
|
function createSkipState() {
|
|
8035
7887
|
return { skipped: false, reason: null };
|
|
8036
7888
|
}
|
|
@@ -8081,7 +7933,7 @@ function createTurnControlMcpServer(acts, skipState) {
|
|
|
8081
7933
|
SKIP_TURN_TOOL,
|
|
8082
7934
|
TURN_CONTROL_TOOL_DESCRIPTIONS.skip_turn,
|
|
8083
7935
|
{
|
|
8084
|
-
reason:
|
|
7936
|
+
reason: z15.string().min(1).max(MAX_SKIP_REASON_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.skip_turn.reason)
|
|
8085
7937
|
},
|
|
8086
7938
|
async (args) => {
|
|
8087
7939
|
skipState.skipped = true;
|
|
@@ -8097,22 +7949,22 @@ function createTurnControlMcpServer(acts, skipState) {
|
|
|
8097
7949
|
ASK_TOOL,
|
|
8098
7950
|
TURN_CONTROL_TOOL_DESCRIPTIONS.ask,
|
|
8099
7951
|
{
|
|
8100
|
-
targetUserId:
|
|
8101
|
-
question:
|
|
8102
|
-
headline:
|
|
8103
|
-
options:
|
|
8104
|
-
questions:
|
|
8105
|
-
|
|
8106
|
-
headline:
|
|
8107
|
-
body:
|
|
8108
|
-
options:
|
|
8109
|
-
|
|
7952
|
+
targetUserId: z15.string().regex(ASK_TARGET_PATTERN, { error: ASK_TARGET_INVALID }).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.targetUserId),
|
|
7953
|
+
question: z15.string().min(1).max(MAX_ASK_QUESTION_BYTES).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.question),
|
|
7954
|
+
headline: z15.string().min(1).max(MAX_ASK_HEADLINE_BYTES).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.headline),
|
|
7955
|
+
options: z15.array(z15.string({ error: ASK_OPTION_NOT_A_STRING }).min(1).max(MAX_ASK_OPTION_BYTES)).min(MIN_ASK_OPTIONS).max(MAX_ASK_OPTIONS).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.options),
|
|
7956
|
+
questions: z15.array(
|
|
7957
|
+
z15.object({
|
|
7958
|
+
headline: z15.string().min(1).max(MAX_ASK_HEADLINE_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["questions.headline"]),
|
|
7959
|
+
body: z15.string().min(1).max(MAX_ASK_QUESTION_BYTES).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["questions.body"]),
|
|
7960
|
+
options: z15.array(
|
|
7961
|
+
z15.string({ error: ASK_OPTION_NOT_A_STRING }).min(1).max(MAX_ASK_OPTION_BYTES)
|
|
8110
7962
|
).min(MIN_ASK_OPTIONS).max(MAX_ASK_OPTIONS).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["questions.options"])
|
|
8111
7963
|
})
|
|
8112
7964
|
).min(1).max(MAX_ASK_ITEMS).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.questions),
|
|
8113
|
-
replaces:
|
|
8114
|
-
askId:
|
|
8115
|
-
reason:
|
|
7965
|
+
replaces: z15.object({
|
|
7966
|
+
askId: z15.string().uuid().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["replaces.askId"]),
|
|
7967
|
+
reason: z15.string().min(1).max(MAX_WITHDRAW_REASON_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["replaces.reason"])
|
|
8116
7968
|
}).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.replaces)
|
|
8117
7969
|
},
|
|
8118
7970
|
async (args) => {
|
|
@@ -8153,9 +8005,9 @@ function createTurnControlMcpServer(acts, skipState) {
|
|
|
8153
8005
|
WAKE_ME_TOOL,
|
|
8154
8006
|
TURN_CONTROL_TOOL_DESCRIPTIONS.wake_me,
|
|
8155
8007
|
{
|
|
8156
|
-
afterSeconds:
|
|
8157
|
-
at:
|
|
8158
|
-
note:
|
|
8008
|
+
afterSeconds: z15.number().int().positive().optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.wake_me.afterSeconds),
|
|
8009
|
+
at: z15.string().datetime({ offset: true }).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.wake_me.at),
|
|
8010
|
+
note: z15.string().min(1).max(MAX_WAKE_NOTE_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.wake_me.note)
|
|
8159
8011
|
},
|
|
8160
8012
|
async (args) => {
|
|
8161
8013
|
const hasAfter = args.afterSeconds !== void 0;
|
|
@@ -8189,8 +8041,8 @@ function createTurnControlMcpServer(acts, skipState) {
|
|
|
8189
8041
|
WITHDRAW_ASK_TOOL,
|
|
8190
8042
|
TURN_CONTROL_TOOL_DESCRIPTIONS.withdraw_ask,
|
|
8191
8043
|
{
|
|
8192
|
-
askId:
|
|
8193
|
-
reason:
|
|
8044
|
+
askId: z15.string().uuid().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.withdraw_ask.askId),
|
|
8045
|
+
reason: z15.string().min(1).max(MAX_WITHDRAW_REASON_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.withdraw_ask.reason)
|
|
8194
8046
|
},
|
|
8195
8047
|
async (args) => performAct(acts, {
|
|
8196
8048
|
kind: "withdraw_ask",
|
|
@@ -8249,7 +8101,6 @@ function buildCompanionTurnRequest(params) {
|
|
|
8249
8101
|
prompt: t.prompt,
|
|
8250
8102
|
content: t.content,
|
|
8251
8103
|
config: t.config,
|
|
8252
|
-
policy: t.policy,
|
|
8253
8104
|
session: t.session,
|
|
8254
8105
|
cabane: {
|
|
8255
8106
|
mcpUrl: cabaneMcpUrl(params.baseUrl),
|
|
@@ -8266,7 +8117,9 @@ function buildCompanionTurnRequest(params) {
|
|
|
8266
8117
|
...Object.keys(params.mcpServers).length > 0 ? { mcpServers: params.mcpServers } : {},
|
|
8267
8118
|
...params.secretEnv && Object.keys(params.secretEnv).length > 0 ? { secretEnv: params.secretEnv } : {},
|
|
8268
8119
|
// The auto-memory escape hatch, when the operator set it.
|
|
8269
|
-
...params.claudeCode ? { claudeCode: params.claudeCode } : {}
|
|
8120
|
+
...params.claudeCode ? { claudeCode: params.claudeCode } : {},
|
|
8121
|
+
// Whether the person's Codex config sets its own sandbox mode.
|
|
8122
|
+
...params.codex ? { codex: params.codex } : {}
|
|
8270
8123
|
},
|
|
8271
8124
|
// Host-injected turn-control tools, under their own namespace.
|
|
8272
8125
|
extra: {
|
|
@@ -8284,42 +8137,49 @@ var init_build_options = __esm({
|
|
|
8284
8137
|
}
|
|
8285
8138
|
});
|
|
8286
8139
|
|
|
8287
|
-
// src/codex-
|
|
8288
|
-
import {
|
|
8289
|
-
import {
|
|
8140
|
+
// src/codex-config.ts
|
|
8141
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
8142
|
+
import { homedir as homedir7 } from "os";
|
|
8290
8143
|
import { join as join15 } from "path";
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
await writeFile(path, contents, { encoding: "utf8", mode: 384 });
|
|
8295
|
-
return {
|
|
8296
|
-
path,
|
|
8297
|
-
cleanup: async () => {
|
|
8298
|
-
await rm(dir2, { recursive: true, force: true });
|
|
8299
|
-
}
|
|
8300
|
-
};
|
|
8144
|
+
import { parse } from "smol-toml";
|
|
8145
|
+
function codexConfigPath(env = process.env, home = homedir7()) {
|
|
8146
|
+
return join15(env.CODEX_HOME || join15(home, ".codex"), "config.toml");
|
|
8301
8147
|
}
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8148
|
+
async function readCodexConfigSetsSandboxMode(path = codexConfigPath()) {
|
|
8149
|
+
let text;
|
|
8150
|
+
try {
|
|
8151
|
+
text = await readFile2(path, "utf8");
|
|
8152
|
+
} catch {
|
|
8153
|
+
return false;
|
|
8154
|
+
}
|
|
8155
|
+
return configSetsSandboxMode(text);
|
|
8156
|
+
}
|
|
8157
|
+
function configSetsSandboxMode(text) {
|
|
8158
|
+
try {
|
|
8159
|
+
return parse(text).sandbox_mode !== void 0;
|
|
8160
|
+
} catch {
|
|
8161
|
+
return false;
|
|
8162
|
+
}
|
|
8163
|
+
}
|
|
8164
|
+
var init_codex_config = __esm({
|
|
8165
|
+
"src/codex-config.ts"() {
|
|
8305
8166
|
"use strict";
|
|
8306
|
-
PREFIX = "cabane-codex-instructions-";
|
|
8307
8167
|
}
|
|
8308
8168
|
});
|
|
8309
8169
|
|
|
8310
8170
|
// src/codex-images.ts
|
|
8311
|
-
import { mkdtemp
|
|
8312
|
-
import { tmpdir
|
|
8171
|
+
import { mkdtemp, rm, writeFile } from "fs/promises";
|
|
8172
|
+
import { tmpdir } from "os";
|
|
8313
8173
|
import { join as join16 } from "path";
|
|
8314
8174
|
function createCodexImagePreparer(opts) {
|
|
8315
8175
|
const doFetch = opts.fetch ?? fetch;
|
|
8316
8176
|
const timeoutMs = opts.timeoutMs ?? FETCH_TIMEOUT_MS;
|
|
8317
8177
|
return async (urls, signal) => {
|
|
8318
8178
|
signal.throwIfAborted();
|
|
8319
|
-
const dir2 = await
|
|
8179
|
+
const dir2 = await mkdtemp(join16(tmpdir(), `cabane-codex-images-${opts.turnId}-`));
|
|
8320
8180
|
const cleanup = async () => {
|
|
8321
8181
|
try {
|
|
8322
|
-
await
|
|
8182
|
+
await rm(dir2, { recursive: true, force: true });
|
|
8323
8183
|
} catch (err) {
|
|
8324
8184
|
opts.warn(
|
|
8325
8185
|
{ dir: dir2, err: message(err) },
|
|
@@ -8338,7 +8198,7 @@ function createCodexImagePreparer(opts) {
|
|
|
8338
8198
|
if (!ext) throw new Error(`not a vision image type: ${type || "none"}`);
|
|
8339
8199
|
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
8340
8200
|
const path = join16(dir2, `image-${index + 1}.${ext}`);
|
|
8341
|
-
await
|
|
8201
|
+
await writeFile(path, bytes, { mode: 384 });
|
|
8342
8202
|
return path;
|
|
8343
8203
|
} catch (err) {
|
|
8344
8204
|
if (!signal.aborted) {
|
|
@@ -8378,10 +8238,10 @@ var init_codex_images = __esm({
|
|
|
8378
8238
|
// src/codex-compactions.ts
|
|
8379
8239
|
import { createReadStream } from "fs";
|
|
8380
8240
|
import { readdir } from "fs/promises";
|
|
8381
|
-
import { homedir as
|
|
8241
|
+
import { homedir as homedir8 } from "os";
|
|
8382
8242
|
import { join as join17 } from "path";
|
|
8383
8243
|
import { createInterface as createInterface2 } from "readline";
|
|
8384
|
-
function createCodexCompactionCounter(codexHome = process.env.CODEX_HOME || join17(
|
|
8244
|
+
function createCodexCompactionCounter(codexHome = process.env.CODEX_HOME || join17(homedir8(), ".codex")) {
|
|
8385
8245
|
return async (threadId) => {
|
|
8386
8246
|
try {
|
|
8387
8247
|
const rollout = await findRollout(codexHome, threadId);
|
|
@@ -8592,7 +8452,7 @@ var init_prepared = __esm({
|
|
|
8592
8452
|
// src/secrets.ts
|
|
8593
8453
|
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
8594
8454
|
import { join as join19 } from "path";
|
|
8595
|
-
import { z as
|
|
8455
|
+
import { z as z16 } from "zod";
|
|
8596
8456
|
function secretsPath() {
|
|
8597
8457
|
return join19(cabaneDir(), "secrets.json");
|
|
8598
8458
|
}
|
|
@@ -8716,7 +8576,7 @@ var init_secrets = __esm({
|
|
|
8716
8576
|
init_config();
|
|
8717
8577
|
init_errors();
|
|
8718
8578
|
PLACEHOLDER_RE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
8719
|
-
secretStoreSchema =
|
|
8579
|
+
secretStoreSchema = z16.record(z16.string(), z16.string());
|
|
8720
8580
|
SECRET_ENV_NAME = /^CABANE_MCP_(\d+)$/;
|
|
8721
8581
|
}
|
|
8722
8582
|
});
|
|
@@ -9100,7 +8960,7 @@ var init_turn_execution = __esm({
|
|
|
9100
8960
|
init_work_dir();
|
|
9101
8961
|
init_build_options();
|
|
9102
8962
|
init_bundled_binaries();
|
|
9103
|
-
|
|
8963
|
+
init_codex_config();
|
|
9104
8964
|
init_codex_images();
|
|
9105
8965
|
init_codex_compactions();
|
|
9106
8966
|
init_config();
|
|
@@ -9181,6 +9041,7 @@ var init_turn_execution = __esm({
|
|
|
9181
9041
|
effectiveCwd;
|
|
9182
9042
|
hookEnv;
|
|
9183
9043
|
turnEnv;
|
|
9044
|
+
codexLocal;
|
|
9184
9045
|
skipState;
|
|
9185
9046
|
turnControlServer;
|
|
9186
9047
|
request;
|
|
@@ -9522,7 +9383,6 @@ var init_turn_execution = __esm({
|
|
|
9522
9383
|
agentUsername: this.opts.agentUsername,
|
|
9523
9384
|
defaultCwd: pairDir,
|
|
9524
9385
|
runtime: this.turnContext.runtime,
|
|
9525
|
-
hostAccess: this.turnContext.policy.hostFs,
|
|
9526
9386
|
triggerEntryPaths: this.turnContext.conversation.triggerEntryPaths ?? [],
|
|
9527
9387
|
title: this.turnContext.conversation.title,
|
|
9528
9388
|
messageBody: this.turnContext.message.body,
|
|
@@ -9585,7 +9445,6 @@ var init_turn_execution = __esm({
|
|
|
9585
9445
|
// wants to add to it.
|
|
9586
9446
|
defaultCwd: pairDir,
|
|
9587
9447
|
runtime: this.turnContext.runtime,
|
|
9588
|
-
hostAccess: this.turnContext.policy.hostFs,
|
|
9589
9448
|
// The trigger message's referenced-entry paths, which a prepare hook
|
|
9590
9449
|
// may key its per-task env off. `[]` from an older API.
|
|
9591
9450
|
triggerEntryPaths: this.turnContext.conversation.triggerEntryPaths ?? [],
|
|
@@ -9618,9 +9477,9 @@ var init_turn_execution = __esm({
|
|
|
9618
9477
|
}
|
|
9619
9478
|
const turnEnv = {
|
|
9620
9479
|
...hookEnv,
|
|
9621
|
-
CABANE_HOST_ACCESS: this.turnContext.policy.hostFs ? "1" : "0",
|
|
9622
9480
|
CABANE_COMPANION_HOME: agentCompanionHome()
|
|
9623
9481
|
};
|
|
9482
|
+
this.codexLocal = this.turnContext.runtime === "codex" ? { configSetsSandboxMode: await readCodexConfigSetsSandboxMode() } : void 0;
|
|
9624
9483
|
this.effectiveCwd = effectiveCwd;
|
|
9625
9484
|
this.hookEnv = hookEnv;
|
|
9626
9485
|
this.turnEnv = turnEnv;
|
|
@@ -9705,7 +9564,8 @@ var init_turn_execution = __esm({
|
|
|
9705
9564
|
activeConversationId: payload.conversationId,
|
|
9706
9565
|
workspaceId,
|
|
9707
9566
|
// The operator's machine-local auto-memory escape hatch, when set.
|
|
9708
|
-
...this.opts.local.claudeCode ? { claudeCode: this.opts.local.claudeCode } : {}
|
|
9567
|
+
...this.opts.local.claudeCode ? { claudeCode: this.opts.local.claudeCode } : {},
|
|
9568
|
+
...this.codexLocal ? { codex: this.codexLocal } : {}
|
|
9709
9569
|
});
|
|
9710
9570
|
}
|
|
9711
9571
|
integrityContext() {
|
|
@@ -9772,7 +9632,6 @@ var init_turn_execution = __esm({
|
|
|
9772
9632
|
createCodexAdapter({
|
|
9773
9633
|
enabled: true,
|
|
9774
9634
|
onWarn,
|
|
9775
|
-
writeInstructionsFile: writeCodexInstructionsFile,
|
|
9776
9635
|
prepareImages: createCodexImagePreparer({
|
|
9777
9636
|
turnId: this.turnId,
|
|
9778
9637
|
warn: (meta, msg) => turnLog.warn(meta, msg)
|
|
@@ -12182,7 +12041,6 @@ var CompanionStateHub = class {
|
|
|
12182
12041
|
agent_id: a.agentId,
|
|
12183
12042
|
username: a.username,
|
|
12184
12043
|
display_name: a.displayName,
|
|
12185
|
-
mode: a.mode,
|
|
12186
12044
|
has_credential: a.hasCredential,
|
|
12187
12045
|
missing_secrets: a.missingSecrets
|
|
12188
12046
|
}))
|
|
@@ -12562,14 +12420,19 @@ var DeviceApi = class {
|
|
|
12562
12420
|
return parsed;
|
|
12563
12421
|
}
|
|
12564
12422
|
// Pull the agents this device should run, across all the owner's workspaces.
|
|
12565
|
-
getAssignments() {
|
|
12566
|
-
return this.request("GET", "/api/companion/assignments");
|
|
12423
|
+
getAssignments(signal) {
|
|
12424
|
+
return this.request("GET", "/api/companion/assignments", void 0, requestSignal(signal));
|
|
12567
12425
|
}
|
|
12568
12426
|
// Ask again for the credential of an agent assigned to this device, when the saved copy is
|
|
12569
12427
|
// gone. The previous credential stops working; any agent not assigned here is a 404, and a
|
|
12570
12428
|
// second ask inside a minute a 429.
|
|
12571
|
-
reissueAgentCredential(agentId) {
|
|
12572
|
-
return this.request(
|
|
12429
|
+
reissueAgentCredential(agentId, signal) {
|
|
12430
|
+
return this.request(
|
|
12431
|
+
"POST",
|
|
12432
|
+
`/api/companion/agents/${encodeURIComponent(agentId)}/credential`,
|
|
12433
|
+
void 0,
|
|
12434
|
+
requestSignal(signal, 3e4)
|
|
12435
|
+
);
|
|
12573
12436
|
}
|
|
12574
12437
|
getRuns(cursor) {
|
|
12575
12438
|
const query = new URLSearchParams({ limit: "100", order: "asc" });
|
|
@@ -12609,6 +12472,10 @@ function errorMessage2(status2, body) {
|
|
|
12609
12472
|
if (typeof body === "string" && body.length > 0) return `${status2} ${body.slice(0, 200)}`;
|
|
12610
12473
|
return `${status2} error`;
|
|
12611
12474
|
}
|
|
12475
|
+
function requestSignal(signal, timeoutMs = 5e3) {
|
|
12476
|
+
const deadline = AbortSignal.timeout(timeoutMs);
|
|
12477
|
+
return signal ? AbortSignal.any([signal, deadline]) : deadline;
|
|
12478
|
+
}
|
|
12612
12479
|
|
|
12613
12480
|
// src/supervisor.ts
|
|
12614
12481
|
init_config();
|
|
@@ -13088,7 +12955,7 @@ init_launch();
|
|
|
13088
12955
|
// src/manifest.ts
|
|
13089
12956
|
var DEVICE_MANIFEST = {
|
|
13090
12957
|
runtimes: [{ name: "claude-code", version: null }],
|
|
13091
|
-
capabilities: {
|
|
12958
|
+
capabilities: { browser: true, userMcp: true }
|
|
13092
12959
|
};
|
|
13093
12960
|
function buildCompanionManifest(opts) {
|
|
13094
12961
|
const v = opts.versions ?? {};
|
|
@@ -13448,27 +13315,27 @@ function compareVersions(a, b) {
|
|
|
13448
13315
|
init_config();
|
|
13449
13316
|
import { mkdirSync as mkdirSync13, readFileSync as readFileSync13, renameSync as renameSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
13450
13317
|
import { dirname as dirname10, join as join25 } from "path";
|
|
13451
|
-
import { z as
|
|
13452
|
-
var resultSchema =
|
|
13453
|
-
kind:
|
|
13454
|
-
from:
|
|
13455
|
-
to:
|
|
13456
|
-
at:
|
|
13457
|
-
reason:
|
|
13318
|
+
import { z as z17 } from "zod";
|
|
13319
|
+
var resultSchema = z17.object({
|
|
13320
|
+
kind: z17.enum(["updated", "rolled_back", "failed"]),
|
|
13321
|
+
from: z17.string(),
|
|
13322
|
+
to: z17.string(),
|
|
13323
|
+
at: z17.string(),
|
|
13324
|
+
reason: z17.string().optional()
|
|
13458
13325
|
});
|
|
13459
|
-
var pendingSchema =
|
|
13460
|
-
from:
|
|
13326
|
+
var pendingSchema = z17.object({
|
|
13327
|
+
from: z17.string(),
|
|
13461
13328
|
// The package root `current` pointed at before the swap, so a rollback can point it back.
|
|
13462
|
-
fromRoot:
|
|
13463
|
-
to:
|
|
13464
|
-
at:
|
|
13465
|
-
boots:
|
|
13329
|
+
fromRoot: z17.string(),
|
|
13330
|
+
to: z17.string(),
|
|
13331
|
+
at: z17.string(),
|
|
13332
|
+
boots: z17.number().int().nonnegative()
|
|
13466
13333
|
});
|
|
13467
|
-
var stateSchema =
|
|
13468
|
-
server:
|
|
13334
|
+
var stateSchema = z17.object({
|
|
13335
|
+
server: z17.object({ latest: z17.string(), minimum: z17.string(), at: z17.string() }).optional(),
|
|
13469
13336
|
pending: pendingSchema.optional(),
|
|
13470
13337
|
lastResult: resultSchema.optional(),
|
|
13471
|
-
failed:
|
|
13338
|
+
failed: z17.object({ version: z17.string(), attempts: z17.number().int(), at: z17.string() }).optional()
|
|
13472
13339
|
});
|
|
13473
13340
|
function updateStatePath(dir2 = cabaneDir()) {
|
|
13474
13341
|
return join25(dir2, "update.json");
|
|
@@ -13984,6 +13851,7 @@ var CompanionSupervisor = class {
|
|
|
13984
13851
|
pollTimer = null;
|
|
13985
13852
|
refreshing = false;
|
|
13986
13853
|
stopped = false;
|
|
13854
|
+
reconciliationAbort = new AbortController();
|
|
13987
13855
|
// The version-skew warning is logged once, not every heartbeat.
|
|
13988
13856
|
versionSkewWarned = false;
|
|
13989
13857
|
// This device's id, captured from the heartbeat / assignments response. The SSE
|
|
@@ -14029,6 +13897,7 @@ var CompanionSupervisor = class {
|
|
|
14029
13897
|
// heartbeat + poll loops. A companion with no device token (logged out) does
|
|
14030
13898
|
// nothing but say so.
|
|
14031
13899
|
async start() {
|
|
13900
|
+
if (this.stopped) return;
|
|
14032
13901
|
this.log.info(
|
|
14033
13902
|
{ protocolVersion: TURN_PROTOCOL_VERSION, version: COMPANION_VERSION },
|
|
14034
13903
|
`Companion ${COMPANION_VERSION} starting on ${this.config.deviceLabel ?? hostname2()}`
|
|
@@ -14113,6 +13982,7 @@ var CompanionSupervisor = class {
|
|
|
14113
13982
|
});
|
|
14114
13983
|
this.updater.start();
|
|
14115
13984
|
await this.refreshAssignments();
|
|
13985
|
+
if (this.stopped) return;
|
|
14116
13986
|
this.deviceSub.start();
|
|
14117
13987
|
this.heartbeatTimer = setInterval(() => this.kickHeartbeat(), HEARTBEAT_INTERVAL_MS);
|
|
14118
13988
|
this.heartbeatTimer.unref?.();
|
|
@@ -14225,10 +14095,12 @@ var CompanionSupervisor = class {
|
|
|
14225
14095
|
let items;
|
|
14226
14096
|
let device;
|
|
14227
14097
|
try {
|
|
14228
|
-
const resp = await this.deviceApi.getAssignments();
|
|
14098
|
+
const resp = await this.deviceApi.getAssignments(this.reconciliationAbort.signal);
|
|
14099
|
+
if (this.stopped) return;
|
|
14229
14100
|
items = resp.assignments;
|
|
14230
14101
|
device = resp.device;
|
|
14231
14102
|
} catch (err) {
|
|
14103
|
+
if (this.stopped) return;
|
|
14232
14104
|
this.log.error(
|
|
14233
14105
|
{ ...apiErrorLogFields(err) },
|
|
14234
14106
|
"Couldn't load the assigned agents. Check that this device is active in Cabane; trying again shortly."
|
|
@@ -14247,6 +14119,7 @@ var CompanionSupervisor = class {
|
|
|
14247
14119
|
if (it.credential) setCredential(device.id, it.agentId, it.credential);
|
|
14248
14120
|
}
|
|
14249
14121
|
items = await this.restoreMissingCredentials(device.id, items);
|
|
14122
|
+
if (this.stopped) return;
|
|
14250
14123
|
this.uncredentialed = new Set(
|
|
14251
14124
|
items.filter((it) => !it.credential && !getCredential(device.id, it.agentId)).map((it) => `${it.workspaceId}|${it.agentId}`)
|
|
14252
14125
|
);
|
|
@@ -14259,6 +14132,7 @@ var CompanionSupervisor = class {
|
|
|
14259
14132
|
for (const wsId of [...this.workspaces.keys()]) {
|
|
14260
14133
|
if (!byWorkspace.has(wsId)) await this.removeWorkspace(wsId);
|
|
14261
14134
|
}
|
|
14135
|
+
if (this.stopped) return;
|
|
14262
14136
|
for (const [wsId, list] of byWorkspace) this.reconcileWorkspace(device.id, wsId, list);
|
|
14263
14137
|
const pruned = pruneCredentials(
|
|
14264
14138
|
device.id,
|
|
@@ -14290,6 +14164,7 @@ var CompanionSupervisor = class {
|
|
|
14290
14164
|
async restoreMissingCredentials(deviceId, items) {
|
|
14291
14165
|
const restored = [];
|
|
14292
14166
|
for (const it of items) {
|
|
14167
|
+
if (this.stopped) return restored;
|
|
14293
14168
|
if (it.credential || getCredential(deviceId, it.agentId)) {
|
|
14294
14169
|
restored.push(it);
|
|
14295
14170
|
continue;
|
|
@@ -14301,7 +14176,11 @@ var CompanionSupervisor = class {
|
|
|
14301
14176
|
continue;
|
|
14302
14177
|
}
|
|
14303
14178
|
try {
|
|
14304
|
-
const { credential } = await this.deviceApi.reissueAgentCredential(
|
|
14179
|
+
const { credential } = await this.deviceApi.reissueAgentCredential(
|
|
14180
|
+
it.agentId,
|
|
14181
|
+
this.reconciliationAbort.signal
|
|
14182
|
+
);
|
|
14183
|
+
if (this.stopped) return restored;
|
|
14305
14184
|
setCredential(deviceId, it.agentId, credential);
|
|
14306
14185
|
this.log.info(
|
|
14307
14186
|
{ agentId: it.agentId, agentName: it.agentDisplayName },
|
|
@@ -14365,7 +14244,6 @@ var CompanionSupervisor = class {
|
|
|
14365
14244
|
agentId: it.agentId,
|
|
14366
14245
|
username: it.agentUsername,
|
|
14367
14246
|
displayName: it.agentDisplayName,
|
|
14368
|
-
mode: runConfig.hostAccess ? "full" : "none",
|
|
14369
14247
|
hasCredential: false,
|
|
14370
14248
|
missingSecrets: missing
|
|
14371
14249
|
});
|
|
@@ -14385,7 +14263,6 @@ var CompanionSupervisor = class {
|
|
|
14385
14263
|
agentId: it.agentId,
|
|
14386
14264
|
username: it.agentUsername,
|
|
14387
14265
|
displayName: it.agentDisplayName,
|
|
14388
|
-
mode: runConfig.hostAccess ? "full" : "none",
|
|
14389
14266
|
hasCredential: true,
|
|
14390
14267
|
missingSecrets: missing
|
|
14391
14268
|
});
|
|
@@ -15178,6 +15055,7 @@ var CompanionSupervisor = class {
|
|
|
15178
15055
|
// ---- control actions ----
|
|
15179
15056
|
async shutdown() {
|
|
15180
15057
|
this.stopped = true;
|
|
15058
|
+
this.reconciliationAbort.abort();
|
|
15181
15059
|
this.updater?.stop();
|
|
15182
15060
|
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
|
|
15183
15061
|
if (this.detectTimer) clearInterval(this.detectTimer);
|
|
@@ -15455,33 +15333,46 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
15455
15333
|
});
|
|
15456
15334
|
let restartForUpdate = async () => {
|
|
15457
15335
|
};
|
|
15458
|
-
|
|
15459
|
-
config: cfg,
|
|
15460
|
-
log: log2,
|
|
15461
|
-
hub,
|
|
15462
|
-
...opts.checkHarness ? { checkHarness: opts.checkHarness } : {},
|
|
15463
|
-
...embeddedRunner({
|
|
15464
|
-
electron: Boolean(process.versions.electron),
|
|
15465
|
-
execPath: process.execPath,
|
|
15466
|
-
moduleUrl: import.meta.url,
|
|
15467
|
-
env: process.env
|
|
15468
|
-
}) ?? {},
|
|
15469
|
-
restartForUpdate: () => restartForUpdate()
|
|
15470
|
-
});
|
|
15471
|
-
await supervisor.start();
|
|
15336
|
+
let supervisor;
|
|
15472
15337
|
let control = null;
|
|
15473
15338
|
let dashboard = null;
|
|
15339
|
+
let stopped = false;
|
|
15340
|
+
const stop2 = async () => {
|
|
15341
|
+
if (stopped) return;
|
|
15342
|
+
stopped = true;
|
|
15343
|
+
clearRuntimeStateIfOurs(instanceId);
|
|
15344
|
+
process.removeListener("exit", clearRuntimeOnExit);
|
|
15345
|
+
await Promise.allSettled([
|
|
15346
|
+
...supervisor ? [supervisor.shutdown()] : [],
|
|
15347
|
+
...control ? [control.close()] : [],
|
|
15348
|
+
...dashboard ? [dashboard.close()] : []
|
|
15349
|
+
]);
|
|
15350
|
+
};
|
|
15474
15351
|
try {
|
|
15352
|
+
supervisor = new CompanionSupervisor({
|
|
15353
|
+
config: cfg,
|
|
15354
|
+
log: log2,
|
|
15355
|
+
hub,
|
|
15356
|
+
...opts.checkHarness ? { checkHarness: opts.checkHarness } : {},
|
|
15357
|
+
...embeddedRunner({
|
|
15358
|
+
electron: Boolean(process.versions.electron),
|
|
15359
|
+
execPath: process.execPath,
|
|
15360
|
+
moduleUrl: import.meta.url,
|
|
15361
|
+
env: process.env
|
|
15362
|
+
}) ?? {},
|
|
15363
|
+
restartForUpdate: () => restartForUpdate()
|
|
15364
|
+
});
|
|
15365
|
+
const active2 = supervisor;
|
|
15475
15366
|
control = await startControlServer({
|
|
15476
15367
|
status: () => ({
|
|
15477
15368
|
...hub.statusJson(),
|
|
15478
|
-
...
|
|
15479
|
-
detection:
|
|
15369
|
+
...active2.lastHeartbeat(),
|
|
15370
|
+
detection: active2.currentDetection()
|
|
15480
15371
|
}),
|
|
15481
|
-
detect: () =>
|
|
15482
|
-
allow: (runtime) =>
|
|
15483
|
-
update: (auto) =>
|
|
15484
|
-
stop: () => void
|
|
15372
|
+
detect: () => active2.redetect(),
|
|
15373
|
+
allow: (runtime) => active2.allowHarness(runtime),
|
|
15374
|
+
update: (auto) => active2.update(auto),
|
|
15375
|
+
stop: () => void stop2().then(() => active2.requestStop())
|
|
15485
15376
|
});
|
|
15486
15377
|
if (opts.dashboard) {
|
|
15487
15378
|
const preferredPort = opts.port ?? cfg.dashboardPort;
|
|
@@ -15492,6 +15383,7 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
15492
15383
|
});
|
|
15493
15384
|
hub.setDashboardUrl(dashboard.url);
|
|
15494
15385
|
}
|
|
15386
|
+
if (stopped) throw new Error("The companion was stopped during startup.");
|
|
15495
15387
|
writeRuntimeState({
|
|
15496
15388
|
pid: process.pid,
|
|
15497
15389
|
socket: control.path,
|
|
@@ -15509,7 +15401,7 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
15509
15401
|
clearRuntimeStateIfOurs(instanceId);
|
|
15510
15402
|
process.removeListener("exit", clearRuntimeOnExit);
|
|
15511
15403
|
await Promise.allSettled([
|
|
15512
|
-
supervisor.shutdown(),
|
|
15404
|
+
...supervisor ? [supervisor.shutdown()] : [],
|
|
15513
15405
|
...control ? [control.close()] : [],
|
|
15514
15406
|
...dashboard ? [dashboard.close()] : []
|
|
15515
15407
|
]);
|
|
@@ -15521,22 +15413,17 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
15521
15413
|
});
|
|
15522
15414
|
throw err;
|
|
15523
15415
|
}
|
|
15524
|
-
|
|
15525
|
-
const stop2 = async () => {
|
|
15526
|
-
if (stopped) return;
|
|
15527
|
-
stopped = true;
|
|
15528
|
-
clearRuntimeStateIfOurs(instanceId);
|
|
15529
|
-
process.removeListener("exit", clearRuntimeOnExit);
|
|
15530
|
-
try {
|
|
15531
|
-
await supervisor.shutdown();
|
|
15532
|
-
await closeSurfaces(control, dashboard);
|
|
15533
|
-
} catch {
|
|
15534
|
-
}
|
|
15535
|
-
};
|
|
15416
|
+
const active = supervisor;
|
|
15536
15417
|
restartForUpdate = async () => {
|
|
15537
15418
|
await stop2();
|
|
15538
15419
|
process.exit(0);
|
|
15539
15420
|
};
|
|
15421
|
+
void active.start().catch(async (err) => {
|
|
15422
|
+
log2.error({ err }, "Couldn't initialize the companion.");
|
|
15423
|
+
recordCrash({ reason: String(err), origin: "startup", at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
15424
|
+
await stop2();
|
|
15425
|
+
process.exitCode = 1;
|
|
15426
|
+
});
|
|
15540
15427
|
return {
|
|
15541
15428
|
ok: true,
|
|
15542
15429
|
runtime: {
|
|
@@ -15544,15 +15431,38 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
15544
15431
|
socketPath: control.path,
|
|
15545
15432
|
config: cfg,
|
|
15546
15433
|
stop: stop2,
|
|
15547
|
-
heartbeatNow: () =>
|
|
15434
|
+
heartbeatNow: () => active.heartbeatNow(),
|
|
15548
15435
|
harnesses: () => hub.statusJson().harnesses ?? [],
|
|
15549
|
-
detect: () =>
|
|
15436
|
+
detect: () => active.redetect()
|
|
15550
15437
|
}
|
|
15551
15438
|
};
|
|
15552
15439
|
}
|
|
15553
|
-
|
|
15554
|
-
|
|
15555
|
-
|
|
15440
|
+
|
|
15441
|
+
// src/start-observation.ts
|
|
15442
|
+
init_cabane_command();
|
|
15443
|
+
async function observeStart(deps = {}) {
|
|
15444
|
+
const read2 = deps.readState ?? readLiveRuntimeState;
|
|
15445
|
+
const verify = deps.verify ?? verifyRuntime;
|
|
15446
|
+
const now = deps.now ?? Date.now;
|
|
15447
|
+
const sleep3 = deps.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
|
|
15448
|
+
const deadline = now() + 8e3;
|
|
15449
|
+
for (; ; ) {
|
|
15450
|
+
const state = await read2();
|
|
15451
|
+
if (state?.socket && await verify(state) === "ours") return state;
|
|
15452
|
+
if (now() >= deadline) return null;
|
|
15453
|
+
await sleep3(Math.min(150, Math.max(0, deadline - now())));
|
|
15454
|
+
}
|
|
15455
|
+
}
|
|
15456
|
+
function markLaunchRequester() {
|
|
15457
|
+
process.title = "cabane-launcher";
|
|
15458
|
+
}
|
|
15459
|
+
function unconfirmedStartLines(service2) {
|
|
15460
|
+
return [
|
|
15461
|
+
"Cabane's start was requested, but readiness isn't confirmed yet. It may still finish starting in the background.",
|
|
15462
|
+
`Check: ${cabane("status")}`,
|
|
15463
|
+
...service2 ? [`Service: ${cabane("service status")}`] : [],
|
|
15464
|
+
`Log: ${cabane("logs")}`
|
|
15465
|
+
];
|
|
15556
15466
|
}
|
|
15557
15467
|
|
|
15558
15468
|
// src/commands/start.ts
|
|
@@ -15571,7 +15481,7 @@ import {
|
|
|
15571
15481
|
rmSync as rmSync12,
|
|
15572
15482
|
writeFileSync as writeFileSync10
|
|
15573
15483
|
} from "fs";
|
|
15574
|
-
import { homedir as
|
|
15484
|
+
import { homedir as homedir9, userInfo as userInfo3 } from "os";
|
|
15575
15485
|
import { dirname as dirname12, join as join29, sep as sep3 } from "path";
|
|
15576
15486
|
function servicePlatform(platform = process.platform) {
|
|
15577
15487
|
if (platform === "linux") return "systemd";
|
|
@@ -15589,7 +15499,7 @@ function launchdLabel(env = process.env) {
|
|
|
15589
15499
|
function serviceNode(execPath = process.execPath) {
|
|
15590
15500
|
return existsSync17(pinnedNodePath()) ? pinnedNodePath() : execPath;
|
|
15591
15501
|
}
|
|
15592
|
-
function servicePaths(platform, env = process.env, home =
|
|
15502
|
+
function servicePaths(platform, env = process.env, home = homedir9(), node = serviceNode()) {
|
|
15593
15503
|
const unitFile = platform === "systemd" ? join29(
|
|
15594
15504
|
env.XDG_CONFIG_HOME || join29(home, ".config"),
|
|
15595
15505
|
"systemd",
|
|
@@ -15831,7 +15741,7 @@ async function seedManagedInstall(deps) {
|
|
|
15831
15741
|
return { ok: true, root: target };
|
|
15832
15742
|
}
|
|
15833
15743
|
function tildeDir() {
|
|
15834
|
-
return cabaneDir().replace(
|
|
15744
|
+
return cabaneDir().replace(homedir9(), "~");
|
|
15835
15745
|
}
|
|
15836
15746
|
function unitNode(platform, unitFile) {
|
|
15837
15747
|
let text;
|
|
@@ -15961,15 +15871,11 @@ init_config();
|
|
|
15961
15871
|
import { spawn as spawn7 } from "child_process";
|
|
15962
15872
|
import { closeSync as closeSync3, mkdirSync as mkdirSync17, openSync as openSync4 } from "fs";
|
|
15963
15873
|
init_config();
|
|
15964
|
-
var STARTUP_TIMEOUT_MS = 8e3;
|
|
15965
|
-
var POLL_INTERVAL_MS = 150;
|
|
15966
15874
|
async function startDaemon(opts = {}, deps = {}) {
|
|
15967
15875
|
const ensureRuntime = deps.ensureRuntime ?? warnAboutHarnessReadiness;
|
|
15968
15876
|
const readState = deps.readState ?? readLiveRuntimeState;
|
|
15969
15877
|
const verify = deps.verify ?? ((s) => verifyRuntime(s));
|
|
15970
15878
|
const spawnDetached = deps.spawnDetached ?? defaultSpawnDetached;
|
|
15971
|
-
const sleep3 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
15972
|
-
const now = deps.now ?? (() => Date.now());
|
|
15973
15879
|
const quiet = opts.report === "failures" || opts.report === "silent";
|
|
15974
15880
|
const cfg = (deps.requireCfg ?? requireConfig)();
|
|
15975
15881
|
ensureRuntime(cfg, {
|
|
@@ -15981,7 +15887,18 @@ async function startDaemon(opts = {}, deps = {}) {
|
|
|
15981
15887
|
});
|
|
15982
15888
|
const existing = readState();
|
|
15983
15889
|
if (existing) {
|
|
15984
|
-
|
|
15890
|
+
const verdict = await verify(existing);
|
|
15891
|
+
if (verdict !== "stale") {
|
|
15892
|
+
if (verdict !== "ours") {
|
|
15893
|
+
const state2 = await observeStart({ ...deps, readState, verify });
|
|
15894
|
+
if (!state2) {
|
|
15895
|
+
if (opts.report !== "silent")
|
|
15896
|
+
process.stdout.write(unconfirmedStartLines(false).join("\n") + "\n");
|
|
15897
|
+
process.exitCode = 1;
|
|
15898
|
+
return { started: false, state: null, unconfirmed: true };
|
|
15899
|
+
}
|
|
15900
|
+
return { started: false, state: state2 };
|
|
15901
|
+
}
|
|
15985
15902
|
if (opts.report !== "silent")
|
|
15986
15903
|
process.stdout.write(alreadyRunningLines(existing.pid).join("\n") + "\n");
|
|
15987
15904
|
return { started: false, state: existing };
|
|
@@ -15999,28 +15916,21 @@ async function startDaemon(opts = {}, deps = {}) {
|
|
|
15999
15916
|
spawned.unref();
|
|
16000
15917
|
return spawned;
|
|
16001
15918
|
};
|
|
16002
|
-
|
|
16003
|
-
const waitForReady = async () => {
|
|
16004
|
-
const deadline = now() + STARTUP_TIMEOUT_MS;
|
|
16005
|
-
let seen = readState();
|
|
16006
|
-
while (!ready(seen) && now() < deadline) {
|
|
16007
|
-
await sleep3(POLL_INTERVAL_MS);
|
|
16008
|
-
seen = readState();
|
|
16009
|
-
}
|
|
16010
|
-
return ready(seen) ? seen : null;
|
|
16011
|
-
};
|
|
15919
|
+
markLaunchRequester();
|
|
16012
15920
|
const child = launchDetached();
|
|
16013
|
-
const state = await
|
|
15921
|
+
const state = await observeStart({ ...deps, readState, verify });
|
|
16014
15922
|
if (!state) {
|
|
16015
|
-
const error =
|
|
15923
|
+
const error = child.failure?.();
|
|
15924
|
+
if (error) {
|
|
15925
|
+
if (opts.report !== "silent") process.stdout.write(`Cabane couldn't start: ${error}
|
|
15926
|
+
`);
|
|
15927
|
+
process.exitCode = 1;
|
|
15928
|
+
return { started: false, state: null, error };
|
|
15929
|
+
}
|
|
16016
15930
|
if (opts.report !== "silent")
|
|
16017
|
-
process.stdout.write(
|
|
16018
|
-
`Cabane Companion was launched (pid ${child?.pid ?? "?"}) but didn't report ready within ${STARTUP_TIMEOUT_MS / 1e3}s.
|
|
16019
|
-
Check ${companionLogPath()} for errors, or run \`${cabane("status")}\`.
|
|
16020
|
-
`
|
|
16021
|
-
);
|
|
15931
|
+
process.stdout.write(unconfirmedStartLines(false).join("\n") + "\n");
|
|
16022
15932
|
process.exitCode = 1;
|
|
16023
|
-
return { started: false, state: null,
|
|
15933
|
+
return { started: false, state: null, unconfirmed: true };
|
|
16024
15934
|
}
|
|
16025
15935
|
if (quiet) return { started: true, state };
|
|
16026
15936
|
process.stdout.write(
|
|
@@ -16040,11 +15950,19 @@ function defaultSpawnDetached(args) {
|
|
|
16040
15950
|
mkdirSync17(cabaneDir(), { recursive: true });
|
|
16041
15951
|
const logFd = openSync4(companionLogPath(), "a");
|
|
16042
15952
|
try {
|
|
16043
|
-
|
|
15953
|
+
const child = spawn7(process.execPath, [cliPath, ...args], {
|
|
16044
15954
|
detached: true,
|
|
16045
15955
|
stdio: ["ignore", logFd, logFd],
|
|
16046
15956
|
env: { ...process.env, CABANE_COMPANION_DAEMON: "1" }
|
|
16047
15957
|
});
|
|
15958
|
+
let failure;
|
|
15959
|
+
child.once("error", (err) => {
|
|
15960
|
+
failure = err.message;
|
|
15961
|
+
});
|
|
15962
|
+
child.once("exit", (code, signal) => {
|
|
15963
|
+
failure = `The background process exited (${signal ?? code}). Check cabane logs.`;
|
|
15964
|
+
});
|
|
15965
|
+
return { pid: child.pid, unref: () => child.unref(), failure: () => failure };
|
|
16048
15966
|
} finally {
|
|
16049
15967
|
closeSync3(logFd);
|
|
16050
15968
|
}
|
|
@@ -16164,8 +16082,10 @@ async function start(opts = {}, deps = {}) {
|
|
|
16164
16082
|
try {
|
|
16165
16083
|
return await startScript(opts, deps);
|
|
16166
16084
|
} catch (err) {
|
|
16167
|
-
|
|
16168
|
-
|
|
16085
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
16086
|
+
if (process.env.CABANE_COMPANION_SUPERVISED === "1")
|
|
16087
|
+
getLogger().error({ err }, `Cabane couldn't start: ${reason}`);
|
|
16088
|
+
for (const line of startFailure(reason, reloadLine())) write(line);
|
|
16169
16089
|
process.exitCode = 1;
|
|
16170
16090
|
return null;
|
|
16171
16091
|
} finally {
|
|
@@ -16201,23 +16121,41 @@ async function startScript(opts, deps) {
|
|
|
16201
16121
|
mode = "attached";
|
|
16202
16122
|
}
|
|
16203
16123
|
} else if (installed) {
|
|
16204
|
-
|
|
16205
|
-
|
|
16206
|
-
|
|
16207
|
-
|
|
16208
|
-
|
|
16209
|
-
|
|
16124
|
+
markLaunchRequester();
|
|
16125
|
+
const before = await serviceState();
|
|
16126
|
+
if (!serviceLaunchPending(before)) {
|
|
16127
|
+
const result = await startService();
|
|
16128
|
+
if (!result.ok) {
|
|
16129
|
+
running = await observeStart({ ...deps, readState: deps.live });
|
|
16130
|
+
const current2 = await serviceState();
|
|
16131
|
+
if (!running && !serviceLaunchPending(current2)) throw new Error(result.lines.join(" "));
|
|
16132
|
+
}
|
|
16133
|
+
}
|
|
16134
|
+
running ??= await observeStart({ ...deps, readState: deps.live });
|
|
16135
|
+
if (!running) {
|
|
16136
|
+
const current2 = await serviceState();
|
|
16137
|
+
if (!current2.nodeOk || current2.state === "failed")
|
|
16138
|
+
throw new Error(
|
|
16139
|
+
!current2.nodeOk ? "The service Node executable is missing." : "The service failed. Check cabane logs."
|
|
16140
|
+
);
|
|
16141
|
+
return reportUnconfirmed(true);
|
|
16210
16142
|
}
|
|
16211
|
-
|
|
16212
|
-
mode = "service";
|
|
16143
|
+
const current = await serviceState();
|
|
16144
|
+
mode = current.state === "running" && current.pid === running.pid ? "service" : "existing";
|
|
16213
16145
|
} else {
|
|
16214
16146
|
const result = await startDaemon({ ...opts, report: "silent" });
|
|
16147
|
+
if (result.unconfirmed) return reportUnconfirmed(false);
|
|
16215
16148
|
if (!result.state)
|
|
16216
16149
|
throw new Error(result.error ?? "The background process did not report ready.");
|
|
16217
16150
|
running = result.state;
|
|
16218
16151
|
mode = result.started ? "background" : "existing";
|
|
16219
16152
|
}
|
|
16220
16153
|
}
|
|
16154
|
+
if (!runtime && mode === "existing") {
|
|
16155
|
+
running = await observeStart({ ...deps, readState: deps.live });
|
|
16156
|
+
if (!running) return reportUnconfirmed(installed);
|
|
16157
|
+
}
|
|
16158
|
+
const attached = runtime ? runAttached(runtime) : null;
|
|
16221
16159
|
const socket = runtime?.socketPath ?? running?.socket;
|
|
16222
16160
|
let status2 = await (deps.waitAck ?? waitForAcknowledgement)(socket);
|
|
16223
16161
|
let detection = status2.detection ?? null;
|
|
@@ -16231,6 +16169,26 @@ async function startScript(opts, deps) {
|
|
|
16231
16169
|
const latest = await readTerminalStatus(socket);
|
|
16232
16170
|
if (latest.lastHeartbeatAckAt || latest.lastHeartbeatRefused) status2 = latest;
|
|
16233
16171
|
}
|
|
16172
|
+
if (!runtime) {
|
|
16173
|
+
const current = await (deps.live ?? liveCompanionState)();
|
|
16174
|
+
if (!current || await (deps.verify ?? verifyRuntime)(current) !== "ours") {
|
|
16175
|
+
if (installed) {
|
|
16176
|
+
const service2 = await serviceState();
|
|
16177
|
+
if (!service2.pid && ["not loaded", "dead", "inactive", "failed"].includes(service2.state))
|
|
16178
|
+
throw new Error("The service stopped before startup completed.");
|
|
16179
|
+
}
|
|
16180
|
+
return reportUnconfirmed(installed);
|
|
16181
|
+
}
|
|
16182
|
+
if (current.pid !== running?.pid || current.instanceId !== running?.instanceId) {
|
|
16183
|
+
status2 = await readTerminalStatus(current.socket);
|
|
16184
|
+
detection = status2.detection ?? null;
|
|
16185
|
+
}
|
|
16186
|
+
running = current;
|
|
16187
|
+
if (mode === "service") {
|
|
16188
|
+
const service2 = await serviceState();
|
|
16189
|
+
if (service2.state !== "running" || service2.pid !== current.pid) mode = "existing";
|
|
16190
|
+
}
|
|
16191
|
+
}
|
|
16234
16192
|
const personalUrl = `${cfg.baseUrl.replace(/\/$/, "")}/settings/devices`;
|
|
16235
16193
|
const address = opts.pairing ? opts.pairing.devicesUrl : personalUrl;
|
|
16236
16194
|
if (!daemon)
|
|
@@ -16247,10 +16205,18 @@ async function startScript(opts, deps) {
|
|
|
16247
16205
|
write(line);
|
|
16248
16206
|
if (runtime) {
|
|
16249
16207
|
setConsoleLogging(true);
|
|
16250
|
-
await
|
|
16208
|
+
await attached;
|
|
16251
16209
|
}
|
|
16252
16210
|
return detection;
|
|
16253
16211
|
}
|
|
16212
|
+
function serviceLaunchPending(state) {
|
|
16213
|
+
return !!state.pid || ["running", "activating", "auto-restart"].includes(state.state) || state.platform === "launchd" && state.installed && !["not loaded", "unknown", "failed"].includes(state.state);
|
|
16214
|
+
}
|
|
16215
|
+
function reportUnconfirmed(service2) {
|
|
16216
|
+
for (const line of unconfirmedStartLines(service2)) write(line);
|
|
16217
|
+
process.exitCode = 1;
|
|
16218
|
+
return null;
|
|
16219
|
+
}
|
|
16254
16220
|
async function detectOverSocket(socket) {
|
|
16255
16221
|
const body = await controlRequest(socket, { cmd: "detect" }, 15e3);
|
|
16256
16222
|
return body.detection;
|
|
@@ -16610,7 +16576,7 @@ init_cabane_command();
|
|
|
16610
16576
|
init_install_kind();
|
|
16611
16577
|
init_cabane_command();
|
|
16612
16578
|
import { readdirSync as readdirSync5, realpathSync as realpathSync6, existsSync as existsSync18 } from "fs";
|
|
16613
|
-
import { homedir as
|
|
16579
|
+
import { homedir as homedir10 } from "os";
|
|
16614
16580
|
import { dirname as dirname13, join as join30 } from "path";
|
|
16615
16581
|
init_config();
|
|
16616
16582
|
init_secrets();
|
|
@@ -16624,7 +16590,7 @@ init_term();
|
|
|
16624
16590
|
|
|
16625
16591
|
// src/commands/stop.ts
|
|
16626
16592
|
var TERM_GRACE_MS = 6e3;
|
|
16627
|
-
var
|
|
16593
|
+
var POLL_INTERVAL_MS = 150;
|
|
16628
16594
|
async function stop(deps = {}) {
|
|
16629
16595
|
if ((deps.serviceInstalled ?? serviceInstalled)()) {
|
|
16630
16596
|
const outcome = await (deps.stopService ?? stopService)();
|
|
@@ -16670,7 +16636,7 @@ async function stop(deps = {}) {
|
|
|
16670
16636
|
process.stdout.write("companion: stopped.\n");
|
|
16671
16637
|
return;
|
|
16672
16638
|
}
|
|
16673
|
-
await sleep3(
|
|
16639
|
+
await sleep3(POLL_INTERVAL_MS);
|
|
16674
16640
|
}
|
|
16675
16641
|
process.stdout.write(
|
|
16676
16642
|
`companion: didn't exit within ${TERM_GRACE_MS / 1e3}s, sending SIGKILL.
|
|
@@ -16827,7 +16793,10 @@ function row2(label, value) {
|
|
|
16827
16793
|
async function status(deps = {}) {
|
|
16828
16794
|
const out = (line) => void process.stdout.write(`${line}
|
|
16829
16795
|
`);
|
|
16830
|
-
const
|
|
16796
|
+
const candidate = readLiveRuntimeState({ sweep: false });
|
|
16797
|
+
const verdict = candidate ? await verifyRuntime(candidate) : null;
|
|
16798
|
+
const running = verdict === "stale" ? null : candidate;
|
|
16799
|
+
const service2 = await serviceState();
|
|
16831
16800
|
for (const line of pathLines(reloadLine())) out(line);
|
|
16832
16801
|
const strays = strayStatusLines(running, deps.strays);
|
|
16833
16802
|
out(` ${versionLine()}`);
|
|
@@ -16849,7 +16818,7 @@ async function status(deps = {}) {
|
|
|
16849
16818
|
out(row2("Paired with", `${cfg.baseUrl}${cfg.deviceLabel ? ` as ${cfg.deviceLabel}` : ""}`));
|
|
16850
16819
|
let detection = null;
|
|
16851
16820
|
if (running) {
|
|
16852
|
-
const mode =
|
|
16821
|
+
const mode = verdict !== "ours" ? "identity unconfirmed" : runningMode(running, service2);
|
|
16853
16822
|
const uptime = formatUptime(running.startedAt);
|
|
16854
16823
|
out(row2("Running", `${mode} (pid ${running.pid}${uptime ? `, up ${uptime}` : ""})`));
|
|
16855
16824
|
detection = await runningDetection(running);
|
|
@@ -16867,7 +16836,6 @@ async function status(deps = {}) {
|
|
|
16867
16836
|
)
|
|
16868
16837
|
);
|
|
16869
16838
|
for (const line of strays) out(line);
|
|
16870
|
-
const service2 = await serviceState();
|
|
16871
16839
|
for (const line of serviceRows(service2)) out(line);
|
|
16872
16840
|
for (const line of updateRows(readUpdateState(), cfg.update?.auto, {
|
|
16873
16841
|
version: false,
|
|
@@ -16895,7 +16863,7 @@ function versionLine() {
|
|
|
16895
16863
|
const install = companionInstall();
|
|
16896
16864
|
const own = installedCommandPath({ install });
|
|
16897
16865
|
if (install === "script") {
|
|
16898
|
-
return `Cabane ${COMPANION_VERSION} \xB7 installed by the install script (${tildePath(join30(
|
|
16866
|
+
return `Cabane ${COMPANION_VERSION} \xB7 installed by the install script (${tildePath(join30(homedir10(), ".cabane"))})`;
|
|
16899
16867
|
}
|
|
16900
16868
|
if (own)
|
|
16901
16869
|
return `Cabane ${COMPANION_VERSION} \xB7 installed with npm (${tildePath(dirname13(dirname13(own)))})`;
|
|
@@ -16922,7 +16890,7 @@ function otherInstall(install, own) {
|
|
|
16922
16890
|
};
|
|
16923
16891
|
if (install === "npm") {
|
|
16924
16892
|
const script = join30(
|
|
16925
|
-
|
|
16893
|
+
homedir10(),
|
|
16926
16894
|
".cabane",
|
|
16927
16895
|
"node",
|
|
16928
16896
|
"lib",
|
|
@@ -16930,17 +16898,20 @@ function otherInstall(install, own) {
|
|
|
16930
16898
|
"@cabane",
|
|
16931
16899
|
"companion"
|
|
16932
16900
|
);
|
|
16933
|
-
return existsSync18(script) ? join30(
|
|
16901
|
+
return existsSync18(script) ? join30(homedir10(), ".cabane") : null;
|
|
16934
16902
|
}
|
|
16935
16903
|
const found = firstOnPath("cabane-companion", process.env.PATH);
|
|
16936
16904
|
if (!found) return null;
|
|
16937
16905
|
const ownAlias = own ? real(join30(dirname13(own), "cabane-companion")) : null;
|
|
16938
16906
|
return real(found) === ownAlias ? null : dirname13(found);
|
|
16939
16907
|
}
|
|
16940
|
-
|
|
16941
|
-
|
|
16942
|
-
|
|
16943
|
-
|
|
16908
|
+
function runningMode(running, service2) {
|
|
16909
|
+
if (service2.installed) {
|
|
16910
|
+
if (service2.state === "running" && service2.pid === running.pid) return "as a service";
|
|
16911
|
+
if (service2.state === "unknown" || service2.pid === running.pid || service2.state === "running" && !service2.pid)
|
|
16912
|
+
return "run mode unconfirmed";
|
|
16913
|
+
}
|
|
16914
|
+
return running.daemon ? "in the background" : "in a terminal";
|
|
16944
16915
|
}
|
|
16945
16916
|
async function runningDetection(running) {
|
|
16946
16917
|
if (!running.socket) return null;
|