openrune 1.0.1 → 1.1.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/README.ko.md CHANGED
@@ -19,6 +19,10 @@
19
19
  <img src="https://img.shields.io/badge/license-MIT-green" alt="license" />
20
20
  </p>
21
21
 
22
+ <p align="center">
23
+ <img src="screenshot-chatting-ui.png" width="800" alt="Rune 데스크톱 UI — 4개 에이전트 협업" />
24
+ </p>
25
+
22
26
  ---
23
27
 
24
28
  ## 사전 준비
@@ -67,6 +71,7 @@ Rune은 다른 접근을 합니다: **에이전트가 파일입니다.**
67
71
  | **스케줄링** | 수동 실행만 가능 | Cron, 파일 변경, git-commit 트리거 |
68
72
  | **권한** | 세션에서 상속 | 에이전트별 제어 (`fileWrite`, `bash`, `allowPaths`) |
69
73
  | **실행** | 대화형 | 헤드리스, 파이프라인, CI/CD 지원 |
74
+ | **자기 수정** | 기본 제공 없음 | `rune loop` — 자동 리뷰-수정 반복 |
70
75
 
71
76
  Rune 에이전트는 세션, 머신, 팀을 넘어 살아남습니다. 한 번 만들면 영원히 실행.
72
77
 
@@ -210,6 +215,41 @@ rune pipe architect.rune coder.rune "Build a REST API with Express" --auto
210
215
 
211
216
  architect가 설계 → coder가 구현 (파일 작성, 의존성 설치).
212
217
 
218
+ ### 자기 수정 루프
219
+
220
+ 에이전트가 자동으로 자신의 작업을 리뷰하고 수정합니다:
221
+
222
+ ```bash
223
+ rune loop coder.rune reviewer.rune "Build a REST API with Express" --until "no critical issues" --max-iterations 3 --auto
224
+ ```
225
+
226
+ ```
227
+ 🔁 Starting self-correction loop (max 3 iterations)
228
+ Stop condition: "no critical issues"
229
+
230
+ ━━━ Iteration 1/3 ━━━
231
+
232
+ ▶ [doer] coder — API 구현
233
+ ✓ coder done
234
+
235
+ ▶ [reviewer] reviewer — 치명적 이슈 2개 발견
236
+ ✓ reviewer done
237
+
238
+ ━━━ Iteration 2/3 ━━━
239
+
240
+ ▶ [doer] coder — 이슈 수정
241
+ ✓ coder done
242
+
243
+ ▶ [reviewer] reviewer — "no critical issues found"
244
+ ✓ reviewer done
245
+
246
+ ✅ Condition met: "no critical issues"
247
+
248
+ 🔁 Loop completed after 2 iterations
249
+ ```
250
+
251
+ 구현자가 구현하고, 리뷰어가 리뷰합니다. 문제가 발견되면 피드백이 자동으로 구현자에게 전달됩니다 — 조건이 충족되거나 최대 반복 횟수에 도달할 때까지.
252
+
213
253
  ### 자동화 트리거
214
254
 
215
255
  ```bash
@@ -301,6 +341,10 @@ rune open reviewer.rune
301
341
 
302
342
  또는 Finder에서 `.rune` 파일을 더블 클릭하세요.
303
343
 
344
+ <p align="center">
345
+ <img src="screenshot-chatting-ui.png" width="800" alt="Rune 데스크톱 UI" />
346
+ </p>
347
+
304
348
  - **실시간 활동** — 도구 호출, 결과, 권한 요청을 실시간으로 확인.
305
349
  - **내장 터미널** — Claude Code 출력과 사용자 명령어를 나란히.
306
350
  - **우클릭으로 생성** — macOS Quick Action으로 Finder에서 에이전트 생성.
@@ -316,6 +360,7 @@ rune open reviewer.rune
316
360
  | `rune new <name> [--role "..."]` | 에이전트 생성 |
317
361
  | `rune run <file> "prompt" [--auto] [--output json]` | 헤드리스 실행 |
318
362
  | `rune pipe <a> <b> [...] "prompt" [--auto]` | 에이전트 체이닝 |
363
+ | `rune loop <doer> <reviewer> "prompt" [--until "..."] [--max-iterations N] [--auto]` | 자기 수정 루프 |
319
364
  | `rune watch <file> --on <event> --prompt "..."` | 자동화 트리거 |
320
365
  | `rune open <file>` | 데스크톱 UI |
321
366
  | `rune list` | 현재 디렉토리의 에이전트 목록 |
package/README.md CHANGED
@@ -19,6 +19,10 @@
19
19
  <img src="https://img.shields.io/badge/license-MIT-green" alt="license" />
20
20
  </p>
21
21
 
22
+ <p align="center">
23
+ <img src="screenshot-chatting-ui.png" width="800" alt="Rune Desktop UI — 4 agents collaborating" />
24
+ </p>
25
+
22
26
  ---
23
27
 
24
28
  ## Prerequisites
@@ -67,6 +71,7 @@ Rune takes a different approach: **agents are files.**
67
71
  | **Scheduling** | Manual execution only | Cron, file-change, and git-commit triggers |
68
72
  | **Permissions** | Inherited from session | Per-agent controls (`fileWrite`, `bash`, `allowPaths`) |
69
73
  | **Execution** | Interactive | Headless, pipelines, CI/CD-ready |
74
+ | **Self-correction** | Not built-in | `rune loop` — automatic review-fix cycles |
70
75
 
71
76
  Rune agents survive across sessions, machines, and teams. Build once, run forever.
72
77
 
@@ -210,6 +215,41 @@ rune pipe architect.rune coder.rune "Build a REST API with Express" --auto
210
215
 
211
216
  architect designs → coder implements (writes files, installs deps).
212
217
 
218
+ ### Self-correction loop
219
+
220
+ Agents review and fix their own work automatically:
221
+
222
+ ```bash
223
+ rune loop coder.rune reviewer.rune "Build a REST API with Express" --until "no critical issues" --max-iterations 3 --auto
224
+ ```
225
+
226
+ ```
227
+ 🔁 Starting self-correction loop (max 3 iterations)
228
+ Stop condition: "no critical issues"
229
+
230
+ ━━━ Iteration 1/3 ━━━
231
+
232
+ ▶ [doer] coder — implements the API
233
+ ✓ coder done
234
+
235
+ ▶ [reviewer] reviewer — finds 2 critical issues
236
+ ✓ reviewer done
237
+
238
+ ━━━ Iteration 2/3 ━━━
239
+
240
+ ▶ [doer] coder — fixes the issues
241
+ ✓ coder done
242
+
243
+ ▶ [reviewer] reviewer — "no critical issues found"
244
+ ✓ reviewer done
245
+
246
+ ✅ Condition met: "no critical issues"
247
+
248
+ 🔁 Loop completed after 2 iterations
249
+ ```
250
+
251
+ The doer implements, the reviewer reviews. If issues are found, feedback goes back to the doer automatically — until the condition is met or max iterations are reached.
252
+
213
253
  ### Automated triggers
214
254
 
215
255
  ```bash
@@ -301,6 +341,10 @@ rune open reviewer.rune
301
341
 
302
342
  Or double-click any `.rune` file in Finder.
303
343
 
344
+ <p align="center">
345
+ <img src="screenshot-chatting-ui.png" width="800" alt="Rune Desktop UI" />
346
+ </p>
347
+
304
348
  - **Real-time activity** — See tool calls, results, and permission requests as they happen.
305
349
  - **Built-in terminal** — Claude Code output and your own commands, side by side.
306
350
  - **Right-click to create** — macOS Quick Action for creating agents from Finder.
@@ -316,6 +360,7 @@ Or double-click any `.rune` file in Finder.
316
360
  | `rune new <name> [--role "..."]` | Create agent |
317
361
  | `rune run <file> "prompt" [--auto] [--output json]` | Run headlessly |
318
362
  | `rune pipe <a> <b> [...] "prompt" [--auto]` | Chain agents |
363
+ | `rune loop <doer> <reviewer> "prompt" [--until "..."] [--max-iterations N] [--auto]` | Self-correction loop |
319
364
  | `rune watch <file> --on <event> --prompt "..."` | Automated triggers |
320
365
  | `rune open <file>` | Desktop UI |
321
366
  | `rune list` | List agents in current directory |
package/bin/rune.js CHANGED
@@ -27,6 +27,7 @@ switch (command) {
27
27
  case 'open': return openRune(args[0])
28
28
  case 'run': return runRune(args[0], args.slice(1))
29
29
  case 'pipe': return pipeRunes(args)
30
+ case 'loop': return loopRunes(args)
30
31
  case 'watch': return watchRune(args[0], args.slice(1))
31
32
  case 'list': return listRunes()
32
33
  case 'uninstall': return uninstall()
@@ -1156,6 +1157,236 @@ async function pipeRunes(args) {
1156
1157
  }
1157
1158
  }
1158
1159
 
1160
+ // ── loop (self-correction) ──────────────────────
1161
+
1162
+ async function loopRunes(args) {
1163
+ // Parse: rune loop coder.rune reviewer.rune "prompt" [--until "condition"] [--max-iterations N] [--auto]
1164
+ const runeFiles = []
1165
+ let prompt = ''
1166
+ let untilCondition = ''
1167
+ let maxIterations = 5
1168
+ let autoMode = false
1169
+
1170
+ for (let i = 0; i < args.length; i++) {
1171
+ if (args[i] === '--until' && args[i + 1]) {
1172
+ untilCondition = args[++i]
1173
+ } else if (args[i] === '--max-iterations' && args[i + 1]) {
1174
+ maxIterations = parseInt(args[++i], 10)
1175
+ } else if (args[i] === '--auto') {
1176
+ autoMode = true
1177
+ } else if (args[i].endsWith('.rune')) {
1178
+ runeFiles.push(args[i])
1179
+ } else if (!prompt) {
1180
+ prompt = args[i]
1181
+ }
1182
+ }
1183
+
1184
+ if (runeFiles.length < 2 || !prompt) {
1185
+ console.log('Usage: rune loop <doer.rune> <reviewer.rune> "prompt" [--until "condition"] [--max-iterations N] [--auto]')
1186
+ console.log('')
1187
+ console.log('Options:')
1188
+ console.log(' --until "..." Stop when the reviewer\'s output contains this text')
1189
+ console.log(' --max-iterations N Maximum number of loop iterations (default: 5)')
1190
+ console.log(' --auto Allow agents to write files and run commands')
1191
+ console.log('')
1192
+ console.log('Example:')
1193
+ console.log(' rune loop coder.rune reviewer.rune "Build a REST API" --until "no critical issues" --max-iterations 3 --auto')
1194
+ console.log('')
1195
+ console.log('The first agent implements, the last agent reviews.')
1196
+ console.log('If the reviewer finds issues, feedback is sent back to the first agent automatically.')
1197
+ process.exit(1)
1198
+ }
1199
+
1200
+ // Validate files
1201
+ for (const file of runeFiles) {
1202
+ const filePath = path.resolve(process.cwd(), file)
1203
+ if (!fs.existsSync(filePath)) {
1204
+ console.error(` ❌ File not found: ${filePath}`)
1205
+ process.exit(1)
1206
+ }
1207
+ }
1208
+
1209
+ const doerFile = runeFiles[0]
1210
+ const reviewerFile = runeFiles[runeFiles.length - 1]
1211
+ const doerPath = path.resolve(process.cwd(), doerFile)
1212
+ const reviewerPath = path.resolve(process.cwd(), reviewerFile)
1213
+
1214
+ let currentPrompt = prompt
1215
+ let iteration = 0
1216
+ let converged = false
1217
+
1218
+ console.log(`\n🔁 Starting self-correction loop (max ${maxIterations} iterations)`)
1219
+ if (untilCondition) console.log(` Stop condition: "${untilCondition}"`)
1220
+ console.log('')
1221
+
1222
+ while (iteration < maxIterations && !converged) {
1223
+ iteration++
1224
+ console.log(` ━━━ Iteration ${iteration}/${maxIterations} ━━━\n`)
1225
+
1226
+ // Step 1: Doer implements
1227
+ const doerRune = JSON.parse(fs.readFileSync(doerPath, 'utf-8'))
1228
+ const doerFolder = path.dirname(doerPath)
1229
+ const doerSystem = []
1230
+ if (doerRune.role) doerSystem.push(`Your role: ${doerRune.role}`)
1231
+ if (doerRune.memory && doerRune.memory.length > 0) {
1232
+ doerSystem.push('Saved memory:')
1233
+ doerRune.memory.forEach((m, j) => doerSystem.push(`${j + 1}. ${m}`))
1234
+ }
1235
+
1236
+ const doerContext = iteration > 1
1237
+ ? `You are in iteration ${iteration} of a self-correction loop. The reviewer found issues with your previous work:\n\n${currentPrompt}\n\nFix the issues and improve your implementation.`
1238
+ : currentPrompt
1239
+
1240
+ console.log(` ▶ [doer] ${doerRune.name} (${doerRune.role || 'assistant'})`)
1241
+
1242
+ const doerOutput = await runAgent(doerRune.name, doerFolder, doerSystem, doerContext, autoMode)
1243
+
1244
+ doerRune.history = doerRune.history || []
1245
+ doerRune.history.push({ role: 'user', text: doerContext, ts: Date.now() })
1246
+ doerRune.history.push({ role: 'assistant', text: doerOutput, ts: Date.now() })
1247
+ fs.writeFileSync(doerPath, JSON.stringify(doerRune, null, 2))
1248
+
1249
+ console.log(` ✓ ${doerRune.name} done\n`)
1250
+
1251
+ // Step 2: Reviewer reviews
1252
+ const reviewerRune = JSON.parse(fs.readFileSync(reviewerPath, 'utf-8'))
1253
+ const reviewerFolder = path.dirname(reviewerPath)
1254
+ const reviewerSystem = []
1255
+ if (reviewerRune.role) reviewerSystem.push(`Your role: ${reviewerRune.role}`)
1256
+ if (reviewerRune.memory && reviewerRune.memory.length > 0) {
1257
+ reviewerSystem.push('Saved memory:')
1258
+ reviewerRune.memory.forEach((m, j) => reviewerSystem.push(`${j + 1}. ${m}`))
1259
+ }
1260
+
1261
+ const reviewerContext = `You are the reviewer in iteration ${iteration} of a self-correction loop. Review the work done by ${doerRune.name}:\n\n${doerOutput}\n\nIf there are issues, describe them clearly so the implementer can fix them. If the work is satisfactory, say so clearly.`
1262
+
1263
+ console.log(` ▶ [reviewer] ${reviewerRune.name} (${reviewerRune.role || 'assistant'})`)
1264
+
1265
+ const reviewerOutput = await runAgent(reviewerRune.name, reviewerFolder, reviewerSystem, reviewerContext, false)
1266
+
1267
+ reviewerRune.history = reviewerRune.history || []
1268
+ reviewerRune.history.push({ role: 'user', text: reviewerContext, ts: Date.now() })
1269
+ reviewerRune.history.push({ role: 'assistant', text: reviewerOutput, ts: Date.now() })
1270
+ fs.writeFileSync(reviewerPath, JSON.stringify(reviewerRune, null, 2))
1271
+
1272
+ console.log(` ✓ ${reviewerRune.name} done\n`)
1273
+
1274
+ // Check convergence
1275
+ if (untilCondition) {
1276
+ const lower = reviewerOutput.toLowerCase()
1277
+ if (lower.includes(untilCondition.toLowerCase())) {
1278
+ converged = true
1279
+ console.log(` ✅ Condition met: "${untilCondition}"`)
1280
+ }
1281
+ }
1282
+
1283
+ if (!converged) {
1284
+ currentPrompt = reviewerOutput
1285
+ }
1286
+ }
1287
+
1288
+ if (!converged && iteration >= maxIterations) {
1289
+ console.log(` ⚠️ Max iterations (${maxIterations}) reached`)
1290
+ }
1291
+
1292
+ console.log(`\n🔁 Loop completed after ${iteration} iteration${iteration > 1 ? 's' : ''}\n`)
1293
+ }
1294
+
1295
+ async function runAgent(name, folderPath, systemParts, prompt, autoMode) {
1296
+ if (autoMode) {
1297
+ const mcpPath = path.join(folderPath, '.mcp.json')
1298
+ const mcpBackup = path.join(folderPath, '.mcp.json.loop.bak')
1299
+ let mcpHidden = false
1300
+ if (fs.existsSync(mcpPath)) {
1301
+ fs.renameSync(mcpPath, mcpBackup)
1302
+ mcpHidden = true
1303
+ }
1304
+
1305
+ const claudeArgs = ['-p', '--print',
1306
+ '--dangerously-skip-permissions',
1307
+ '--verbose',
1308
+ '--output-format', 'stream-json',
1309
+ ]
1310
+ if (systemParts.length > 0) {
1311
+ claudeArgs.push('--system-prompt', systemParts.join('\n'))
1312
+ }
1313
+ claudeArgs.push(prompt)
1314
+
1315
+ const output = await new Promise((resolve, reject) => {
1316
+ const child = spawn('claude', claudeArgs, {
1317
+ cwd: folderPath,
1318
+ stdio: ['ignore', 'pipe', 'pipe'],
1319
+ env: { ...process.env },
1320
+ })
1321
+
1322
+ let fullOutput = ''
1323
+ let buffer = ''
1324
+ child.stdout.on('data', (data) => {
1325
+ buffer += data.toString()
1326
+ const lines = buffer.split('\n')
1327
+ buffer = lines.pop()
1328
+ for (const line of lines) {
1329
+ if (!line.trim()) continue
1330
+ try {
1331
+ const event = JSON.parse(line)
1332
+ if (event.type === 'assistant' && event.message && event.message.content) {
1333
+ for (const block of event.message.content) {
1334
+ if (block.type === 'tool_use') {
1335
+ const tool = block.name || 'unknown'
1336
+ const input = block.input || {}
1337
+ if (tool === 'Bash') console.log(` ▶ Bash: ${(input.command || '').slice(0, 120)}`)
1338
+ else if (tool === 'Write') console.log(` ▶ Write: ${input.file_path || ''}`)
1339
+ else if (tool === 'Edit') console.log(` ▶ Edit: ${input.file_path || ''}`)
1340
+ else if (tool === 'Read') console.log(` ▶ Read: ${input.file_path || ''}`)
1341
+ else console.log(` ▶ ${tool}`)
1342
+ } else if (block.type === 'text' && block.text && block.text.trim()) {
1343
+ console.log(` 💬 ${block.text.trim().slice(0, 200)}`)
1344
+ }
1345
+ }
1346
+ }
1347
+ if (event.type === 'result') {
1348
+ fullOutput = event.result || ''
1349
+ }
1350
+ } catch {}
1351
+ }
1352
+ })
1353
+ child.stderr.on('data', (d) => { process.stderr.write(d) })
1354
+ child.on('close', (code) => {
1355
+ if (mcpHidden && fs.existsSync(mcpBackup)) fs.renameSync(mcpBackup, mcpPath)
1356
+ if (code !== 0) reject(new Error(`Agent ${name} exited with code ${code}`))
1357
+ else resolve(fullOutput.trim())
1358
+ })
1359
+ })
1360
+
1361
+ return output
1362
+ } else {
1363
+ const tmpdir = require('os').tmpdir()
1364
+ const claudeArgs = ['-p', '--print', '--add-dir', folderPath]
1365
+ if (systemParts.length > 0) {
1366
+ claudeArgs.push('--system-prompt', systemParts.join('\n') + `\nWorking folder: ${folderPath}`)
1367
+ }
1368
+ claudeArgs.push('--', prompt)
1369
+
1370
+ const output = await new Promise((resolve, reject) => {
1371
+ const child = spawn('claude', claudeArgs, {
1372
+ cwd: tmpdir,
1373
+ stdio: ['ignore', 'pipe', 'pipe'],
1374
+ env: { ...process.env },
1375
+ })
1376
+
1377
+ let stdout = ''
1378
+ child.stdout.on('data', (d) => { stdout += d.toString() })
1379
+ child.stderr.on('data', (d) => { process.stderr.write(d) })
1380
+ child.on('close', (code) => {
1381
+ if (code !== 0) reject(new Error(`Agent ${name} exited with code ${code}`))
1382
+ else resolve(stdout.trim())
1383
+ })
1384
+ })
1385
+
1386
+ return output
1387
+ }
1388
+ }
1389
+
1159
1390
  // ── watch (triggers) ────────────────────────────
1160
1391
 
1161
1392
  function watchRune(file, restArgs) {
@@ -1425,6 +1656,10 @@ Usage:
1425
1656
  --log <file.json> Save structured log (tool calls, cost, duration)
1426
1657
  rune pipe <a.rune> <b.rune> ... "prompt" Chain agents in a pipeline
1427
1658
  --output json|text Output format (default: text)
1659
+ rune loop <doer.rune> <reviewer.rune> "prompt" Self-correction loop
1660
+ --until "condition" Stop when reviewer output contains this text
1661
+ --max-iterations N Max iterations (default: 5)
1662
+ --auto Allow agents to write files and run commands
1428
1663
  rune watch <file.rune> Set up automated triggers
1429
1664
  --on <event> Event: file-change, git-commit, git-push, cron
1430
1665
  --prompt "..." Prompt to send when triggered
@@ -1438,6 +1673,7 @@ Examples:
1438
1673
  rune new reviewer --role "Code reviewer, security focused"
1439
1674
  rune run reviewer.rune "Review the latest commit"
1440
1675
  rune pipe coder.rune reviewer.rune "Implement a login page"
1676
+ rune loop coder.rune reviewer.rune "Build a REST API" --until "no critical issues" --max-iterations 3 --auto
1441
1677
  rune watch reviewer.rune --on git-commit --prompt "Review this commit"
1442
1678
  rune watch monitor.rune --on cron --interval 5m --prompt "Check server health"
1443
1679
  echo "Fix the bug in auth.ts" | rune run backend.rune
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openrune",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Persistent AI agents for Claude Code — build once, run forever.",
5
5
  "keywords": ["ai", "agent", "claude", "desktop", "electron", "mcp", "claude-code", "toolkit", "automation"],
6
6
  "repository": {
Binary file