@100xprompt/brain 0.0.0-stage → 0.1.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/LICENSE +48 -0
- package/README.md +235 -2
- package/package.json +48 -4
- package/src/adapters/aider/index.ts +8 -0
- package/src/adapters/aider/reader.ts +129 -0
- package/src/adapters/aider/records.ts +220 -0
- package/src/adapters/aider/writer.ts +269 -0
- package/src/adapters/amazon-q-cli/index.ts +8 -0
- package/src/adapters/amazon-q-cli/reader.ts +84 -0
- package/src/adapters/amazon-q-cli/records.ts +191 -0
- package/src/adapters/amazon-q-cli/writer.ts +370 -0
- package/src/adapters/amazon-q-ide/index.ts +9 -0
- package/src/adapters/amazon-q-ide/reader.ts +131 -0
- package/src/adapters/amazon-q-ide/records.ts +141 -0
- package/src/adapters/amazon-q-ide/writer.ts +325 -0
- package/src/adapters/antigravity/index.ts +9 -0
- package/src/adapters/antigravity/proto.ts +194 -0
- package/src/adapters/antigravity/reader.ts +424 -0
- package/src/adapters/antigravity/records.ts +236 -0
- package/src/adapters/antigravity/writer.ts +463 -0
- package/src/adapters/auggie/index.ts +36 -0
- package/src/adapters/claude-code/index.ts +8 -0
- package/src/adapters/claude-code/reader.ts +639 -0
- package/src/adapters/claude-code/records.ts +88 -0
- package/src/adapters/claude-code/writer.ts +329 -0
- package/src/adapters/cline/index.ts +12 -0
- package/src/adapters/cline/reader.ts +432 -0
- package/src/adapters/cline/records.ts +151 -0
- package/src/adapters/cline/vscdb.ts +56 -0
- package/src/adapters/cline/writer.ts +656 -0
- package/src/adapters/cline/xml.ts +177 -0
- package/src/adapters/cline4/index.ts +9 -0
- package/src/adapters/cline4/reader.ts +179 -0
- package/src/adapters/cline4/records.ts +92 -0
- package/src/adapters/cline4/writer.ts +361 -0
- package/src/adapters/codebuff/index.ts +8 -0
- package/src/adapters/codebuff/project.ts +207 -0
- package/src/adapters/codebuff/reader.ts +215 -0
- package/src/adapters/codebuff/records.ts +173 -0
- package/src/adapters/codebuff/writer.ts +383 -0
- package/src/adapters/codex/index.ts +8 -0
- package/src/adapters/codex/native.ts +239 -0
- package/src/adapters/codex/reader.ts +747 -0
- package/src/adapters/codex/records.ts +114 -0
- package/src/adapters/codex/writer.ts +479 -0
- package/src/adapters/continue/index.ts +9 -0
- package/src/adapters/continue/reader.ts +68 -0
- package/src/adapters/continue/records.ts +418 -0
- package/src/adapters/continue/writer.ts +162 -0
- package/src/adapters/copilot-cli/index.ts +9 -0
- package/src/adapters/copilot-cli/reader.ts +186 -0
- package/src/adapters/copilot-cli/records.ts +241 -0
- package/src/adapters/copilot-cli/writer.ts +353 -0
- package/src/adapters/crush/index.ts +8 -0
- package/src/adapters/crush/reader.ts +180 -0
- package/src/adapters/crush/records.ts +169 -0
- package/src/adapters/crush/writer.ts +333 -0
- package/src/adapters/cursor-cli/index.ts +405 -0
- package/src/adapters/cursor-ide/agentkv.ts +95 -0
- package/src/adapters/cursor-ide/index.ts +501 -0
- package/src/adapters/cursor-ide/transcripts.ts +122 -0
- package/src/adapters/cursor-ide/workspaces.ts +119 -0
- package/src/adapters/cursor-ide/writer.ts +170 -0
- package/src/adapters/devin/index.ts +8 -0
- package/src/adapters/devin/reader.ts +305 -0
- package/src/adapters/devin/records.ts +277 -0
- package/src/adapters/devin/writer.ts +523 -0
- package/src/adapters/factory-droid/index.ts +12 -0
- package/src/adapters/factory-droid/reader.ts +184 -0
- package/src/adapters/factory-droid/records.ts +79 -0
- package/src/adapters/factory-droid/writer.ts +350 -0
- package/src/adapters/gemini-cli/exchanges.ts +228 -0
- package/src/adapters/gemini-cli/index.ts +396 -0
- package/src/adapters/gemini-cli/records.ts +521 -0
- package/src/adapters/gemini-cli/writer.ts +354 -0
- package/src/adapters/goose/index.ts +8 -0
- package/src/adapters/goose/reader.ts +138 -0
- package/src/adapters/goose/records.ts +199 -0
- package/src/adapters/goose/writer.ts +340 -0
- package/src/adapters/grok-cli/image.ts +44 -0
- package/src/adapters/grok-cli/index.ts +8 -0
- package/src/adapters/grok-cli/reader.ts +302 -0
- package/src/adapters/grok-cli/records.ts +172 -0
- package/src/adapters/grok-cli/writer.ts +329 -0
- package/src/adapters/hermes/index.ts +8 -0
- package/src/adapters/hermes/reader.ts +249 -0
- package/src/adapters/hermes/records.ts +285 -0
- package/src/adapters/hermes/writer.ts +452 -0
- package/src/adapters/iflow-cli/index.ts +72 -0
- package/src/adapters/iflow-cli/records.ts +215 -0
- package/src/adapters/iflow-cli/writer.ts +204 -0
- package/src/adapters/index.ts +112 -0
- package/src/adapters/kilo-cli/index.ts +21 -0
- package/src/adapters/kilo-code/index.ts +11 -0
- package/src/adapters/kilo-code/writer.ts +92 -0
- package/src/adapters/kimi/index.ts +9 -0
- package/src/adapters/kimi/reader.ts +92 -0
- package/src/adapters/kimi/records.ts +221 -0
- package/src/adapters/kimi/writer.ts +367 -0
- package/src/adapters/kiro-cli/index.ts +8 -0
- package/src/adapters/kiro-cli/reader.ts +87 -0
- package/src/adapters/kiro-cli/records.ts +260 -0
- package/src/adapters/kiro-cli/writer.ts +305 -0
- package/src/adapters/kiro-ide/index.ts +8 -0
- package/src/adapters/kiro-ide/writer.ts +52 -0
- package/src/adapters/letta/index.ts +8 -0
- package/src/adapters/letta/reader.ts +177 -0
- package/src/adapters/letta/records.ts +119 -0
- package/src/adapters/letta/writer.ts +413 -0
- package/src/adapters/mastracode/index.ts +8 -0
- package/src/adapters/mastracode/reader.ts +323 -0
- package/src/adapters/mastracode/records.ts +336 -0
- package/src/adapters/mastracode/writer.ts +385 -0
- package/src/adapters/mistral-vibe/index.ts +9 -0
- package/src/adapters/mistral-vibe/reader.ts +62 -0
- package/src/adapters/mistral-vibe/records.ts +104 -0
- package/src/adapters/mistral-vibe/writer.ts +359 -0
- package/src/adapters/muse/index.ts +10 -0
- package/src/adapters/muse/reader.ts +332 -0
- package/src/adapters/muse/records.ts +104 -0
- package/src/adapters/muse/writer.ts +387 -0
- package/src/adapters/omp/index.ts +13 -0
- package/src/adapters/omp/reader.ts +314 -0
- package/src/adapters/omp/records.ts +153 -0
- package/src/adapters/omp/writer.ts +216 -0
- package/src/adapters/opencode/ids.ts +52 -0
- package/src/adapters/opencode/index.ts +31 -0
- package/src/adapters/opencode/note.ts +8 -0
- package/src/adapters/opencode/reader.ts +452 -0
- package/src/adapters/opencode/tools.ts +174 -0
- package/src/adapters/opencode/v2.ts +367 -0
- package/src/adapters/opencode/writer.ts +379 -0
- package/src/adapters/openhands/index.ts +8 -0
- package/src/adapters/openhands/reader.ts +222 -0
- package/src/adapters/openhands/records.ts +136 -0
- package/src/adapters/openhands/writer.ts +404 -0
- package/src/adapters/pearai/index.ts +10 -0
- package/src/adapters/pi/index.ts +28 -0
- package/src/adapters/pi/reader.ts +261 -0
- package/src/adapters/pi/records.ts +99 -0
- package/src/adapters/pi/writer.ts +362 -0
- package/src/adapters/qoder-cli/index.ts +8 -0
- package/src/adapters/qoder-cli/reader.ts +83 -0
- package/src/adapters/qoder-cli/records.ts +45 -0
- package/src/adapters/qoder-cli/writer.ts +72 -0
- package/src/adapters/qwen-code/index.ts +84 -0
- package/src/adapters/qwen-code/records.ts +194 -0
- package/src/adapters/qwen-code/writer.ts +224 -0
- package/src/adapters/tabnine-cli/index.ts +8 -0
- package/src/adapters/tabnine-cli/reader.ts +106 -0
- package/src/adapters/tabnine-cli/records.ts +33 -0
- package/src/adapters/tabnine-cli/writer.ts +156 -0
- package/src/adapters/types.ts +73 -0
- package/src/adapters/void/index.ts +9 -0
- package/src/adapters/void/reader.ts +48 -0
- package/src/adapters/void/records.ts +285 -0
- package/src/adapters/void/writer.ts +93 -0
- package/src/adapters/vscode-copilot/index.ts +9 -0
- package/src/adapters/vscode-copilot/reader.ts +212 -0
- package/src/adapters/vscode-copilot/records.ts +388 -0
- package/src/adapters/vscode-copilot/shared.ts +68 -0
- package/src/adapters/vscode-copilot/writer.ts +546 -0
- package/src/adapters/zed/index.ts +8 -0
- package/src/adapters/zed/reader.ts +198 -0
- package/src/adapters/zed/records.ts +87 -0
- package/src/adapters/zed/writer.ts +341 -0
- package/src/brief-writers.ts +71 -0
- package/src/brief.ts +238 -0
- package/src/cli.ts +351 -0
- package/src/dashboard/cli.ts +148 -0
- package/src/dashboard/conversions.ts +170 -0
- package/src/dashboard/report.ts +261 -0
- package/src/dashboard/send.ts +213 -0
- package/src/dashboard/state.ts +95 -0
- package/src/fsutil.ts +133 -0
- package/src/gateway/common.ts +232 -0
- package/src/gateway/connect.ts +847 -0
- package/src/gateway/connectors/aider.ts +356 -0
- package/src/gateway/connectors/amazon-q-cli.ts +6 -0
- package/src/gateway/connectors/amazon-q-ide.ts +6 -0
- package/src/gateway/connectors/antigravity.ts +6 -0
- package/src/gateway/connectors/auggie.ts +6 -0
- package/src/gateway/connectors/cline-sdk.ts +85 -0
- package/src/gateway/connectors/cline.ts +34 -0
- package/src/gateway/connectors/codebuff.ts +6 -0
- package/src/gateway/connectors/continue.ts +579 -0
- package/src/gateway/connectors/copilot-cli.ts +138 -0
- package/src/gateway/connectors/crush.ts +313 -0
- package/src/gateway/connectors/cursor-cli.ts +6 -0
- package/src/gateway/connectors/cursor.ts +6 -0
- package/src/gateway/connectors/devin.ts +6 -0
- package/src/gateway/connectors/factory-droid.ts +308 -0
- package/src/gateway/connectors/goose.ts +79 -0
- package/src/gateway/connectors/grok-cli.ts +82 -0
- package/src/gateway/connectors/hermes.ts +230 -0
- package/src/gateway/connectors/iflow-cli.ts +50 -0
- package/src/gateway/connectors/index.ts +86 -0
- package/src/gateway/connectors/kilo-cli.ts +434 -0
- package/src/gateway/connectors/kilo-code.ts +32 -0
- package/src/gateway/connectors/kimi-code.ts +241 -0
- package/src/gateway/connectors/kiro-cli.ts +6 -0
- package/src/gateway/connectors/kiro-ide.ts +6 -0
- package/src/gateway/connectors/letta-code.ts +170 -0
- package/src/gateway/connectors/mastracode.ts +91 -0
- package/src/gateway/connectors/mistral-vibe.ts +495 -0
- package/src/gateway/connectors/muse.ts +27 -0
- package/src/gateway/connectors/omp.ts +279 -0
- package/src/gateway/connectors/openhands.ts +296 -0
- package/src/gateway/connectors/pearai.ts +52 -0
- package/src/gateway/connectors/pi.ts +357 -0
- package/src/gateway/connectors/qoder-cli.ts +6 -0
- package/src/gateway/connectors/qwen-code.ts +411 -0
- package/src/gateway/connectors/roo.ts +43 -0
- package/src/gateway/connectors/tabnine-cli.ts +6 -0
- package/src/gateway/connectors/void.ts +6 -0
- package/src/gateway/connectors/vscode-copilot.ts +286 -0
- package/src/gateway/connectors/windsurf.ts +6 -0
- package/src/gateway/connectors/zed.ts +29 -0
- package/src/gateway/signin.ts +323 -0
- package/src/handoff.ts +104 -0
- package/src/hooks.ts +455 -0
- package/src/memory/audit.ts +514 -0
- package/src/memory/blobs.ts +94 -0
- package/src/memory/cli.ts +117 -0
- package/src/memory/distill.ts +381 -0
- package/src/memory/externalize.ts +435 -0
- package/src/memory/lookups.ts +70 -0
- package/src/memory/mcp-install.ts +401 -0
- package/src/memory/mcp-server.ts +296 -0
- package/src/memory/prepare.ts +179 -0
- package/src/memory/preview.ts +154 -0
- package/src/memory/project.ts +35 -0
- package/src/memory/redact.ts +72 -0
- package/src/memory/relevance.ts +187 -0
- package/src/memory/repomap.ts +516 -0
- package/src/memory/store.ts +270 -0
- package/src/memory/sync.ts +166 -0
- package/src/memory/tidy.ts +193 -0
- package/src/memory/types.ts +118 -0
- package/src/org/admin-ops.ts +96 -0
- package/src/org/auth.ts +112 -0
- package/src/org/backup.ts +115 -0
- package/src/org/cli.ts +438 -0
- package/src/org/client.ts +404 -0
- package/src/org/config.ts +44 -0
- package/src/org/crypto.ts +537 -0
- package/src/org/db.ts +435 -0
- package/src/org/graph.ts +318 -0
- package/src/org/oidc.ts +435 -0
- package/src/org/policy.ts +398 -0
- package/src/org/server.ts +920 -0
- package/src/org/sso.ts +316 -0
- package/src/org/tls.ts +66 -0
- package/src/org/types.ts +193 -0
- package/src/self.ts +68 -0
- package/src/session/attachments.ts +53 -0
- package/src/session/peekcache.ts +50 -0
- package/src/session/types.ts +104 -0
- package/src/session/util.ts +130 -0
- package/src/sqlite.ts +48 -0
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto"
|
|
2
|
+
import { mkdirSync, realpathSync, rmSync, writeFileSync } from "node:fs"
|
|
3
|
+
import { basename, dirname } from "node:path"
|
|
4
|
+
import { toolLabel } from "../../brief.ts"
|
|
5
|
+
import { exists, isDir, readJsonl, versionOf } from "../../fsutil.ts"
|
|
6
|
+
import type { Session, Turn } from "../../session/types.ts"
|
|
7
|
+
import { countTurns, unpaired } from "../../session/util.ts"
|
|
8
|
+
import { continuedNote } from "../claude-code/records.ts"
|
|
9
|
+
import type { Readiness, WriteOptions, WriteResult, Writer } from "../types.ts"
|
|
10
|
+
import { parseSession, sessionFiles } from "./reader.ts"
|
|
11
|
+
import { BIN, BUILD_SHA, FAILED_PREFIX, isObj, LABEL, type Rec, sessionPath, TOOL } from "./records.ts"
|
|
12
|
+
|
|
13
|
+
// Continues a portable session in Muse Code as a new session of its own: one durable event stream at
|
|
14
|
+
// sessions/YYYY/MM/DD/<uuid>/session.jsonl under Muse's data home, which `muse exec --session-id <uuid>`
|
|
15
|
+
// resumes. The record lifecycle is the one session-migrate (xhluca/session-migrate, MIT) found Muse needs to
|
|
16
|
+
// rebuild a model's context from a history it did not write itself (docs/muse-qwen-kimi-formats.md), with the
|
|
17
|
+
// field values Muse 0.2.1 writes in the real capture:
|
|
18
|
+
//
|
|
19
|
+
// runtime.session.metadata, session.opened.observed, then per prompt runtime.user_intent.accepted (the
|
|
20
|
+
// prompt as model_messages and refill_blocks), a run `started` event, runtime.user_intent.materialized
|
|
21
|
+
// naming both, the model's committed messages, tool-call batches and their result batch, a `terminal` run
|
|
22
|
+
// event; session.end last.
|
|
23
|
+
//
|
|
24
|
+
// Envelopes carry contiguous sequences, fresh UUIDs and strictly increasing recorded_at microseconds. Each run
|
|
25
|
+
// event other than a result batch has its own source_run_record_id and per-run sequence, a result batch's
|
|
26
|
+
// batch_id is its call batch's message_id, and a call keeps its id as call_id with `fc_<id>` as its item id,
|
|
27
|
+
// as Muse writes them. Muse keeps no error flag on a result: a failure is worded "tool failed: ...", as Muse
|
|
28
|
+
// words its own. Read calls become Muse's read_file; other calls keep their names.
|
|
29
|
+
//
|
|
30
|
+
// The note on where the session came from is a first text block of the first prompt's model_messages (what
|
|
31
|
+
// the model gets); refill_blocks and the started event hold the person's words alone. Muse keeps no images,
|
|
32
|
+
// and replays no reasoning or compaction from outside, so those are counted as lost.
|
|
33
|
+
|
|
34
|
+
/** Muse Code versions whose session format this writer was checked against. */
|
|
35
|
+
export const TESTED_VERSIONS = ["0.2.1"]
|
|
36
|
+
|
|
37
|
+
/** The model and provider Muse 0.2.1 recorded in the capture, and session-migrate's documented Muse target. */
|
|
38
|
+
export const DEFAULT_MODEL = "meta/muse-glimmer-30b"
|
|
39
|
+
export const DEFAULT_PROVIDER = "meta"
|
|
40
|
+
|
|
41
|
+
const shellQuote = (s: string) => (/^[\w./@%+=:,-]+$/.test(s) ? s : `'${s.replace(/'/g, `'\\''`)}'`)
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The installed version ("Muse Code 0.2.1 (0.2.1-R1215.1)" gives "0.2.1"), or undefined when it is missing.
|
|
45
|
+
* The binary is looked up on `PATH` as it is now: Bun.which without one does not see a PATH changed after start.
|
|
46
|
+
*/
|
|
47
|
+
export function museVersion(bin = BIN, PATH = process.env.PATH ?? ""): string | undefined {
|
|
48
|
+
const path = bin.includes("/") ? bin : Bun.which(bin, { PATH })
|
|
49
|
+
if (!path) return undefined
|
|
50
|
+
return versionOf(path)?.match(/(\d+\.\d+\.\d+)/)?.[1]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Muse records the workspace as the process sees it: the resolved path (/private/tmp, not /tmp). */
|
|
54
|
+
function realCwd(cwd: string): string {
|
|
55
|
+
try {
|
|
56
|
+
return realpathSync(cwd)
|
|
57
|
+
} catch {
|
|
58
|
+
return cwd
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type Call = Extract<Turn, { kind: "tool_call" }>
|
|
63
|
+
|
|
64
|
+
const hex = () => randomUUID().replace(/-/g, "").slice(0, 24)
|
|
65
|
+
|
|
66
|
+
/** A read from another tool as Muse's own read_file, when it names a file; undefined keeps the call as it was. */
|
|
67
|
+
function asMuseTool(c: Call): { name: string; args: Rec } | undefined {
|
|
68
|
+
if (c.toolKind !== "read" || !isObj(c.input)) return undefined
|
|
69
|
+
const path = ["path", "file_path", "filePath", "absolute_path", "target_file", "file"].map((k) => (c.input as Rec)[k]).find((v) => typeof v === "string" && v)
|
|
70
|
+
return path ? { name: "read_file", args: { path } } : undefined
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface MusePlan {
|
|
74
|
+
/** Every record of the new session file, in order. */
|
|
75
|
+
records: Rec[]
|
|
76
|
+
lost: string[]
|
|
77
|
+
/** What a read-back must find. */
|
|
78
|
+
expect: { users: number; assistants: number; calls: number }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The records of a new Muse session `id` continuing `session`. Pure apart from random ids. */
|
|
82
|
+
export function planMuse(session: Session, o: { id: string; cwd: string; now: Date; model: string; provider: string; version: string }): MusePlan {
|
|
83
|
+
const drops = new Map<string, number>()
|
|
84
|
+
const drop = (what: string, n = 1) => drops.set(what, (drops.get(what) ?? 0) + n)
|
|
85
|
+
const label = toolLabel(session.header.tool)
|
|
86
|
+
const expect = { users: 0, assistants: 0, calls: 0 }
|
|
87
|
+
|
|
88
|
+
// recorded_at: microseconds, strictly increasing; turn times where they move forward, never past now.
|
|
89
|
+
const nowUs = o.now.getTime() * 1000
|
|
90
|
+
const known = session.turns.map((t) => (t.at ? Date.parse(t.at) : Number.NaN)).find(Number.isFinite)
|
|
91
|
+
let clock = Math.min(known ?? o.now.getTime(), o.now.getTime()) * 1000 - 1
|
|
92
|
+
const stamp = (at?: string) => {
|
|
93
|
+
const t = at ? Date.parse(at) * 1000 : Number.NaN
|
|
94
|
+
clock = Number.isFinite(t) && t > clock && t <= nowUs ? t : clock + 1
|
|
95
|
+
return clock
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const records: Rec[] = []
|
|
99
|
+
const append = (payloadType: string, payload: Rec, at?: string, causation: string | null = null) => {
|
|
100
|
+
const r: Rec = {
|
|
101
|
+
schema_version: 1,
|
|
102
|
+
id: randomUUID(),
|
|
103
|
+
stream: { kind: "session", id: o.id },
|
|
104
|
+
sequence: records.length + 1,
|
|
105
|
+
recorded_at: stamp(at),
|
|
106
|
+
record_type: "event",
|
|
107
|
+
durability: "durable",
|
|
108
|
+
causation_id: causation,
|
|
109
|
+
payload_type: payloadType,
|
|
110
|
+
payload_schema_version: 1,
|
|
111
|
+
payload,
|
|
112
|
+
}
|
|
113
|
+
records.push(r)
|
|
114
|
+
return r
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
append("runtime.session.metadata", {
|
|
118
|
+
kind: "metadata",
|
|
119
|
+
record: { build: { semver: o.version, sha: BUILD_SHA }, model_id: o.model, provider_id: o.provider, tool_surface_version: "2", web_search_mode: "client", workspace_root: o.cwd },
|
|
120
|
+
})
|
|
121
|
+
append("session.opened.observed", {
|
|
122
|
+
kind: "session_opened",
|
|
123
|
+
record: { data_dir_free_bytes: 0, resume: false, schema_version: 1, security_mode: "normal", session_id: o.id, workspace_free_bytes: 0 },
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
// A run is one prompt and everything the model did for it.
|
|
127
|
+
let run: { id: string; seq: number } | undefined
|
|
128
|
+
let turn = 0
|
|
129
|
+
let noted = false
|
|
130
|
+
const note = continuedNote(label, session.header.id, o.now.toISOString().slice(0, 10))
|
|
131
|
+
const runEvent = (event: Rec, at?: string, source = true) =>
|
|
132
|
+
append("runtime.session", { event, kind: "run", run_id: run!.id, ...(source ? { source_run_record_id: randomUUID(), source_run_record_sequence: ++run!.seq } : {}) }, at)
|
|
133
|
+
const endRun = () => {
|
|
134
|
+
if (!run) return
|
|
135
|
+
runEvent({ eot_gate_ms: 0, kind: "terminal", reason: null, terminal: "completed", time_to_first_token_ms: 0, turn_duration_ms: 0 })
|
|
136
|
+
run = undefined
|
|
137
|
+
}
|
|
138
|
+
/** Opens a run for `prompt`; without one (the session opens with the model), the note is the prompt. */
|
|
139
|
+
const startRun = (prompt: string | undefined, at?: string) => {
|
|
140
|
+
endRun()
|
|
141
|
+
const text = prompt ?? note
|
|
142
|
+
const blocks = noted || prompt === undefined ? [{ kind: "text", text }] : [{ kind: "text", text: note }, { kind: "text", text }]
|
|
143
|
+
noted = true
|
|
144
|
+
// Muse uses one id for the command, the intent and its run.
|
|
145
|
+
const id = randomUUID()
|
|
146
|
+
const accepted = append(
|
|
147
|
+
"runtime.user_intent.accepted",
|
|
148
|
+
{ bindings: [], delivery_policy: "session_current", intent_id: id, model_messages: [{ content: blocks }], refill_blocks: [{ kind: "text", text }], semantic_kind: { kind: "chat" }, source_session_id: o.id, surface: "main", wake_policy: "start_once" },
|
|
149
|
+
at,
|
|
150
|
+
id,
|
|
151
|
+
)
|
|
152
|
+
run = { id, seq: 0 }
|
|
153
|
+
const started = runEvent({ kind: "started", prompt: text }, at)
|
|
154
|
+
append(
|
|
155
|
+
"runtime.user_intent.materialized",
|
|
156
|
+
{
|
|
157
|
+
envelope_record_id: accepted.id,
|
|
158
|
+
envelope_sequence: accepted.sequence,
|
|
159
|
+
intent_id: id,
|
|
160
|
+
outcome: { cut_before: `session:${o.id}:turn:${turn}`, kind: "top_level_turn_started", run_id: id, run_started_session_record_id: started.id, run_started_source_record_id: started.payload.source_run_record_id },
|
|
161
|
+
source_session_id: o.id,
|
|
162
|
+
},
|
|
163
|
+
at,
|
|
164
|
+
)
|
|
165
|
+
turn++
|
|
166
|
+
}
|
|
167
|
+
const ensureRun = (at?: string) => {
|
|
168
|
+
if (!run) startRun(undefined, at)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Call ids stay as they were when Muse can carry them, else get a call_ id; each is used once.
|
|
172
|
+
const ids = new Map<string, string>()
|
|
173
|
+
const used = new Set<string>()
|
|
174
|
+
let remapped = 0
|
|
175
|
+
let renamed = 0
|
|
176
|
+
const idFor = (src: string) => {
|
|
177
|
+
let v = ids.get(src)
|
|
178
|
+
if (v) return v
|
|
179
|
+
v = /^[A-Za-z0-9_.:-]{1,64}$/.test(src) && !used.has(src) ? src : `call_${hex()}`
|
|
180
|
+
while (used.has(v)) v = `call_${hex()}`
|
|
181
|
+
if (v !== src) remapped++
|
|
182
|
+
ids.set(src, v)
|
|
183
|
+
used.add(v)
|
|
184
|
+
return v
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// One model response: its text and its tool-call batch share a response id; results come back as one batch.
|
|
188
|
+
let response: string | undefined
|
|
189
|
+
type Batch = { messageId: string; calls: Array<{ src: string; id: string; name: string; args: string }>; results: Map<string, string>; written: boolean; at?: string }
|
|
190
|
+
let batch: Batch | undefined
|
|
191
|
+
const given = new Set<string>()
|
|
192
|
+
const writeCalls = () => {
|
|
193
|
+
if (!batch || batch.written) return
|
|
194
|
+
response ??= `resp_${hex()}`
|
|
195
|
+
runEvent({ kind: "assistant_tool_calls_committed", message_id: batch.messageId, response_id: response, tool_calls: batch.calls.map((c) => ({ args: c.args, call_id: c.id, id: `fc_${c.id}`, name: c.name })) }, batch.at)
|
|
196
|
+
batch.written = true
|
|
197
|
+
}
|
|
198
|
+
const closeBatch = () => {
|
|
199
|
+
if (!batch) return
|
|
200
|
+
writeCalls()
|
|
201
|
+
const b = batch
|
|
202
|
+
const results = b.calls.map((c, i) => {
|
|
203
|
+
let text = b.results.get(c.src)
|
|
204
|
+
if (text === undefined) {
|
|
205
|
+
drop("tool calls without a result (closed as failed)")
|
|
206
|
+
text = `${FAILED_PREFIX}no result was recorded for this call; the session moved on before it finished.`
|
|
207
|
+
}
|
|
208
|
+
return { text, tool_call_id: c.id, tool_call_index: i }
|
|
209
|
+
})
|
|
210
|
+
// A result batch carries no source run record, as Muse writes it.
|
|
211
|
+
runEvent({ batch_id: b.messageId, kind: "tool_result_batch_committed", results }, b.at, false)
|
|
212
|
+
batch = undefined
|
|
213
|
+
response = undefined
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
for (const t of session.turns) {
|
|
217
|
+
switch (t.kind) {
|
|
218
|
+
case "user": {
|
|
219
|
+
closeBatch()
|
|
220
|
+
response = undefined
|
|
221
|
+
let text = t.text
|
|
222
|
+
for (const a of t.attachments ?? []) {
|
|
223
|
+
if (a.kind === "file" && a.path) text += `\n\n[Attached file: ${a.path}]`
|
|
224
|
+
else drop(`${a.kind === "image" ? "images" : "attachments"} (a Muse session keeps no image)`)
|
|
225
|
+
}
|
|
226
|
+
if (!text.trim()) break
|
|
227
|
+
startRun(text, t.at)
|
|
228
|
+
expect.users++
|
|
229
|
+
break
|
|
230
|
+
}
|
|
231
|
+
case "assistant": {
|
|
232
|
+
closeBatch()
|
|
233
|
+
if (!t.text.trim()) break
|
|
234
|
+
ensureRun(t.at)
|
|
235
|
+
response = `resp_${hex()}`
|
|
236
|
+
runEvent({ kind: "assistant_message_committed", message_id: randomUUID(), provider_item_id: `msg_${response}`, response_id: response, text: t.text }, t.at)
|
|
237
|
+
expect.assistants++
|
|
238
|
+
break
|
|
239
|
+
}
|
|
240
|
+
case "reasoning":
|
|
241
|
+
if (t.text.trim()) drop("reasoning (Muse replays no reasoning written from outside)")
|
|
242
|
+
break
|
|
243
|
+
case "tool_call": {
|
|
244
|
+
if (ids.has(t.id)) {
|
|
245
|
+
drop("repeated tool call ids")
|
|
246
|
+
break
|
|
247
|
+
}
|
|
248
|
+
// A call after results is the next response.
|
|
249
|
+
if (batch?.results.size) closeBatch()
|
|
250
|
+
ensureRun(t.at)
|
|
251
|
+
const own = asMuseTool(t)
|
|
252
|
+
const name = own?.name ?? (t.name.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 64) || "tool")
|
|
253
|
+
if (name !== t.name) renamed++
|
|
254
|
+
let args: Rec
|
|
255
|
+
if (own) args = own.args
|
|
256
|
+
else if (isObj(t.input)) args = t.input
|
|
257
|
+
else {
|
|
258
|
+
args = { input: t.input ?? null }
|
|
259
|
+
drop("tool inputs that were not objects (wrapped)")
|
|
260
|
+
}
|
|
261
|
+
batch ??= { messageId: randomUUID(), calls: [], results: new Map(), written: false, ...(t.at ? { at: t.at } : {}) }
|
|
262
|
+
batch.calls.push({ src: t.id, id: idFor(t.id), name, args: JSON.stringify(args) })
|
|
263
|
+
expect.calls++
|
|
264
|
+
break
|
|
265
|
+
}
|
|
266
|
+
case "tool_result": {
|
|
267
|
+
if (!ids.has(t.callId)) {
|
|
268
|
+
drop("tool results without a call")
|
|
269
|
+
break
|
|
270
|
+
}
|
|
271
|
+
if (given.has(t.callId)) {
|
|
272
|
+
drop("repeated tool results")
|
|
273
|
+
break
|
|
274
|
+
}
|
|
275
|
+
// Its call's batch was already closed (with a failure) when something else came between them.
|
|
276
|
+
if (!batch || !batch.calls.some((c) => c.src === t.callId)) {
|
|
277
|
+
drop("tool results recorded after the session moved on")
|
|
278
|
+
break
|
|
279
|
+
}
|
|
280
|
+
given.add(t.callId)
|
|
281
|
+
writeCalls()
|
|
282
|
+
let out = t.fullPath ? `${t.output}\n\n[Full output: ${t.fullPath}]` : t.output
|
|
283
|
+
if (!t.ok && !out.startsWith(FAILED_PREFIX.trimEnd())) out = `${FAILED_PREFIX}${out}`
|
|
284
|
+
batch.results.set(t.callId, out)
|
|
285
|
+
if (t.at) batch.at = t.at
|
|
286
|
+
if (batch.results.size === batch.calls.length) closeBatch()
|
|
287
|
+
break
|
|
288
|
+
}
|
|
289
|
+
case "compaction":
|
|
290
|
+
closeBatch()
|
|
291
|
+
drop("compaction summaries (the turns they replaced are all kept)")
|
|
292
|
+
break
|
|
293
|
+
case "file_edit":
|
|
294
|
+
if (!t.callId || !ids.has(t.callId)) drop("file edits outside a tool call")
|
|
295
|
+
break
|
|
296
|
+
case "subagent":
|
|
297
|
+
drop("subagent transcripts (their results stay in the parent's tool results)")
|
|
298
|
+
break
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
closeBatch()
|
|
302
|
+
endRun()
|
|
303
|
+
if (!expect.users && !expect.assistants && !expect.calls) throw new Error("nothing to continue: the session has no messages or tool calls")
|
|
304
|
+
append("session.end", { kind: "session_end", record: { exit_reason: "clean", resource_usage: {}, schema_version: 1, session_id: o.id, uptime_ms: 0 } })
|
|
305
|
+
|
|
306
|
+
const lost = [...drops].map(([what, n]) => `${what} (${n})`)
|
|
307
|
+
if (renamed) lost.push(`original tool names (${renamed} calls renamed to Muse's read_file or to fit its rules)`)
|
|
308
|
+
if (remapped) lost.push(`tool call ids remapped (${remapped})`)
|
|
309
|
+
return { records, lost, expect }
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Options for tests and for people who run Muse with another model; the defaults suit an installed Muse 0.2.1. */
|
|
313
|
+
export interface MuseWriterOptions {
|
|
314
|
+
/** The installed version, instead of asking `muse --version`. */
|
|
315
|
+
version?: string
|
|
316
|
+
bin?: string
|
|
317
|
+
/** The PATH to look the binary up on (default: process.env.PATH when ready() runs). */
|
|
318
|
+
PATH?: string
|
|
319
|
+
model?: string
|
|
320
|
+
provider?: string
|
|
321
|
+
/** New session ids (default: random UUIDs). */
|
|
322
|
+
newId?: () => string
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function museWriter(opts: MuseWriterOptions = {}): Writer {
|
|
326
|
+
const installed = () => opts.version ?? museVersion(opts.bin ?? BIN, opts.PATH ?? process.env.PATH ?? "")
|
|
327
|
+
const newId = opts.newId ?? (() => randomUUID())
|
|
328
|
+
return {
|
|
329
|
+
tool: TOOL,
|
|
330
|
+
label: LABEL,
|
|
331
|
+
way: "native",
|
|
332
|
+
|
|
333
|
+
async ready(w: WriteOptions): Promise<Readiness> {
|
|
334
|
+
const v = installed()
|
|
335
|
+
if (!v) return { ok: false, why: `${LABEL} is not installed (\`${opts.bin ?? BIN} --version\` did not run)` }
|
|
336
|
+
if (!TESTED_VERSIONS.includes(v)) return { ok: false, why: `${LABEL} ${v} is not a version this writer was checked against (${TESTED_VERSIONS.join(", ")})` }
|
|
337
|
+
if (!isDir(w.cwd)) return { ok: false, why: `${w.cwd} is not a folder; ${LABEL} records the workspace a session runs in` }
|
|
338
|
+
return { ok: true }
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
async write(session: Session, w: WriteOptions): Promise<WriteResult> {
|
|
342
|
+
const cwd = realCwd(w.cwd)
|
|
343
|
+
const now = new Date()
|
|
344
|
+
// Muse finds a session by its id in any date folder, so the id must be new in all of them.
|
|
345
|
+
const taken = new Set(sessionFiles(w.home).map((p) => basename(dirname(p))))
|
|
346
|
+
let id = newId()
|
|
347
|
+
for (let tries = 0; taken.has(id) || exists(dirname(sessionPath(w.home, id, now))); tries++) {
|
|
348
|
+
if (tries >= 20) throw new Error(`could not find an unused ${LABEL} session id; nothing was written`)
|
|
349
|
+
id = newId()
|
|
350
|
+
}
|
|
351
|
+
const path = sessionPath(w.home, id, now)
|
|
352
|
+
const plan = planMuse(session, { id, cwd, now, model: opts.model ?? DEFAULT_MODEL, provider: opts.provider ?? DEFAULT_PROVIDER, version: TESTED_VERSIONS[TESTED_VERSIONS.length - 1]! })
|
|
353
|
+
if (!w.dryRun) {
|
|
354
|
+
const dir = dirname(path)
|
|
355
|
+
mkdirSync(dirname(dir), { recursive: true, mode: 0o700 })
|
|
356
|
+
// mkdir without recursive on the session folder itself: an existing session is never touched.
|
|
357
|
+
mkdirSync(dir, { mode: 0o700 })
|
|
358
|
+
try {
|
|
359
|
+
writeFileSync(path, `${plan.records.map((r) => JSON.stringify(r)).join("\n")}\n`, { flag: "wx", mode: 0o600 })
|
|
360
|
+
const { rows, bad } = readJsonl(path)
|
|
361
|
+
const back = parseSession(rows, bad, path, w.home)
|
|
362
|
+
const n = countTurns(back)
|
|
363
|
+
const pairs = unpaired(back)
|
|
364
|
+
if (n.user !== plan.expect.users || n.assistant !== plan.expect.assistants || n.tool_call !== plan.expect.calls || pairs.calls.length || pairs.results.length)
|
|
365
|
+
throw new Error(
|
|
366
|
+
`the new ${LABEL} session did not read back as written (users ${n.user}/${plan.expect.users}, replies ${n.assistant}/${plan.expect.assistants}, tool calls ${n.tool_call}/${plan.expect.calls}, unpaired ${pairs.calls.length + pairs.results.length}); nothing was kept`,
|
|
367
|
+
)
|
|
368
|
+
} catch (e) {
|
|
369
|
+
rmSync(dir, { recursive: true, force: true })
|
|
370
|
+
throw e
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
tool: TOOL,
|
|
375
|
+
way: "native",
|
|
376
|
+
sessionId: id,
|
|
377
|
+
written: [path],
|
|
378
|
+
open: `cd ${shellQuote(cwd)} && ${opts.bin ?? BIN} exec --session-id ${id} --workspace ${shellQuote(cwd)} "<your next message>"`,
|
|
379
|
+
note: `${LABEL} resumes a session by id with \`muse exec --session-id\` (the path session-migrate checked with Muse 0.2.1); it answers with your own Muse provider and model settings.`,
|
|
380
|
+
lost: plan.lost,
|
|
381
|
+
dryRun: !!w.dryRun,
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export const museNativeWriter = museWriter()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Reader, Writer } from "../types.ts"
|
|
2
|
+
import { ompReader } from "./reader.ts"
|
|
3
|
+
import { ompWriter } from "./writer.ts"
|
|
4
|
+
|
|
5
|
+
// Oh My Pi (can1357/oh-my-pi, `omp`), a fork of Pi: reads its sessions from ~/.omp/agent and each profile's
|
|
6
|
+
// ~/.omp/profiles/<name>/agent, and continues any session as a new native OMP session, `omp --resume <id>`.
|
|
7
|
+
// Format and checks ported from xhluca/session-migrate (MIT), docs/omp-format.md and formats/omp.py; see records.ts.
|
|
8
|
+
|
|
9
|
+
export { OMP_TESTED_VERSIONS } from "./records.ts"
|
|
10
|
+
export { ompReader, ompWriter }
|
|
11
|
+
|
|
12
|
+
export const readers: Reader[] = [ompReader]
|
|
13
|
+
export const writers: Writer[] = [ompWriter()]
|