@chibitek/chibi 0.1.161 → 0.1.168
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/dist/assets/{abnfDiagram-O67JEVCF-Bb-Unz3V.js → abnfDiagram-O67JEVCF-DUyzT6Xq.js} +1 -1
- package/dist/assets/{arc-HBMH7B4K.js → arc-B4-kO642.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-CYwkb2PE.js → architectureDiagram-NJMV4G6O-DgKdnNL2.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-DPYYNxBH.js → blockDiagram-BEXU5L5S-B6oV3kDr.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-DZ9hKDlA.js → c4Diagram-YGBWAQC7-Dp0E43jx.js} +1 -1
- package/dist/assets/channel-BidDehbw.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-OKrUlts6.js → chunk-2Q5K7J3B-Cfv1WVUo.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-Tuur0lqz.js → chunk-3FUC2YCW-_-STY2yO.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-BL8QOZeP.js → chunk-5VM5RSS4-C6MZm1cm.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-DMi8-ixt.js → chunk-GTNCS2PH-C__aVIJy.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-Dd7yNJ0G.js → chunk-GWA4HPMP-fSrARJuB.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-g2dOoIMX.js → chunk-JWPE2WC7-CJioJB6h.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-Cum7feOh.js → chunk-NETBCI7D-o6MBGCq_.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-BzCC1jc4.js → chunk-WEXAMYUT-O-TFLbdc.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-Ck63jUMo.js → chunk-XXDRQBXY-DYS9CQbl.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-DFSuRVMZ.js → classDiagram-v2-NBCMYWYE-BC4bSH09.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-CegRj_kY.js → cose-bilkent-JH36ORCC-7NKuC3-C.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-cZS_kt1A.js → cynefin-OW5HDTMX-CuWv5cOw.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-DfQa9Rht.js → cynefinDiagram-VND7K2PF-D_l_JrG5.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-DJ9qftJu.js → dagre-6A5THRUB-D7eucSmr.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-KXUGUahJ.js → diagram-22UHCM2B-CADR5v1e.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-BudCYrCp.js → diagram-3UASUU5V-DF_fBOZn.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-Du2O03NS.js → diagram-ATOU4E4O-DE5p59WH.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-DXybXGYf.js → diagram-CDSNMT55-vthvYooA.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-DfrknXGE.js → diagram-MLGK6HIB-ByMHy4S-.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-C29FmaJm.js → diagram-MPIPVDR6-C9hKBUnO.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-BF9wS0Nz.js → ebnfDiagram-ZINNZB2B-Djv6hWEl.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-B2Fah1VF.js → elk-276RUBZZ-CmUjf18z.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-Di3q4bpF.js → erDiagram-OPXOYQCR-DkmXQUok.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-D1UiqwbF.js → flowDiagram-KWPJA3E3-6pz2poYn.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-BjZqMt7o.js → ganttDiagram-FUAMR5RP-Cx9Fbw75.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-DiZtdip7.js → gitGraphDiagram-X574FWY7-BR5MsYkP.js} +1 -1
- package/dist/assets/{index-dbQveHan.js → index-BoF5cGZq.js} +1 -1
- package/dist/assets/index-COwCaN7p.css +1 -0
- package/dist/assets/{index-XFwW09vd.js → index-Ckv52eaP.js} +191 -192
- package/dist/assets/{infoDiagram-VRGFBTTK-BJUJCgio.js → infoDiagram-VRGFBTTK-DoF183Am.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-BX0XdHUx.js → ishikawaDiagram-OU5B5YK6-4T_-hM1-.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-Bg904IIx.js → journeyDiagram-ZHPQQLJL-Ca0T3Ngd.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-hkZMFMHZ.js → kanban-definition-PNTS6WVX-DNp_DAr9.js} +1 -1
- package/dist/assets/{linear-Bh29RTbQ.js → linear-COUoS2bF.js} +1 -1
- package/dist/assets/{mermaid.core-aqB1jW_w.js → mermaid.core-DQHowMIV.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-D5GMV-Zd.js → mindmap-definition-NLK3R4M7-CMgO1Mkh.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-7_wFtkFH.js → pegDiagram-GJSIUBJH-BCVCZbsq.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-DXtZBQ4f.js → pieDiagram-5QR66LMP-DxaMlX62.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-C1v3ydTF.js → quadrantDiagram-O4NWA36T-qUD0pkur.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-CLmhiOXg.js → railroadDiagram-XR7U4H2S-Dc6rcAJB.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-BKxI2rh_.js → requirementDiagram-PLB6GJNP-DMTfI_lR.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-gKK-Qs7b.js → sankeyDiagram-IPEJSGJF-B0BWZys8.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-BT94Uowp.js → sequenceDiagram-PO4LG4MO-DZVt17mL.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-RDJL6sCM.js → sizeCapture-INFHLROL-BFQztpNW.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-DqHMzv-J.js → stateDiagram-v2-GCMORJYK-GeFCVCDr.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-B-NN0ql6.js → swimlanes-2SLR337P-DTqQXTvP.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-CRQqFEdH.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-C51S9tpN.js → timeline-definition-EJHVYXUP-BoiNrkP7.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-DyfNMmhq.js → usecaseDiagram-POWQR4AR-CdfsSO2g.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-D924r33S.js → vennDiagram-UO4OBE2U-CHc_Fn1O.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-DXSfsjUr.js → wardleyDiagram-VNRHLVJA-Bv3KWVEF.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-mYX9tnBU.js → xychartDiagram-PMCCYNJV-CQFknLPS.js} +1 -1
- package/dist/handbook/atomic-handbook.html +5 -0
- package/dist/index.html +2 -2
- package/dist-server/container-mcp.js +12 -6
- package/dist-server/drivers/agents-proxy.js +65 -41
- package/dist-server/index.js +5302 -4653
- package/dist-server/openmausbot.js +506 -288
- package/dist-server/pair-cli.js +506 -288
- package/dist-server/server/auto-approve.js +4 -1
- package/dist-server/server/brand.js +11 -0
- package/dist-server/server/config.js +19 -24
- package/dist-server/server/connection-credential.js +59 -0
- package/dist-server/server/container-computer.js +5 -26
- package/dist-server/server/credential-card-request.js +52 -0
- package/dist-server/server/dictation.js +6 -3
- package/dist-server/server/drivers/agents-call.js +3 -23
- package/dist-server/server/drivers/agents-catalog.js +2 -20
- package/dist-server/server/drivers/agents-credential.js +57 -0
- package/dist-server/server/drivers/builtIn.js +9 -1
- package/dist-server/server/drivers/claude-elicitation.js +76 -0
- package/dist-server/server/drivers/claude-stdin.js +47 -0
- package/dist-server/server/drivers/claude.js +23 -39
- package/dist-server/server/drivers/codex-elicitation.js +93 -0
- package/dist-server/server/drivers/codex.js +41 -107
- package/dist-server/server/engine-platform-fleet.js +32 -0
- package/dist-server/server/external-room.js +24 -0
- package/dist-server/server/index.js +105 -116
- package/dist-server/server/mcp-elicitation.js +120 -0
- package/dist-server/server/mcp-http.js +52 -2
- package/dist-server/server/mcp-probe.js +3 -0
- package/dist-server/server/peer-roster.js +43 -0
- package/dist-server/server/phone-secret.js +12 -4
- package/dist-server/server/routes/dictation.js +6 -1
- package/dist-server/server/routine-mochii-sync.js +252 -0
- package/dist-server/server/system-prompt.js +1 -1
- package/dist-server/server/text-actions.js +25 -0
- package/dist-server/shared/credential-request.js +15 -6
- package/dist-server/shared/engine-platforms.js +19 -0
- package/dist-server/vps-container-mcp.js +12 -6
- package/package.json +1 -1
- package/dist/assets/channel-Nx85fHZ4.js +0 -1
- package/dist/assets/index-B2fJMh8N.css +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BO2gR34z.js +0 -8
|
@@ -33,6 +33,8 @@ import { SPAWNED_PROXIES } from "../proxy-paths.js";
|
|
|
33
33
|
import { rtkRewrite } from "../rtk-rewrite.js";
|
|
34
34
|
import { volatileNote, volatileSectionsOf, withNote } from "./volatile-notes.js";
|
|
35
35
|
import { extractMcpImages } from "../mcp-tool-images.js";
|
|
36
|
+
import { claudeUserMessage, diagnosticClaudeUserMessage } from "./claude-stdin.js";
|
|
37
|
+
import { createClaudeElicitations } from "./claude-elicitation.js";
|
|
36
38
|
import { ASK_USER_QUESTION_TOOL, askQuestionSummary, parseAskQuestions, parseChoices, questionChoices, } from "../../shared/ask-question.js";
|
|
37
39
|
const CONNECTOR_CACHE_MS = 5 * 60 * 1000;
|
|
38
40
|
/** Parent-session credentials/routing that a named account must not inherit.
|
|
@@ -812,45 +814,6 @@ function decodeConfig(raw) {
|
|
|
812
814
|
...(disallowedTools !== undefined ? { disallowedTools } : {}),
|
|
813
815
|
};
|
|
814
816
|
}
|
|
815
|
-
/** Claude's stream-json input accepts the same image source blocks as the
|
|
816
|
-
* Anthropic Messages API. Keep the old string form for text-only turns so a
|
|
817
|
-
* CLI update cannot disturb the overwhelmingly common path. */
|
|
818
|
-
function claudeUserMessage(text, images) {
|
|
819
|
-
if (!images?.length)
|
|
820
|
-
return { type: "user", message: { role: "user", content: text } };
|
|
821
|
-
const content = images.map((image) => ({
|
|
822
|
-
type: "image",
|
|
823
|
-
source: {
|
|
824
|
-
type: "base64",
|
|
825
|
-
media_type: image.mime,
|
|
826
|
-
data: readFileSync(image.path).toString("base64"),
|
|
827
|
-
},
|
|
828
|
-
}));
|
|
829
|
-
if (text)
|
|
830
|
-
content.push({ type: "text", text });
|
|
831
|
-
return { type: "user", message: { role: "user", content } };
|
|
832
|
-
}
|
|
833
|
-
/** Native traces are routinely attached to bug reports. Preserve the image
|
|
834
|
-
* block's shape and size for debugging, but never persist its base64 bytes. */
|
|
835
|
-
function diagnosticClaudeUserMessage(message) {
|
|
836
|
-
if (!Array.isArray(message.message.content))
|
|
837
|
-
return message;
|
|
838
|
-
return {
|
|
839
|
-
...message,
|
|
840
|
-
message: {
|
|
841
|
-
...message.message,
|
|
842
|
-
content: message.message.content.map((block) => block.type === "image"
|
|
843
|
-
? {
|
|
844
|
-
...block,
|
|
845
|
-
source: {
|
|
846
|
-
...block.source,
|
|
847
|
-
data: `[image data: ${block.source.data.length} base64 chars]`,
|
|
848
|
-
},
|
|
849
|
-
}
|
|
850
|
-
: block),
|
|
851
|
-
},
|
|
852
|
-
};
|
|
853
|
-
}
|
|
854
817
|
export const ClaudeDriver = {
|
|
855
818
|
driverKind: DRIVER_KIND,
|
|
856
819
|
metadata: { displayName: "Claude", supportsMultipleInstances: true },
|
|
@@ -919,6 +882,7 @@ export const ClaudeDriver = {
|
|
|
919
882
|
const sumCost = (a, b) => a === null ? b : b === null ? a : a + b;
|
|
920
883
|
const STEER_FOLLOWUP_MS = 1_500;
|
|
921
884
|
const sessions = new Map();
|
|
885
|
+
const elicitations = createClaudeElicitations({ emit: (event) => emit(event), base: (threadId, turnId) => base(threadId, turnId) });
|
|
922
886
|
// "Always allow this session", per thread: the actions the person allowed
|
|
923
887
|
// for the native Claude session. Kept here, not on the broker, so a
|
|
924
888
|
// relaunch with --resume (idle close, retry) still honors them; a fresh
|
|
@@ -960,6 +924,7 @@ export const ClaudeDriver = {
|
|
|
960
924
|
if (!s || s.closing)
|
|
961
925
|
return;
|
|
962
926
|
s.closing = true;
|
|
927
|
+
elicitations.endThread(threadId);
|
|
963
928
|
if (s.idleTimer)
|
|
964
929
|
clearTimeout(s.idleTimer);
|
|
965
930
|
// Broker ownership belongs to this session. Detach and close it now,
|
|
@@ -1831,6 +1796,22 @@ export const ClaudeDriver = {
|
|
|
1831
1796
|
}
|
|
1832
1797
|
break;
|
|
1833
1798
|
}
|
|
1799
|
+
case "control_request":
|
|
1800
|
+
// an MCP server's confirmation question (server/drivers/claude-elicitation.ts)
|
|
1801
|
+
elicitations.handle(o, {
|
|
1802
|
+
threadId,
|
|
1803
|
+
turnId: currentTurnId(),
|
|
1804
|
+
closing: session.closing,
|
|
1805
|
+
write: (frame) => {
|
|
1806
|
+
try {
|
|
1807
|
+
if (session.child.stdin.writable && !session.child.stdin.destroyed)
|
|
1808
|
+
session.child.stdin.write(JSON.stringify(frame) + "\n");
|
|
1809
|
+
appendNative(threadId, { dir: "out", source: "claude.sdk.message", msg: frame });
|
|
1810
|
+
}
|
|
1811
|
+
catch { }
|
|
1812
|
+
},
|
|
1813
|
+
});
|
|
1814
|
+
break;
|
|
1834
1815
|
case "user":
|
|
1835
1816
|
for (const b of Array.isArray(o.message?.content) ? o.message.content : []) {
|
|
1836
1817
|
if (b.type === "tool_result") {
|
|
@@ -2272,6 +2253,9 @@ export const ClaudeDriver = {
|
|
|
2272
2253
|
steer,
|
|
2273
2254
|
interruptTurn: async (threadId) => active.get(threadId)?.stop(),
|
|
2274
2255
|
respondToRequest: async (threadId, requestId, decision) => {
|
|
2256
|
+
const confirmation = elicitations.respond(threadId, requestId, decision);
|
|
2257
|
+
if (confirmation)
|
|
2258
|
+
return confirmation;
|
|
2275
2259
|
// fail-closed by construction: no broker, or an ask that already
|
|
2276
2260
|
// timed out / settled, is `unavailable` — the caller denies
|
|
2277
2261
|
const broker = sessions.get(threadId)?.broker ?? active.get(threadId)?.broker;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// MCP elicitation forms Codex's app-server forwards (mcpServer/elicitation/request)
|
|
2
|
+
// that Atomic can answer on a permission card: Codex's own app-access approvals,
|
|
3
|
+
// and a configured server's plain confirmation (server/mcp-elicitation.ts).
|
|
4
|
+
import { elicitationConfirm } from "../mcp-elicitation.js";
|
|
5
|
+
const plainRecord = (value) => value && typeof value === "object" && !Array.isArray(value)
|
|
6
|
+
? value
|
|
7
|
+
: null;
|
|
8
|
+
export const containsControlCharacter = (value) => {
|
|
9
|
+
for (const character of value) {
|
|
10
|
+
const code = character.charCodeAt(0);
|
|
11
|
+
if (code <= 0x1f || code === 0x7f)
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
};
|
|
16
|
+
function boundedLabel(value) {
|
|
17
|
+
if (typeof value !== "string")
|
|
18
|
+
return null;
|
|
19
|
+
const label = value.trim();
|
|
20
|
+
return label && label.length <= 160 && !containsControlCharacter(label) ? label : null;
|
|
21
|
+
}
|
|
22
|
+
function ordinaryApprovalValue(value) {
|
|
23
|
+
const normalized = value.trim().toLowerCase();
|
|
24
|
+
if (/session|always|permanent|forever|persistent/.test(normalized))
|
|
25
|
+
return false;
|
|
26
|
+
return normalized === "once" || /^(?:accept|approve|allow)(?:ed|[-_]?once)?$/.test(normalized);
|
|
27
|
+
}
|
|
28
|
+
/** Recognize only schema-backed app-access approvals. Arbitrary MCP forms
|
|
29
|
+
* (credentials, free text, URLs, or required fields without a one-time enum)
|
|
30
|
+
* remain user input and are declined; Full access never fabricates them. */
|
|
31
|
+
export function mcpAppApprovalForm(params) {
|
|
32
|
+
const request = plainRecord(params);
|
|
33
|
+
if (!request || request.mode !== "form")
|
|
34
|
+
return null;
|
|
35
|
+
const metadata = plainRecord(request._meta);
|
|
36
|
+
const target = plainRecord(metadata?.target);
|
|
37
|
+
const toolParams = plainRecord(metadata?.tool_params);
|
|
38
|
+
const message = boundedLabel(request.message) ?? "App access requested";
|
|
39
|
+
const appName = [
|
|
40
|
+
metadata?.app_name,
|
|
41
|
+
metadata?.appName,
|
|
42
|
+
metadata?.app,
|
|
43
|
+
target?.app,
|
|
44
|
+
target?.name,
|
|
45
|
+
toolParams?.app_name,
|
|
46
|
+
toolParams?.app,
|
|
47
|
+
metadata?.connector_name,
|
|
48
|
+
metadata?.connectorName,
|
|
49
|
+
].map(boundedLabel).find(Boolean) ?? message.match(/^Allow ChatGPT to use (.+?)\?$/i)?.[1]?.trim();
|
|
50
|
+
// The application identity is the second half of the discriminator. A
|
|
51
|
+
// required approval-looking enum by itself must not turn an arbitrary form
|
|
52
|
+
// into a permission prompt.
|
|
53
|
+
if (!appName)
|
|
54
|
+
return null;
|
|
55
|
+
const schema = plainRecord(request.requestedSchema);
|
|
56
|
+
const properties = plainRecord(schema?.properties);
|
|
57
|
+
const required = schema?.required;
|
|
58
|
+
if (!properties ||
|
|
59
|
+
!Array.isArray(required) ||
|
|
60
|
+
required.length === 0 ||
|
|
61
|
+
required.length > 8 ||
|
|
62
|
+
!required.every((key) => typeof key === "string" && key.length > 0 && key.length <= 100))
|
|
63
|
+
return null;
|
|
64
|
+
const content = {};
|
|
65
|
+
for (const key of required) {
|
|
66
|
+
const field = plainRecord(properties[key]);
|
|
67
|
+
if (!field)
|
|
68
|
+
return null;
|
|
69
|
+
const enumValues = Array.isArray(field.enum)
|
|
70
|
+
? field.enum.filter((value) => typeof value === "string")
|
|
71
|
+
: [];
|
|
72
|
+
const oneOfValues = Array.isArray(field.oneOf)
|
|
73
|
+
? field.oneOf
|
|
74
|
+
.map((option) => boundedLabel(plainRecord(option)?.const))
|
|
75
|
+
.filter((value) => Boolean(value))
|
|
76
|
+
: [];
|
|
77
|
+
const chosen = [...oneOfValues, ...enumValues].find(ordinaryApprovalValue);
|
|
78
|
+
if (!chosen)
|
|
79
|
+
return null;
|
|
80
|
+
content[key] = chosen;
|
|
81
|
+
}
|
|
82
|
+
const tool = boundedLabel(appName) ?? boundedLabel(request.serverName) ?? "app_access";
|
|
83
|
+
return { tool, summary: message, allowResult: { action: "accept", content } };
|
|
84
|
+
}
|
|
85
|
+
/** A configured server (by its mount name) asking the person to confirm a
|
|
86
|
+
* change: Addigy confirms every write this way. The card always needs the
|
|
87
|
+
* person, whatever the approval mode. Null for anything else. */
|
|
88
|
+
export function codexServerConfirm(params, customByMountName) {
|
|
89
|
+
const serverName = plainRecord(params)?.serverName;
|
|
90
|
+
if (typeof serverName !== "string" || !customByMountName.has(serverName))
|
|
91
|
+
return null;
|
|
92
|
+
return elicitationConfirm(params);
|
|
93
|
+
}
|
|
@@ -15,6 +15,8 @@ import { describeProbeFailure, describeSpawnFailure, killCliTree, probeCli, spaw
|
|
|
15
15
|
import { setupLog } from "../setup-log.js";
|
|
16
16
|
import { isHarnessOwnedMcpEnvName } from "../mcp-registry.js";
|
|
17
17
|
import { toolAnnotations } from "../mcp-tool-hints.js";
|
|
18
|
+
import { elicitationAnswer, elicitationSummary, elicitationTool } from "../mcp-elicitation.js";
|
|
19
|
+
import { codexServerConfirm, containsControlCharacter, mcpAppApprovalForm } from "./codex-elicitation.js";
|
|
18
20
|
import { newEventId, newId } from "../contracts.js";
|
|
19
21
|
import { decodeCodexSelection, readCodexModelCatalog, STATIC_CODEX_MODELS } from "./codex-catalog.js";
|
|
20
22
|
import { codexLocalProviderArgs } from "./local-inject.js";
|
|
@@ -181,83 +183,6 @@ function additionalPermissionSummary(permissions, reason) {
|
|
|
181
183
|
const plainRecord = (value) => value && typeof value === "object" && !Array.isArray(value)
|
|
182
184
|
? value
|
|
183
185
|
: null;
|
|
184
|
-
const containsControlCharacter = (value) => {
|
|
185
|
-
for (const character of value) {
|
|
186
|
-
const code = character.charCodeAt(0);
|
|
187
|
-
if (code <= 0x1f || code === 0x7f)
|
|
188
|
-
return true;
|
|
189
|
-
}
|
|
190
|
-
return false;
|
|
191
|
-
};
|
|
192
|
-
function boundedLabel(value) {
|
|
193
|
-
if (typeof value !== "string")
|
|
194
|
-
return null;
|
|
195
|
-
const label = value.trim();
|
|
196
|
-
return label && label.length <= 160 && !containsControlCharacter(label) ? label : null;
|
|
197
|
-
}
|
|
198
|
-
function ordinaryApprovalValue(value) {
|
|
199
|
-
const normalized = value.trim().toLowerCase();
|
|
200
|
-
if (/session|always|permanent|forever|persistent/.test(normalized))
|
|
201
|
-
return false;
|
|
202
|
-
return normalized === "once" || /^(?:accept|approve|allow)(?:ed|[-_]?once)?$/.test(normalized);
|
|
203
|
-
}
|
|
204
|
-
/** Recognize only schema-backed app-access approvals. Arbitrary MCP forms
|
|
205
|
-
* (credentials, free text, URLs, or required fields without a one-time enum)
|
|
206
|
-
* remain user input and are declined; Full access never fabricates them. */
|
|
207
|
-
function mcpAppApprovalForm(params) {
|
|
208
|
-
const request = plainRecord(params);
|
|
209
|
-
if (!request || request.mode !== "form")
|
|
210
|
-
return null;
|
|
211
|
-
const metadata = plainRecord(request._meta);
|
|
212
|
-
const target = plainRecord(metadata?.target);
|
|
213
|
-
const toolParams = plainRecord(metadata?.tool_params);
|
|
214
|
-
const message = boundedLabel(request.message) ?? "App access requested";
|
|
215
|
-
const appName = [
|
|
216
|
-
metadata?.app_name,
|
|
217
|
-
metadata?.appName,
|
|
218
|
-
metadata?.app,
|
|
219
|
-
target?.app,
|
|
220
|
-
target?.name,
|
|
221
|
-
toolParams?.app_name,
|
|
222
|
-
toolParams?.app,
|
|
223
|
-
metadata?.connector_name,
|
|
224
|
-
metadata?.connectorName,
|
|
225
|
-
].map(boundedLabel).find(Boolean) ?? message.match(/^Allow ChatGPT to use (.+?)\?$/i)?.[1]?.trim();
|
|
226
|
-
// The application identity is the second half of the discriminator. A
|
|
227
|
-
// required approval-looking enum by itself must not turn an arbitrary form
|
|
228
|
-
// into a permission prompt.
|
|
229
|
-
if (!appName)
|
|
230
|
-
return null;
|
|
231
|
-
const schema = plainRecord(request.requestedSchema);
|
|
232
|
-
const properties = plainRecord(schema?.properties);
|
|
233
|
-
const required = schema?.required;
|
|
234
|
-
if (!properties ||
|
|
235
|
-
!Array.isArray(required) ||
|
|
236
|
-
required.length === 0 ||
|
|
237
|
-
required.length > 8 ||
|
|
238
|
-
!required.every((key) => typeof key === "string" && key.length > 0 && key.length <= 100))
|
|
239
|
-
return null;
|
|
240
|
-
const content = {};
|
|
241
|
-
for (const key of required) {
|
|
242
|
-
const field = plainRecord(properties[key]);
|
|
243
|
-
if (!field)
|
|
244
|
-
return null;
|
|
245
|
-
const enumValues = Array.isArray(field.enum)
|
|
246
|
-
? field.enum.filter((value) => typeof value === "string")
|
|
247
|
-
: [];
|
|
248
|
-
const oneOfValues = Array.isArray(field.oneOf)
|
|
249
|
-
? field.oneOf
|
|
250
|
-
.map((option) => boundedLabel(plainRecord(option)?.const))
|
|
251
|
-
.filter((value) => Boolean(value))
|
|
252
|
-
: [];
|
|
253
|
-
const chosen = [...oneOfValues, ...enumValues].find(ordinaryApprovalValue);
|
|
254
|
-
if (!chosen)
|
|
255
|
-
return null;
|
|
256
|
-
content[key] = chosen;
|
|
257
|
-
}
|
|
258
|
-
const tool = boundedLabel(appName) ?? boundedLabel(request.serverName) ?? "app_access";
|
|
259
|
-
return { tool, summary: message, allowResult: { action: "accept", content } };
|
|
260
|
-
}
|
|
261
186
|
/** Codex persists these values on its native thread. Keep them explicit on
|
|
262
187
|
* start, resume, and every turn so switching modes cannot leave a more
|
|
263
188
|
* permissive sandbox/reviewer stuck to the next request. */
|
|
@@ -920,7 +845,9 @@ export const CodexDriver = {
|
|
|
920
845
|
const isLegacyMcpPermission = method === "mcpServer/elicitation/request" &&
|
|
921
846
|
params?._meta?.codex_approval_kind === "mcp_tool_call";
|
|
922
847
|
const mcpAppApproval = isMcpElicitation ? mcpAppApprovalForm(params) : null;
|
|
923
|
-
|
|
848
|
+
// one of the person's own servers confirming a change (Addigy's writes)
|
|
849
|
+
const serverConfirm = isMcpElicitation && !isLegacyMcpPermission && !mcpAppApproval ? codexServerConfirm(params, customByMountName) : null;
|
|
850
|
+
const isMcpPermission = isLegacyMcpPermission || mcpAppApproval !== null || serverConfirm !== null;
|
|
924
851
|
const isQuestion = method === "item/tool/requestUserInput";
|
|
925
852
|
const isAdditionalPermission = method === "item/permissions/requestApproval";
|
|
926
853
|
const isPermission = legacy || isMcpPermission || isAdditionalPermission ||
|
|
@@ -976,25 +903,29 @@ export const CodexDriver = {
|
|
|
976
903
|
const mcpToolRef = customServer && mcpTool
|
|
977
904
|
? { server: customServer, tool: mcpTool, ...(toolAnnotations(params?._meta?.annotations) ? { annotations: toolAnnotations(params._meta.annotations) } : {}) }
|
|
978
905
|
: undefined;
|
|
979
|
-
const tool =
|
|
980
|
-
?
|
|
981
|
-
:
|
|
982
|
-
?
|
|
906
|
+
const tool = serverConfirm
|
|
907
|
+
? elicitationTool(customByMountName.get(params.serverName))
|
|
908
|
+
: mcpAppApproval
|
|
909
|
+
? mcpAppApproval.tool
|
|
910
|
+
: isLegacyMcpPermission
|
|
911
|
+
? (mcpTool ?? "mcp")
|
|
912
|
+
: isAdditionalPermission
|
|
913
|
+
? "permissions"
|
|
914
|
+
: method === "item/fileChange/requestApproval" || method === "applyPatchApproval"
|
|
915
|
+
? "edit"
|
|
916
|
+
: isQuestion
|
|
917
|
+
? "ask_user"
|
|
918
|
+
: "shell";
|
|
919
|
+
const permissionResult = (allow) => serverConfirm
|
|
920
|
+
? elicitationAnswer(serverConfirm, allow)
|
|
921
|
+
: isMcpPermission
|
|
922
|
+
? allow
|
|
923
|
+
? (mcpAppApproval?.allowResult ?? { action: "accept", content: {} })
|
|
924
|
+
: { action: "decline" }
|
|
983
925
|
: isAdditionalPermission
|
|
984
|
-
?
|
|
985
|
-
:
|
|
986
|
-
|
|
987
|
-
: isQuestion
|
|
988
|
-
? "ask_user"
|
|
989
|
-
: "shell";
|
|
990
|
-
const permissionResult = (allow) => isMcpPermission
|
|
991
|
-
? allow
|
|
992
|
-
? (mcpAppApproval?.allowResult ?? { action: "accept", content: {} })
|
|
993
|
-
: { action: "decline" }
|
|
994
|
-
: isAdditionalPermission
|
|
995
|
-
? { permissions: allow ? grantedPermissions(params.permissions) : {}, scope: "turn" }
|
|
996
|
-
: { decision: allow ? (legacy ? "approved" : "accept") : legacy ? "denied" : "decline" };
|
|
997
|
-
if (autoAcceptPermissions && isPermission) {
|
|
926
|
+
? { permissions: allow ? grantedPermissions(params.permissions) : {}, scope: "turn" }
|
|
927
|
+
: { decision: allow ? (legacy ? "approved" : "accept") : legacy ? "denied" : "decline" };
|
|
928
|
+
if (autoAcceptPermissions && isPermission && !serverConfirm) {
|
|
998
929
|
return send({
|
|
999
930
|
jsonrpc: "2.0",
|
|
1000
931
|
id: msg.id,
|
|
@@ -1004,15 +935,17 @@ export const CodexDriver = {
|
|
|
1004
935
|
const requestId = newId();
|
|
1005
936
|
const summary = isAdditionalPermission
|
|
1006
937
|
? additionalPermissionSummary(params.permissions, params.reason)
|
|
1007
|
-
:
|
|
1008
|
-
? (
|
|
1009
|
-
:
|
|
1010
|
-
? params.
|
|
1011
|
-
:
|
|
1012
|
-
? params.
|
|
1013
|
-
:
|
|
1014
|
-
? params.
|
|
1015
|
-
:
|
|
938
|
+
: serverConfirm
|
|
939
|
+
? elicitationSummary(customByMountName.get(params.serverName), serverConfirm)
|
|
940
|
+
: isMcpPermission
|
|
941
|
+
? (mcpAppApproval?.summary ?? (typeof params.message === "string" ? params.message : "MCP access requested"))
|
|
942
|
+
: typeof params.command === "string"
|
|
943
|
+
? params.command
|
|
944
|
+
: Array.isArray(params.questions)
|
|
945
|
+
? params.questions.map((q) => q.question ?? q.header).filter(Boolean).join(" · ")
|
|
946
|
+
: typeof params.reason === "string"
|
|
947
|
+
? params.reason
|
|
948
|
+
: tool;
|
|
1016
949
|
const choices = isQuestion
|
|
1017
950
|
? (params.questions?.[0]?.options ?? []).map((o) => o.label).slice(0, 5)
|
|
1018
951
|
: undefined;
|
|
@@ -1031,7 +964,8 @@ export const CodexDriver = {
|
|
|
1031
964
|
send({
|
|
1032
965
|
jsonrpc: "2.0",
|
|
1033
966
|
id: msg.id,
|
|
1034
|
-
|
|
967
|
+
// a server's question nobody answered is "no answer", not a no
|
|
968
|
+
result: serverConfirm && source !== "user" ? { action: "cancel" } : permissionResult(behavior === "allow"),
|
|
1035
969
|
});
|
|
1036
970
|
}
|
|
1037
971
|
emit({ ...base(threadId, turnId), type: "request.resolved", requestId, behavior, source });
|
|
@@ -1048,7 +982,7 @@ export const CodexDriver = {
|
|
|
1048
982
|
summary,
|
|
1049
983
|
choices,
|
|
1050
984
|
approvalScope: controlsHost ? "local-computer" : undefined,
|
|
1051
|
-
requiresExplicitApproval: isAdditionalPermission || undefined,
|
|
985
|
+
requiresExplicitApproval: isAdditionalPermission || serverConfirm !== null || undefined,
|
|
1052
986
|
...(mcpToolRef ? { mcpTool: mcpToolRef } : {}),
|
|
1053
987
|
});
|
|
1054
988
|
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Which engines the runtime fleet carries on this OS. The gate itself lives in
|
|
2
|
+
// shared/engine-platforms.ts (e.g. Qwen off on Windows for now); this module
|
|
3
|
+
// applies it to instance maps so server/config.ts stays a config loader.
|
|
4
|
+
import { engineOnPlatform } from "../shared/engine-platforms.js";
|
|
5
|
+
/** Apple Intelligence runs only on Apple silicon Macs; elsewhere the engine
|
|
6
|
+
* is left out of the fleet rather than listed as unavailable. */
|
|
7
|
+
export function onDeviceFleet(platform = process.platform, arch = process.arch) {
|
|
8
|
+
return platform === "darwin" && arch === "arm64"
|
|
9
|
+
? { appleIntelligence: { driver: "appleIntelligence", displayName: "Apple Intelligence (on this Mac)" } }
|
|
10
|
+
: {};
|
|
11
|
+
}
|
|
12
|
+
/** An engine switched off on this OS gets no runtime instance: not in the
|
|
13
|
+
* picker, not installable, and a caller looking one up finds nothing to load.
|
|
14
|
+
* Only the runtime map drops it; a saved entry stays on disk and comes back
|
|
15
|
+
* when the engine is turned on again. Mutates and returns `map`. */
|
|
16
|
+
export function dropEnginesOffPlatform(map, platform) {
|
|
17
|
+
for (const [id, entry] of Object.entries(map)) {
|
|
18
|
+
if (!engineOnPlatform(entry.driver, platform))
|
|
19
|
+
delete map[id];
|
|
20
|
+
}
|
|
21
|
+
return map;
|
|
22
|
+
}
|
|
23
|
+
/** The runtime map leaves out engines switched off on this OS; a save must not
|
|
24
|
+
* erase them from config.json, so saved entries for them go back into `map`
|
|
25
|
+
* exactly as saved. Mutates and returns `map`. */
|
|
26
|
+
export function restoreEnginesOffPlatform(map, saved, platform) {
|
|
27
|
+
for (const [id, entry] of Object.entries(saved ?? {})) {
|
|
28
|
+
if (!Object.hasOwn(map, id) && !engineOnPlatform(entry.driver, platform))
|
|
29
|
+
map[id] = structuredClone(entry);
|
|
30
|
+
}
|
|
31
|
+
return map;
|
|
32
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Who an external (shared) room admits: people, each with their own
|
|
2
|
+
// orchestrator, and no other bot. A guest team's specialists stay home and
|
|
3
|
+
// work through the orchestrator its person brought (Erick's rule, 2026-10-27).
|
|
4
|
+
//
|
|
5
|
+
// Pure: no store, no server. The invite flow supplies the records.
|
|
6
|
+
import { isPrimaryBot } from "../shared/primary-bot.js";
|
|
7
|
+
/** How many people one external room holds, each bringing one orchestrator.
|
|
8
|
+
* One value so it can be raised later. */
|
|
9
|
+
export const EXTERNAL_ROOM_MAX_HUMANS = 4;
|
|
10
|
+
const key = (email) => email.trim().toLowerCase();
|
|
11
|
+
/** Why `email` cannot join, or undefined when it may. */
|
|
12
|
+
export function externalRoomHumanProblem(guests, email) {
|
|
13
|
+
if (guests.some(guest => key(guest.email) === key(email)))
|
|
14
|
+
return undefined;
|
|
15
|
+
if (guests.length >= EXTERNAL_ROOM_MAX_HUMANS)
|
|
16
|
+
return `An external room holds at most ${EXTERNAL_ROOM_MAX_HUMANS} people`;
|
|
17
|
+
}
|
|
18
|
+
/** Why `bot` cannot join, or undefined when it may: it must be the
|
|
19
|
+
* orchestrator one of the room's people brought. */
|
|
20
|
+
export function externalRoomBotProblem(guests, bot) {
|
|
21
|
+
const brought = guests.some(guest => guest.orchestratorId === bot.id);
|
|
22
|
+
if (!brought || !(bot.chiefOfStaff || isPrimaryBot(bot)))
|
|
23
|
+
return "An external room admits people and each person's own orchestrator only";
|
|
24
|
+
}
|