isorite 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/main.js +684 -116
- package/docs/setup-for-agents.md +4 -0
- package/docs/setup.md +20 -0
- package/docs/terminal.md +15 -0
- package/package.json +1 -1
- package/plugin/server.ts +4 -2
- package/web/_app/immutable/assets/{0.BKnIhFWM.css → 0.CTbO0owi.css} +1 -1
- package/web/_app/immutable/chunks/B2TCcw-P.js +6 -0
- package/web/_app/immutable/chunks/{BoNMG-YE.js → B4sg4e8i.js} +1 -1
- package/web/_app/immutable/chunks/BAkGB2fM.js +1 -0
- package/web/_app/immutable/chunks/{Cn-vtDpV.js → C1hqerMA.js} +1 -1
- package/web/_app/immutable/chunks/{CVbgxz8Y.js → CMTjeXEe.js} +1 -1
- package/web/_app/immutable/chunks/{WBFggAXs.js → C_ER1Any.js} +1 -1
- package/web/_app/immutable/chunks/{B7GA1FFB.js → Cgml88Pl.js} +1 -1
- package/web/_app/immutable/chunks/CurmVJyp.js +1 -0
- package/web/_app/immutable/chunks/{DHfKGggc.js → D29fKEzc.js} +1 -1
- package/web/_app/immutable/chunks/{62dhNtvj.js → D3BEa5-_.js} +3 -3
- package/web/_app/immutable/chunks/{CYNZ4aV_.js → DPnfDiTn.js} +1 -1
- package/web/_app/immutable/chunks/{DsBJWNWe.js → DsIcKd8x.js} +1 -1
- package/web/_app/immutable/chunks/DtPULAe0.js +1 -0
- package/web/_app/immutable/chunks/{CWmfjLLv.js → yXUzBjUq.js} +1 -1
- package/web/_app/immutable/entry/{app.Dt52eXt2.js → app.idRgnoYp.js} +2 -2
- package/web/_app/immutable/entry/start.CEx_UQO6.js +1 -0
- package/web/_app/immutable/nodes/0.BUd_gY_8.js +26 -0
- package/web/_app/immutable/nodes/{1.B5qkcrvB.js → 1.DHK7VmG0.js} +1 -1
- package/web/_app/immutable/nodes/2.CDAt-6SJ.js +1 -0
- package/web/_app/immutable/nodes/3.4Wv5nmaw.js +1 -0
- package/web/_app/immutable/nodes/{4.BhNJiqT-.js → 4.Ceb5g4Ps.js} +1 -1
- package/web/_app/immutable/nodes/{5.DXTbGemS.js → 5.DioYOANs.js} +1 -1
- package/web/_app/immutable/nodes/6.CzYfNb6g.js +1 -0
- package/web/_app/version.json +1 -1
- package/web/index.html +15 -15
- package/web/_app/immutable/chunks/B3dCo0od.js +0 -1
- package/web/_app/immutable/chunks/BNjH_DUx.js +0 -1
- package/web/_app/immutable/chunks/D6Vzg2HD.js +0 -6
- package/web/_app/immutable/chunks/DkvLWnSd.js +0 -1
- package/web/_app/immutable/entry/start.mjqIl2PI.js +0 -1
- package/web/_app/immutable/nodes/0.DIhIOwuo.js +0 -26
- package/web/_app/immutable/nodes/2.CK3q7brK.js +0 -1
- package/web/_app/immutable/nodes/3.CrUewa31.js +0 -1
- package/web/_app/immutable/nodes/6.BZWaFgFP.js +0 -1
package/dist/main.js
CHANGED
|
@@ -1622,7 +1622,7 @@ function locate$1(note, p) {
|
|
|
1622
1622
|
}
|
|
1623
1623
|
/** The note with a proposal written in. For a rewrite, `region` writes that one alone; without it, every region
|
|
1624
1624
|
* still open. Null when the place (or, for a rewrite, any of the places asked for) cannot be found. */
|
|
1625
|
-
function written(note, p, region) {
|
|
1625
|
+
function written$1(note, p, region) {
|
|
1626
1626
|
if (p.form === "rewrite") {
|
|
1627
1627
|
const places = (region !== void 0 ? [region] : p.open ?? p.regions.map((_, k) => k)).map((k) => ({
|
|
1628
1628
|
k,
|
|
@@ -2704,6 +2704,43 @@ say plainly what you would need to look up to say more.`;
|
|
|
2704
2704
|
}));
|
|
2705
2705
|
//#endregion
|
|
2706
2706
|
//#region packages/engine/src/lookup.ts
|
|
2707
|
+
/** Which base a call names, and its arguments for that base: `base`, or a note's id written `memory:tools/x`. */
|
|
2708
|
+
function baseOf(args) {
|
|
2709
|
+
const { base, ...rest } = args;
|
|
2710
|
+
let named = typeof base === "string" ? base.trim() : "";
|
|
2711
|
+
for (const k of ["id", "note"]) {
|
|
2712
|
+
const m = typeof rest[k] === "string" ? /^([a-z0-9][a-z0-9-]{0,39}):(?!\/\/)(.+)$/.exec(rest[k]) : null;
|
|
2713
|
+
if (m) {
|
|
2714
|
+
named ||= m[1];
|
|
2715
|
+
if (m[1] === named) rest[k] = m[2];
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
return {
|
|
2719
|
+
base: named,
|
|
2720
|
+
args: rest
|
|
2721
|
+
};
|
|
2722
|
+
}
|
|
2723
|
+
/** The tools' definitions with `base`, for a request that has other bases lent to it. */
|
|
2724
|
+
function withBase(defs, names, always = false) {
|
|
2725
|
+
if (!names.length && !always) return defs;
|
|
2726
|
+
const base = {
|
|
2727
|
+
type: "string",
|
|
2728
|
+
description: `Another knowledge base lent to you, by name: ${names.length ? names.join(", ") : "one brief_base names"}. This base when not given. A note there may also be named <base>:folder/name.`
|
|
2729
|
+
};
|
|
2730
|
+
return defs.map((d) => ({
|
|
2731
|
+
...d,
|
|
2732
|
+
function: {
|
|
2733
|
+
...d.function,
|
|
2734
|
+
parameters: {
|
|
2735
|
+
...d.function.parameters,
|
|
2736
|
+
properties: {
|
|
2737
|
+
...d.function.parameters.properties,
|
|
2738
|
+
base
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
}));
|
|
2743
|
+
}
|
|
2707
2744
|
/** A note's id from what a model writes for one: /basics/entropy.md, basics/entropy, [x](/basics/entropy.md). */
|
|
2708
2745
|
function noteId(b, raw) {
|
|
2709
2746
|
const id = String(raw ?? "").trim().replace(/^.*\]\(/, "").replace(/\)$/, "").replace(/\s+"[^"]*"$/, "").split("#")[0].replace(/^\/+/, "").replace(/\.md$/i, "");
|
|
@@ -2779,17 +2816,19 @@ var init_lookup = __esmMin((() => {
|
|
|
2779
2816
|
named = /* @__PURE__ */ new Map();
|
|
2780
2817
|
b;
|
|
2781
2818
|
similar;
|
|
2782
|
-
|
|
2819
|
+
away;
|
|
2820
|
+
constructor(b, note, similar = null, away = null) {
|
|
2783
2821
|
this.b = b;
|
|
2784
2822
|
this.held = [note];
|
|
2785
2823
|
this.similar = similar;
|
|
2824
|
+
this.away = away;
|
|
2786
2825
|
}
|
|
2787
2826
|
defs() {
|
|
2788
|
-
return this.similar ? [
|
|
2827
|
+
return withBase(this.similar ? [
|
|
2789
2828
|
TOOLS[0],
|
|
2790
2829
|
FIND_SIMILAR,
|
|
2791
2830
|
...TOOLS.slice(1)
|
|
2792
|
-
] : TOOLS;
|
|
2831
|
+
] : TOOLS, this.away?.lent().map((l) => l.name) ?? []);
|
|
2793
2832
|
}
|
|
2794
2833
|
/** The notes opened, each once, in order. */
|
|
2795
2834
|
read() {
|
|
@@ -2809,6 +2848,19 @@ var init_lookup = __esmMin((() => {
|
|
|
2809
2848
|
notes: []
|
|
2810
2849
|
};
|
|
2811
2850
|
let text;
|
|
2851
|
+
const there = baseOf(args);
|
|
2852
|
+
if (there.base) {
|
|
2853
|
+
try {
|
|
2854
|
+
text = await this.elsewhere(name, there.base, there.args, step);
|
|
2855
|
+
} catch (err) {
|
|
2856
|
+
text = this.fail(step, `That could not be looked up: ${err.message}`);
|
|
2857
|
+
}
|
|
2858
|
+
this.steps.push(step);
|
|
2859
|
+
return {
|
|
2860
|
+
text,
|
|
2861
|
+
step
|
|
2862
|
+
};
|
|
2863
|
+
}
|
|
2812
2864
|
try {
|
|
2813
2865
|
text = name === "search_notes" ? this.search(args, step) : name === "find_similar" ? await this.meaning(args, step) : name === "outline_note" ? this.outline(args, step) : name === "read_note" ? this.section(args, step) : this.fail(step, `There is no tool ${name}.`);
|
|
2814
2866
|
} catch (err) {
|
|
@@ -2820,6 +2872,22 @@ var init_lookup = __esmMin((() => {
|
|
|
2820
2872
|
step
|
|
2821
2873
|
};
|
|
2822
2874
|
}
|
|
2875
|
+
/** A look in another base lent to the request: what that base gives, said as being from there. */
|
|
2876
|
+
async elsewhere(name, base, args, step) {
|
|
2877
|
+
const lent = this.away?.lent().map((l) => l.name) ?? [];
|
|
2878
|
+
if (!lent.includes(base)) return this.fail(step, lent.length ? `There is no base ${base} lent here: the bases are ${lent.join(", ")}. Leave base out for this one.` : `There is no base ${base} lent here: leave base out.`);
|
|
2879
|
+
const note = typeof args.id === "string" ? args.id.replace(/^\/+/, "").replace(/\.md$/i, "") : void 0;
|
|
2880
|
+
step.how = name === "search_notes" ? "keyword" : name === "find_similar" ? "meaning" : "read";
|
|
2881
|
+
step.away = {
|
|
2882
|
+
base,
|
|
2883
|
+
...note ? { note } : {},
|
|
2884
|
+
...typeof args.section === "string" && args.section ? { section: args.section } : {}
|
|
2885
|
+
};
|
|
2886
|
+
const query = String(args.query ?? args.text ?? "").trim();
|
|
2887
|
+
if (query) step.query = query;
|
|
2888
|
+
step.said = note ? `Read ${note} in ${base}` : query ? `Searched ${base} for "${query}"` : `Looked in ${base}`;
|
|
2889
|
+
return this.away.look(base, name, args);
|
|
2890
|
+
}
|
|
2823
2891
|
fail(step, why) {
|
|
2824
2892
|
step.failed = true;
|
|
2825
2893
|
step.said = why;
|
|
@@ -3147,6 +3215,7 @@ function turnMessages(a) {
|
|
|
3147
3215
|
const held = reads.length ? `# Notes you read earlier in this conversation\n\n${reads.map(sentText).join("\n\n")}` : "";
|
|
3148
3216
|
const aside = a.aside?.length ? `# Read earlier, set aside\n\n${a.aside.map((n) => `- ${n.title} (/${n.id}.md)`).join("\n")}` : "";
|
|
3149
3217
|
const agents = a.agents?.trim() ? `# Their instructions for this knowledge base (AGENTS.md)\n\n${a.agents.trim()}` : "";
|
|
3218
|
+
const lent = a.lookup && a.lent?.length ? `# Other knowledge bases lent to you\n\nYou may read these with the same look-up tools, giving their base; you change notes only in this one. Look in them where what is asked may be there: how the person works, their tools, what was settled before.\n\n${a.lent.map((l) => `- ${l.name}: ${l.about}`).join("\n")}` : "";
|
|
3150
3219
|
const known = understood.length ? `# Their understanding, as you have recorded it\n\n${understood.map(sentText).join("\n\n")}` : "";
|
|
3151
3220
|
const broken = a.broken?.length ? `# Code links that no longer resolve\n\nSince you were last told, these links in their notes point to files no longer in their repository. Mention it only where it bears on what they ask.\n\n${a.broken.map((b) => `- in ${b.title} (/${b.id}.md): ${b.href}`).join("\n")}` : "";
|
|
3152
3221
|
const request = `# What they ask now\n\n${a.asked}`;
|
|
@@ -3161,6 +3230,7 @@ function turnMessages(a) {
|
|
|
3161
3230
|
role: "user",
|
|
3162
3231
|
content: [
|
|
3163
3232
|
agents,
|
|
3233
|
+
lent,
|
|
3164
3234
|
earlier,
|
|
3165
3235
|
known,
|
|
3166
3236
|
gave,
|
|
@@ -3174,7 +3244,7 @@ function turnMessages(a) {
|
|
|
3174
3244
|
{
|
|
3175
3245
|
name: "instructions and tools",
|
|
3176
3246
|
detail: `${a.mode} mode${checking ? ", checking" : ""}${a.lookup ? "" : ", no look-ups"}`,
|
|
3177
|
-
tokens: estimate(system) + (a.lookup ? 500 : 0) + turnTools(a.mode, a.sent).length * 120
|
|
3247
|
+
tokens: estimate(system) + estimate(lent) + (a.lookup ? 500 : 0) + turnTools(a.mode, a.sent).length * 120
|
|
3178
3248
|
},
|
|
3179
3249
|
{
|
|
3180
3250
|
name: "earlier in this conversation",
|
|
@@ -3749,7 +3819,7 @@ function keepDone(before, next) {
|
|
|
3749
3819
|
return next;
|
|
3750
3820
|
}
|
|
3751
3821
|
}
|
|
3752
|
-
var SIGN_IN_MS, REPLY_TOKENS, NOTE_TOKENS, DRAFT_PREFIX, DRAFT_DIR, isDraftId, bare, inBase, BAD_PART, TURN_ID, escapeRe, MAX_SENT, BRIEF, NOTE_ARG, MARKED, EDIT_NOTE, WRITE_NOTE, Engine;
|
|
3822
|
+
var SIGN_IN_MS, fromBase, REPLY_TOKENS, NOTE_TOKENS, DRAFT_PREFIX, DRAFT_DIR, isDraftId, bare, inBase, BAD_PART, TURN_ID, escapeRe, MAX_SENT, BRIEF, NOTE_ARG, MARKED, EDIT_NOTE, WRITE_NOTE, Engine;
|
|
3753
3823
|
var init_engine = __esmMin((() => {
|
|
3754
3824
|
init_src$1();
|
|
3755
3825
|
init_api();
|
|
@@ -3762,6 +3832,7 @@ var init_engine = __esmMin((() => {
|
|
|
3762
3832
|
init_meaning();
|
|
3763
3833
|
init_provider();
|
|
3764
3834
|
SIGN_IN_MS = 6e5;
|
|
3835
|
+
fromBase = (base, text) => `(From the base ${base}: name its notes ${base}:<id>, or give base "${base}".)\n${text}`;
|
|
3765
3836
|
REPLY_TOKENS = 2500;
|
|
3766
3837
|
NOTE_TOKENS = 6e3;
|
|
3767
3838
|
DRAFT_PREFIX = "@draft/";
|
|
@@ -4023,7 +4094,7 @@ var init_engine = __esmMin((() => {
|
|
|
4023
4094
|
}));
|
|
4024
4095
|
}
|
|
4025
4096
|
async agentTools() {
|
|
4026
|
-
|
|
4097
|
+
const tools = [
|
|
4027
4098
|
BRIEF,
|
|
4028
4099
|
TOOLS[0],
|
|
4029
4100
|
...this.similar ? [FIND_SIMILAR] : [],
|
|
@@ -4031,9 +4102,48 @@ var init_engine = __esmMin((() => {
|
|
|
4031
4102
|
EDIT_NOTE,
|
|
4032
4103
|
WRITE_NOTE
|
|
4033
4104
|
];
|
|
4105
|
+
return this.opts.elsewhere ? withBase(tools, this.lent().map((l) => l.name), true) : tools;
|
|
4106
|
+
}
|
|
4107
|
+
/** The bases lent to this one's sessions, not this one. */
|
|
4108
|
+
lent() {
|
|
4109
|
+
return (this.opts.elsewhere?.lent() ?? []).filter((l) => l.name !== this.opts.name);
|
|
4110
|
+
}
|
|
4111
|
+
/** Another base lent here, by name, or why there is none of that name. */
|
|
4112
|
+
other(base) {
|
|
4113
|
+
const lent = this.lent();
|
|
4114
|
+
const engine = lent.some((l) => l.name === base) ? this.opts.elsewhere?.base(base) : null;
|
|
4115
|
+
if (!engine) throw new EngineError(lent.length ? `there is no base ${base} lent here: the bases are ${lent.map((l) => l.name).join(", ")}; leave base out for ${this.opts.name}` : `there is no base ${base} lent here: leave base out`, 404);
|
|
4116
|
+
return engine;
|
|
4117
|
+
}
|
|
4118
|
+
/** For Iso: a look in the other bases lent here, as their own look-ups give it, drawn on no Atlas. */
|
|
4119
|
+
get away() {
|
|
4120
|
+
if (!this.opts.elsewhere) return null;
|
|
4121
|
+
return {
|
|
4122
|
+
lent: () => this.lent(),
|
|
4123
|
+
look: async (base, tool, args) => fromBase(base, (await new Lookup(this.other(base).bundle, "", this.other(base).similar).call(tool, JSON.stringify(args))).text)
|
|
4124
|
+
};
|
|
4125
|
+
}
|
|
4126
|
+
/** What this base is, in a line: its AGENTS.md's first line of text, else its name and size. */
|
|
4127
|
+
async about() {
|
|
4128
|
+
return ((await this.agents()).text ?? "").split("\n").map((l) => l.replace(/^#+\s*/, "").trim()).find((l) => l && !l.startsWith("---")) ?? `${this.bundle.concepts.size} notes`;
|
|
4034
4129
|
}
|
|
4035
4130
|
async agentLook({ agent, session, tool, args, asked = "" }) {
|
|
4036
|
-
|
|
4131
|
+
const there = baseOf(args);
|
|
4132
|
+
if (there.base && there.base !== this.opts.name) {
|
|
4133
|
+
const done = await this.other(there.base).agentLook({
|
|
4134
|
+
agent,
|
|
4135
|
+
session,
|
|
4136
|
+
tool,
|
|
4137
|
+
args: there.args,
|
|
4138
|
+
asked
|
|
4139
|
+
});
|
|
4140
|
+
return { text: fromBase(there.base, done.text) };
|
|
4141
|
+
}
|
|
4142
|
+
args = there.args;
|
|
4143
|
+
if (tool === BRIEF.function.name) {
|
|
4144
|
+
const lent = this.lent();
|
|
4145
|
+
return { text: this.brief() + (lent.length ? `\n\nOther knowledge bases lent to you, each with its own practice (brief_base with its base, before you use it):\n${lent.map((l) => `- ${l.name}: ${l.about}`).join("\n")}\nPass base to any tool to look or write there.` : "") };
|
|
4146
|
+
}
|
|
4037
4147
|
if (tool === EDIT_NOTE.function.name || tool === WRITE_NOTE.function.name) {
|
|
4038
4148
|
const str = (v, name) => {
|
|
4039
4149
|
if (typeof v !== "string") throw new EngineError(`${tool} needs ${name}, as text`, 400);
|
|
@@ -4075,6 +4185,7 @@ var init_engine = __esmMin((() => {
|
|
|
4075
4185
|
session
|
|
4076
4186
|
});
|
|
4077
4187
|
}
|
|
4188
|
+
if (!busy) for (const l of this.lent()) await this.opts.elsewhere?.base(l.name)?.agentStatus(agent, session, false);
|
|
4078
4189
|
}
|
|
4079
4190
|
async agentWrite({ agent, session, note, asked, edit }) {
|
|
4080
4191
|
const who = `agent:${agent}`;
|
|
@@ -4600,7 +4711,7 @@ var init_engine = __esmMin((() => {
|
|
|
4600
4711
|
tier: req.tier,
|
|
4601
4712
|
place
|
|
4602
4713
|
});
|
|
4603
|
-
const lookup = new Lookup(this.bundle, req.note, this.similar);
|
|
4714
|
+
const lookup = new Lookup(this.bundle, req.note, this.similar, this.away);
|
|
4604
4715
|
(async () => {
|
|
4605
4716
|
let ran = null, offered = false;
|
|
4606
4717
|
try {
|
|
@@ -5203,6 +5314,7 @@ var init_engine = __esmMin((() => {
|
|
|
5203
5314
|
sent: [...sent, ...reads],
|
|
5204
5315
|
aside,
|
|
5205
5316
|
...agentsText ? { agents: agentsText } : {},
|
|
5317
|
+
...this.lent().length ? { lent: this.lent() } : {},
|
|
5206
5318
|
asked: req.asked ?? ""
|
|
5207
5319
|
});
|
|
5208
5320
|
const ratio = hand.ratio ?? 1, scaled = (n) => Math.round(n * ratio);
|
|
@@ -5551,6 +5663,7 @@ var init_engine = __esmMin((() => {
|
|
|
5551
5663
|
sent,
|
|
5552
5664
|
aside: w.aside,
|
|
5553
5665
|
...w.agentsText ? { agents: w.agentsText } : {},
|
|
5666
|
+
...this.lent().length ? { lent: this.lent() } : {},
|
|
5554
5667
|
asked: req.asked,
|
|
5555
5668
|
broken: await this.brokenNews("iso")
|
|
5556
5669
|
});
|
|
@@ -5600,7 +5713,7 @@ var init_engine = __esmMin((() => {
|
|
|
5600
5713
|
});
|
|
5601
5714
|
};
|
|
5602
5715
|
const who = `iso:${this.provider.name}/${this.provider.tiers[req.tier]}`;
|
|
5603
|
-
const lookup = new Lookup(this.bundle, req.given.find((g) => g.kind !== "folder")?.id ?? "", this.similar);
|
|
5716
|
+
const lookup = new Lookup(this.bundle, req.given.find((g) => g.kind !== "folder")?.id ?? "", this.similar, this.away);
|
|
5604
5717
|
const looks = new Set(lookup.defs().map((d) => d.function.name));
|
|
5605
5718
|
const tools = {
|
|
5606
5719
|
defs: () => [...lookupOn ? lookup.defs() : [], ...own],
|
|
@@ -5611,7 +5724,7 @@ var init_engine = __esmMin((() => {
|
|
|
5611
5724
|
try {
|
|
5612
5725
|
args = JSON.parse(raw || "{}");
|
|
5613
5726
|
} catch {}
|
|
5614
|
-
const id = noteId(this.bundle, args.id), want = typeof args.section === "string" ? args.section.replace(/^#+\s*/, "").trim() : "";
|
|
5727
|
+
const id = args.base ? null : noteId(this.bundle, args.id), want = typeof args.section === "string" ? args.section.replace(/^#+\s*/, "").trim() : "";
|
|
5615
5728
|
const held = id ? inHand.get(id) : void 0;
|
|
5616
5729
|
if (id && held && (held === "whole" || want && held.includes(want))) {
|
|
5617
5730
|
const step = {
|
|
@@ -5635,6 +5748,8 @@ var init_engine = __esmMin((() => {
|
|
|
5635
5748
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) args = parsed;
|
|
5636
5749
|
} catch {}
|
|
5637
5750
|
if (!own.some((d) => d.function.name === name)) return { text: `There is no tool ${name} in ${mode} mode.` };
|
|
5751
|
+
const there = baseOf(args);
|
|
5752
|
+
if (there.base && there.base !== this.opts.name) return { text: `Notes are changed only in ${this.opts.name}, the base you are in: ${there.base} is lent to you to read.` };
|
|
5638
5753
|
return { text: await this.turnTool(name, args, {
|
|
5639
5754
|
turn,
|
|
5640
5755
|
conversation,
|
|
@@ -6224,10 +6339,10 @@ var init_engine = __esmMin((() => {
|
|
|
6224
6339
|
let text;
|
|
6225
6340
|
if (p.form === "rewrite") {
|
|
6226
6341
|
if (region !== void 0 && !p.open?.includes(region)) throw new EngineError("that change is no longer waiting: it was kept or removed", 409);
|
|
6227
|
-
text = written(now, p, region);
|
|
6342
|
+
text = written$1(now, p, region);
|
|
6228
6343
|
if (text === null) throw new EngineError(region === void 0 ? "a passage this would rewrite is no longer in the note as it was. Keep the others one by one." : "the passage this would rewrite is no longer in the note as it was", 409);
|
|
6229
6344
|
} else {
|
|
6230
|
-
text = written(now, p) ?? written(now, {
|
|
6345
|
+
text = written$1(now, p) ?? written$1(now, {
|
|
6231
6346
|
...p,
|
|
6232
6347
|
after: null,
|
|
6233
6348
|
before: null
|
|
@@ -6780,6 +6895,62 @@ function gatewayTlsFrom(config, dir, env = process.env) {
|
|
|
6780
6895
|
async function hasModel$1(config = configPath()) {
|
|
6781
6896
|
return await new ConfigSecrets(config).get("provider.key") !== null || providerConfig(config).url !== OPENROUTER.url;
|
|
6782
6897
|
}
|
|
6898
|
+
const BASE_NAME = /^[a-z0-9][a-z0-9-]{0,39}$/;
|
|
6899
|
+
const expand = (p) => p === "~" ? homedir() : p.startsWith("~/") ? join(homedir(), p.slice(2)) : resolve(p);
|
|
6900
|
+
/** A path as written in the file: under the home folder, with ~. */
|
|
6901
|
+
const written = (p) => {
|
|
6902
|
+
const home = homedir();
|
|
6903
|
+
return p === home ? "~" : p.startsWith(home + "/") ? "~" + p.slice(home.length) : p;
|
|
6904
|
+
};
|
|
6905
|
+
/** Where the bases are listed, beside the configuration. */
|
|
6906
|
+
const basesPath = (config = configPath()) => join(dirname(config), "bases.toml");
|
|
6907
|
+
/** The bases listed, in the order written. A row without a path, or with a name isorite
|
|
6908
|
+
* would not give one, is passed over. */
|
|
6909
|
+
function knownBases(config = configPath()) {
|
|
6910
|
+
return Object.entries(readConfig(basesPath(config))).flatMap(([name, v]) => {
|
|
6911
|
+
const row = table(v);
|
|
6912
|
+
return BASE_NAME.test(name) && typeof row.path === "string" && row.path ? [{
|
|
6913
|
+
name,
|
|
6914
|
+
path: expand(row.path),
|
|
6915
|
+
lend: row.lend === true
|
|
6916
|
+
}] : [];
|
|
6917
|
+
});
|
|
6918
|
+
}
|
|
6919
|
+
/** A name for a base from its folder: small letters, digits and dashes. */
|
|
6920
|
+
function baseName(folder) {
|
|
6921
|
+
return basename(folder).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "base";
|
|
6922
|
+
}
|
|
6923
|
+
/** The base at `folder` by its name in the configuration, listed first if it is not: under its folder's name, or
|
|
6924
|
+
* that name with -2, -3… where another folder has it. `as` names it, and `lend` lends it. */
|
|
6925
|
+
function rememberBase(folder, { path = configPath(), as, lend } = {}) {
|
|
6926
|
+
const at = resolve(folder), had = knownBases(path).find((b) => b.path === at);
|
|
6927
|
+
if (had && lend === void 0) return had.name;
|
|
6928
|
+
const bases = readConfig(basesPath(path));
|
|
6929
|
+
let name = had?.name;
|
|
6930
|
+
if (!name) {
|
|
6931
|
+
const stem = as && BASE_NAME.test(as) ? as : baseName(at), taken = new Set(Object.keys(bases));
|
|
6932
|
+
name = stem;
|
|
6933
|
+
for (let n = 2; taken.has(name); n++) name = `${stem.slice(0, 36)}-${n}`;
|
|
6934
|
+
}
|
|
6935
|
+
bases[name] = {
|
|
6936
|
+
...table(bases[name]),
|
|
6937
|
+
path: written(at),
|
|
6938
|
+
...lend === void 0 ? {} : { lend }
|
|
6939
|
+
};
|
|
6940
|
+
writeConfig(bases, basesPath(path));
|
|
6941
|
+
return name;
|
|
6942
|
+
}
|
|
6943
|
+
/** Lend a known base to every session, or stop lending it. False when there is no base of that name. */
|
|
6944
|
+
function lendBase(name, lend, path = configPath()) {
|
|
6945
|
+
const bases = readConfig(basesPath(path));
|
|
6946
|
+
if (!BASE_NAME.test(name) || !bases[name]) return false;
|
|
6947
|
+
bases[name] = {
|
|
6948
|
+
...table(bases[name]),
|
|
6949
|
+
lend
|
|
6950
|
+
};
|
|
6951
|
+
writeConfig(bases, basesPath(path));
|
|
6952
|
+
return true;
|
|
6953
|
+
}
|
|
6783
6954
|
//#endregion
|
|
6784
6955
|
//#region packages/server/src/model.ts
|
|
6785
6956
|
/** Where the server looks for the model, in order, and why each place is there. */
|
|
@@ -6821,7 +6992,7 @@ function modelInfo(dir) {
|
|
|
6821
6992
|
} catch {}
|
|
6822
6993
|
const graph = sha(readFileSync(join(dir, "model.onnx"))), granite = graph === FILES[0][2];
|
|
6823
6994
|
const pooling = own.pooling === "mean" ? "mean" : "cls";
|
|
6824
|
-
const name = typeof own.name === "string" && own.name ? own.name : granite ? NAME$
|
|
6995
|
+
const name = typeof own.name === "string" && own.name ? own.name : granite ? NAME$2 : basename(dir);
|
|
6825
6996
|
return {
|
|
6826
6997
|
dir,
|
|
6827
6998
|
name,
|
|
@@ -7003,11 +7174,11 @@ async function modelCommand(from, say = console.log, env = process.env, sources
|
|
|
7003
7174
|
return 1;
|
|
7004
7175
|
}
|
|
7005
7176
|
}
|
|
7006
|
-
var NAME$
|
|
7177
|
+
var NAME$2, MODEL, RELEASE, PACKAGE, HUB, FILES, sha, userModelsDir, userModelDir, hasModel, ModelFetchError, modelFiles;
|
|
7007
7178
|
var init_model = __esmMin((() => {
|
|
7008
7179
|
init_src();
|
|
7009
|
-
NAME$
|
|
7010
|
-
MODEL = `${NAME$
|
|
7180
|
+
NAME$2 = "granite-embedding-small-english-r2";
|
|
7181
|
+
MODEL = `${NAME$2}-q8`;
|
|
7011
7182
|
RELEASE = "https://github.com/lachlanjs/isorite/releases/download/model-granite-1.0.0/isorite-model-granite-1.0.0.tgz";
|
|
7012
7183
|
PACKAGE = "isorite-model-granite@1.0.0";
|
|
7013
7184
|
HUB = `https://huggingface.co/onnx-community/granite-embedding-small-english-r2-ONNX/resolve/1dc7835ba0cb9c76a3618d0bf0c427c97671b3c8`;
|
|
@@ -7035,7 +7206,7 @@ var init_model = __esmMin((() => {
|
|
|
7035
7206
|
];
|
|
7036
7207
|
sha = (buf) => createHash("sha256").update(buf).digest("hex");
|
|
7037
7208
|
userModelsDir = (env = process.env) => join(env.XDG_DATA_HOME || join(homedir(), ".local", "share"), "isorite", "models");
|
|
7038
|
-
userModelDir = (env = process.env) => join(userModelsDir(env), NAME$
|
|
7209
|
+
userModelDir = (env = process.env) => join(userModelsDir(env), NAME$2);
|
|
7039
7210
|
hasModel = (dir) => existsSync(join(dir, "model.onnx")) && existsSync(join(dir, "tokenizer.json"));
|
|
7040
7211
|
ModelFetchError = class extends Error {
|
|
7041
7212
|
tried;
|
|
@@ -7223,7 +7394,132 @@ function relay(live, key) {
|
|
|
7223
7394
|
};
|
|
7224
7395
|
}
|
|
7225
7396
|
//#endregion
|
|
7397
|
+
//#region packages/server/src/bases.ts
|
|
7398
|
+
const NAME$1 = /^[a-z0-9][a-z0-9-]{0,39}$/;
|
|
7399
|
+
var Bases = class {
|
|
7400
|
+
first;
|
|
7401
|
+
#config;
|
|
7402
|
+
#make;
|
|
7403
|
+
#open = /* @__PURE__ */ new Map();
|
|
7404
|
+
#ready = /* @__PURE__ */ new Map();
|
|
7405
|
+
#about = /* @__PURE__ */ new Map();
|
|
7406
|
+
#holds = 0;
|
|
7407
|
+
#idle;
|
|
7408
|
+
/** `make` opens a base; `idle` is called when the last hold is let go (a server isorite term started). */
|
|
7409
|
+
constructor({ config, first, make, idle }) {
|
|
7410
|
+
this.#config = config;
|
|
7411
|
+
this.first = first;
|
|
7412
|
+
this.#make = make;
|
|
7413
|
+
this.#idle = idle;
|
|
7414
|
+
}
|
|
7415
|
+
/** The first base, and every lent one. */
|
|
7416
|
+
async start() {
|
|
7417
|
+
await this.get(this.first);
|
|
7418
|
+
await Promise.all(knownBases(this.#config).filter((b) => b.lend).map((b) => this.get(b.name)?.catch((e) => console.error(`isorite: ${b.name} (${b.path}) could not be opened: ${e.message}`))));
|
|
7419
|
+
}
|
|
7420
|
+
/** A known base, opened if it is not yet; null where none has that name. */
|
|
7421
|
+
get(name) {
|
|
7422
|
+
const had = this.#open.get(name);
|
|
7423
|
+
if (had) return had;
|
|
7424
|
+
const known = NAME$1.test(name) ? knownBases(this.#config).find((b) => b.name === name) : void 0;
|
|
7425
|
+
if (!known) return null;
|
|
7426
|
+
const opening = this.#make(known).then(async (opened) => {
|
|
7427
|
+
this.#ready.set(name, opened);
|
|
7428
|
+
this.#about.set(name, await opened.engine.about());
|
|
7429
|
+
opened.engine.events((e) => {
|
|
7430
|
+
if (e.type === "note-changed") opened.engine.about().then((a) => this.#about.set(name, a));
|
|
7431
|
+
});
|
|
7432
|
+
return opened;
|
|
7433
|
+
});
|
|
7434
|
+
opening.catch(() => this.#open.delete(name));
|
|
7435
|
+
this.#open.set(name, opening);
|
|
7436
|
+
return opening;
|
|
7437
|
+
}
|
|
7438
|
+
/** Every open base, for what is kept into use in all of them (the provider). */
|
|
7439
|
+
each(fn) {
|
|
7440
|
+
for (const o of this.#ready.values()) fn(o.engine);
|
|
7441
|
+
}
|
|
7442
|
+
lent() {
|
|
7443
|
+
return knownBases(this.#config).filter((b) => b.lend && this.#ready.has(b.name)).map((b) => ({
|
|
7444
|
+
name: b.name,
|
|
7445
|
+
about: this.#about.get(b.name) ?? ""
|
|
7446
|
+
}));
|
|
7447
|
+
}
|
|
7448
|
+
base(name) {
|
|
7449
|
+
return this.#ready.get(name)?.engine ?? null;
|
|
7450
|
+
}
|
|
7451
|
+
list() {
|
|
7452
|
+
return knownBases(this.#config).map((b) => ({
|
|
7453
|
+
name: b.name,
|
|
7454
|
+
path: b.path,
|
|
7455
|
+
lend: b.lend,
|
|
7456
|
+
open: this.#ready.has(b.name)
|
|
7457
|
+
}));
|
|
7458
|
+
}
|
|
7459
|
+
async lend(name, on) {
|
|
7460
|
+
if (!lendBase(name, on, this.#config)) return false;
|
|
7461
|
+
if (on) await this.get(name);
|
|
7462
|
+
return true;
|
|
7463
|
+
}
|
|
7464
|
+
async open(path) {
|
|
7465
|
+
const name = rememberBase(resolve(path), { path: this.#config });
|
|
7466
|
+
await this.get(name);
|
|
7467
|
+
return {
|
|
7468
|
+
name,
|
|
7469
|
+
first: name === this.first
|
|
7470
|
+
};
|
|
7471
|
+
}
|
|
7472
|
+
hold() {
|
|
7473
|
+
this.#holds++;
|
|
7474
|
+
let let_go = false;
|
|
7475
|
+
return () => {
|
|
7476
|
+
if (let_go) return;
|
|
7477
|
+
let_go = true;
|
|
7478
|
+
if (--this.#holds === 0) setTimeout(() => {
|
|
7479
|
+
if (this.#holds === 0) this.#idle?.();
|
|
7480
|
+
}, 1e3).unref();
|
|
7481
|
+
};
|
|
7482
|
+
}
|
|
7483
|
+
/** A request to the server: to a base's API by its address, /b/<name>/api/…, or to the first. A base's page is
|
|
7484
|
+
* the app's own page with ?base=<name> (SvelteKit's hash router needs its page at the root it was built for):
|
|
7485
|
+
* /b/<name> is sent there. */
|
|
7486
|
+
async fetch(req, env) {
|
|
7487
|
+
const url = new URL(req.url);
|
|
7488
|
+
const m = /^\/b\/([^/]+)(\/.*)?$/.exec(url.pathname);
|
|
7489
|
+
if (!m || !NAME$1.test(m[1])) return (await this.get(this.first)).fetch(req, env);
|
|
7490
|
+
const name = m[1];
|
|
7491
|
+
if (!m[2]?.startsWith("/api/")) return new Response(null, {
|
|
7492
|
+
status: 302,
|
|
7493
|
+
headers: { Location: `/?base=${name}${m[2]?.startsWith("/#") ? m[2].slice(1) : ""}` }
|
|
7494
|
+
});
|
|
7495
|
+
const opening = this.get(name);
|
|
7496
|
+
if (!opening) return new Response(JSON.stringify({ error: `no base ${name} is known here: isorite bases lists them` }), {
|
|
7497
|
+
status: 404,
|
|
7498
|
+
headers: { "Content-Type": "application/json" }
|
|
7499
|
+
});
|
|
7500
|
+
let opened;
|
|
7501
|
+
try {
|
|
7502
|
+
opened = await opening;
|
|
7503
|
+
} catch (e) {
|
|
7504
|
+
return new Response(JSON.stringify({ error: `${name} could not be opened: ${e.message}` }), {
|
|
7505
|
+
status: 500,
|
|
7506
|
+
headers: { "Content-Type": "application/json" }
|
|
7507
|
+
});
|
|
7508
|
+
}
|
|
7509
|
+
url.pathname = m[2];
|
|
7510
|
+
const body = req.method === "GET" || req.method === "HEAD" ? null : req.body;
|
|
7511
|
+
return opened.fetch(new Request(url, {
|
|
7512
|
+
method: req.method,
|
|
7513
|
+
headers: req.headers,
|
|
7514
|
+
body,
|
|
7515
|
+
signal: req.signal,
|
|
7516
|
+
...body ? { duplex: "half" } : {}
|
|
7517
|
+
}), env);
|
|
7518
|
+
}
|
|
7519
|
+
};
|
|
7520
|
+
//#endregion
|
|
7226
7521
|
//#region packages/server/src/terminal.ts
|
|
7522
|
+
init_src();
|
|
7227
7523
|
const OUTPUT = 4e3;
|
|
7228
7524
|
const LOOKUPS = /* @__PURE__ */ new Set([
|
|
7229
7525
|
"search_notes",
|
|
@@ -7234,6 +7530,7 @@ const LOOKUPS = /* @__PURE__ */ new Set([
|
|
|
7234
7530
|
]);
|
|
7235
7531
|
/** isorite's tools that change a note: their line opens it, where the agent's words are marked. */
|
|
7236
7532
|
const CHANGES = /* @__PURE__ */ new Set(["edit_note", "write_note"]);
|
|
7533
|
+
const isorite = (name) => LOOKUPS.has(name) || CHANGES.has(name);
|
|
7237
7534
|
/** What a tool call acted on, in a line. */
|
|
7238
7535
|
function titleOf(name, input = {}) {
|
|
7239
7536
|
const s = (k) => typeof input[k] === "string" ? input[k] : "";
|
|
@@ -7281,16 +7578,21 @@ function toTurns(messages, { live = /* @__PURE__ */ new Map(), ours = /* @__PURE
|
|
|
7281
7578
|
const input = c.state.input ?? {};
|
|
7282
7579
|
const output = (c.state.content ?? []).map((x) => x.text ?? "").join("").slice(0, OUTPUT);
|
|
7283
7580
|
const status = c.state.status === "completed" ? "done" : /error|fail/.test(c.state.status) ? "failed" : "running";
|
|
7581
|
+
const { base, args } = isorite(c.name) ? baseOf(input) : {
|
|
7582
|
+
base: "",
|
|
7583
|
+
args: input
|
|
7584
|
+
};
|
|
7284
7585
|
const item = {
|
|
7285
7586
|
kind: "tool",
|
|
7286
7587
|
id: c.id,
|
|
7287
7588
|
name: c.name,
|
|
7288
|
-
title: titleOf(c.name,
|
|
7589
|
+
title: (base ? `${base}: ` : "") + titleOf(c.name, args),
|
|
7289
7590
|
status,
|
|
7290
7591
|
output
|
|
7291
7592
|
};
|
|
7292
|
-
const named = LOOKUPS.has(c.name) ?
|
|
7593
|
+
const named = LOOKUPS.has(c.name) ? args.id : CHANGES.has(c.name) ? args.note : void 0;
|
|
7293
7594
|
if (typeof named === "string" && named) item.note = named.replace(/^\//, "").replace(/\.md$/, "");
|
|
7595
|
+
if (base) item.base = base;
|
|
7294
7596
|
const diff = diffOf(c.name, input);
|
|
7295
7597
|
if (diff) item.diff = diff;
|
|
7296
7598
|
turn.items.push(item);
|
|
@@ -7722,7 +8024,7 @@ const AgentStatus = z.object({
|
|
|
7722
8024
|
session: z.string().min(1).max(200),
|
|
7723
8025
|
busy: z.boolean()
|
|
7724
8026
|
}).strict();
|
|
7725
|
-
function createApp({ api, token, loopback, web, allowHosts = [], notes, terminal, gateway, setup }) {
|
|
8027
|
+
function createApp({ api, token, loopback, web, allowHosts = [], notes, terminal, gateway, setup, bases, base }) {
|
|
7726
8028
|
const app = new Hono();
|
|
7727
8029
|
const allowed = new Set([...allowHosts].map((h) => h.toLowerCase()));
|
|
7728
8030
|
const reply = (c, status, value) => c.body(JSON.stringify(value), status, {
|
|
@@ -7925,9 +8227,52 @@ function createApp({ api, token, loopback, web, allowHosts = [], notes, terminal
|
|
|
7925
8227
|
post("/api/settings/provider/models", z.object({ draft: SetupDraft.nullable() }).strict(), async (c, b) => ({ models: await setup.models(local(c) ? b.draft : null) }));
|
|
7926
8228
|
post("/api/settings/report", z.object({}).strict(), async () => ({ text: await setup.report() }));
|
|
7927
8229
|
}
|
|
8230
|
+
if (bases) {
|
|
8231
|
+
app.get("/api/bases", (c) => answer(c, async () => ({
|
|
8232
|
+
here: base ?? "",
|
|
8233
|
+
local: local(c),
|
|
8234
|
+
bases: bases.list()
|
|
8235
|
+
})));
|
|
8236
|
+
const mine = (c) => {
|
|
8237
|
+
if (!local(c)) throw new EngineError("the bases are changed only from this machine", 403);
|
|
8238
|
+
};
|
|
8239
|
+
post("/api/bases/lend", z.object({
|
|
8240
|
+
name: z.string().max(40),
|
|
8241
|
+
lend: z.boolean()
|
|
8242
|
+
}).strict(), async (c, b) => {
|
|
8243
|
+
mine(c);
|
|
8244
|
+
if (!await bases.lend(b.name, b.lend)) throw new EngineError(`no base ${b.name} is known here`, 404);
|
|
8245
|
+
return { bases: bases.list() };
|
|
8246
|
+
});
|
|
8247
|
+
post("/api/bases/open", z.object({ path: z.string().min(1).max(4e3) }).strict(), async (c, b) => {
|
|
8248
|
+
mine(c);
|
|
8249
|
+
if (!isAbsolute(b.path) || !existsSync(b.path) || !statSync(b.path).isDirectory()) throw new EngineError(`no folder at ${b.path}`, 404);
|
|
8250
|
+
return bases.open(b.path);
|
|
8251
|
+
});
|
|
8252
|
+
app.get("/api/server/hold", (c) => {
|
|
8253
|
+
if (!local(c)) return refuse(c, 403, "held only from this machine");
|
|
8254
|
+
return streamSSE(c, async (stream) => {
|
|
8255
|
+
const release = bases.hold();
|
|
8256
|
+
const beat = setInterval(() => {
|
|
8257
|
+
stream.write(": held\n\n");
|
|
8258
|
+
}, 25e3);
|
|
8259
|
+
await stream.writeSSE({
|
|
8260
|
+
event: "held",
|
|
8261
|
+
data: "{}"
|
|
8262
|
+
});
|
|
8263
|
+
await new Promise((done) => stream.onAbort(done));
|
|
8264
|
+
clearInterval(beat);
|
|
8265
|
+
release();
|
|
8266
|
+
});
|
|
8267
|
+
});
|
|
8268
|
+
}
|
|
7928
8269
|
app.get("/api/agent/tools", (c) => answer(c, () => api.agentTools()));
|
|
7929
8270
|
post("/api/agent/look", AgentLook, (_c, b) => api.agentLook(b));
|
|
7930
|
-
app.get("/api/agent/base", (c) => answer(c, async () => ({
|
|
8271
|
+
app.get("/api/agent/base", (c) => answer(c, async () => ({
|
|
8272
|
+
notes: notes ?? null,
|
|
8273
|
+
name: base ?? null,
|
|
8274
|
+
lent: (bases?.list() ?? []).filter((b) => b.lend && b.name !== base).map((b) => b.name)
|
|
8275
|
+
})));
|
|
7931
8276
|
post("/api/agent/write", AgentChange, (_c, b) => api.agentWrite(b));
|
|
7932
8277
|
post("/api/agent/send", z.object({
|
|
7933
8278
|
note: z.string().max(400),
|
|
@@ -8368,28 +8713,47 @@ var Setup = class {
|
|
|
8368
8713
|
};
|
|
8369
8714
|
//#endregion
|
|
8370
8715
|
//#region packages/server/src/version.ts
|
|
8371
|
-
const VERSION = "0.
|
|
8716
|
+
const VERSION = "0.8.1";
|
|
8372
8717
|
//#endregion
|
|
8373
|
-
//#region packages/server/src/
|
|
8374
|
-
|
|
8375
|
-
|
|
8718
|
+
//#region packages/server/src/git.ts
|
|
8719
|
+
/** The top of the repository `folder` is in, or null: the nearest folder holding `.git`, a directory or, in a
|
|
8720
|
+
* worktree or submodule, a file. No `git` is run. */
|
|
8721
|
+
function repoOf(folder) {
|
|
8722
|
+
for (let at = resolve(folder);; at = dirname(at)) {
|
|
8723
|
+
if (existsSync(join(at, ".git"))) return at;
|
|
8724
|
+
if (dirname(at) === at) return null;
|
|
8725
|
+
}
|
|
8726
|
+
}
|
|
8727
|
+
/** The environment for a `git` isorite runs: none of the shell's GIT_* (a GIT_DIR would point it elsewhere), no
|
|
8728
|
+
* system configuration (where a company's hooks and helpers are usually set), and no prompt for a password. */
|
|
8729
|
+
function plainGitEnv(env = process.env) {
|
|
8730
|
+
return {
|
|
8731
|
+
...Object.fromEntries(Object.entries(env).filter(([k]) => !k.startsWith("GIT_"))),
|
|
8732
|
+
GIT_CONFIG_NOSYSTEM: "1",
|
|
8733
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
8734
|
+
};
|
|
8735
|
+
}
|
|
8736
|
+
/** `git init` in a folder, given `timeout` ms; whether it was made a repository. */
|
|
8737
|
+
function gitInit(folder, { env = process.env, timeout = 1e4 } = {}) {
|
|
8376
8738
|
try {
|
|
8377
|
-
|
|
8378
|
-
cwd,
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8384
|
-
|
|
8385
|
-
}).trim();
|
|
8739
|
+
execFileSync("git", ["init", "--quiet"], {
|
|
8740
|
+
cwd: folder,
|
|
8741
|
+
stdio: "ignore",
|
|
8742
|
+
env: plainGitEnv(env),
|
|
8743
|
+
timeout,
|
|
8744
|
+
killSignal: "SIGKILL"
|
|
8745
|
+
});
|
|
8746
|
+
return existsSync(join(folder, ".git"));
|
|
8386
8747
|
} catch {
|
|
8387
|
-
return
|
|
8748
|
+
return false;
|
|
8388
8749
|
}
|
|
8389
8750
|
}
|
|
8751
|
+
//#endregion
|
|
8752
|
+
//#region packages/server/src/studybase.ts
|
|
8753
|
+
init_src$1();
|
|
8390
8754
|
/** Where format 1 put a study base when not told: beside the knowledge base's repository, named after it. */
|
|
8391
8755
|
function defaultStudyPath(notes) {
|
|
8392
|
-
const repo =
|
|
8756
|
+
const repo = repoOf(notes) ?? resolve(notes);
|
|
8393
8757
|
return join(dirname(repo), basename(repo) + "-study");
|
|
8394
8758
|
}
|
|
8395
8759
|
/** A study base's configuration, and its knowledge base's folder as an absolute path. */
|
|
@@ -8439,15 +8803,16 @@ function newBase(path) {
|
|
|
8439
8803
|
makeOwn(at);
|
|
8440
8804
|
return at;
|
|
8441
8805
|
}
|
|
8442
|
-
async function openEngine({ notes }, device = deviceId(), config = configPath(), http, model = modelDir(), secrets = new ConfigSecrets(config)) {
|
|
8806
|
+
async function openEngine({ notes }, device = deviceId(), config = configPath(), http, model = modelDir(), secrets = new ConfigSecrets(config), extras = {}) {
|
|
8443
8807
|
const provider = providerConfig(config);
|
|
8444
8808
|
const own = makeOwn(notes);
|
|
8809
|
+
const embedder = extras.embedder !== void 0 ? extras.embedder : model ? nodeEmbedder(model) : null;
|
|
8445
8810
|
return Engine.open({
|
|
8446
8811
|
notes: new NodeFiles(notes),
|
|
8447
8812
|
own: new NodeFiles(own),
|
|
8448
8813
|
clock: systemClock,
|
|
8449
8814
|
device,
|
|
8450
|
-
name: basename(notes),
|
|
8815
|
+
name: extras.name ?? basename(notes),
|
|
8451
8816
|
http: http ?? gatewayHttp(provider, gatewayTls(config)),
|
|
8452
8817
|
secrets,
|
|
8453
8818
|
provider,
|
|
@@ -8455,13 +8820,14 @@ async function openEngine({ notes }, device = deviceId(), config = configPath(),
|
|
|
8455
8820
|
const dir = resolve(notes, path);
|
|
8456
8821
|
return existsSync(dir) && statSync(dir).isDirectory() ? new NodeFiles(dir) : null;
|
|
8457
8822
|
},
|
|
8458
|
-
...
|
|
8459
|
-
embedder
|
|
8823
|
+
...embedder ? {
|
|
8824
|
+
embedder,
|
|
8460
8825
|
cache: {
|
|
8461
8826
|
files: new NodeFiles(cacheDir()),
|
|
8462
8827
|
name: `vectors-${createHash("sha256").update(notes).digest("hex").slice(0, 16)}.json`
|
|
8463
8828
|
}
|
|
8464
|
-
} : {}
|
|
8829
|
+
} : {},
|
|
8830
|
+
...extras.elsewhere ? { elsewhere: extras.elsewhere } : {}
|
|
8465
8831
|
});
|
|
8466
8832
|
}
|
|
8467
8833
|
/** The guide's finder, as a provider: no key, no sign-in, and a name that says what it is. */
|
|
@@ -8486,16 +8852,49 @@ async function liveProvider(config, stand) {
|
|
|
8486
8852
|
}
|
|
8487
8853
|
/** A link most terminals let you click (OSC 8), when the output is a terminal; the address as it is otherwise. */
|
|
8488
8854
|
const link$1 = (url, tty = Boolean(process.stdout.isTTY)) => tty ? `\x1b]8;;${url}\x1b\\${url}\x1b]8;;\x1b\\` : url;
|
|
8489
|
-
/**
|
|
8490
|
-
*
|
|
8491
|
-
|
|
8855
|
+
/** The server's bases, opened: the one named (remembered in bases.toml, M20) and every lent one, each with its
|
|
8856
|
+
* engine and app, and the server's answer to a request for any of them. `stand`: in place of the provider, an
|
|
8857
|
+
* answerer of its own, as the guide's finder is when no model is set up (M16), asked while there is no model here.
|
|
8858
|
+
* `idle`: what to do once the last of those holding the server lets it go. */
|
|
8859
|
+
async function openServer(path, { host = "127.0.0.1", port = 4767, allowHosts = [], stand, idle, config = configPath(), model = modelDir() } = {}) {
|
|
8492
8860
|
const opened = locate(path);
|
|
8493
|
-
const config = configPath();
|
|
8494
8861
|
const current = () => liveProvider(config, stand);
|
|
8495
8862
|
let live = await current();
|
|
8496
|
-
const secrets = new ConfigSecrets(config);
|
|
8497
|
-
const
|
|
8498
|
-
|
|
8863
|
+
const secrets = new ConfigSecrets(config), device = deviceId(config);
|
|
8864
|
+
const first = rememberBase(opened.notes, { path: config });
|
|
8865
|
+
const embedder = model ? nodeEmbedder(model) : null;
|
|
8866
|
+
const token = randomBytes(24).toString("base64url"), loopback = LOOPBACK.has(host);
|
|
8867
|
+
const gateway = loopback ? relay(() => live, () => secrets.get(KEY)) : void 0;
|
|
8868
|
+
const web = existsSync(join(WEB_DIR, "index.html")) ? WEB_DIR : null;
|
|
8869
|
+
const bases = new Bases({
|
|
8870
|
+
config,
|
|
8871
|
+
first,
|
|
8872
|
+
...idle ? { idle } : {},
|
|
8873
|
+
make: async (known) => {
|
|
8874
|
+
const api = await openEngine({ notes: known.path }, device, config, live.http, model, secrets, {
|
|
8875
|
+
name: known.name,
|
|
8876
|
+
elsewhere: bases,
|
|
8877
|
+
embedder
|
|
8878
|
+
});
|
|
8879
|
+
api.setProvider(live.provider, live.http);
|
|
8880
|
+
return {
|
|
8881
|
+
engine: api,
|
|
8882
|
+
fetch: createApp({
|
|
8883
|
+
api,
|
|
8884
|
+
token,
|
|
8885
|
+
loopback,
|
|
8886
|
+
web,
|
|
8887
|
+
allowHosts,
|
|
8888
|
+
notes: known.path,
|
|
8889
|
+
terminal: new Terminal(),
|
|
8890
|
+
gateway,
|
|
8891
|
+
setup,
|
|
8892
|
+
bases,
|
|
8893
|
+
base: known.name
|
|
8894
|
+
}).fetch
|
|
8895
|
+
};
|
|
8896
|
+
}
|
|
8897
|
+
});
|
|
8499
8898
|
const setup = new Setup({
|
|
8500
8899
|
config,
|
|
8501
8900
|
notes: opened.notes,
|
|
@@ -8503,25 +8902,29 @@ async function serve$1(path, { host = "127.0.0.1", port = 4767, allowHosts = [],
|
|
|
8503
8902
|
port,
|
|
8504
8903
|
kept: async () => {
|
|
8505
8904
|
live = await current();
|
|
8506
|
-
api.setProvider(live.provider, live.http);
|
|
8905
|
+
bases.each((api) => api.setProvider(live.provider, live.http));
|
|
8507
8906
|
}
|
|
8508
8907
|
});
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
const terminal = new Terminal();
|
|
8512
|
-
const app = createApp({
|
|
8513
|
-
api,
|
|
8514
|
-
token: randomBytes(24).toString("base64url"),
|
|
8515
|
-
loopback: LOOPBACK.has(host),
|
|
8516
|
-
web,
|
|
8517
|
-
allowHosts,
|
|
8908
|
+
await bases.start();
|
|
8909
|
+
return {
|
|
8518
8910
|
notes: opened.notes,
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
|
|
8911
|
+
bases,
|
|
8912
|
+
embedder: Boolean(embedder),
|
|
8913
|
+
fetch: (req, env) => bases.fetch(req, env)
|
|
8914
|
+
};
|
|
8915
|
+
}
|
|
8916
|
+
/** `held`: started by isorite term or mcp, it stops once the last of them lets it go. */
|
|
8917
|
+
async function serve$1(path, { host = "127.0.0.1", port = 4767, allowHosts = [], stand, held = false } = {}) {
|
|
8918
|
+
let stop = () => void 0;
|
|
8919
|
+
const { notes, bases, embedder, fetch } = await openServer(path, {
|
|
8920
|
+
host,
|
|
8921
|
+
port,
|
|
8922
|
+
allowHosts,
|
|
8923
|
+
...stand ? { stand } : {},
|
|
8924
|
+
...held ? { idle: () => stop() } : {}
|
|
8522
8925
|
});
|
|
8523
8926
|
const server = serve({
|
|
8524
|
-
fetch
|
|
8927
|
+
fetch,
|
|
8525
8928
|
hostname: host,
|
|
8526
8929
|
port
|
|
8527
8930
|
});
|
|
@@ -8529,11 +8932,13 @@ async function serve$1(path, { host = "127.0.0.1", port = 4767, allowHosts = [],
|
|
|
8529
8932
|
console.error(e.code === "EADDRINUSE" ? `port ${port} is in use (isorite serve --port <n>)` : e.message);
|
|
8530
8933
|
process.exit(1);
|
|
8531
8934
|
});
|
|
8532
|
-
console.log(`isorite: ${
|
|
8533
|
-
console.log(
|
|
8534
|
-
|
|
8935
|
+
console.log(`isorite: ${notes}`);
|
|
8936
|
+
console.log(embedder ? " search by meaning: on" : " search by meaning: off (isorite model fetches its model)");
|
|
8937
|
+
const lent = bases.lent().filter((l) => l.name !== bases.first).map((l) => l.name);
|
|
8938
|
+
if (lent.length) console.log(` lent to every session: ${lent.join(", ")} (isorite bases)`);
|
|
8939
|
+
console.log(existsSync(join(WEB_DIR, "index.html")) ? ` ${link$1(`http://localhost:${port}/`)}` : ` the app is not built (npm run build); the engine alone is at http://localhost:${port}/api/`);
|
|
8535
8940
|
if (process.env.SSH_CONNECTION && LOOPBACK.has(host)) console.log(` over SSH, the link opens on this machine; from your own, first run: ssh -L ${port}:localhost:${port} ${hostname()}`);
|
|
8536
|
-
|
|
8941
|
+
stop = () => {
|
|
8537
8942
|
server.close();
|
|
8538
8943
|
process.exit(0);
|
|
8539
8944
|
};
|
|
@@ -8560,6 +8965,12 @@ function openBrowser(url) {
|
|
|
8560
8965
|
}).on("error", () => void 0).unref();
|
|
8561
8966
|
} catch {}
|
|
8562
8967
|
}
|
|
8968
|
+
/** How long a step may take before it's said what is being waited for (M21), and where it's said: stderr, as
|
|
8969
|
+
* `mcp` speaks on stdout. */
|
|
8970
|
+
const SLOW = 2e3;
|
|
8971
|
+
const waiting = (line) => {
|
|
8972
|
+
process.stderr.write(line + "\n");
|
|
8973
|
+
};
|
|
8563
8974
|
/** The name the server on this URL serves, or null when nothing answers. */
|
|
8564
8975
|
async function serving(url) {
|
|
8565
8976
|
try {
|
|
@@ -8569,49 +8980,82 @@ async function serving(url) {
|
|
|
8569
8980
|
return null;
|
|
8570
8981
|
}
|
|
8571
8982
|
}
|
|
8572
|
-
/** The isorite server on the port, and how to let it go: one already running for this base
|
|
8573
|
-
* no base is named), or one started for the base, its output kept in a
|
|
8574
|
-
|
|
8575
|
-
|
|
8983
|
+
/** The isorite server on the port, and how to let it go: one already running (for this base, or holding it as
|
|
8984
|
+
* well from now on, M20; or for any, when no base is named), or one started for the base, its output kept in a
|
|
8985
|
+
* log. `url` is the base's own address there. A server this started stops once the last of those using it lets it
|
|
8986
|
+
* go; one you started yourself goes on. */
|
|
8987
|
+
async function ensureServer(path, { port = 4767, env = process.env, wait = 12e4, say = waiting } = {}) {
|
|
8988
|
+
const root = `http://127.0.0.1:${port}`;
|
|
8576
8989
|
const opened = path === void 0 ? null : locate(path);
|
|
8577
|
-
|
|
8578
|
-
if (name
|
|
8579
|
-
if (opened
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8990
|
+
let name = await serving(root), started = null;
|
|
8991
|
+
if (name === null) {
|
|
8992
|
+
if (!opened) throw new StudyError(`nothing is serving on port ${port}: name a base`);
|
|
8993
|
+
const log = join(tmpdir(), `isorite-term-${port}.log`);
|
|
8994
|
+
const out = openSync(log, "w");
|
|
8995
|
+
started = spawn(process.execPath, [
|
|
8996
|
+
MAIN,
|
|
8997
|
+
"serve",
|
|
8998
|
+
path,
|
|
8999
|
+
"--port",
|
|
9000
|
+
String(port),
|
|
9001
|
+
"--held"
|
|
9002
|
+
], {
|
|
9003
|
+
stdio: [
|
|
9004
|
+
"ignore",
|
|
9005
|
+
out,
|
|
9006
|
+
out
|
|
9007
|
+
],
|
|
9008
|
+
env,
|
|
9009
|
+
detached: true
|
|
9010
|
+
});
|
|
9011
|
+
const exited = new Promise((done) => started.once("exit", () => done()));
|
|
9012
|
+
const begun = Date.now();
|
|
9013
|
+
let told = false;
|
|
9014
|
+
while ((name = await serving(root)) === null) {
|
|
9015
|
+
if (!told && Date.now() - begun > SLOW) {
|
|
9016
|
+
say(`isorite: waiting for the isorite server; its output is in ${log}`);
|
|
9017
|
+
told = true;
|
|
9018
|
+
}
|
|
9019
|
+
if (started.exitCode !== null || Date.now() - begun > wait) {
|
|
9020
|
+
started.kill();
|
|
9021
|
+
throw new StudyError(`the isorite server did not start; its output is in ${log}`);
|
|
9022
|
+
}
|
|
9023
|
+
await Promise.race([exited, new Promise((r) => setTimeout(r, 250))]);
|
|
8608
9024
|
}
|
|
8609
|
-
|
|
9025
|
+
started.unref();
|
|
8610
9026
|
}
|
|
9027
|
+
let url = root;
|
|
9028
|
+
if (opened) {
|
|
9029
|
+
const at = await fetch(`${root}/api/bases/open`, {
|
|
9030
|
+
method: "POST",
|
|
9031
|
+
headers: {
|
|
9032
|
+
"Content-Type": "application/json",
|
|
9033
|
+
"x-isorite-token": await tokenOf(root),
|
|
9034
|
+
Origin: root
|
|
9035
|
+
},
|
|
9036
|
+
body: JSON.stringify({ path: opened.notes })
|
|
9037
|
+
}).catch(() => null);
|
|
9038
|
+
if (at?.ok) {
|
|
9039
|
+
const base = await at.json();
|
|
9040
|
+
if (!base.first) url = `${root}/b/${base.name}`;
|
|
9041
|
+
} else if (name !== basename(opened.notes)) throw new StudyError(`port ${port} is serving ${name}, not ${basename(opened.notes)}: stop it, or use another port (--port <n>)`);
|
|
9042
|
+
}
|
|
9043
|
+
const holding = new AbortController();
|
|
9044
|
+
const held = fetch(`${root}/api/server/hold`, { signal: holding.signal }).then((r) => {
|
|
9045
|
+
if (r.ok && r.body) (async () => {
|
|
9046
|
+
try {
|
|
9047
|
+
for await (const _ of r.body);
|
|
9048
|
+
} catch {}
|
|
9049
|
+
})();
|
|
9050
|
+
return r.ok;
|
|
9051
|
+
}, () => false);
|
|
8611
9052
|
return {
|
|
8612
9053
|
url,
|
|
8613
9054
|
release: () => {
|
|
8614
|
-
|
|
9055
|
+
holding.abort();
|
|
9056
|
+
held.then((ok) => {
|
|
9057
|
+
if (!ok && started) started.kill();
|
|
9058
|
+
});
|
|
8615
9059
|
}
|
|
8616
9060
|
};
|
|
8617
9061
|
}
|
|
@@ -8632,7 +9076,7 @@ async function tell(url, path, body) {
|
|
|
8632
9076
|
headers: {
|
|
8633
9077
|
"Content-Type": "application/json",
|
|
8634
9078
|
"x-isorite-token": token,
|
|
8635
|
-
Origin: url
|
|
9079
|
+
Origin: new URL(url).origin
|
|
8636
9080
|
},
|
|
8637
9081
|
body: JSON.stringify(body)
|
|
8638
9082
|
});
|
|
@@ -8640,7 +9084,7 @@ async function tell(url, path, body) {
|
|
|
8640
9084
|
}
|
|
8641
9085
|
/** OpenCode's own server, on this machine only, with a password of its own; its output kept in a log. The terminal
|
|
8642
9086
|
* attaches to it, and so does the isorite server, which shows its session in the Iso pane (M11). */
|
|
8643
|
-
async function startOpenCode(command, { cwd, env, wait = 6e4 }) {
|
|
9087
|
+
async function startOpenCode(command, { cwd, env, wait = 6e4, say = waiting }) {
|
|
8644
9088
|
const port = await freePort(), password = randomBytes(24).toString("base64url"), url = `http://127.0.0.1:${port}`;
|
|
8645
9089
|
const log = join(tmpdir(), `isorite-opencode-${port}.log`);
|
|
8646
9090
|
const out = openSync(log, "w");
|
|
@@ -8664,7 +9108,17 @@ async function startOpenCode(command, { cwd, env, wait = 6e4 }) {
|
|
|
8664
9108
|
});
|
|
8665
9109
|
const auth = { authorization: "Basic " + Buffer.from(`opencode:${password}`).toString("base64") };
|
|
8666
9110
|
const started = Date.now();
|
|
9111
|
+
let told = 0;
|
|
8667
9112
|
for (;;) {
|
|
9113
|
+
const waited = Date.now() - started;
|
|
9114
|
+
if (told === 0 && waited > SLOW) {
|
|
9115
|
+
say("isorite: waiting for OpenCode's server");
|
|
9116
|
+
told = 1;
|
|
9117
|
+
}
|
|
9118
|
+
if (told === 1 && waited > 1e4) {
|
|
9119
|
+
say(`isorite: OpenCode's server has not answered in 10 s (a git it runs may be waiting); its output is in ${log}`);
|
|
9120
|
+
told = 2;
|
|
9121
|
+
}
|
|
8668
9122
|
if (server.exitCode !== null || Date.now() - started > wait) {
|
|
8669
9123
|
server.kill();
|
|
8670
9124
|
throw new StudyError(`OpenCode's server did not start; its output is in ${log}`);
|
|
@@ -8723,9 +9177,9 @@ async function modelsFor(env, server) {
|
|
|
8723
9177
|
}
|
|
8724
9178
|
/** What `isorite term` says before OpenCode takes the screen, and again when it gives it back: where isorite is in
|
|
8725
9179
|
* the browser, which models the terminal uses and where they come from, and, over SSH, how to reach the browser. */
|
|
8726
|
-
function where(port, models, env, tty) {
|
|
9180
|
+
function where(port, models, env, tty, page = "/") {
|
|
8727
9181
|
const names = models.gateway?.models.join(", ");
|
|
8728
|
-
const lines = [`isorite: ${link$1(`http://localhost:${port}
|
|
9182
|
+
const lines = [`isorite: ${link$1(`http://localhost:${port}${page}`, tty)}`, models.from === "environment" ? ` the terminal's models: ${names}, at ${models.gateway.url} (ISORITE_GATEWAY_URL)` : models.from === "config" ? ` the terminal's models: ${names}, as config.toml names them, through the isorite server` : " the terminal's models: OpenCode's own, as no key or certificate is named in config.toml"];
|
|
8729
9183
|
if (env.SSH_CONNECTION) lines.push(` over SSH, the link opens on this machine; from your own, first run: ssh -L ${port}:localhost:${port} ${hostname()}`);
|
|
8730
9184
|
return lines;
|
|
8731
9185
|
}
|
|
@@ -8752,7 +9206,7 @@ async function term(path, { port = 4767, cwd = process.cwd(), env: given = proce
|
|
|
8752
9206
|
throw e;
|
|
8753
9207
|
}
|
|
8754
9208
|
const { gateway, env } = models;
|
|
8755
|
-
const said = where(port, models, given);
|
|
9209
|
+
const said = where(port, models, given, void 0, new URL(server.url).pathname.replace(/\/?$/, "/"));
|
|
8756
9210
|
for (const line of said) console.log(line);
|
|
8757
9211
|
const agents = path === void 0 ? null : join(locate(path).notes, AGENTS_FILE);
|
|
8758
9212
|
const home = writeHome(defaultHome(env), {
|
|
@@ -8762,7 +9216,8 @@ async function term(path, { port = 4767, cwd = process.cwd(), env: given = proce
|
|
|
8762
9216
|
const shared = {
|
|
8763
9217
|
...env,
|
|
8764
9218
|
OPENCODE_CONFIG_DIR: home,
|
|
8765
|
-
ISORITE_URL: server.url
|
|
9219
|
+
ISORITE_URL: server.url,
|
|
9220
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
8766
9221
|
};
|
|
8767
9222
|
let backend;
|
|
8768
9223
|
try {
|
|
@@ -8786,7 +9241,8 @@ async function term(path, { port = 4767, cwd = process.cwd(), env: given = proce
|
|
|
8786
9241
|
...given,
|
|
8787
9242
|
OPENCODE_CONFIG_DIR: home,
|
|
8788
9243
|
ISORITE_URL: server.url,
|
|
8789
|
-
OPENCODE_PASSWORD: backend.password
|
|
9244
|
+
OPENCODE_PASSWORD: backend.password,
|
|
9245
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
8790
9246
|
}
|
|
8791
9247
|
});
|
|
8792
9248
|
const forward = (signal) => () => child.kill(signal);
|
|
@@ -9225,7 +9681,7 @@ async function mcp(path, { port = 4767, agent = "mcp", quiet = QUIET_MS, input =
|
|
|
9225
9681
|
headers: {
|
|
9226
9682
|
"Content-Type": "application/json",
|
|
9227
9683
|
[TOKEN_HEADER]: await token,
|
|
9228
|
-
Origin: url
|
|
9684
|
+
Origin: new URL(url).origin
|
|
9229
9685
|
},
|
|
9230
9686
|
body: JSON.stringify(body)
|
|
9231
9687
|
});
|
|
@@ -9255,7 +9711,7 @@ async function mcp(path, { port = 4767, agent = "mcp", quiet = QUIET_MS, input =
|
|
|
9255
9711
|
name: "isorite",
|
|
9256
9712
|
version: "0.1.0"
|
|
9257
9713
|
},
|
|
9258
|
-
instructions: "A knowledge base of notes. Call brief_base first, then look things up with search_notes, outline_note and read_note, following a note's links before searching again. Cite a note by its title and section."
|
|
9714
|
+
instructions: "A knowledge base of notes. Call brief_base first, then look things up with search_notes, outline_note and read_note, following a note's links before searching again. Cite a note by its title and section. Other knowledge bases lent to you (the agent's memory among them) are named by brief_base: pass base to the same tools to look or write there, and follow the practice each one's brief gives."
|
|
9259
9715
|
};
|
|
9260
9716
|
if (rpc.method === "ping") return {};
|
|
9261
9717
|
if (rpc.method === "tools/list") return { tools: (await get("/api/agent/tools")).map(({ function: t }) => ({
|
|
@@ -10491,6 +10947,90 @@ async function writeGeometry(folder, now = Date.now()) {
|
|
|
10491
10947
|
return { notes };
|
|
10492
10948
|
}
|
|
10493
10949
|
//#endregion
|
|
10950
|
+
//#region packages/server/src/memory.ts
|
|
10951
|
+
init_src$1();
|
|
10952
|
+
const MEMORY_FOLDER = join(homedir(), "isorite-memory");
|
|
10953
|
+
/** The memory's practice, as the agent is to follow it: read with brief_base, whichever base a session is for. */
|
|
10954
|
+
const MEMORY_AGENTS = `# What you keep across projects: the person, their tools, and what you have learned working with them.
|
|
10955
|
+
|
|
10956
|
+
This knowledge base is your memory. It is lent to every session, whatever project the session is for. Look in it,
|
|
10957
|
+
and keep it, with the knowledge base tools, giving \`base: "memory"\`.
|
|
10958
|
+
|
|
10959
|
+
## When to look
|
|
10960
|
+
|
|
10961
|
+
- At the start of a task: search for the person, the project's tools and the kind of work.
|
|
10962
|
+
- Before asking the person something they may have told you before.
|
|
10963
|
+
|
|
10964
|
+
## What to keep
|
|
10965
|
+
|
|
10966
|
+
Keep what will be useful in another project, or in a later session of this one:
|
|
10967
|
+
- **you/**: how the person likes to work, what they have corrected you on or confirmed, and why.
|
|
10968
|
+
- **tools/**: tools, services and machines, and how they are set up and reached.
|
|
10969
|
+
- **practice/**: ways of working that went well or badly, and mistakes not to repeat.
|
|
10970
|
+
- **projects/**: what is going on in each project that its own files don't say: goals, decisions, constraints,
|
|
10971
|
+
with dates as dates.
|
|
10972
|
+
|
|
10973
|
+
Don't keep what a project's own files, notes or history already say, what matters only to this conversation, or
|
|
10974
|
+
anything secret: keys, passwords, tokens.
|
|
10975
|
+
|
|
10976
|
+
## How to keep it
|
|
10977
|
+
|
|
10978
|
+
- One note for each thing worth keeping: its title says it, its first lines give the fact, then **Why:** and
|
|
10979
|
+
**When it applies:**.
|
|
10980
|
+
- Link it to the notes it touches.
|
|
10981
|
+
- Update the note that is there rather than writing a second one, and delete a note that turns out to be wrong.
|
|
10982
|
+
- Write as you learn, not at the end: the person sees each note you write marked as yours, and can undo it.
|
|
10983
|
+
`;
|
|
10984
|
+
/** Make the memory, or say where it is: the folder (as isorite new makes it), a git repository of its own, its
|
|
10985
|
+
* practice, and its row in bases.toml, lent. */
|
|
10986
|
+
function memory(folder = MEMORY_FOLDER, { config = configPath(), out = console.log, env = process.env } = {}) {
|
|
10987
|
+
const at = resolve(folder), known = knownBases(config);
|
|
10988
|
+
const had = known.find((b) => b.path === at) ?? (folder === MEMORY_FOLDER ? known.find((b) => b.name === "memory") : void 0);
|
|
10989
|
+
if (had && existsSync(join(had.path, "AGENTS.md"))) {
|
|
10990
|
+
out(`the memory is ${had.name}, at ${had.path}: ${had.lend ? "lent to every session" : `not lent (isorite bases lend ${had.name})`}`);
|
|
10991
|
+
return 0;
|
|
10992
|
+
}
|
|
10993
|
+
if (!existsSync(join(at, "AGENTS.md"))) {
|
|
10994
|
+
if (existsSync(at) && readdirSync(at).some((f) => f !== ".isorite" && f !== ".git")) throw new StudyError(`${at} has files in it already, and no ${AGENTS_FILE}: name another folder, isorite memory <folder>`);
|
|
10995
|
+
newBase(at);
|
|
10996
|
+
writeFileSync(join(at, AGENTS_FILE), MEMORY_AGENTS);
|
|
10997
|
+
out(existsSync(join(at, ".git")) || gitInit(at, { env }) ? `isorite memory: made at ${at}, a private repository of its own: push it where you like` : `isorite memory: made at ${at}; not a git repository (git did not answer, or is not here): git init it yourself`);
|
|
10998
|
+
}
|
|
10999
|
+
const name = rememberBase(at, {
|
|
11000
|
+
path: config,
|
|
11001
|
+
as: "memory",
|
|
11002
|
+
lend: true
|
|
11003
|
+
});
|
|
11004
|
+
out(` ${name}: lent to every session's agent and Iso, as listed in ${basesPath(config)}`);
|
|
11005
|
+
out(` watch it learn: isorite serve ${at}, or switch to ${name} in the page of a running server`);
|
|
11006
|
+
return 0;
|
|
11007
|
+
}
|
|
11008
|
+
/** isorite bases: the bases this device knows; `lend <name>` and `keep <name>` (lend it no longer). */
|
|
11009
|
+
function bases(args, { config = configPath(), out = console.log } = {}) {
|
|
11010
|
+
const [verb, name] = args;
|
|
11011
|
+
if (verb === "lend" || verb === "keep") {
|
|
11012
|
+
const known = knownBases(config).find((b) => b.name === name);
|
|
11013
|
+
if (!known) {
|
|
11014
|
+
out(`no base ${name ?? ""} is known: isorite bases lists them`);
|
|
11015
|
+
return 2;
|
|
11016
|
+
}
|
|
11017
|
+
rememberBase(known.path, {
|
|
11018
|
+
path: config,
|
|
11019
|
+
lend: verb === "lend"
|
|
11020
|
+
});
|
|
11021
|
+
out(`${known.name}: ${verb === "lend" ? "lent to every session" : "not lent"}`);
|
|
11022
|
+
return 0;
|
|
11023
|
+
}
|
|
11024
|
+
const known = knownBases(config);
|
|
11025
|
+
if (!known.length) {
|
|
11026
|
+
out("no bases yet: each one you open is listed here (isorite serve, term, mcp); isorite memory makes the agent's memory");
|
|
11027
|
+
return 0;
|
|
11028
|
+
}
|
|
11029
|
+
const width = Math.max(...known.map((b) => b.name.length));
|
|
11030
|
+
for (const b of known) out(`${b.name.padEnd(width)} ${b.lend ? "lent " : " "} ${b.path}${existsSync(b.path) ? "" : " (not there)"}`);
|
|
11031
|
+
return 0;
|
|
11032
|
+
}
|
|
11033
|
+
//#endregion
|
|
10494
11034
|
//#region packages/server/src/main.ts
|
|
10495
11035
|
init_src$1();
|
|
10496
11036
|
init_model();
|
|
@@ -10519,6 +11059,10 @@ const USAGE = `isorite: an agentic harness for understanding
|
|
|
10519
11059
|
provider (asked a few words, a few hundred tokens), the terminal and
|
|
10520
11060
|
the notes; --report gives it as one block, secrets hidden, to paste
|
|
10521
11061
|
to someone (or an AI) helping
|
|
11062
|
+
isorite memory [<folder>] the agent's memory: a knowledge base (~/isorite-memory unless named),
|
|
11063
|
+
a git repository of its own, lent to every session's agent and Iso
|
|
11064
|
+
isorite bases [lend|keep <name>] the bases this device knows, each listed as it is opened; lend one to
|
|
11065
|
+
every session, or keep it to itself
|
|
10522
11066
|
isorite help setup how isorite is set up, every setting and failure: written for an AI
|
|
10523
11067
|
helping you set it up (docs/setup.md is for people)
|
|
10524
11068
|
isorite guide [<folder>] [--port <n>] the guide: a knowledge base about isorite, with things to do in it,
|
|
@@ -10557,7 +11101,8 @@ function main(argv) {
|
|
|
10557
11101
|
const allowHosts = rest.flatMap((a, i) => a === "--allow-host" && rest[i + 1] ? [rest[i + 1]] : []);
|
|
10558
11102
|
return serve$1(rest[0], {
|
|
10559
11103
|
...port === void 0 ? {} : { port },
|
|
10560
|
-
allowHosts
|
|
11104
|
+
allowHosts,
|
|
11105
|
+
held: rest.includes("--held")
|
|
10561
11106
|
}).then(() => new Promise(() => void 0), (e) => {
|
|
10562
11107
|
if (e instanceof StudyError) {
|
|
10563
11108
|
console.error(e.message);
|
|
@@ -10613,6 +11158,8 @@ function main(argv) {
|
|
|
10613
11158
|
}
|
|
10614
11159
|
return modelCommand(rest.includes("--from") ? from : void 0);
|
|
10615
11160
|
}
|
|
11161
|
+
if (command === "memory") return memory(rest.find((a) => !a.startsWith("--")));
|
|
11162
|
+
if (command === "bases") return bases(rest);
|
|
10616
11163
|
if (command === "--version" || command === "-v") {
|
|
10617
11164
|
console.log(VERSION);
|
|
10618
11165
|
return 0;
|
|
@@ -10669,11 +11216,32 @@ function main(argv) {
|
|
|
10669
11216
|
process.stdout.write(USAGE);
|
|
10670
11217
|
return command && command !== "help" && command !== "--help" ? 2 : 0;
|
|
10671
11218
|
}
|
|
11219
|
+
/** The commands that start something, which say so at once (M21): a subprocess that stalls after this is then
|
|
11220
|
+
* seen to stall. To stderr, as `mcp` speaks on stdout; once, not again when the command runs itself again with
|
|
11221
|
+
* the proxy on (ISORITE_STARTING). */
|
|
11222
|
+
const STARTS = /* @__PURE__ */ new Set([
|
|
11223
|
+
"serve",
|
|
11224
|
+
"term",
|
|
11225
|
+
"mcp",
|
|
11226
|
+
"guide",
|
|
11227
|
+
"tutorial",
|
|
11228
|
+
"--tutorial"
|
|
11229
|
+
]);
|
|
11230
|
+
function starting(argv, env = process.env, write = (s) => process.stderr.write(s)) {
|
|
11231
|
+
if (!STARTS.has(argv[0] ?? "") || env.ISORITE_STARTING) return false;
|
|
11232
|
+
write("isorite: starting…\n");
|
|
11233
|
+
return true;
|
|
11234
|
+
}
|
|
10672
11235
|
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(resolve(process.argv[1]))).href) {
|
|
11236
|
+
starting(process.argv.slice(2));
|
|
11237
|
+
delete process.env.ISORITE_STARTING;
|
|
10673
11238
|
const env = proxied(process.env);
|
|
10674
|
-
if (env) process.exit(await rerun(
|
|
11239
|
+
if (env) process.exit(await rerun({
|
|
11240
|
+
...env,
|
|
11241
|
+
ISORITE_STARTING: "1"
|
|
11242
|
+
}));
|
|
10675
11243
|
trustSystem();
|
|
10676
11244
|
process.exit(await main(process.argv.slice(2)));
|
|
10677
11245
|
}
|
|
10678
11246
|
//#endregion
|
|
10679
|
-
export { check, main };
|
|
11247
|
+
export { check, main, starting };
|