@tablation/crew 0.33.0 → 0.34.0
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/crew.example.yaml +2 -2
- package/dist/cli.js +149 -5
- package/package.json +1 -1
package/crew.example.yaml
CHANGED
|
@@ -29,8 +29,7 @@ ship:
|
|
|
29
29
|
|
|
30
30
|
# Optional (CREW-1511): named toolchains — a harness adapter plus the binary
|
|
31
31
|
# and model to run it with. The `agent:` block above keeps working and
|
|
32
|
-
# defines an implicit toolchain named `claude`.
|
|
33
|
-
# exists today. See docs/toolchains.md.
|
|
32
|
+
# defines an implicit toolchain named `claude`. Harnesses: claude, gemini. See docs/toolchains.md.
|
|
34
33
|
# toolchain: claude # the ship's default; a route may override it
|
|
35
34
|
# toolchains:
|
|
36
35
|
# claude-wrapped:
|
|
@@ -42,6 +41,7 @@ ship:
|
|
|
42
41
|
# deep: { model: claude-opus-5-5 }
|
|
43
42
|
# args: [] # appended to the harness argv
|
|
44
43
|
# env: {} # added to the session environment
|
|
44
|
+
# gemini: { harness: gemini, bin: gemini, model: gemini-2.5-pro }
|
|
45
45
|
|
|
46
46
|
# shell: bash # defaults per platform
|
|
47
47
|
extraPath: "/opt/homebrew/bin" # prepended for hooks
|
package/dist/cli.js
CHANGED
|
@@ -616,6 +616,28 @@ function geminiMcpState(s, url) {
|
|
|
616
616
|
if (e.headers?.Authorization !== `Bearer $${PAIR_KEY_VAR}`) return { state: "different", detail: `it does not read its key from $${PAIR_KEY_VAR}` };
|
|
617
617
|
return { state: "registered" };
|
|
618
618
|
}
|
|
619
|
+
function inspectGeminiMcp(o) {
|
|
620
|
+
const p = geminiPaths(o.home ?? homedir2());
|
|
621
|
+
const settings = readGeminiSettings(p.config);
|
|
622
|
+
if (!settings) return { state: "different", detail: `${p.config} is not a JSON object` };
|
|
623
|
+
const st = geminiMcpState(settings, o.url);
|
|
624
|
+
return st.state === "registered" ? { state: "registered", url: o.url } : st;
|
|
625
|
+
}
|
|
626
|
+
function ensureGeminiMcp(o) {
|
|
627
|
+
const p = geminiPaths(o.home ?? homedir2());
|
|
628
|
+
const settings = readGeminiSettings(p.config);
|
|
629
|
+
if (!settings) return `gemini: ${p.config} is not a JSON object \u2014 fix it by hand; nothing was written`;
|
|
630
|
+
const st = geminiMcpState(settings, o.url);
|
|
631
|
+
if (st.state === "registered") return `gemini: ${SERVER} MCP server already registered in ${p.config}`;
|
|
632
|
+
if (st.state === "different" && !o.replace) {
|
|
633
|
+
return `gemini: left ${p.config} alone \u2014 a "${SERVER}" server exists and ${st.detail}. Re-run with --mcp replace to overwrite it.`;
|
|
634
|
+
}
|
|
635
|
+
if (!o.dryRun) {
|
|
636
|
+
writeFileMkdir(p.config, `${JSON.stringify({ ...settings, mcpServers: { ...settings.mcpServers, [SERVER]: geminiEntry(o.url) } }, null, 2)}
|
|
637
|
+
`);
|
|
638
|
+
}
|
|
639
|
+
return `gemini: ${o.dryRun ? "(dry run) would register" : "registered"} ${SERVER} in ${p.config} (key read from $${PAIR_KEY_VAR})`;
|
|
640
|
+
}
|
|
619
641
|
var geminiPair = {
|
|
620
642
|
harness: "gemini",
|
|
621
643
|
displayName: "Gemini CLI",
|
|
@@ -828,9 +850,120 @@ function harnessPromptNote(harness) {
|
|
|
828
850
|
|
|
829
851
|
// src/toolchain.ts
|
|
830
852
|
import { basename } from "path";
|
|
853
|
+
|
|
854
|
+
// src/adapters/gemini.ts
|
|
855
|
+
var BILLING_VARS_TO_UNSET2 = [
|
|
856
|
+
"GEMINI_API_KEY",
|
|
857
|
+
"GOOGLE_API_KEY",
|
|
858
|
+
"GOOGLE_GENAI_USE_VERTEXAI",
|
|
859
|
+
"GOOGLE_GENAI_USE_GCA",
|
|
860
|
+
"GOOGLE_CLOUD_PROJECT",
|
|
861
|
+
"GOOGLE_CLOUD_PROJECT_ID",
|
|
862
|
+
"GOOGLE_CLOUD_LOCATION",
|
|
863
|
+
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
864
|
+
"GOOGLE_GEMINI_BASE_URL",
|
|
865
|
+
"GEMINI_MODEL"
|
|
866
|
+
];
|
|
867
|
+
var READ_TOOLS = ["read_file", "read_many_files", "list_directory", "glob", "search_file_content"];
|
|
868
|
+
function geminiGrantFor(p) {
|
|
869
|
+
if (p.level === "read") return { approvalMode: "default", allowed: [...READ_TOOLS, "run_shell_command"] };
|
|
870
|
+
if (p.level === "write") return { approvalMode: "default", allowed: [...READ_TOOLS, "run_shell_command", "write_file"] };
|
|
871
|
+
return { approvalMode: "yolo", allowed: [] };
|
|
872
|
+
}
|
|
873
|
+
function geminiGrantArgs(role) {
|
|
874
|
+
const g = geminiGrantFor(profileFor(role));
|
|
875
|
+
return ["--approval-mode", g.approvalMode, ...g.allowed.length ? ["--allowed-tools", g.allowed.join(",")] : []];
|
|
876
|
+
}
|
|
877
|
+
function mapGeminiLine(line) {
|
|
878
|
+
const obj = line;
|
|
879
|
+
if (!obj || typeof obj !== "object") return [];
|
|
880
|
+
if (obj.type === "message" && obj.role === "assistant" && obj.content) return [{ kind: "text", text: obj.content }];
|
|
881
|
+
if (obj.type === "tool_use") return [{ kind: "tool", tool: obj.tool_name, target: toolTarget(obj.tool_name === "run_shell_command" ? "Bash" : obj.tool_name, obj.parameters) }];
|
|
882
|
+
return [];
|
|
883
|
+
}
|
|
884
|
+
function createGeminiStreamMapper() {
|
|
885
|
+
let pending = "";
|
|
886
|
+
const flush = () => {
|
|
887
|
+
const text = pending;
|
|
888
|
+
pending = "";
|
|
889
|
+
return text ? [{ kind: "text", text }] : [];
|
|
890
|
+
};
|
|
891
|
+
return {
|
|
892
|
+
flush,
|
|
893
|
+
map(line) {
|
|
894
|
+
const obj = line;
|
|
895
|
+
if (obj && typeof obj === "object" && obj.type === "message" && obj.role === "assistant") {
|
|
896
|
+
if (obj.content) pending += obj.content;
|
|
897
|
+
return [];
|
|
898
|
+
}
|
|
899
|
+
return [...flush(), ...mapGeminiLine(line)];
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
function extractGeminiResult(line) {
|
|
904
|
+
const obj = line;
|
|
905
|
+
if (obj?.type === "init") return obj.session_id ? { sessionId: obj.session_id } : void 0;
|
|
906
|
+
if (!obj || obj.type !== "result") return void 0;
|
|
907
|
+
const s = obj.stats;
|
|
908
|
+
return {
|
|
909
|
+
inputTokens: s?.input_tokens,
|
|
910
|
+
outputTokens: s?.output_tokens,
|
|
911
|
+
cacheReadTokens: s?.cached
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
var geminiAdapter = {
|
|
915
|
+
harness: "gemini",
|
|
916
|
+
displayName: "Gemini CLI",
|
|
917
|
+
vendor: "google",
|
|
918
|
+
ownsModel: (model) => model.startsWith("gemini-"),
|
|
919
|
+
// Gemini CLI has no per-run reasoning knob, so a tier's `reasoning` is ignored.
|
|
920
|
+
defaultBin: "gemini",
|
|
921
|
+
defaultModel: "gemini-2.5-pro",
|
|
922
|
+
defaultTiers: {
|
|
923
|
+
light: { model: "gemini-2.5-flash" },
|
|
924
|
+
standard: { model: "gemini-2.5-pro" },
|
|
925
|
+
deep: { model: "gemini-2.5-pro" }
|
|
926
|
+
},
|
|
927
|
+
limitations: [
|
|
928
|
+
"the tool grant is an allow list of tools that skip confirmation, not a sandbox: run_shell_command is allowed in every profile and sub-commands cannot be restricted",
|
|
929
|
+
"there is no deny list: destructive shell patterns (rm -rf, forced pushes, curl | sh) are not refused at the tool layer, unlike the Claude adapter",
|
|
930
|
+
"the edit profile runs in yolo mode (auto-approves every tool); the write profile (QA) withholds replace, not shell writes",
|
|
931
|
+
"design tools (skills, design canvas) are unavailable on this harness",
|
|
932
|
+
"the tracker MCP server is read from ~/.gemini/settings.json (there is no per-run --mcp-config), and MCP tool calls in the read and write profiles are not on the allow list",
|
|
933
|
+
"the stream carries no thinking blocks and streams assistant text in fragments, so the runner coalesces fragments into one Agent Log cycle per assistant turn; no turn count or cost is reported"
|
|
934
|
+
],
|
|
935
|
+
invocation(spec, o) {
|
|
936
|
+
const args = [
|
|
937
|
+
// The prompt arrives on stdin; `-p` puts the CLI in non-interactive mode.
|
|
938
|
+
"-p",
|
|
939
|
+
"",
|
|
940
|
+
"--model",
|
|
941
|
+
o.model,
|
|
942
|
+
"--output-format",
|
|
943
|
+
"stream-json",
|
|
944
|
+
...geminiGrantArgs(o.role),
|
|
945
|
+
...spec.args
|
|
946
|
+
];
|
|
947
|
+
return spec.launcher ? { bin: spec.launcherBin ?? spec.launcher, args: [spec.bin, ...args] } : { bin: spec.bin, args };
|
|
948
|
+
},
|
|
949
|
+
runEnv: (spec) => ({ ...spec.env }),
|
|
950
|
+
provider: () => "google",
|
|
951
|
+
unsetEnv: BILLING_VARS_TO_UNSET2,
|
|
952
|
+
mapStreamLine: mapGeminiLine,
|
|
953
|
+
createStreamMapper: createGeminiStreamMapper,
|
|
954
|
+
extractResult: extractGeminiResult,
|
|
955
|
+
extractTurnTokens: () => void 0,
|
|
956
|
+
inspectMcp(o) {
|
|
957
|
+
return inspectGeminiMcp(o);
|
|
958
|
+
},
|
|
959
|
+
ensureMcp: (o) => ensureGeminiMcp(o)
|
|
960
|
+
};
|
|
961
|
+
|
|
962
|
+
// src/toolchain.ts
|
|
831
963
|
var TIERS = ["light", "standard", "deep"];
|
|
832
964
|
var ADAPTERS = {
|
|
833
|
-
[claudeAdapter.harness]: claudeAdapter
|
|
965
|
+
[claudeAdapter.harness]: claudeAdapter,
|
|
966
|
+
[geminiAdapter.harness]: geminiAdapter
|
|
834
967
|
};
|
|
835
968
|
var HARNESS_NAMES = Object.keys(ADAPTERS);
|
|
836
969
|
function isHarness(name) {
|
|
@@ -6847,6 +6980,7 @@ function spawnAgent(plan, emit) {
|
|
|
6847
6980
|
let result;
|
|
6848
6981
|
const cycles = [];
|
|
6849
6982
|
const attribution = new TicketAttributionTracker();
|
|
6983
|
+
const mapper = adapter.createStreamMapper?.() ?? { map: (p) => adapter.mapStreamLine(p), flush: () => [] };
|
|
6850
6984
|
const handleLine = (line) => {
|
|
6851
6985
|
if (!line) return;
|
|
6852
6986
|
try {
|
|
@@ -6861,9 +6995,16 @@ function spawnAgent(plan, emit) {
|
|
|
6861
6995
|
return;
|
|
6862
6996
|
}
|
|
6863
6997
|
try {
|
|
6864
|
-
|
|
6998
|
+
const extracted = adapter.extractResult(parsed);
|
|
6999
|
+
if (extracted) result = { ...result, ...extracted };
|
|
7000
|
+
consume(parsed, mapper.map(parsed));
|
|
7001
|
+
} catch {
|
|
7002
|
+
}
|
|
7003
|
+
};
|
|
7004
|
+
const consume = (parsed, events) => {
|
|
7005
|
+
{
|
|
6865
7006
|
let turnText = "";
|
|
6866
|
-
for (const ev of
|
|
7007
|
+
for (const ev of events) {
|
|
6867
7008
|
const e = {
|
|
6868
7009
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6869
7010
|
cycle: emit.cycle,
|
|
@@ -6901,9 +7042,8 @@ function spawnAgent(plan, emit) {
|
|
|
6901
7042
|
`);
|
|
6902
7043
|
}
|
|
6903
7044
|
}
|
|
6904
|
-
const turnTokens = adapter.extractTurnTokens(parsed);
|
|
7045
|
+
const turnTokens = parsed === void 0 ? void 0 : adapter.extractTurnTokens(parsed);
|
|
6905
7046
|
if (turnTokens !== void 0) attribution.add(turnText, turnTokens);
|
|
6906
|
-
} catch {
|
|
6907
7047
|
}
|
|
6908
7048
|
};
|
|
6909
7049
|
child.stdout.on("data", (chunk) => {
|
|
@@ -6919,6 +7059,10 @@ function spawnAgent(plan, emit) {
|
|
|
6919
7059
|
});
|
|
6920
7060
|
child.on("close", async (code) => {
|
|
6921
7061
|
if (buf) handleLine(buf);
|
|
7062
|
+
try {
|
|
7063
|
+
consume(void 0, mapper.flush());
|
|
7064
|
+
} catch {
|
|
7065
|
+
}
|
|
6922
7066
|
await Promise.all([endSink(rawSink), endSink(eventsSink)]);
|
|
6923
7067
|
if (spawnFailed) return;
|
|
6924
7068
|
const attributedTicket = attribution.winner(plan.ticket);
|
package/package.json
CHANGED