@eir-labs/coltrane 0.24.30 → 0.24.32
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/agents/change-verifier.json +4 -2
- package/agents/deploy-scout.json +2 -2
- package/agents/red-law-reviewer.json +54 -0
- package/agents/red-spec-attester.json +53 -0
- package/agents/red-spec-builder.json +56 -0
- package/agents/red-spec-drafter.json +1 -1
- package/agents/spec-reviewer.json +1 -1
- package/dist/src/chart.js +4 -0
- package/dist/src/chart.js.map +1 -1
- package/dist/src/chat_completions_port.d.ts +41 -0
- package/dist/src/chat_completions_port.js +221 -0
- package/dist/src/chat_completions_port.js.map +1 -0
- package/dist/src/claude_invoker.d.ts +18 -0
- package/dist/src/claude_invoker.js +771 -22
- package/dist/src/claude_invoker.js.map +1 -1
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.js +49 -8
- package/dist/src/cli.js.map +1 -1
- package/dist/src/completions_invoker.d.ts +79 -0
- package/dist/src/completions_invoker.js +220 -0
- package/dist/src/completions_invoker.js.map +1 -0
- package/dist/src/composition.d.ts +13 -0
- package/dist/src/composition.js +16 -0
- package/dist/src/composition.js.map +1 -1
- package/dist/src/genome_schema.d.ts +200 -0
- package/dist/src/genome_schema.js +37 -0
- package/dist/src/genome_schema.js.map +1 -1
- package/dist/src/genome_store.d.ts +30 -2
- package/dist/src/genome_store.js +51 -8
- package/dist/src/genome_store.js.map +1 -1
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.js +9 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/invoker_selection.d.ts +58 -0
- package/dist/src/invoker_selection.js +110 -0
- package/dist/src/invoker_selection.js.map +1 -0
- package/dist/src/ledger.d.ts +45 -2
- package/dist/src/ledger.js +23 -3
- package/dist/src/ledger.js.map +1 -1
- package/dist/src/mcp.js +2 -2
- package/dist/src/mcp.js.map +1 -1
- package/dist/src/outputs.d.ts +24 -0
- package/dist/src/outputs.js +1 -0
- package/dist/src/outputs.js.map +1 -1
- package/dist/src/registry.js +16 -0
- package/dist/src/registry.js.map +1 -1
- package/dist/src/repo_index.d.ts +13 -0
- package/dist/src/repo_index.js +90 -10
- package/dist/src/repo_index.js.map +1 -1
- package/dist/src/reside_backing.d.ts +3 -2
- package/dist/src/reside_backing.js +8 -2
- package/dist/src/reside_backing.js.map +1 -1
- package/dist/src/reuse.d.ts +8 -0
- package/dist/src/reuse.js.map +1 -1
- package/dist/src/run_deps.d.ts +10 -10
- package/dist/src/run_deps.js +25 -15
- package/dist/src/run_deps.js.map +1 -1
- package/dist/src/runtime.d.ts +205 -109
- package/dist/src/runtime.js +908 -145
- package/dist/src/runtime.js.map +1 -1
- package/dist/src/server.js +148 -45
- package/dist/src/server.js.map +1 -1
- package/dist/src/server_relay.js +28 -0
- package/dist/src/server_relay.js.map +1 -1
- package/dist/src/skill_runner.mjs +9 -2
- package/dist/src/skill_subprocess.d.ts +20 -1
- package/dist/src/skill_subprocess.js +11 -4
- package/dist/src/skill_subprocess.js.map +1 -1
- package/dist/src/transcript_store.d.ts +14 -0
- package/dist/src/transcript_store.js +39 -0
- package/dist/src/transcript_store.js.map +1 -0
- package/dist/src/turn_loop.d.ts +173 -0
- package/dist/src/turn_loop.js +283 -0
- package/dist/src/turn_loop.js.map +1 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/worker.js +6 -0
- package/dist/src/worker.js.map +1 -1
- package/dist/src/worker_env.d.ts +4 -2
- package/dist/src/worker_env.js +42 -0
- package/dist/src/worker_env.js.map +1 -1
- package/domain_types/change-set.json +33 -22
- package/domain_types/red-spec.json +32 -23
- package/domain_types/seat-primer.json +65 -0
- package/package.json +1 -1
- package/standards/build-from-red-spec-v0.json +93 -0
- package/standards/draft-red-laws-v0.json +68 -0
package/dist/src/server.js
CHANGED
|
@@ -14,7 +14,7 @@ import { RUNNING_GIGS_METHOD, ABORT_FOR_RESTART_METHOD } from "./server_relay.js
|
|
|
14
14
|
import { MCP_TOOLS, requiresApproval, AGENT_STATUS_ORDER, STANDARD_STATUS_ORDER, SKILL_STATUS_ORDER, checkPromotion, PromotionError, } from "./mcp.js";
|
|
15
15
|
import { loadRegistry, domainTypeDefect } from "./registry.js";
|
|
16
16
|
import { resolveGenome } from "./loader.js";
|
|
17
|
-
import { SkillSchema, AgentObjectSchema, StandardSchema, DomainTypeSchema, ChartSchema, VenueSchema, VenueObjectSchema, venueDefect } from "./genome_schema.js";
|
|
17
|
+
import { SkillSchema, AgentObjectSchema, StandardSchema, DomainTypeSchema, ChartSchema, VenueSchema, VenueObjectSchema, venueDefect, EffortSchema } from "./genome_schema.js";
|
|
18
18
|
import { composeChart, runChart, chartHash, chartEntrySeedTypes, dispatchTarget, } from "./chart.js";
|
|
19
19
|
import { runSkillFixtures, executeSkill, loadFixtures } from "./skill_subprocess.js";
|
|
20
20
|
import { evolveSkill } from "./skills.js";
|
|
@@ -27,11 +27,12 @@ import { standardSimulate } from "./simulate.js";
|
|
|
27
27
|
import { runGig, BudgetExhausted, GigAborted, ResumeRefused, partialGigUsage, partialBudgetState } from "./runtime.js";
|
|
28
28
|
import { assembleRunDeps, resolveWorkingRepo } from "./run_deps.js";
|
|
29
29
|
import { createCheckpointStore, createReuseStore } from "./reuse.js";
|
|
30
|
-
import {
|
|
30
|
+
import { killLiveChairChildren } from "./claude_invoker.js";
|
|
31
|
+
import { selectChairInvoker } from "./invoker_selection.js";
|
|
31
32
|
import { dockerComposeRealizer } from "./venue_realizer.js";
|
|
32
33
|
import { institutionPlacementResolver } from "./placement_institutions.js";
|
|
33
34
|
import { isDepth, DEPTHS } from "./pricing.js";
|
|
34
|
-
import { ENGINE_MCP_SERVER } from "./tool_providers.js";
|
|
35
|
+
import { ENGINE_MCP_SERVER, isHostBuiltin, toolBaseName } from "./tool_providers.js";
|
|
35
36
|
import { gigScopeRefusal, missingWorkerEnv, } from "./venue_credential.js";
|
|
36
37
|
import { composeStandard, defineAgent, CompositionError } from "./composition.js";
|
|
37
38
|
import { PRIMITIVE_OUTPUT_TYPE } from "./core_types.js";
|
|
@@ -134,6 +135,21 @@ function readDepth(v) {
|
|
|
134
135
|
return { error: `unknown depth "${String(v)}" — expected one of: ${DEPTHS.join(", ")}` };
|
|
135
136
|
return { depth: v };
|
|
136
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* #seat-effort (F2) — read an optional `effort` argument, mirroring `readDepth`. Absent/empty → no
|
|
140
|
+
* dispatch effort (the agent's own `effort` or its tier default stands). Present but not one of the
|
|
141
|
+
* five levels → an ERROR that names the field and the value, refused BEFORE anything runs: an
|
|
142
|
+
* unlisted effort must never quietly run at a guessed level. Validated against the one Zod source
|
|
143
|
+
* (EffortSchema), so the door and the genome load refuse the same set.
|
|
144
|
+
*/
|
|
145
|
+
function readEffort(v) {
|
|
146
|
+
if (v === undefined || v === null || v === "")
|
|
147
|
+
return {};
|
|
148
|
+
const parsed = EffortSchema.safeParse(v);
|
|
149
|
+
if (!parsed.success)
|
|
150
|
+
return { error: `unknown effort "${String(v)}" — expected one of: ${EffortSchema.options.join(", ")}` };
|
|
151
|
+
return { effort: parsed.data };
|
|
152
|
+
}
|
|
137
153
|
/**
|
|
138
154
|
* Normalize the user-passed `schema` for type_register (Rob #131).
|
|
139
155
|
*
|
|
@@ -763,16 +779,28 @@ async function runImpl(slug, args, deps, approval) {
|
|
|
763
779
|
};
|
|
764
780
|
}
|
|
765
781
|
}
|
|
766
|
-
// Optional budget arg —
|
|
767
|
-
//
|
|
768
|
-
|
|
782
|
+
// Optional budget arg — a per-gig ceiling in US DOLLARS, { max_usd }. Validated HERE, before
|
|
783
|
+
// anything runs (F1/F2): a malformed budget, or one carrying a retired append-unit field, is
|
|
784
|
+
// REFUSED naming the offending field/value — never silently run with no ceiling.
|
|
785
|
+
const budgetRaw = args["budget"];
|
|
769
786
|
let budget;
|
|
770
|
-
if (
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
787
|
+
if (budgetRaw !== undefined) {
|
|
788
|
+
if (typeof budgetRaw !== "object" || budgetRaw === null || Array.isArray(budgetRaw)) {
|
|
789
|
+
return { ok: false, requires_approval: approval, error: `gig_dispatch: budget must be an object naming max_usd in USD; got ${String(budgetRaw)} — set { max_usd: <dollars> }` };
|
|
790
|
+
}
|
|
791
|
+
const b = budgetRaw;
|
|
792
|
+
const retired = ["opening", "base_cost", "k", "pool"].filter((f) => f in b);
|
|
793
|
+
if (retired.length > 0) {
|
|
794
|
+
return { ok: false, requires_approval: approval, error: `gig_dispatch: budget carries the retired append-unit field(s) ${retired.map((f) => `"${f}"`).join(", ")} — budgets are now { max_usd } in US dollars; pass max_usd instead` };
|
|
795
|
+
}
|
|
796
|
+
const mu = b["max_usd"];
|
|
797
|
+
if (typeof mu !== "number") {
|
|
798
|
+
return { ok: false, requires_approval: approval, error: `gig_dispatch: budget.max_usd is required and must be a number of USD; got ${mu === undefined ? "undefined" : String(mu)}` };
|
|
799
|
+
}
|
|
800
|
+
if (!Number.isFinite(mu) || mu <= 0) {
|
|
801
|
+
return { ok: false, requires_approval: approval, error: `gig_dispatch: budget.max_usd must be a positive finite number of USD; got ${String(mu)}` };
|
|
802
|
+
}
|
|
803
|
+
budget = { max_usd: mu };
|
|
776
804
|
}
|
|
777
805
|
const gigInput = args["input"] ?? {};
|
|
778
806
|
// #237 — `depth` was advertised here and never read. Every dispatch ran at full depth,
|
|
@@ -781,6 +809,25 @@ async function runImpl(slug, args, deps, approval) {
|
|
|
781
809
|
if (depthArg.error)
|
|
782
810
|
return { ok: false, requires_approval: approval, error: depthArg.error };
|
|
783
811
|
const depth = depthArg.depth;
|
|
812
|
+
// #seat-effort (O1/F2) — `effort` is advertised on gig_dispatch (src/mcp.ts) and read here.
|
|
813
|
+
// An out-of-range value is refused BEFORE the chart/standard path spawns anything (F2); a
|
|
814
|
+
// valid one threads into runGig beside `depth` so the resolver (resolveEffort) sees it.
|
|
815
|
+
const effortArg = readEffort(args["effort"]);
|
|
816
|
+
if (effortArg.error)
|
|
817
|
+
return { ok: false, requires_approval: approval, error: effortArg.error };
|
|
818
|
+
const effort = effortArg.effort;
|
|
819
|
+
// contract-seat-context-ceiling-v1 (O1/F1) — `max_context_tokens` is advertised on gig_dispatch
|
|
820
|
+
// (src/mcp.ts) and read here. A non-positive-integer is refused BEFORE anything spawns, naming
|
|
821
|
+
// the field and the offending value; a valid one threads into runGig beside `effort` so the
|
|
822
|
+
// resolver (resolveMaxContextTokens) sees it. Absent ⇒ no dispatch ceiling.
|
|
823
|
+
const rawCap = args["max_context_tokens"];
|
|
824
|
+
let max_context_tokens;
|
|
825
|
+
if (rawCap !== undefined && rawCap !== null) {
|
|
826
|
+
if (typeof rawCap !== "number" || !Number.isInteger(rawCap) || rawCap <= 0) {
|
|
827
|
+
return { ok: false, requires_approval: approval, error: `gig_dispatch: max_context_tokens must be a positive integer; got ${String(rawCap)}` };
|
|
828
|
+
}
|
|
829
|
+
max_context_tokens = rawCap;
|
|
830
|
+
}
|
|
784
831
|
// ── reuse a sealed output instead of re-deriving it ──────────────────────────────
|
|
785
832
|
// Both halves are opt-in, and both are named on the dispatch call so the decision is
|
|
786
833
|
// recorded where the run is requested rather than inferred from server configuration.
|
|
@@ -879,7 +926,12 @@ async function runImpl(slug, args, deps, approval) {
|
|
|
879
926
|
// onto each chair's spawn. Absent = the substrate is skipped (server-less venues, or a
|
|
880
927
|
// bare deps without a realizer wired).
|
|
881
928
|
...(deps.venueRealizer ? { venueRealizer: deps.venueRealizer } : {}),
|
|
882
|
-
|
|
929
|
+
// The address-stamping tree (records-by-address), carried into the performance so each
|
|
930
|
+
// movement stamps against the same repository root the server was bootstrapped with —
|
|
931
|
+
// runChart forwards it to every movement via `...deps`. Threaded only when present, never
|
|
932
|
+
// process.cwd().
|
|
933
|
+
...(deps.genome_dir ? { tree_root: deps.genome_dir } : {}),
|
|
934
|
+
...(depth ? { depth } : {}), ...(effort ? { effort } : {}), ...(max_context_tokens !== undefined ? { max_context_tokens } : {}), ...reuseWiring, ...humanWiring,
|
|
883
935
|
};
|
|
884
936
|
/** The ARRANGEMENT's manifest. A chart has no single genome_hash or run_fingerprint — it
|
|
885
937
|
* has a chart_hash and one run per movement — so the reply says what a chart run is
|
|
@@ -956,7 +1008,7 @@ async function runImpl(slug, args, deps, approval) {
|
|
|
956
1008
|
if (e instanceof BudgetExhausted) {
|
|
957
1009
|
const partial = partialGigUsage(e);
|
|
958
1010
|
return { ok: false, requires_approval: approval, error: e.message,
|
|
959
|
-
data: { budget_exhausted: true, agent_slug: e.agent_slug,
|
|
1011
|
+
data: { budget_exhausted: true, agent_slug: e.agent_slug, unit: e.unit, max_usd: e.max_usd, spent_usd: e.spent_usd, budget_state: e.state,
|
|
960
1012
|
...(partial ? { usage: partial } : {}) } };
|
|
961
1013
|
}
|
|
962
1014
|
throw e;
|
|
@@ -1093,6 +1145,11 @@ async function runImpl(slug, args, deps, approval) {
|
|
|
1093
1145
|
toolProviders: deps.toolProviders, mcpServerConfigs: deps.mcpServerConfigs, // dispatch preflight resolves against the invoker's environment
|
|
1094
1146
|
venue, venues: deps.venues, venueRealizer: deps.venueRealizer,
|
|
1095
1147
|
repoUrl: dispatchRepoUrl,
|
|
1148
|
+
// The address-stamping tree (records-by-address): the repository root this server was
|
|
1149
|
+
// bootstrapped with. The CLI reaches this same door through `dispatchTool`, so its stamps
|
|
1150
|
+
// resolve against the bootstrapped root too. Never process.cwd(): absent genome_dir → no
|
|
1151
|
+
// tree_root, and a laws/changes seal then refuses `tree_root_unknown`.
|
|
1152
|
+
tree_root: deps.genome_dir,
|
|
1096
1153
|
});
|
|
1097
1154
|
// The gig id this run seals under — minted ONCE for both doors so the single-flight lock
|
|
1098
1155
|
// names the same holder whether the caller blocked (wait:true) or polled (async default).
|
|
@@ -1133,7 +1190,7 @@ async function runImpl(slug, args, deps, approval) {
|
|
|
1133
1190
|
try {
|
|
1134
1191
|
const res = await runGig(standard, gigInput, {
|
|
1135
1192
|
...dispatchDeps, gig_id: gigId,
|
|
1136
|
-
...(depth ? { depth } : {}), ...reuseWiring, ...humanWiring,
|
|
1193
|
+
...(depth ? { depth } : {}), ...(effort ? { effort } : {}), ...(max_context_tokens !== undefined ? { max_context_tokens } : {}), ...reuseWiring, ...humanWiring,
|
|
1137
1194
|
});
|
|
1138
1195
|
// Terminal (complete) frees the tree; a parked gig (awaiting_approval) RETAINS it.
|
|
1139
1196
|
if (releaseLock && res.status !== "awaiting_approval")
|
|
@@ -1176,7 +1233,7 @@ async function runImpl(slug, args, deps, approval) {
|
|
|
1176
1233
|
// stopped, and the operator needs them in the same reply as the depletion notice.
|
|
1177
1234
|
const partial = partialGigUsage(e);
|
|
1178
1235
|
return { ok: false, requires_approval: approval, error: e.message,
|
|
1179
|
-
data: { budget_exhausted: true, agent_slug: e.agent_slug,
|
|
1236
|
+
data: { budget_exhausted: true, agent_slug: e.agent_slug, unit: e.unit, max_usd: e.max_usd, spent_usd: e.spent_usd, budget_state: e.state,
|
|
1180
1237
|
...(partial ? { usage: partial } : {}) } };
|
|
1181
1238
|
}
|
|
1182
1239
|
throw e;
|
|
@@ -1226,7 +1283,7 @@ async function runImpl(slug, args, deps, approval) {
|
|
|
1226
1283
|
// A wire added to the shared assembler reaches this default async path by construction.
|
|
1227
1284
|
const runPromise = runGig(standard, gigInput, {
|
|
1228
1285
|
...dispatchDeps,
|
|
1229
|
-
gig_id: gigId, onProgress, signal: controller.signal, ...(depth ? { depth } : {}), ...reuseWiring, ...humanWiring,
|
|
1286
|
+
gig_id: gigId, onProgress, signal: controller.signal, ...(depth ? { depth } : {}), ...(effort ? { effort } : {}), ...(max_context_tokens !== undefined ? { max_context_tokens } : {}), ...reuseWiring, ...humanWiring,
|
|
1230
1287
|
});
|
|
1231
1288
|
// A REFUSED resume must be answered in THIS reply, not discovered later by polling. The
|
|
1232
1289
|
// gate throws in runGig's SYNCHRONOUS phase — before its first `await`, which is exactly
|
|
@@ -1657,8 +1714,35 @@ async function runImpl(slug, args, deps, approval) {
|
|
|
1657
1714
|
const extension = args["extension"] ?? undefined;
|
|
1658
1715
|
const addProps = extension?.schema?.properties ??
|
|
1659
1716
|
args["fields_to_add"] ?? {};
|
|
1660
|
-
|
|
1661
|
-
|
|
1717
|
+
// RECORDS BY ADDRESS — a field can be RETIRED through the genome's mouth, not only added. Each
|
|
1718
|
+
// named field is removed from `properties` AND dropped from `required_fields` in the SAME call
|
|
1719
|
+
// that applies any additions; the merge below then versions through proposeTypeChange, which
|
|
1720
|
+
// classifies a removal as `breaking` (approval required). Two refusals guard it, BEFORE the
|
|
1721
|
+
// version bumps and BEFORE the "changes nothing" guard: a name the type does not DECLARE cannot
|
|
1722
|
+
// be retired, and a field cannot be retired while the same call still REQUIRES it (an explicit
|
|
1723
|
+
// `extension.schema.required` naming it) — a type that requires a field it no longer declares is
|
|
1724
|
+
// the contradiction domainTypeDefect would catch downstream, refused here by name instead.
|
|
1725
|
+
const retire = Array.isArray(args["fields_to_retire"]) ? args["fields_to_retire"] : [];
|
|
1726
|
+
const undeclared = retire.filter((f) => !Object.hasOwn(baseProps, f));
|
|
1727
|
+
if (undeclared.length > 0) {
|
|
1728
|
+
return {
|
|
1729
|
+
ok: false, requires_approval: approval,
|
|
1730
|
+
error: `type_extend cannot retire ${undeclared.map((f) => `"${f}"`).join(", ")} from "${baseDef.slug}" — ` +
|
|
1731
|
+
`the type does not declare ${undeclared.length > 1 ? "those fields" : "that field"}. A retirement names a property the type currently has.`,
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
const explicitRequired = extension?.schema?.required;
|
|
1735
|
+
const retiredButRequired = explicitRequired ? retire.filter((f) => explicitRequired.includes(f)) : [];
|
|
1736
|
+
if (retiredButRequired.length > 0) {
|
|
1737
|
+
return {
|
|
1738
|
+
ok: false, requires_approval: approval,
|
|
1739
|
+
error: `type_extend cannot both retire and require ${retiredButRequired.map((f) => `"${f}"`).join(", ")} in one call ` +
|
|
1740
|
+
`on "${baseDef.slug}" — a field cannot be retired while the same call still lists it in required_fields. Drop it from \`required\` to retire it.`,
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
const retired = new Set(retire);
|
|
1744
|
+
const nextProps = Object.fromEntries(Object.entries({ ...baseProps, ...addProps }).filter(([k]) => !retired.has(k)));
|
|
1745
|
+
const nextRequired = (explicitRequired ?? baseDef.required_fields).filter((f) => !retired.has(f));
|
|
1662
1746
|
// A MUTATION THAT CHANGES NOTHING SAYS SO. `addProps` reads exactly two shapes —
|
|
1663
1747
|
// `extension.schema.properties` and `fields_to_add`. An `extension` supplied in any OTHER
|
|
1664
1748
|
// shape (top-level JSON Schema keywords, say) matches neither, so addProps is {} and the
|
|
@@ -1669,7 +1753,10 @@ async function runImpl(slug, args, deps, approval) {
|
|
|
1669
1753
|
// Sibling of the agent_evolve no-op reported in #325; both refuse here rather than guess.
|
|
1670
1754
|
const addedNothing = Object.keys(addProps).length === 0;
|
|
1671
1755
|
const requiredUnchanged = JSON.stringify([...nextRequired].sort()) === JSON.stringify([...baseDef.required_fields].sort());
|
|
1672
|
-
|
|
1756
|
+
// A retirement (already validated to remove a declared property) IS a change, even with no
|
|
1757
|
+
// additions and no required-set drift — so the "changes nothing" guard must not fire when one
|
|
1758
|
+
// is present, or a lawful retirement would be refused as a no-op.
|
|
1759
|
+
if (addedNothing && requiredUnchanged && retire.length === 0) {
|
|
1673
1760
|
return {
|
|
1674
1761
|
ok: false,
|
|
1675
1762
|
requires_approval: approval,
|
|
@@ -2255,16 +2342,22 @@ async function runImpl(slug, args, deps, approval) {
|
|
|
2255
2342
|
built[key] = args[key];
|
|
2256
2343
|
}
|
|
2257
2344
|
const def = built;
|
|
2258
|
-
// Governance gate: each allowed_tools slug must be registered
|
|
2259
|
-
//
|
|
2260
|
-
//
|
|
2345
|
+
// Governance gate: each allowed_tools slug must be registered OR be a host builtin the
|
|
2346
|
+
// invoker knows (Read, Write(src/**), Bash(npx vitest run:*) — judged on the base name).
|
|
2347
|
+
// tool_propose alone does NOT register; tool_register lands the slug. Unknown names are
|
|
2348
|
+
// rejected so the cage cannot grant scope to a tool nothing provides. Host builtins were
|
|
2349
|
+
// refused here until 2026-09-16, so no code-touching seat could be authored through this
|
|
2350
|
+
// surface at all (every one in the genome was committed as a file instead). Registering one
|
|
2351
|
+
// is not the cure: the registry is the engine's own tool surface, and `Read` is not an engine
|
|
2352
|
+
// tool. Admitting a host builtin grants nothing by itself — dispatch still resolves every
|
|
2353
|
+
// grant and fails closed, and a venue still narrows the set to its equipment.
|
|
2261
2354
|
if (def.allowed_tools && def.allowed_tools.length > 0) {
|
|
2262
|
-
const unknown = def.allowed_tools.filter((s) => !REGISTERED_TOOL_SLUGS.has(s));
|
|
2355
|
+
const unknown = def.allowed_tools.filter((s) => !REGISTERED_TOOL_SLUGS.has(s) && !isHostBuiltin(toolBaseName(s)));
|
|
2263
2356
|
if (unknown.length > 0) {
|
|
2264
2357
|
return {
|
|
2265
2358
|
ok: false,
|
|
2266
2359
|
requires_approval: approval,
|
|
2267
|
-
error: `agent_define: unknown/unregistered allowed_tools slug${unknown.length > 1 ? "s" : ""}: ${unknown.join(", ")} — call tool_propose then tool_register first`,
|
|
2360
|
+
error: `agent_define: unknown/unregistered allowed_tools slug${unknown.length > 1 ? "s" : ""}: ${unknown.join(", ")} — not a host builtin and not registered; call tool_propose then tool_register first`,
|
|
2268
2361
|
};
|
|
2269
2362
|
}
|
|
2270
2363
|
}
|
|
@@ -3796,27 +3889,37 @@ export function bootstrapServerDeps(genomeRoot) {
|
|
|
3796
3889
|
// Silence admits, so a genome with no institutions/ (which is most of them) is unaffected: every
|
|
3797
3890
|
// placement is admitted and the run is byte-identical.
|
|
3798
3891
|
placementResolver: institutionPlacementResolver(genome.institutions ?? new Map()),
|
|
3799
|
-
|
|
3892
|
+
// ONE selector for this door AND the drain (src/invoker_selection.ts). A completions URL in the
|
|
3893
|
+
// environment seats the cheap chat-completions port so `coltrane dispatch` / `gig_dispatch` can
|
|
3894
|
+
// reach a model priced in cents, exactly as `coltrane work` already could; its absence keeps the
|
|
3895
|
+
// host-tool invoker below, configured byte-for-byte as it was — the Claude options are handed to
|
|
3896
|
+
// the selector WHOLE and passed through untouched on that path, so this door's Claude behaviour
|
|
3897
|
+
// is unchanged. The selector also loads the deployment's price table (COLTRANE_PRICES_FILE),
|
|
3898
|
+
// failing startup if it is malformed — the propagation this bootstrap owes.
|
|
3899
|
+
invoke: selectChairInvoker(process.env, {
|
|
3800
3900
|
registry,
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3901
|
+
claude: {
|
|
3902
|
+
registry,
|
|
3903
|
+
model: process.env["COLTRANE_MODEL"],
|
|
3904
|
+
// #185 — per-agent grant resolution wires each agent's MCP servers into its spawn (coltrane's
|
|
3905
|
+
// own server + any the deployment registers in .mcp.json). An unresolvable grant fails closed.
|
|
3906
|
+
mcpServerConfigs,
|
|
3907
|
+
toolProviders, // the genome→provider bridge (above) — makes in_house grants resolvable
|
|
3908
|
+
// The production seal path: a model chair SEALS IN-BAND by calling output_write (validated at
|
|
3909
|
+
// the full write boundary, corrected in-band), and the invoker captures what passed. The
|
|
3910
|
+
// engine server config above is bridged into the spawn and its validate-mode env set, so the
|
|
3911
|
+
// chair's output_write adjudicates-not-persists and the runtime seals exactly once.
|
|
3912
|
+
sealVia: "output_write",
|
|
3913
|
+
// per-chair wall-clock bound; COLTRANE_CHAIR_TIMEOUT_MS overrides for slow deployments
|
|
3914
|
+
...(process.env["COLTRANE_CHAIR_TIMEOUT_MS"] ? { timeout_ms: Number(process.env["COLTRANE_CHAIR_TIMEOUT_MS"]) } : {}),
|
|
3915
|
+
// The reserve grant (#329) had no reachable caller: it was built, tested, and set by nothing,
|
|
3916
|
+
// so a chair that spent its budget still died silently at the cap. This is the operator-level
|
|
3917
|
+
// door to it. Absent = no reserve, which is the prior behaviour exactly — an extension nobody
|
|
3918
|
+
// asked for is spend nobody authorised. The DURABLE fix is a per-chair `turn_reserve` declared
|
|
3919
|
+
// in the standard (PR #331), because a budget is a property of the work rather than of the
|
|
3920
|
+
// player; this env is the deployment-level stopgap until that lands, not a substitute for it.
|
|
3921
|
+
...(process.env["COLTRANE_TURN_RESERVE"] ? { turn_reserve: Number(process.env["COLTRANE_TURN_RESERVE"]) } : {}),
|
|
3922
|
+
},
|
|
3820
3923
|
}),
|
|
3821
3924
|
model_version: process.env["COLTRANE_MODEL"] ?? "claude-cli-default",
|
|
3822
3925
|
skills: genome.skills, // ← skill substrate — runGig resolves agent.skill_slugs into prompt
|