@make-u-free/migi 0.4.6 → 0.5.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/bin/migi.js +18 -10
- package/package.json +1 -1
- package/src/agent.js +113 -19
package/bin/migi.js
CHANGED
|
@@ -149,12 +149,11 @@ async function readChatInput() {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
if (key.ctrl && key.name === 'c') {
|
|
152
|
-
if (drawPending) { drawPending = false; draw() }
|
|
152
|
+
if (drawPending) { drawPending = false; draw() }
|
|
153
153
|
process.stdout.write(`\x1b[${drawnLines - 1 - curLine}B\n`)
|
|
154
154
|
process.stdin.removeListener('keypress', onKey)
|
|
155
155
|
if (process.stdin.isTTY) process.stdin.setRawMode(false)
|
|
156
|
-
|
|
157
|
-
process.exit(0)
|
|
156
|
+
resolve(null) // null = 終了シグナル(メインループで後処理)
|
|
158
157
|
}
|
|
159
158
|
|
|
160
159
|
if (key.name === 'return') {
|
|
@@ -198,18 +197,29 @@ async function readChatInput() {
|
|
|
198
197
|
})
|
|
199
198
|
}
|
|
200
199
|
|
|
200
|
+
// ---- セッション終了(サマリー保存 → 挨拶 → exit) ----
|
|
201
|
+
async function gracefulExit() {
|
|
202
|
+
const saved = await agent.saveSummary(cwd)
|
|
203
|
+
if (saved) {
|
|
204
|
+
console.log(chalk.dim(`\n セッションを記録しました → ${saved}`))
|
|
205
|
+
}
|
|
206
|
+
console.log(chalk.cyan(`\n お疲れ様でした!またね。\n`))
|
|
207
|
+
process.exit(0)
|
|
208
|
+
}
|
|
209
|
+
|
|
201
210
|
// ---- メインループ ----
|
|
202
211
|
async function prompt() {
|
|
203
212
|
// 入力ボックス上辺(ユーザー名をセパレーターに埋め込む)
|
|
204
213
|
console.log('\n' + sepWithLabel(chalk.bold.cyan(userName || 'あなた')))
|
|
205
214
|
|
|
206
|
-
const
|
|
215
|
+
const rawInput = await readChatInput()
|
|
216
|
+
if (rawInput === null) return gracefulExit() // Ctrl+C
|
|
217
|
+
const input = rawInput.trim()
|
|
207
218
|
if (!input) return prompt()
|
|
208
219
|
|
|
209
220
|
// --- ビルトインコマンド ---
|
|
210
221
|
if (input === '/exit' || input === '/quit') {
|
|
211
|
-
|
|
212
|
-
process.exit(0)
|
|
222
|
+
return gracefulExit()
|
|
213
223
|
}
|
|
214
224
|
|
|
215
225
|
if (input === '/config') {
|
|
@@ -249,8 +259,7 @@ async function prompt() {
|
|
|
249
259
|
console.log('\n' + sepWithLabel(chalk.bold.cyan(agentName) + chalk.dim(` [スキル: ${parsed.name}]`)))
|
|
250
260
|
const expanded = expandSkill(skill.content, parsed.args)
|
|
251
261
|
try {
|
|
252
|
-
|
|
253
|
-
console.log('\n' + reply + '\n')
|
|
262
|
+
await agent.chat(expanded)
|
|
254
263
|
} catch (err) {
|
|
255
264
|
console.error(chalk.red('\n エラー: ' + err.message + '\n'))
|
|
256
265
|
}
|
|
@@ -265,8 +274,7 @@ async function prompt() {
|
|
|
265
274
|
// --- 通常チャット ---
|
|
266
275
|
console.log('\n' + sepWithLabel(chalk.bold.cyan(agentName)))
|
|
267
276
|
try {
|
|
268
|
-
|
|
269
|
-
console.log('\n' + reply + '\n')
|
|
277
|
+
await agent.chat(input)
|
|
270
278
|
} catch (err) {
|
|
271
279
|
console.error(chalk.red('\n エラー: ' + err.message + '\n'))
|
|
272
280
|
}
|
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import OpenAI from 'openai'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import { homedir } from 'os'
|
|
4
|
+
import { existsSync, appendFileSync, mkdirSync } from 'fs'
|
|
5
|
+
import { join, dirname } from 'path'
|
|
4
6
|
import { toolSchemas, teamsToolSchema, executeTool } from './tools.js'
|
|
5
7
|
import { createPermissionChecker } from './permissions.js'
|
|
6
8
|
import { httpsAgent } from './tls.js'
|
|
@@ -45,14 +47,18 @@ ${userNameLine}
|
|
|
45
47
|
- 「どうしますか?」と聞く前に、自分でできることをやりきる
|
|
46
48
|
- 完了したらまとめて報告する。途中経過は簡潔に
|
|
47
49
|
|
|
48
|
-
##
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
50
|
+
## メモリと文脈の継続
|
|
51
|
+
- グローバルメモリ: ${homedir()}/.migi/memory.md(ユーザーの好み・習慣・横断的な情報)
|
|
52
|
+
- ワークスペースメモリ: ${cwd}/.migi/memory.md(このプロジェクト固有の情報・決定事項)
|
|
53
|
+
- 形式: "## YYYY-MM-DD" の見出しの下に箇条書きで記録。既存ファイルがあれば追記
|
|
54
|
+
- ユーザーが「覚えておいて」「remember」と言ったら必ず書き出す
|
|
55
|
+
- 言われなくても、以下は自発的に記録する:
|
|
56
|
+
- 重要な意思決定・方針転換
|
|
57
|
+
- ユーザーの好み・こだわり・やり方のクセ
|
|
58
|
+
- 繰り返し登場するテーマやプロジェクト
|
|
59
|
+
- 「次回やること」として明確になったタスク
|
|
60
|
+
- セッション開始時にメモリの内容を参照し、前回の続きから自然に入る
|
|
61
|
+
- 過去の記録と矛盾することをユーザーが言ったら「前回と変わりましたか?」と確認する
|
|
56
62
|
|
|
57
63
|
## 環境
|
|
58
64
|
- 今日の日付: ${new Date().toISOString().split('T')[0]}
|
|
@@ -67,6 +73,53 @@ ${userNameLine}
|
|
|
67
73
|
(context ? `\n## ロードされたコンテキスト\n${context}` : '')
|
|
68
74
|
}
|
|
69
75
|
|
|
76
|
+
// セッションの会話をサマリーして memory.md に保存する
|
|
77
|
+
async saveSummary(cwd) {
|
|
78
|
+
// ユーザー発言が2回未満なら保存しない(短すぎるセッション)
|
|
79
|
+
const userTurns = this.history.filter(m => m.role === 'user').length
|
|
80
|
+
if (userTurns < 2) return null
|
|
81
|
+
|
|
82
|
+
const spinner = new Spinner()
|
|
83
|
+
spinner.start('セッションを記録中…')
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const response = await this.client.chat.completions.create({
|
|
87
|
+
model: this.model,
|
|
88
|
+
messages: [
|
|
89
|
+
{ role: 'system', content: this.systemPrompt },
|
|
90
|
+
...this.history,
|
|
91
|
+
{
|
|
92
|
+
role: 'user',
|
|
93
|
+
content: `このセッションを次回の文脈引き継ぎ用に要約してください。
|
|
94
|
+
以下の形式で箇条書き3〜6行。日本語で簡潔に(1行50字以内)。
|
|
95
|
+
|
|
96
|
+
- 話し合ったこと・決定したこと
|
|
97
|
+
- 完了したこと・作ったもの
|
|
98
|
+
- ユーザーについて学んだこと(好み・やり方など)
|
|
99
|
+
- 次回やること(あれば)
|
|
100
|
+
|
|
101
|
+
形式:「- 〜」の箇条書きのみ。見出しや前置きは不要。`
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const summary = response.choices[0].message.content.trim()
|
|
107
|
+
const today = new Date().toISOString().split('T')[0]
|
|
108
|
+
const entry = `\n## ${today}\n${summary}\n`
|
|
109
|
+
|
|
110
|
+
const memPath = join(cwd, '.migi', 'memory.md')
|
|
111
|
+
const dir = dirname(memPath)
|
|
112
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
113
|
+
appendFileSync(memPath, entry, 'utf-8')
|
|
114
|
+
|
|
115
|
+
spinner.stop()
|
|
116
|
+
return memPath
|
|
117
|
+
} catch (err) {
|
|
118
|
+
spinner.stop()
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
70
123
|
// tool_calls に対応する tool 結果がない壊れた履歴を修復する
|
|
71
124
|
_sanitizeHistory() {
|
|
72
125
|
const cleaned = []
|
|
@@ -97,28 +150,69 @@ ${userNameLine}
|
|
|
97
150
|
|
|
98
151
|
while (true) {
|
|
99
152
|
spinner.start('考え中…')
|
|
100
|
-
|
|
153
|
+
|
|
154
|
+
const stream = await this.client.chat.completions.create({
|
|
101
155
|
model: this.model,
|
|
102
156
|
messages,
|
|
103
157
|
tools: this.tools,
|
|
104
|
-
tool_choice: 'auto'
|
|
158
|
+
tool_choice: 'auto',
|
|
159
|
+
stream: true
|
|
105
160
|
})
|
|
106
|
-
spinner.stop()
|
|
107
161
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
162
|
+
let content = ''
|
|
163
|
+
const tcMap = {} // tool_calls をインデックスで蓄積
|
|
164
|
+
let finishReason = null
|
|
165
|
+
let streaming = false // 最初のコンテンツが届いたか
|
|
166
|
+
|
|
167
|
+
for await (const chunk of stream) {
|
|
168
|
+
const choice = chunk.choices[0]
|
|
169
|
+
if (!choice) continue
|
|
170
|
+
const delta = choice.delta
|
|
171
|
+
if (choice.finish_reason) finishReason = choice.finish_reason
|
|
172
|
+
|
|
173
|
+
// テキストチャンク
|
|
174
|
+
if (delta?.content) {
|
|
175
|
+
if (!streaming) {
|
|
176
|
+
spinner.stop()
|
|
177
|
+
process.stdout.write('\n')
|
|
178
|
+
streaming = true
|
|
179
|
+
}
|
|
180
|
+
content += delta.content
|
|
181
|
+
process.stdout.write(delta.content)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// tool_calls チャンク(引数はストリームで分割されて届く)
|
|
185
|
+
if (delta?.tool_calls) {
|
|
186
|
+
for (const tc of delta.tool_calls) {
|
|
187
|
+
if (!tcMap[tc.index]) tcMap[tc.index] = { id: '', type: 'function', function: { name: '', arguments: '' } }
|
|
188
|
+
if (tc.id) tcMap[tc.index].id += tc.id
|
|
189
|
+
if (tc.function?.name) tcMap[tc.index].function.name += tc.function.name
|
|
190
|
+
if (tc.function?.arguments) tcMap[tc.index].function.arguments += tc.function.arguments
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
spinner.stop()
|
|
111
196
|
|
|
112
197
|
// 通常の返答
|
|
113
|
-
if (
|
|
114
|
-
|
|
198
|
+
if (finishReason === 'stop') {
|
|
199
|
+
process.stdout.write('\n\n')
|
|
200
|
+
const assistantMsg = { role: 'assistant', content }
|
|
201
|
+
messages.push(assistantMsg)
|
|
202
|
+
this.history.push(assistantMsg)
|
|
203
|
+
return content
|
|
115
204
|
}
|
|
116
205
|
|
|
117
206
|
// ツール呼び出し
|
|
118
|
-
if (
|
|
119
|
-
|
|
207
|
+
if (finishReason === 'tool_calls') {
|
|
208
|
+
if (streaming) process.stdout.write('\n')
|
|
209
|
+
const toolCalls = Object.values(tcMap)
|
|
210
|
+
const assistantMsg = { role: 'assistant', content: content || null, tool_calls: toolCalls }
|
|
211
|
+
messages.push(assistantMsg)
|
|
212
|
+
this.history.push(assistantMsg)
|
|
120
213
|
|
|
121
|
-
|
|
214
|
+
const toolResults = []
|
|
215
|
+
for (const toolCall of toolCalls) {
|
|
122
216
|
const args = JSON.parse(toolCall.function.arguments)
|
|
123
217
|
const name = toolCall.function.name
|
|
124
218
|
|