@mojoaar/opencode-mimir 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +50 -1
- package/dist/tools.js +18 -4
- package/package.json +1 -1
- package/src/index.ts +53 -1
- package/src/tools.ts +18 -4
package/dist/index.js
CHANGED
|
@@ -18,6 +18,11 @@ export const MAX_TRANSCRIPT = 32_000;
|
|
|
18
18
|
/** A compaction of nothing is not worth a model call. */
|
|
19
19
|
export const MIN_TRANSCRIPT = 500;
|
|
20
20
|
const LEARN_HINT = 'This session was just compacted. Keep what was decided, what broke and any convention, and skip what Mimir already has.';
|
|
21
|
+
const LEARN_NOW_HINT = 'The user asked for this session to be kept now. Keep what was decided, what broke and any convention, and skip what Mimir already has.';
|
|
22
|
+
const NOTHING_TO_SEND = 'Nothing worth handing over yet.';
|
|
23
|
+
const SENT = 'Mimir is writing what this session settled.';
|
|
24
|
+
const QUEUE_OFF = 'Mimir took it, but its queue is not running.';
|
|
25
|
+
const UNREACHABLE = 'Mimir is unreachable, so nothing was sent.';
|
|
21
26
|
/** Message text is shaped differently by each provider, so read what we can and skip the rest. */
|
|
22
27
|
export function transcript(messages) {
|
|
23
28
|
const lines = [];
|
|
@@ -82,9 +87,15 @@ export default Plugin.define({
|
|
|
82
87
|
// The reminder rides the message count. A shrink means a compaction just
|
|
83
88
|
// happened, which is the worst moment to lose a detail, so it nudges early.
|
|
84
89
|
const counts = new Map();
|
|
90
|
+
// The command below is handed no messages, only a session id, so the hook
|
|
91
|
+
// keeps the newest transcript for it. One string per live session, at most
|
|
92
|
+
// 32 KB, dropped once it has been sent.
|
|
93
|
+
const snapshots = new Map();
|
|
85
94
|
const nudge = await ctx.session.hook('context', async (event) => {
|
|
86
95
|
const state = counts.get(event.sessionID) ?? { turns: 0, nudgedAt: 0 };
|
|
87
96
|
const turns = event.messages?.length ?? 0;
|
|
97
|
+
if (turns > state.turns)
|
|
98
|
+
snapshots.set(event.sessionID, transcript(event.messages ?? []));
|
|
88
99
|
const due = turns < state.turns || turns - state.nudgedAt >= NUDGE_EVERY;
|
|
89
100
|
counts.set(event.sessionID, { turns, nudgedAt: due ? turns : state.nudgedAt });
|
|
90
101
|
if (due)
|
|
@@ -102,19 +113,57 @@ export default Plugin.define({
|
|
|
102
113
|
if (text.length < MIN_TRANSCRIPT)
|
|
103
114
|
return;
|
|
104
115
|
compacted.add(event.sessionID);
|
|
105
|
-
const sent = await callTool('memory_learn', {
|
|
116
|
+
const sent = await callTool('memory_learn', {
|
|
117
|
+
repo: repo ?? undefined,
|
|
118
|
+
transcript: text,
|
|
119
|
+
hint: LEARN_HINT,
|
|
120
|
+
client: 'opencode',
|
|
121
|
+
session: event.sessionID,
|
|
122
|
+
}, { ...config, timeoutMs: 4000 }).catch(() => null);
|
|
106
123
|
// Nothing is written to event.system here: this request's prompt becomes
|
|
107
124
|
// the summary that replaces the conversation. A failure gives the session
|
|
108
125
|
// its one chance back, so an outage does not use the compaction up.
|
|
109
126
|
if (!sent?.ok)
|
|
110
127
|
compacted.delete(event.sessionID);
|
|
111
128
|
});
|
|
129
|
+
// A manual handover, for the sessions that end before they ever compact.
|
|
130
|
+
const command = await ctx.command.transform((editor) => {
|
|
131
|
+
editor.add({
|
|
132
|
+
name: 'mimir-learn',
|
|
133
|
+
description: 'Hand this session to Mimir now.',
|
|
134
|
+
execute: async ({ sessionID }) => {
|
|
135
|
+
const text = snapshots.get(sessionID) ?? '';
|
|
136
|
+
let notice = NOTHING_TO_SEND;
|
|
137
|
+
if (text.length >= MIN_TRANSCRIPT) {
|
|
138
|
+
const sent = await callTool('memory_learn', {
|
|
139
|
+
repo: repo ?? undefined,
|
|
140
|
+
transcript: text,
|
|
141
|
+
hint: LEARN_NOW_HINT,
|
|
142
|
+
client: 'opencode',
|
|
143
|
+
session: sessionID,
|
|
144
|
+
}, { ...config, timeoutMs: 4000 }).catch(() => null);
|
|
145
|
+
const queued = sent?.ok ? sent.result.queued : undefined;
|
|
146
|
+
if (!sent?.ok)
|
|
147
|
+
notice = UNREACHABLE;
|
|
148
|
+
else if (queued === false)
|
|
149
|
+
notice = QUEUE_OFF;
|
|
150
|
+
else {
|
|
151
|
+
notice = SENT;
|
|
152
|
+
// Kept on failure above, so a retry has something to send.
|
|
153
|
+
snapshots.delete(sessionID);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
await ctx.session.synthetic({ sessionID, text: notice }).catch(() => null);
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
});
|
|
112
160
|
const tools = await registerTools(ctx, config, repo);
|
|
113
161
|
return async () => {
|
|
114
162
|
await tools.dispose();
|
|
115
163
|
await session.dispose();
|
|
116
164
|
await nudge.dispose();
|
|
117
165
|
await compaction.dispose();
|
|
166
|
+
await command.dispose();
|
|
118
167
|
};
|
|
119
168
|
},
|
|
120
169
|
});
|
package/dist/tools.js
CHANGED
|
@@ -80,8 +80,16 @@ function rememberTool(repo) {
|
|
|
80
80
|
required: ['path', 'body'],
|
|
81
81
|
additionalProperties: false,
|
|
82
82
|
},
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
// `client` and `session` are provenance, so history can say who wrote it.
|
|
84
|
+
async execute(input, context) {
|
|
85
|
+
const result = await callTool('memory_write', {
|
|
86
|
+
path: input.path,
|
|
87
|
+
body: input.body,
|
|
88
|
+
kind: input.kind,
|
|
89
|
+
repo: repo ?? undefined,
|
|
90
|
+
client: 'opencode',
|
|
91
|
+
session: context?.sessionID,
|
|
92
|
+
}, { timeoutMs: 8000 });
|
|
85
93
|
if (!result.ok)
|
|
86
94
|
return { content: `Could not store that (${result.error}).` };
|
|
87
95
|
const written = result.result;
|
|
@@ -130,8 +138,14 @@ function learnTool(repo) {
|
|
|
130
138
|
required: ['transcript'],
|
|
131
139
|
additionalProperties: false,
|
|
132
140
|
},
|
|
133
|
-
async execute(input) {
|
|
134
|
-
const result = await callTool('memory_learn', {
|
|
141
|
+
async execute(input, context) {
|
|
142
|
+
const result = await callTool('memory_learn', {
|
|
143
|
+
transcript: input?.transcript,
|
|
144
|
+
hint: input?.hint,
|
|
145
|
+
repo: repo ?? undefined,
|
|
146
|
+
client: 'opencode',
|
|
147
|
+
session: context?.sessionID,
|
|
148
|
+
}, { timeoutMs: 8000 });
|
|
135
149
|
if (!result.ok)
|
|
136
150
|
return { content: `Could not hand that over (${result.error}).` };
|
|
137
151
|
const queued = result.result;
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -23,6 +23,12 @@ export const MAX_TRANSCRIPT = 32_000
|
|
|
23
23
|
export const MIN_TRANSCRIPT = 500
|
|
24
24
|
const LEARN_HINT =
|
|
25
25
|
'This session was just compacted. Keep what was decided, what broke and any convention, and skip what Mimir already has.'
|
|
26
|
+
const LEARN_NOW_HINT =
|
|
27
|
+
'The user asked for this session to be kept now. Keep what was decided, what broke and any convention, and skip what Mimir already has.'
|
|
28
|
+
const NOTHING_TO_SEND = 'Nothing worth handing over yet.'
|
|
29
|
+
const SENT = 'Mimir is writing what this session settled.'
|
|
30
|
+
const QUEUE_OFF = 'Mimir took it, but its queue is not running.'
|
|
31
|
+
const UNREACHABLE = 'Mimir is unreachable, so nothing was sent.'
|
|
26
32
|
|
|
27
33
|
/** Message text is shaped differently by each provider, so read what we can and skip the rest. */
|
|
28
34
|
export function transcript(messages: unknown[]): string {
|
|
@@ -91,9 +97,14 @@ export default Plugin.define({
|
|
|
91
97
|
// The reminder rides the message count. A shrink means a compaction just
|
|
92
98
|
// happened, which is the worst moment to lose a detail, so it nudges early.
|
|
93
99
|
const counts = new Map<string, { turns: number; nudgedAt: number }>()
|
|
100
|
+
// The command below is handed no messages, only a session id, so the hook
|
|
101
|
+
// keeps the newest transcript for it. One string per live session, at most
|
|
102
|
+
// 32 KB, dropped once it has been sent.
|
|
103
|
+
const snapshots = new Map<string, string>()
|
|
94
104
|
const nudge = await ctx.session.hook('context', async (event) => {
|
|
95
105
|
const state = counts.get(event.sessionID) ?? { turns: 0, nudgedAt: 0 }
|
|
96
106
|
const turns = event.messages?.length ?? 0
|
|
107
|
+
if (turns > state.turns) snapshots.set(event.sessionID, transcript(event.messages ?? []))
|
|
97
108
|
const due = turns < state.turns || turns - state.nudgedAt >= NUDGE_EVERY
|
|
98
109
|
counts.set(event.sessionID, { turns, nudgedAt: due ? turns : state.nudgedAt })
|
|
99
110
|
|
|
@@ -113,7 +124,13 @@ export default Plugin.define({
|
|
|
113
124
|
|
|
114
125
|
const sent = await callTool(
|
|
115
126
|
'memory_learn',
|
|
116
|
-
{
|
|
127
|
+
{
|
|
128
|
+
repo: repo ?? undefined,
|
|
129
|
+
transcript: text,
|
|
130
|
+
hint: LEARN_HINT,
|
|
131
|
+
client: 'opencode',
|
|
132
|
+
session: event.sessionID,
|
|
133
|
+
},
|
|
117
134
|
{ ...config, timeoutMs: 4000 },
|
|
118
135
|
).catch(() => null)
|
|
119
136
|
|
|
@@ -123,6 +140,40 @@ export default Plugin.define({
|
|
|
123
140
|
if (!sent?.ok) compacted.delete(event.sessionID)
|
|
124
141
|
})
|
|
125
142
|
|
|
143
|
+
// A manual handover, for the sessions that end before they ever compact.
|
|
144
|
+
const command = await ctx.command.transform((editor) => {
|
|
145
|
+
editor.add({
|
|
146
|
+
name: 'mimir-learn',
|
|
147
|
+
description: 'Hand this session to Mimir now.',
|
|
148
|
+
execute: async ({ sessionID }) => {
|
|
149
|
+
const text = snapshots.get(sessionID) ?? ''
|
|
150
|
+
let notice = NOTHING_TO_SEND
|
|
151
|
+
if (text.length >= MIN_TRANSCRIPT) {
|
|
152
|
+
const sent = await callTool(
|
|
153
|
+
'memory_learn',
|
|
154
|
+
{
|
|
155
|
+
repo: repo ?? undefined,
|
|
156
|
+
transcript: text,
|
|
157
|
+
hint: LEARN_NOW_HINT,
|
|
158
|
+
client: 'opencode',
|
|
159
|
+
session: sessionID,
|
|
160
|
+
},
|
|
161
|
+
{ ...config, timeoutMs: 4000 },
|
|
162
|
+
).catch(() => null)
|
|
163
|
+
const queued = sent?.ok ? (sent.result as { queued?: boolean }).queued : undefined
|
|
164
|
+
if (!sent?.ok) notice = UNREACHABLE
|
|
165
|
+
else if (queued === false) notice = QUEUE_OFF
|
|
166
|
+
else {
|
|
167
|
+
notice = SENT
|
|
168
|
+
// Kept on failure above, so a retry has something to send.
|
|
169
|
+
snapshots.delete(sessionID)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
await ctx.session.synthetic({ sessionID, text: notice }).catch(() => null)
|
|
173
|
+
},
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
|
|
126
177
|
const tools = await registerTools(ctx, config, repo)
|
|
127
178
|
|
|
128
179
|
return async () => {
|
|
@@ -130,6 +181,7 @@ export default Plugin.define({
|
|
|
130
181
|
await session.dispose()
|
|
131
182
|
await nudge.dispose()
|
|
132
183
|
await compaction.dispose()
|
|
184
|
+
await command.dispose()
|
|
133
185
|
}
|
|
134
186
|
},
|
|
135
187
|
})
|
package/src/tools.ts
CHANGED
|
@@ -114,10 +114,18 @@ function rememberTool(repo: string | null): ToolDefinition {
|
|
|
114
114
|
required: ['path', 'body'],
|
|
115
115
|
additionalProperties: false,
|
|
116
116
|
},
|
|
117
|
-
|
|
117
|
+
// `client` and `session` are provenance, so history can say who wrote it.
|
|
118
|
+
async execute(input, context) {
|
|
118
119
|
const result = await callTool(
|
|
119
120
|
'memory_write',
|
|
120
|
-
{
|
|
121
|
+
{
|
|
122
|
+
path: input.path,
|
|
123
|
+
body: input.body,
|
|
124
|
+
kind: input.kind,
|
|
125
|
+
repo: repo ?? undefined,
|
|
126
|
+
client: 'opencode',
|
|
127
|
+
session: context?.sessionID,
|
|
128
|
+
},
|
|
121
129
|
{ timeoutMs: 8000 },
|
|
122
130
|
)
|
|
123
131
|
if (!result.ok) return { content: `Could not store that (${result.error}).` }
|
|
@@ -173,10 +181,16 @@ function learnTool(repo: string | null): ToolDefinition {
|
|
|
173
181
|
required: ['transcript'],
|
|
174
182
|
additionalProperties: false,
|
|
175
183
|
},
|
|
176
|
-
async execute(input) {
|
|
184
|
+
async execute(input, context) {
|
|
177
185
|
const result = await callTool(
|
|
178
186
|
'memory_learn',
|
|
179
|
-
{
|
|
187
|
+
{
|
|
188
|
+
transcript: input?.transcript,
|
|
189
|
+
hint: input?.hint,
|
|
190
|
+
repo: repo ?? undefined,
|
|
191
|
+
client: 'opencode',
|
|
192
|
+
session: context?.sessionID,
|
|
193
|
+
},
|
|
180
194
|
{ timeoutMs: 8000 },
|
|
181
195
|
)
|
|
182
196
|
if (!result.ok) return { content: `Could not hand that over (${result.error}).` }
|