@chibitek/chibi 0.1.155 → 0.1.157
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-B8LdsCr1.js → abnfDiagram-O67JEVCF-BkYbog6D.js} +1 -1
- package/dist/assets/{arc-DU5fSmKU.js → arc-QwTuMsxp.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-DdQSIFKv.js → architectureDiagram-NJMV4G6O-GdicO2PQ.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-CaPcGYSS.js → blockDiagram-BEXU5L5S-Dkr29TFF.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-unt0ePgO.js → c4Diagram-YGBWAQC7-Dz_3dstx.js} +1 -1
- package/dist/assets/channel-D2y0i2ps.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-Babg5Fzu.js → chunk-2Q5K7J3B-CGsfu-PI.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-D1-dWVHo.js → chunk-3FUC2YCW-CDqI37_6.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-DU7nSMbT.js → chunk-5VM5RSS4-DnmSuyTb.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-QIjCJIhV.js → chunk-GTNCS2PH-Dh7tXcJT.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-pSByUE5R.js → chunk-GWA4HPMP-D_SM1ZrY.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-Dj2AaR1b.js → chunk-JWPE2WC7-DmeMuTsI.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-pZbN0-FP.js → chunk-NETBCI7D-R1UT9b5f.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-Bqarwe4D.js → chunk-WEXAMYUT-DpPfU1yO.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-WV8lm3aa.js → chunk-XXDRQBXY-DlihfTkM.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-BANBtky1.js → classDiagram-v2-NBCMYWYE-BlTV6lSC.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-CtrktgBC.js → cose-bilkent-JH36ORCC-Br1kPFWw.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-DnhVVfba.js → cynefin-OW5HDTMX-BqPp3ZMd.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-B42SRR6x.js → cynefinDiagram-VND7K2PF-DFfs_k8-.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-DM--vFMK.js → dagre-6A5THRUB-Ch3xIJA7.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-ZBupBrPU.js → diagram-22UHCM2B-DIHUD87N.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-BPQzNIOd.js → diagram-3UASUU5V-CAgQQdeF.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-5kO8vf8Z.js → diagram-ATOU4E4O-9HVKodrI.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-DtxogB86.js → diagram-CDSNMT55-DU2kDq_h.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-Co9-nYw_.js → diagram-MLGK6HIB-D-qTAhHD.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-DWRuMh5y.js → diagram-MPIPVDR6-C31SnI9P.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-DU_nmoG9.js → ebnfDiagram-ZINNZB2B-BHrdiBVd.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-Bcje7S94.js → elk-276RUBZZ-DWyljRo5.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-9aCo_2CI.js → erDiagram-OPXOYQCR-DyY59XPp.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-D4FYFyc5.js → flowDiagram-KWPJA3E3-BevMIeAy.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-WQHX5rbx.js → ganttDiagram-FUAMR5RP-Ct18WHf4.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-DRyzBdiV.js → gitGraphDiagram-X574FWY7-yhixO1N3.js} +1 -1
- package/dist/assets/{index-CG1OvRgO.js → index-BEJ8EKQt.js} +1 -1
- package/dist/assets/index-DGWeJcck.css +1 -0
- package/dist/assets/{index-D_8SD_0K.js → index-ziyH_uer.js} +170 -168
- package/dist/assets/{infoDiagram-VRGFBTTK-D7SOmp4n.js → infoDiagram-VRGFBTTK-CYa-r_S5.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-CdxOnDVX.js → ishikawaDiagram-OU5B5YK6-CnxwM7gY.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-CH7477Oa.js → journeyDiagram-ZHPQQLJL-RKQfXBrp.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-BdpVvkHd.js → kanban-definition-PNTS6WVX-CSlRDIF6.js} +1 -1
- package/dist/assets/{linear-DTGq6qn5.js → linear-Ch19KVyt.js} +1 -1
- package/dist/assets/{mermaid.core-Sn2sv0Ti.js → mermaid.core-Bek7I_t6.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-koCmkYuq.js → mindmap-definition-NLK3R4M7-B_LiP8su.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-C3sqhm8e.js → pegDiagram-GJSIUBJH-DhzQeiZf.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-BELBK122.js → pieDiagram-5QR66LMP-D-BYNKHS.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-Cksi5LSm.js → quadrantDiagram-O4NWA36T-DnCm0heP.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-f1av_EZW.js → railroadDiagram-XR7U4H2S-COqgYnhn.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-CW4O5nER.js → requirementDiagram-PLB6GJNP-Dvt72ajf.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-Dz8ADHNY.js → sankeyDiagram-IPEJSGJF-CisC9nbp.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-Cc3HE0iZ.js → sequenceDiagram-PO4LG4MO-BO8xqvxa.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-gdaZNHnK.js → sizeCapture-INFHLROL-DLk1jBzf.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK--u8LCnab.js → stateDiagram-v2-GCMORJYK-C_wutSE7.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-4MAw4kdO.js → swimlanes-2SLR337P-hqo1UlEi.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-D15JqTL9.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-CL89YkCT.js → timeline-definition-EJHVYXUP-C_DJldPg.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-C92l7YQe.js → usecaseDiagram-POWQR4AR-CsidVXMA.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-17siYhSj.js → vennDiagram-UO4OBE2U-Bs-IIqn_.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-CNwa9VmN.js → wardleyDiagram-VNRHLVJA-CXEUyxaj.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-Dnu6Xzih.js → xychartDiagram-PMCCYNJV-B9qvmm4P.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/container-mcp.js +81 -81
- package/dist-server/drivers/agents-proxy.js +81 -81
- package/dist-server/index.js +2354 -1729
- package/dist-server/openmausbot.js +676 -643
- package/dist-server/pair-cli.js +676 -643
- package/dist-server/server/app-installers.js +107 -7
- package/dist-server/server/cli-help.js +74 -0
- package/dist-server/server/cli.js +32 -74
- package/dist-server/server/device-routing.js +60 -0
- package/dist-server/server/drivers/claude.js +13 -11
- package/dist-server/server/drivers/local-inject.js +3 -4
- package/dist-server/server/engine-tiers.js +5 -14
- package/dist-server/server/index.js +84 -104
- package/dist-server/server/personal-goal-runtime.js +86 -0
- package/dist-server/server/personal-goal-turn.js +125 -0
- package/dist-server/server/personal-goals.js +195 -0
- package/dist-server/server/routes/local-models.js +3 -2
- package/dist-server/server/skill-fetch.js +14 -0
- package/dist-server/shared/engine-choices.js +15 -0
- package/dist-server/shared/engine-tiers.js +74 -2
- package/dist-server/shared/local-models.js +1 -3
- package/dist-server/shared/personal-goal.js +43 -0
- package/dist-server/vps-container-mcp.js +81 -81
- package/package.json +1 -1
- package/dist/assets/channel-GBaPOaz-.js +0 -1
- package/dist/assets/index-C8YP7Zxj.css +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-CamSa0NE.js +0 -8
- package/dist-server/connector-proxy.js +0 -205
- package/dist-server/server/composio.js +0 -1051
- package/dist-server/server/connector-proxy.js +0 -249
- package/dist-server/server/mcp-public-registry.js +0 -325
- package/dist-server/shared/connector-availability.js +0 -12
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { redactSecretsInText } from "./redact.js";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { writeFileAtomic } from "./atomic.js";
|
|
6
|
+
import { parseGroupGoalDecision } from "./group-goal-run.js";
|
|
7
|
+
const goalSchema = z.object({
|
|
8
|
+
id: z.string(), botId: z.string(), threadId: z.string(), objective: z.string(),
|
|
9
|
+
criteria: z.array(z.string()), maxTurns: z.number().int().positive().max(1000),
|
|
10
|
+
tokenBudget: z.number().int().positive().optional(), tokensUsed: z.number().nonnegative(),
|
|
11
|
+
turnCount: z.number().int().nonnegative(),
|
|
12
|
+
status: z.enum(["working", "paused", "completed", "needs-input", "blocked", "limit-reached", "stopped", "failed"]),
|
|
13
|
+
detail: z.string(), createdAt: z.number(), updatedAt: z.number(),
|
|
14
|
+
trigger: z.union([z.object({ kind: z.literal("owner") }), z.object({ kind: z.literal("user"), email: z.string().optional(), label: z.string().optional() })]).optional(),
|
|
15
|
+
receiptMessageId: z.string().optional(), missing: z.number(), noProgress: z.number(),
|
|
16
|
+
repeated: z.number(), lastReply: z.string(), lastBlocker: z.string(), blockers: z.number(),
|
|
17
|
+
});
|
|
18
|
+
const terminal = (goal) => goal.status === "completed" || goal.status === "stopped";
|
|
19
|
+
/** A single durable objective per conversation. No provider, timer or authority lives here. */
|
|
20
|
+
export class PersonalGoals {
|
|
21
|
+
records;
|
|
22
|
+
loadError;
|
|
23
|
+
path;
|
|
24
|
+
changed;
|
|
25
|
+
constructor(path, changed = () => { }) {
|
|
26
|
+
this.path = path;
|
|
27
|
+
this.changed = changed;
|
|
28
|
+
this.records = [];
|
|
29
|
+
try {
|
|
30
|
+
this.records = existsSync(path) ? z.array(goalSchema).parse(JSON.parse(readFileSync(path, "utf8"))) : [];
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
this.loadError = "Saved goal state could not be read. It has been preserved for recovery; goals are paused.";
|
|
34
|
+
}
|
|
35
|
+
for (const goal of this.records)
|
|
36
|
+
if (goal.status === "working") {
|
|
37
|
+
goal.status = "paused";
|
|
38
|
+
goal.detail = "Atomic restarted. Use /goal resume to continue.";
|
|
39
|
+
goal.updatedAt = Date.now();
|
|
40
|
+
}
|
|
41
|
+
if (this.records.length) {
|
|
42
|
+
this.save();
|
|
43
|
+
this.records.forEach(this.changed);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
save() { writeFileAtomic(this.path, JSON.stringify(this.records), { mode: 0o600 }); }
|
|
47
|
+
touch(goal) { goal.updatedAt = Date.now(); goal.detail = redactSecretsInText(goal.detail); this.changed(goal); this.save(); }
|
|
48
|
+
get(threadId) { return this.records.findLast((goal) => goal.threadId === threadId); }
|
|
49
|
+
byId(id) { return this.records.find((goal) => goal.id === id); }
|
|
50
|
+
start(botId, threadId, input) {
|
|
51
|
+
if (this.loadError)
|
|
52
|
+
throw new Error(this.loadError);
|
|
53
|
+
const previous = this.get(threadId);
|
|
54
|
+
if (previous && !terminal(previous))
|
|
55
|
+
throw new Error("This conversation already has a goal. Resume it or stop it first.");
|
|
56
|
+
const goal = goalSchema.parse({ ...input, objective: redactSecretsInText(input.objective), criteria: input.criteria.map(redactSecretsInText), id: randomUUID(), botId, threadId,
|
|
57
|
+
tokensUsed: 0, turnCount: 0, status: "working", detail: "Starting goal.",
|
|
58
|
+
createdAt: Date.now(), updatedAt: Date.now(), missing: 0, noProgress: 0, repeated: 0, lastReply: "", lastBlocker: "", blockers: 0 });
|
|
59
|
+
this.records.push(goal);
|
|
60
|
+
this.touch(goal);
|
|
61
|
+
return goal;
|
|
62
|
+
}
|
|
63
|
+
control(threadId, action) {
|
|
64
|
+
const goal = this.get(threadId);
|
|
65
|
+
if (!goal)
|
|
66
|
+
throw new Error("This conversation has no goal.");
|
|
67
|
+
if (!terminal(goal)) {
|
|
68
|
+
goal.status = action === "pause" ? "paused" : "stopped";
|
|
69
|
+
goal.detail = action === "pause" ? "Paused by you." : "Stopped by you.";
|
|
70
|
+
this.touch(goal);
|
|
71
|
+
}
|
|
72
|
+
return goal;
|
|
73
|
+
}
|
|
74
|
+
resume(threadId, budgets = {}) {
|
|
75
|
+
const goal = this.get(threadId);
|
|
76
|
+
if (!goal || terminal(goal))
|
|
77
|
+
throw new Error("There is no unfinished goal to resume.");
|
|
78
|
+
if (goal.status === "working")
|
|
79
|
+
throw new Error("This goal is already working.");
|
|
80
|
+
const maxTurns = budgets.maxTurns ?? goal.maxTurns;
|
|
81
|
+
const tokenBudget = budgets.tokenBudget ?? goal.tokenBudget;
|
|
82
|
+
if (goal.turnCount >= maxTurns || (tokenBudget !== undefined && goal.tokensUsed >= tokenBudget)) {
|
|
83
|
+
throw new Error("The goal has reached its budget. Resume with a higher --turns or --tokens total.");
|
|
84
|
+
}
|
|
85
|
+
goal.maxTurns = maxTurns;
|
|
86
|
+
goal.tokenBudget = tokenBudget;
|
|
87
|
+
goal.status = "working";
|
|
88
|
+
goal.detail = "Resuming. Inspect current state before continuing.";
|
|
89
|
+
goal.missing = goal.noProgress = goal.repeated = goal.blockers = 0;
|
|
90
|
+
goal.lastReply = goal.lastBlocker = "";
|
|
91
|
+
this.touch(goal);
|
|
92
|
+
return goal;
|
|
93
|
+
}
|
|
94
|
+
begin(id) {
|
|
95
|
+
const goal = this.byId(id);
|
|
96
|
+
if (!goal || goal.status !== "working")
|
|
97
|
+
return false;
|
|
98
|
+
if (goal.turnCount >= goal.maxTurns || (goal.tokenBudget !== undefined && goal.tokensUsed >= goal.tokenBudget)) {
|
|
99
|
+
goal.status = "limit-reached";
|
|
100
|
+
goal.detail = "Goal budget reached.";
|
|
101
|
+
this.touch(goal);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
goal.turnCount++;
|
|
105
|
+
this.touch(goal);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
settle(id, result) {
|
|
109
|
+
const goal = this.byId(id);
|
|
110
|
+
if (!goal)
|
|
111
|
+
return;
|
|
112
|
+
if (result.tokens !== undefined && Number.isFinite(result.tokens))
|
|
113
|
+
goal.tokensUsed += Math.max(0, result.tokens);
|
|
114
|
+
if (goal.status !== "working") {
|
|
115
|
+
this.touch(goal);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const parsed = parseGroupGoalDecision(result.text);
|
|
119
|
+
const decision = parsed.decision;
|
|
120
|
+
const reply = createHash("sha256").update(parsed.visibleText.trim()).digest("hex");
|
|
121
|
+
if (decision?.status !== "blocked") {
|
|
122
|
+
goal.blockers = 0;
|
|
123
|
+
goal.lastBlocker = "";
|
|
124
|
+
}
|
|
125
|
+
goal.repeated = reply && reply === goal.lastReply ? goal.repeated + 1 : 1;
|
|
126
|
+
goal.lastReply = reply;
|
|
127
|
+
goal.noProgress = result.tools ? 0 : goal.noProgress + 1;
|
|
128
|
+
goal.missing = decision ? 0 : goal.missing + 1;
|
|
129
|
+
if (!result.ok) {
|
|
130
|
+
goal.status = "paused";
|
|
131
|
+
goal.detail = "The turn did not finish successfully. Resume when ready.";
|
|
132
|
+
}
|
|
133
|
+
else if (goal.tokenBudget !== undefined && result.tokens === undefined) {
|
|
134
|
+
goal.status = "paused";
|
|
135
|
+
goal.detail = "The engine did not report token usage. The token budget cannot be verified.";
|
|
136
|
+
}
|
|
137
|
+
else if (decision?.status === "completed") {
|
|
138
|
+
// Evidence is reported by the worker, never presented as an independent verification.
|
|
139
|
+
const close = result.text.lastIndexOf("</openmaus-goal>");
|
|
140
|
+
const open = result.text.lastIndexOf("<openmaus-goal>", close);
|
|
141
|
+
const payload = open < 0 ? "{}" : result.text.slice(open + "<openmaus-goal>".length, close);
|
|
142
|
+
let evidence;
|
|
143
|
+
try {
|
|
144
|
+
evidence = JSON.parse(payload ?? "{}").evidence;
|
|
145
|
+
}
|
|
146
|
+
catch { /* malformed evidence cannot complete */ }
|
|
147
|
+
if (goal.criteria.length && (!Array.isArray(evidence) || evidence.length !== goal.criteria.length || evidence.some((item) => typeof item !== "string" || !item.trim()))) {
|
|
148
|
+
goal.detail = "Completion needs evidence for every criterion. Verify the remaining criteria.";
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
goal.status = "completed";
|
|
152
|
+
goal.detail = decision.detail;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else if (decision?.status === "needs-input") {
|
|
156
|
+
goal.status = "needs-input";
|
|
157
|
+
goal.detail = decision.detail;
|
|
158
|
+
}
|
|
159
|
+
else if (decision?.status === "blocked") {
|
|
160
|
+
const blocker = createHash("sha256").update(decision.detail.trim().toLowerCase()).digest("hex");
|
|
161
|
+
goal.blockers = blocker === goal.lastBlocker ? goal.blockers + 1 : 1;
|
|
162
|
+
goal.lastBlocker = blocker;
|
|
163
|
+
goal.detail = goal.blockers >= 3 ? decision.detail : `Try an alternative and verify the blocker: ${decision.detail}`;
|
|
164
|
+
if (goal.blockers >= 3)
|
|
165
|
+
goal.status = "blocked";
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
goal.detail = decision?.detail ?? "Continue the objective and report a valid goal status.";
|
|
169
|
+
}
|
|
170
|
+
if (goal.status === "working") {
|
|
171
|
+
if (goal.turnCount >= goal.maxTurns || (goal.tokenBudget !== undefined && goal.tokensUsed >= goal.tokenBudget)) {
|
|
172
|
+
goal.status = "limit-reached";
|
|
173
|
+
goal.detail = "Goal budget reached. Resume with a higher total to continue.";
|
|
174
|
+
}
|
|
175
|
+
else if (goal.missing >= 3 || goal.noProgress >= 3 || goal.repeated >= 3) {
|
|
176
|
+
goal.status = "paused";
|
|
177
|
+
goal.detail = "No measurable progress across three turns. Resume with more direction.";
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
this.touch(goal);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
export function personalGoalInstructions(goal) {
|
|
184
|
+
return [
|
|
185
|
+
"You are pursuing this conversation's durable goal. Complete authorized work; ordinary turn completion does not end the goal.",
|
|
186
|
+
`Objective: ${goal.objective}`,
|
|
187
|
+
`Completion criteria: ${JSON.stringify(goal.criteria)}. Supply one non-empty evidence string per criterion when completed.`,
|
|
188
|
+
`Turn ${goal.turnCount}/${goal.maxTurns}. Tokens used: ${goal.tokensUsed}; budget: ${goal.tokenBudget ?? "not set"}.`,
|
|
189
|
+
`Progress: ${goal.detail}`,
|
|
190
|
+
"Existing permissions and approval requirements still apply. Never claim completion from intent alone. Ask for required human input using needs-input.",
|
|
191
|
+
'End with a private envelope: <openmaus-goal>{"status":"continue","next":"self","instruction":"next concrete step","detail":"progress"}</openmaus-goal>',
|
|
192
|
+
'Or status completed, needs-input or blocked with detail and no next/instruction. For completed include "evidence":["verification for criterion 1", ...].',
|
|
193
|
+
"Write useful visible results before the envelope. Never show or explain the envelope. On resume inspect current state before acting.",
|
|
194
|
+
].join("\n");
|
|
195
|
+
}
|
|
@@ -9,6 +9,7 @@ import { existsSync } from "node:fs";
|
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { delimiter, join } from "node:path";
|
|
11
11
|
import { isOllamaCloudModel, validLocalModelTag } from "../../shared/local-models.js";
|
|
12
|
+
import { ollamaBinaryHere } from "../app-installers.js";
|
|
12
13
|
import { PASS } from "./table.js";
|
|
13
14
|
export const OLLAMA_LOCAL_URL = "http://127.0.0.1:11434";
|
|
14
15
|
export const OLLAMA_DOWNLOAD_URL = "https://ollama.com/download";
|
|
@@ -77,9 +78,9 @@ export function installedModelsFromTags(payload, bots, engines) {
|
|
|
77
78
|
}
|
|
78
79
|
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
79
80
|
}
|
|
80
|
-
/** The Ollama app, or the `ollama` command on PATH. */
|
|
81
|
+
/** The Ollama app, Atomic's own install of it, or the `ollama` command on PATH. */
|
|
81
82
|
export function ollamaInstalledHere(env = process.env) {
|
|
82
|
-
const apps = process.platform === "darwin" ? ["/Applications/Ollama.app", join(homedir(), "Applications", "Ollama.app")] : [];
|
|
83
|
+
const apps = process.platform === "darwin" ? ["/Applications/Ollama.app", join(homedir(), "Applications", "Ollama.app")] : [ollamaBinaryHere()].filter((path) => Boolean(path));
|
|
83
84
|
if (apps.some((path) => existsSync(path)))
|
|
84
85
|
return true;
|
|
85
86
|
const names = process.platform === "win32" ? ["ollama.exe"] : ["ollama"];
|
|
@@ -11,6 +11,20 @@ import { z } from "zod";
|
|
|
11
11
|
const MAX_FILES = 30;
|
|
12
12
|
const MAX_FILE_BYTES = 256 * 1024;
|
|
13
13
|
const API = "https://api.github.com";
|
|
14
|
+
// Upload bytes, never a client-supplied path on the server's filesystem.
|
|
15
|
+
export const skillImportSchema = z.union([
|
|
16
|
+
z.object({ source: z.string().min(1).max(2000) }).strict(),
|
|
17
|
+
z.object({ file: z.object({
|
|
18
|
+
name: z.string().min(1).max(255).regex(/^[^/\\]+\.md$/i, "Choose a Markdown (.md) skill file")
|
|
19
|
+
.refine(name => [...name].every(char => char.charCodeAt(0) >= 32 && char.charCodeAt(0) !== 127), "Invalid filename"),
|
|
20
|
+
text: z.string().min(1).refine(text => Buffer.byteLength(text, "utf8") <= MAX_FILE_BYTES, "Skill exceeds 256 KiB"),
|
|
21
|
+
}).strict() }).strict(),
|
|
22
|
+
]);
|
|
23
|
+
export async function resolveSkillImport(input) {
|
|
24
|
+
if ("source" in input)
|
|
25
|
+
return fetchSkillFromSource(input.source);
|
|
26
|
+
return { skills: [{ source: `file:${input.file.name}`, files: [{ path: "SKILL.md", content: input.file.text }] }] };
|
|
27
|
+
}
|
|
14
28
|
class ImportLimitError extends Error {
|
|
15
29
|
}
|
|
16
30
|
// One budget for the entire import, including directory discovery. Read the
|
|
@@ -295,6 +295,21 @@ export function choiceFor(choices, engine, instanceId, model) {
|
|
|
295
295
|
}
|
|
296
296
|
return mine.find((choice) => !choice.host) ?? mine[0];
|
|
297
297
|
}
|
|
298
|
+
/** Back to "any account of this provider": the rung names the driver again
|
|
299
|
+
* (a ladder rung may name a driver kind), keeping the rung's model when the
|
|
300
|
+
* driver still offers it. When that exact route is already on the ladder,
|
|
301
|
+
* the driver's first model no rung uses takes its place; undefined when no
|
|
302
|
+
* free route is left, which leaves the rung unchanged. */
|
|
303
|
+
export function anyAccountEntry(entry, engine, others) {
|
|
304
|
+
if (!engine)
|
|
305
|
+
return undefined;
|
|
306
|
+
const used = (model) => others.some((other) => other.engine === engine.driverKind && other.model === model);
|
|
307
|
+
const keep = entry.model && engine.models.some((model) => model.id === entry.model) ? entry.model : undefined;
|
|
308
|
+
if (!used(keep))
|
|
309
|
+
return keep ? { engine: engine.driverKind, model: keep } : { engine: engine.driverKind };
|
|
310
|
+
const alternate = engine.models.map((model) => model.id).find((id) => !used(id));
|
|
311
|
+
return alternate ? { engine: engine.driverKind, model: alternate } : undefined;
|
|
312
|
+
}
|
|
298
313
|
/** Providers with accounts the person should name: brand and how many accounts it has. */
|
|
299
314
|
export function accountsToName(choices) {
|
|
300
315
|
const out = new Map();
|
|
@@ -29,6 +29,77 @@ export function toModelClass(value) {
|
|
|
29
29
|
/** The class one below, or undefined for Añejo. */
|
|
30
30
|
export const classBelow = (modelClass) => MODEL_CLASSES[MODEL_CLASSES.indexOf(modelClass) + 1];
|
|
31
31
|
export const DEFAULT_CLASS_BANDS = { Mezcal: 50, Plata: 44, Reposado: 38 };
|
|
32
|
+
/** Hand-written ladders, used only when no model has a class yet (the
|
|
33
|
+
* server always has the bundled baseline, so in practice the generated
|
|
34
|
+
* ladders below apply). Pay-per-token engines need no rung: they form the
|
|
35
|
+
* automatic last band of every ladder (resolveLadder).
|
|
36
|
+
*
|
|
37
|
+
* Erick's own ladders (2026-10-25), each per-install instance id
|
|
38
|
+
* ("claude-<uuid>") reduced to its driver kind so a fresh install matches
|
|
39
|
+
* whatever account it has (matchesEngine); the exact engine+model
|
|
40
|
+
* duplicates that collapse created are dropped. */
|
|
41
|
+
export const DEFAULT_LADDERS = {
|
|
42
|
+
orchestrator: [
|
|
43
|
+
{ engine: "claudeAgent", model: "claude-opus-5-5", modelClass: "Plata" },
|
|
44
|
+
{ engine: "codex", model: "gpt-6-sol" },
|
|
45
|
+
{ engine: "opencodeGo", model: "opencode-go/gemini-4-argon" },
|
|
46
|
+
{ engine: "hermes", model: "nous:x-ai/grok-4.6" },
|
|
47
|
+
{ engine: "ollamaCloud", model: "glm-5.3" },
|
|
48
|
+
{ engine: "museCode", model: "muse-spark-1.2" },
|
|
49
|
+
{ engine: "hermesAgent", model: "openai-codex:gpt-6-sol", modelClass: "Plata" },
|
|
50
|
+
{ engine: "antigravity", model: "gemini-3.8-flash-medium" },
|
|
51
|
+
{ engine: "cursorAgent", model: "auto" },
|
|
52
|
+
{ engine: "hermesAgent", model: "huggingface:deepseek-ai/DeepSeek-R1-0528", modelClass: "Plata" },
|
|
53
|
+
{ engine: "hermesAgent", model: "opencode-go:deepseek-v4-flash", modelClass: "Plata" },
|
|
54
|
+
{ engine: "hermesAgent", model: "deepinfra:anthropic/claude-opus-5", modelClass: "Plata" },
|
|
55
|
+
{ engine: "hermesAgent", model: "nous:meta/muse-spark-1.3", modelClass: "Plata" },
|
|
56
|
+
{ engine: "hermesAgent", model: "openrouter:openai/gpt-6-sol", modelClass: "Plata" },
|
|
57
|
+
{ engine: "hermesAgent", model: "openai-codex:gpt-6.1-sol", modelClass: "Plata" },
|
|
58
|
+
],
|
|
59
|
+
engineering: [
|
|
60
|
+
{ engine: "codex", model: "gpt-6-astra", modelClass: "Mezcal" },
|
|
61
|
+
{ engine: "claude", model: "claude-opus-5-5" },
|
|
62
|
+
{ engine: "claudeAgent", model: "claude-opus-5-5" },
|
|
63
|
+
{ engine: "opencodeGo", model: "opencode-go/gemini-4-argon" },
|
|
64
|
+
{ engine: "ollamaCloud", model: "kimi-k3" },
|
|
65
|
+
{ engine: "hermesAgent", model: "openai-codex:gpt-6-astra", modelClass: "Mezcal" },
|
|
66
|
+
{ engine: "grok", model: "grok-4.7" },
|
|
67
|
+
{ engine: "grokAgent", model: "grok-4.7" },
|
|
68
|
+
{ engine: "kimi", model: "kimi-code/k3" },
|
|
69
|
+
{ engine: "openai-compat", model: "qwen3.7-plus" },
|
|
70
|
+
{ engine: "hermesAgent", model: "nous:anthropic/claude-opus-5.5" },
|
|
71
|
+
{ engine: "hermesAgent", model: "opencode-zen:claude-fable-5-1", modelClass: "Mezcal" },
|
|
72
|
+
{ engine: "cursorAgent", model: "auto" },
|
|
73
|
+
],
|
|
74
|
+
research: [
|
|
75
|
+
{ engine: "openai-compat", model: "MiniMax-M2.5" },
|
|
76
|
+
{ engine: "vibe", model: "mistral-medium-3.5" },
|
|
77
|
+
{ engine: "antigravityAgent", model: "gemini-3.8-flash-low" },
|
|
78
|
+
{ engine: "cursorAgent", model: "auto" },
|
|
79
|
+
{ engine: "hermesAgent", model: "openrouter:x-ai/grok-4.7", modelClass: "Plata" },
|
|
80
|
+
{ engine: "hermesAgent", model: "custom:local-(localhost:11434):deepseek-v4-pro:cloud" },
|
|
81
|
+
{ engine: "hermesAgent", model: "openrouter:meta/muse-spark-1.3", modelClass: "Plata" },
|
|
82
|
+
{ engine: "hermesAgent", model: "opencode-zen:muse-spark-1.3", modelClass: "Plata" },
|
|
83
|
+
{ engine: "claudeAgent", model: "claude-opus-5", modelClass: "Plata" },
|
|
84
|
+
{ engine: "cursorAgent", model: "claude-opus-5-high", modelClass: "Plata" },
|
|
85
|
+
{ engine: "opencodeGo", model: "opencode/claude-opus-5", modelClass: "Plata" },
|
|
86
|
+
],
|
|
87
|
+
writing: [
|
|
88
|
+
{ engine: "claudeAgent", model: "claude-sonnet-5-5" },
|
|
89
|
+
{ engine: "claude", model: "claude-sonnet-5-5" },
|
|
90
|
+
{ engine: "codex", model: "gpt-5.6-luna" },
|
|
91
|
+
{ engine: "antigravity", model: "gemini-3.8-flash-high" },
|
|
92
|
+
{ engine: "antigravityAgent", model: "gemini-3.8-flash-high" },
|
|
93
|
+
{ engine: "hermes", model: "nous:moonshotai/kimi-k3" },
|
|
94
|
+
],
|
|
95
|
+
routine: [
|
|
96
|
+
{ engine: "antigravityAgent", model: "gemini-3.8-flash-low" },
|
|
97
|
+
{ engine: "antigravity", model: "gemini-3.8-flash-low" },
|
|
98
|
+
{ engine: "openai-compat", model: "MiniMax-M2.5" },
|
|
99
|
+
{ engine: "cursor", model: "auto" },
|
|
100
|
+
{ engine: "mistral", model: "mistral-medium", modelClass: "Añejo" },
|
|
101
|
+
],
|
|
102
|
+
};
|
|
32
103
|
export function classForScore(score, bands = DEFAULT_CLASS_BANDS) {
|
|
33
104
|
if (score >= bands.Mezcal)
|
|
34
105
|
return "Mezcal";
|
|
@@ -45,9 +116,10 @@ export const DEFAULT_TIER_CLASSES = {
|
|
|
45
116
|
// puts Claude's rungs first in its ladder.
|
|
46
117
|
orchestrator: "Plata",
|
|
47
118
|
engineering: "Mezcal",
|
|
48
|
-
|
|
119
|
+
// Research and Routine ask for the cheapest class (Erick, 2026-10-25).
|
|
120
|
+
research: "Añejo",
|
|
49
121
|
writing: "Plata",
|
|
50
|
-
routine: "
|
|
122
|
+
routine: "Añejo",
|
|
51
123
|
};
|
|
52
124
|
/** A person's class choice at an API boundary: a class, or null for
|
|
53
125
|
* "Automatic (by tier)"; undefined when the value is neither. */
|
|
@@ -53,9 +53,7 @@ export const LIGHT_LOCAL_MODEL = "gemma4:e2b-it-qat";
|
|
|
53
53
|
const SIXTEEN_GB = 15.5 * GB;
|
|
54
54
|
const EIGHT_GB = 7.5 * GB;
|
|
55
55
|
export function recommendLocalModel(machine) {
|
|
56
|
-
if (machine.platform
|
|
57
|
-
return { kind: "none", reason: "notMac" };
|
|
58
|
-
if (machine.arch !== "arm64")
|
|
56
|
+
if (machine.platform === "darwin" && machine.arch !== "arm64")
|
|
59
57
|
return { kind: "none", reason: "intel" };
|
|
60
58
|
const model = (tag) => SUGGESTED_LOCAL_MODELS.find((entry) => entry.tag === tag);
|
|
61
59
|
if (machine.memoryBytes >= SIXTEEN_GB)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function parseGoalCommand(text) {
|
|
2
|
+
const match = /^\/goal(?:\s+([\s\S]*))?$/i.exec(text.trim());
|
|
3
|
+
if (!match)
|
|
4
|
+
return null;
|
|
5
|
+
let rest = (match[1] ?? "").trim();
|
|
6
|
+
const action = /^(status|pause|resume|stop|help)(?:\s|$)/i.exec(rest);
|
|
7
|
+
if (action)
|
|
8
|
+
rest = rest.slice(action[0].length).trim();
|
|
9
|
+
let maxTurns;
|
|
10
|
+
let tokenBudget;
|
|
11
|
+
const criteria = [];
|
|
12
|
+
while (rest.startsWith("--")) {
|
|
13
|
+
const flag = /^--(turns|tokens|done)\s+(?:"([^"\n]+)"|'([^'\n]+)'|(\S+))(?:\s|$)/.exec(rest);
|
|
14
|
+
if (!flag)
|
|
15
|
+
throw new Error('Use --turns N, --tokens N, or --done "completion criterion" before the objective.');
|
|
16
|
+
const value = flag[2] ?? flag[3] ?? flag[4];
|
|
17
|
+
if (flag[1] === "done")
|
|
18
|
+
criteria.push(value);
|
|
19
|
+
else {
|
|
20
|
+
const number = Number(value);
|
|
21
|
+
if (!/^\d+$/.test(value) || !Number.isSafeInteger(number) || number < 1 || (flag[1] === "turns" && number > 1000)) {
|
|
22
|
+
throw new Error("Budgets must be positive whole numbers; the turn limit cannot exceed 1000.");
|
|
23
|
+
}
|
|
24
|
+
if (flag[1] === "turns")
|
|
25
|
+
maxTurns = number;
|
|
26
|
+
else
|
|
27
|
+
tokenBudget = number;
|
|
28
|
+
}
|
|
29
|
+
rest = rest.slice(flag[0].length).trim();
|
|
30
|
+
}
|
|
31
|
+
if (action) {
|
|
32
|
+
if (rest || criteria.length || (action[1].toLowerCase() !== "resume" && (maxTurns || tokenBudget))) {
|
|
33
|
+
throw new Error("Only /goal resume accepts --turns and --tokens; other controls take no arguments.");
|
|
34
|
+
}
|
|
35
|
+
return { action: action[1].toLowerCase(), maxTurns, tokenBudget };
|
|
36
|
+
}
|
|
37
|
+
if (!rest)
|
|
38
|
+
return { action: "help" };
|
|
39
|
+
if (rest.length > 20_000 || criteria.length > 20 || criteria.some((item) => item.length > 2000))
|
|
40
|
+
throw new Error("Goal or completion criteria are too long.");
|
|
41
|
+
return { action: "start", objective: rest, maxTurns: maxTurns ?? 20, tokenBudget, criteria };
|
|
42
|
+
}
|
|
43
|
+
export const GOAL_HELP = 'Start: /goal [--turns 20] [--tokens N] [--done "criterion"] objective\nControls: /goal status, /goal pause, /goal resume [--turns N] [--tokens N], /goal stop. Resume budgets are total ceilings, not additional allowances. Token limits are checked between turns; a running turn can exceed the limit.';
|