zigmini 0.8.0-beta.4 → 0.8.0-beta.6
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 +4 -3
- package/README.md +4 -3
- package/assets/agent/gateway-rules.md +1 -0
- package/assets/android/zigmini.apk +0 -0
- package/assets/android/zigmini.apk.json +3 -3
- package/assets/build.json +1 -1
- package/docs/manual.ko.md +4 -3
- package/docs/manual.md +4 -3
- package/host/omo-bridge/continuity-control.ts +3 -2
- package/host/omo-check/patches/omo-5.1.35-gateway-port.manifest.json +13 -4
- package/host/omo-check/patches/omo-5.1.35-image-replay.patch +45 -0
- package/host/omo-check/patches/omo-5.1.35-phantom-attach.patch +31 -0
- package/host/omo-check/patches/omo-5.1.35-question-capability.patch +65 -0
- package/package.json +1 -1
- package/src/cli/doctor.ts +5 -5
- package/src/cli/main.ts +15 -1
- package/src/cli/rotate-main.ts +411 -0
- package/src/engine/schema-guard.ts +1 -1
- package/src/hub.ts +184 -43
- package/src/main.ts +2 -2
- package/src/omo-check.ts +2 -2
- package/src/protocol.ts +3 -0
- package/src/transcript.ts +1 -1
- package/src/update/handoff.ts +1 -1
- package/src/usage.ts +1 -1
- package/src/version.ts +6 -6
- package/web/app.js +68 -31
- package/web/cardbind.d.ts +3 -0
- package/web/cardbind.js +15 -0
- package/web/devlog.js +2 -2
- package/web/gc.d.ts +2 -0
- package/web/gc.js +12 -1
- package/web/index.html +12 -12
- package/web/style.css +8 -0
- package/web/tooltip.d.ts +16 -0
- package/web/tooltip.js +114 -0
- package/web/update.d.ts +1 -1
- package/web/update.js +10 -3
package/README.ko.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# zigmini
|
|
4
4
|
|
|
5
|
-
zigmini는 맥에서 도는 개인 AI 에이전트 게이트웨이입니다. 맥의 웹 화면과 Android 앱에서
|
|
5
|
+
zigmini는 맥에서 도는 개인 AI 에이전트 게이트웨이입니다. 맥의 웹 화면과 Android 앱에서 에이전트와 대화하고, 작업을 맡기고, 알림을 받습니다. 게이트웨이와 브리지는 macOS 로그인 때 launchd가 자동으로 띄웁니다. Docker는 필요하지 않습니다.
|
|
6
6
|
|
|
7
7
|
## 요구 사항
|
|
8
8
|
|
|
@@ -103,7 +103,7 @@ zigmini는 에이전트를 권한 프리셋 `full-access`로 실행합니다.
|
|
|
103
103
|
|
|
104
104
|
1. 권한 안내를 보여 주고 동의를 받습니다.
|
|
105
105
|
2. 홈 폴더 `~/.zigmini`를 만듭니다. 이 설치의 규칙 파일(`~/.zigmini/rules.md`)이 없으면 빈 파일을 만듭니다. 아래 "에이전트 규칙"을 보세요.
|
|
106
|
-
3. 에이전트 엔진(
|
|
106
|
+
3. 에이전트 엔진(omo-ai)의 고정 버전을 내려받아 해시를 확인하고 패치합니다. 버전은 패키지가 정합니다(`src/engine/compat.json`, 이 문서를 쓸 때 엔진 5.1.35). 엔진 라이선스 요약을 보여 주고 동의(`y`)를 받습니다.
|
|
107
107
|
4. 에이전트 폴더와 모델 계정 로그인 상태를 확인합니다. 기본 에이전트 폴더는 `~/.omo/agent`이고, 같은 폴더를 쓰는 다른 OMO와 로그인 정보를 같이 씁니다. 로그인하지 않았으면 로그인 명령을 출력합니다. 그 명령을 실행한 뒤 `/login`을 입력합니다.
|
|
108
108
|
5. Notion 토큰을 묻습니다(선택). 비우면 일정 기능이 꺼집니다.
|
|
109
109
|
6. 외부 접속 방식을 묻습니다. 아래 "외부 접속"을 보세요.
|
|
@@ -187,7 +187,7 @@ zigmini update <버전> # 지정한 버전으로 업데이트
|
|
|
187
187
|
|
|
188
188
|
새 버전이 무중단 전환을 지원하면(0.8.0-beta.1 다음 버전부터) 진행 중인 턴을 끊지 않습니다.
|
|
189
189
|
|
|
190
|
-
1. 새 브리지와
|
|
190
|
+
1. 새 브리지와 엔진(새 실행 세대)가 다른 포트에서 먼저 뜨고 확인을 받습니다. 확인에 실패하면 실행 중인 것은 그대로 두고 설치만 되돌립니다.
|
|
191
191
|
2. 게이트웨이만 다시 시작합니다. 웹과 폰은 수 초 뒤 다시 연결됩니다.
|
|
192
192
|
3. 새 세션은 새 세대에서 열립니다. 이전 세대의 세션은 각자 턴이 끝나면 새 세대로 옮겨집니다.
|
|
193
193
|
4. 이전 세대는 세션이 모두 옮겨지면 끝납니다. 메모리가 충분하면 최대 6시간, 아니면 최대 1시간을 기다립니다. 그때까지 끝나지 않은 턴은 끊깁니다. 마무리되면 main 채팅에 한 줄 알림이 옵니다.
|
|
@@ -208,6 +208,7 @@ zigmini update <버전> # 지정한 버전으로 업데이트
|
|
|
208
208
|
| `zigmini pair [--apk]` | 폰 등록 QR 또는 앱 내려받기 QR을 그립니다 |
|
|
209
209
|
| `zigmini consent` | 권한 안내를 보이고 동의를 기록합니다 |
|
|
210
210
|
| `zigmini update [버전] [--force]` | 업데이트합니다 |
|
|
211
|
+
| `zigmini rotate-main [--restart] [--wait <초>] [--foreground]` | main 대화를 새 엔진 세션으로 교체합니다. 웹의 "새 세션"과 같은 요청입니다. 분리 실행하고 결과는 `~/.zigmini/logs/rotate-main.log`에 남깁니다. `--restart`는 게이트웨이를 먼저 멈추고 다시 켭니다 |
|
|
211
212
|
| `zigmini migrate` | 옛 Docker 설치(`~/.zigmini-gateway`)를 새 구조로 복사합니다. 옛 폴더는 바꾸지 않습니다 |
|
|
212
213
|
| `zigmini migrate-run` | 유휴를 기다리고, 옛 실행을 멈추고, 백업하고, migrate와 setup을 실행한 뒤 확인합니다. 실패하면 옛 실행으로 되돌립니다 |
|
|
213
214
|
| `zigmini --version` | 설치된 버전을 출력합니다 |
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@ English | [한국어](README.ko.md)
|
|
|
2
2
|
|
|
3
3
|
# zigmini
|
|
4
4
|
|
|
5
|
-
zigmini: a personal AI agent gateway that runs on a Mac. You talk with the agent
|
|
5
|
+
zigmini: a personal AI agent gateway that runs on a Mac. You talk with the agent, give it work and get notifications from the web screen on the Mac and from an Android app. launchd starts the gateway and the bridge automatically when you log in to macOS. Docker is not necessary.
|
|
6
6
|
|
|
7
7
|
## Requirements
|
|
8
8
|
|
|
@@ -103,7 +103,7 @@ The gateway runs the agent with the permission preset `full-access`. The agent d
|
|
|
103
103
|
|
|
104
104
|
1. It shows the permission notice and asks for your consent.
|
|
105
105
|
2. It creates the home folder `~/.zigmini`. If the rules file of this installation (`~/.zigmini/rules.md`) does not exist, it creates an empty one. See "Agent rules" below.
|
|
106
|
-
3. It downloads the pinned version of the agent engine (
|
|
106
|
+
3. It downloads the pinned version of the agent engine (omo-ai), checks its hash and patches it. The package decides the version (`src/engine/compat.json`; engine 5.1.35 at the time of writing). It shows a summary of the engine license and asks for your consent (`y`).
|
|
107
107
|
4. It checks the agent folder and the login state of the model account. The default agent folder is `~/.omo/agent`. Other OMO installations that use the same folder share the login data. If you are not logged in, it prints a login command. Run that command, then type `/login`.
|
|
108
108
|
5. It asks for a Notion token (optional). If you leave it empty, the schedule feature is off.
|
|
109
109
|
6. It asks for the external access method. See "External access" below.
|
|
@@ -187,7 +187,7 @@ When a new app version is available, the app shows "새 앱 버전" (new app ver
|
|
|
187
187
|
|
|
188
188
|
If the new version supports a zero-downtime switch (versions after 0.8.0-beta.1), the update does not cut the turns in progress.
|
|
189
189
|
|
|
190
|
-
1. A new bridge and
|
|
190
|
+
1. A new bridge and engine (a new runtime generation) start first on other ports and must pass a check. If the check fails, the update keeps the running ones and rolls back only the installation.
|
|
191
191
|
2. Only the gateway restarts. The web and the phone connect again after a few seconds.
|
|
192
192
|
3. New sessions open in the new generation. Each session of the previous generation moves to the new generation when its turn ends.
|
|
193
193
|
4. The previous generation ends when all its sessions have moved. It waits up to 6 hours if there is enough memory, otherwise up to 1 hour. Turns that are not done by then are cut. When the switch is complete, a one-line notice appears in the main chat.
|
|
@@ -208,6 +208,7 @@ An update to a version that does not support the zero-downtime switch (including
|
|
|
208
208
|
| `zigmini pair [--apk]` | Draws the phone registration QR code or the app download QR code |
|
|
209
209
|
| `zigmini consent` | Shows the permission notice and records the consent |
|
|
210
210
|
| `zigmini update [버전] [--force]` | Updates (`버전`: version) |
|
|
211
|
+
| `zigmini rotate-main [--restart] [--wait <초>] [--foreground]` | Moves the main conversation to a fresh engine session, like "새 세션" (New session) in the web. Runs detached and logs to `~/.zigmini/logs/rotate-main.log`. `--restart` stops and starts the gateway first (`<초>`: seconds) |
|
|
211
212
|
| `zigmini migrate` | Copies an old Docker installation (`~/.zigmini-gateway`) to the new layout. The old folder is not changed |
|
|
212
213
|
| `zigmini migrate-run` | Waits until idle, stops the old runtime, backs up, runs migrate and setup, then checks. If this fails, it goes back to the old runtime |
|
|
213
214
|
| `zigmini --version` | Prints the installed version |
|
|
@@ -18,3 +18,4 @@ zigmini 패키지 공통 에이전트 규칙입니다. 게이트웨이 브리지
|
|
|
18
18
|
13. 사용자의 시간 표현은 사용자의 현지 시간으로 해석합니다. 보고하는 시각도 현지 시간으로 씁니다.
|
|
19
19
|
14. 사용자는 게이트웨이 프로필의 닉네임으로 부릅니다.
|
|
20
20
|
15. main은 오래 기다리거나 오래 실행하는 작업(배포, 전체 테스트, 뮤테이션 테스트, 다른 작업 기다리기)을 맡기면, 그 작업의 세션 기록이 30분 넘게 바뀌지 않을 때 main을 깨우는 감시를 직접 겁니다. 작업이 끝나면 감시를 끕니다. 작업의 "기다리는 중"·"진행 중" 보고만 믿지 않습니다. 사용자에게 그렇게 말하기 전에 그 작업의 마지막 기록 시각과 실행 중인 프로세스를 확인합니다. 작업이 멈췄으면 바로 그 작업에 조치를 보내고 사용자에게 알립니다. 작업은 긴 명령을 세션과 분리해 실행하고, 결과 파일을 직접 확인하며, 시간 상한을 겁니다.
|
|
21
|
+
16. 작업 하나에 나눌 수 있는 항목이 여럿이거나(예: 버그 여러 개, 구현·검증·문서 단계) 앞 단계를 기다려야 하는 단계가 있으면, 작업 세션은 mass-ulw 스킬을 씁니다. 작업 세션은 처음에 단계와 하위 작업을 정합니다. 서로 기다릴 필요가 없는 하위 작업은 최대한 병렬로 돌립니다. main은 이런 작업을 맡길 때 작업 지시문에 mass-ulw를 쓰라고 적습니다. 파일 하나 고침, 문서 한 줄 같은 작은 작업에는 mass-ulw를 쓰지 않습니다. 같은 파일을 고치는 하위 작업은 같은 단계에서 함께 돌리지 않습니다. 이런 하위 작업은 각자 worktree를 쓰거나 순서대로 돌립니다. 하위 작업을 병렬로 돌려도 무거운 명령(전체 테스트, 뮤테이션 테스트, 빌드 등)은 그 프로젝트나 설치 규칙이 정한 동시 실행 상한을 지킵니다. 규칙 8의 정리와 규칙 15의 감시는 하위 작업에도 적용합니다. 사용자가 질문만 하면 진행 중인 작업을 mass-ulw로 바꾸지 않습니다(규칙 4).
|
|
Binary file
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"versionCode":
|
|
3
|
-
"versionName": "0.8.0-beta.
|
|
4
|
-
"sha256": "
|
|
2
|
+
"versionCode": 25,
|
|
3
|
+
"versionName": "0.8.0-beta.6",
|
|
4
|
+
"sha256": "4fc49f125efaa2ae1118d4724b7a2370c7d1c80227f9228858266eea64678155"
|
|
5
5
|
}
|
package/assets/build.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"commit":"
|
|
1
|
+
{"commit":"46f57dd257c4451fbc4f2d9f4022501e20798032"}
|
package/docs/manual.ko.md
CHANGED
|
@@ -19,7 +19,7 @@ zigmini는 화면 두 개를 제공합니다.
|
|
|
19
19
|
|
|
20
20
|
웹 화면은 세 부분으로 나뉩니다.
|
|
21
21
|
|
|
22
|
-
- 왼쪽 사이드바: 대화 목록, 새 대화 버튼(+), 작업 상태 영역, 내 프로필, 에이전트 프로필이 있습니다. 사이드바 맨 위의 "
|
|
22
|
+
- 왼쪽 사이드바: 대화 목록, 새 대화 버튼(+), 작업 상태 영역, 내 프로필, 에이전트 프로필이 있습니다. 사이드바 맨 위의 "엔진" 배지는 엔진 연결 상태를 보여 줍니다.
|
|
23
23
|
- 가운데 채팅: 헤더, 메시지 목록, 입력창이 있습니다.
|
|
24
24
|
- 오른쪽 패널: 확인함, 설정, 이벤트 패널이 열립니다.
|
|
25
25
|
|
|
@@ -208,7 +208,7 @@ Android 앱(APK)을 받는 QR이 보입니다. 폰 카메라로 QR을 찍어 APK
|
|
|
208
208
|
|
|
209
209
|
#### 정리
|
|
210
210
|
|
|
211
|
-
게이트웨이가 지워도 되는 후보를 보여 줍니다. 후보는 DB 백업,
|
|
211
|
+
게이트웨이가 지워도 되는 후보를 보여 줍니다. 후보는 DB 백업, 엔진 버전 폴더, 업데이트 남은 파일, 임시 폴더, 이전 RPC 데몬입니다. 지울 항목을 고르고 "선택한 항목 정리"를 누릅니다. 후보 수 옆의 "전체 선택"을 누르면 모든 후보를 한 번에 고르거나 모두 풉니다.
|
|
212
212
|
|
|
213
213
|
#### 음성 설정
|
|
214
214
|
|
|
@@ -265,7 +265,7 @@ Ctrl 단축키는 Cmd, Alt, Shift와 함께 누르면 동작하지 않습니다.
|
|
|
265
265
|
|
|
266
266
|
### 4.1 연결
|
|
267
267
|
|
|
268
|
-
앱 헤더 왼쪽에 지그 얼굴과 상태 태그가 있습니다. 상태 태그에는 "게이트웨이 재연결 중", "
|
|
268
|
+
앱 헤더 왼쪽에 지그 얼굴과 상태 태그가 있습니다. 상태 태그에는 "게이트웨이 재연결 중", "엔진 끊김", "엔진 연결됨", "작업 상태 확인 중" 중 하나가 보입니다.
|
|
269
269
|
|
|
270
270
|
| 조작 | 동작 |
|
|
271
271
|
| --- | --- |
|
|
@@ -378,5 +378,6 @@ Ctrl 단축키는 Cmd, Alt, Shift와 함께 누르면 동작하지 않습니다.
|
|
|
378
378
|
- `zigmini pair [--apk]`: 폰 등록 QR이나 앱 내려받기 QR을 보여 줍니다.
|
|
379
379
|
- `zigmini consent`: 권한 안내를 보이고 동의를 기록합니다.
|
|
380
380
|
- `zigmini update [버전]`: 업데이트합니다.
|
|
381
|
+
- `zigmini rotate-main [--restart]`: main 대화를 새 세션으로 교체합니다. "새 세션"과 같습니다. 먼저 운영자 허가를 받습니다. 자세한 내용은 운영 문서를 보세요.
|
|
381
382
|
|
|
382
383
|
전체 목록은 README의 '명령' 절을 보세요.
|
package/docs/manual.md
CHANGED
|
@@ -19,7 +19,7 @@ zigmini has two surfaces.
|
|
|
19
19
|
|
|
20
20
|
The web screen has three parts.
|
|
21
21
|
|
|
22
|
-
- Left sidebar: the conversation list, the new conversation button (+), the work status area, your profile and the agent profile. The "
|
|
22
|
+
- Left sidebar: the conversation list, the new conversation button (+), the work status area, your profile and the agent profile. The "엔진" (engine) badge at the top shows the engine connection.
|
|
23
23
|
- Center chat: the header, the message list and the input box.
|
|
24
24
|
- Right panels: the inbox, settings and the event panel open here.
|
|
25
25
|
|
|
@@ -208,7 +208,7 @@ A QR for the Android app (APK) shows. Scan it with the phone camera, get the APK
|
|
|
208
208
|
|
|
209
209
|
#### Cleanup (정리)
|
|
210
210
|
|
|
211
|
-
The gateway lists things that are safe to delete: DB backups,
|
|
211
|
+
The gateway lists things that are safe to delete: DB backups, engine version folders, leftover update files, temp folders and old RPC daemons. Tick the ones to remove and click 선택한 항목 정리 (clean selected). The 전체 선택 (select all) box beside the count ticks every candidate at once, or clears them all.
|
|
212
212
|
|
|
213
213
|
#### Voice settings
|
|
214
214
|
|
|
@@ -265,7 +265,7 @@ Ctrl shortcuts do nothing with Cmd, Alt or Shift held. They match by key positio
|
|
|
265
265
|
|
|
266
266
|
### 4.1 Connections
|
|
267
267
|
|
|
268
|
-
The app header shows the Zig face and a status tag on the left. The tag reads one of "게이트웨이 재연결 중" (reconnecting), "
|
|
268
|
+
The app header shows the Zig face and a status tag on the left. The tag reads one of "게이트웨이 재연결 중" (reconnecting), "엔진 끊김" (engine disconnected), "엔진 연결됨" (engine connected) or "작업 상태 확인 중" (checking work status).
|
|
269
269
|
|
|
270
270
|
| Action | What it does |
|
|
271
271
|
| --- | --- |
|
|
@@ -378,5 +378,6 @@ Main commands for the Mac terminal:
|
|
|
378
378
|
- `zigmini pair [--apk]`: shows the phone registration QR or the app download QR.
|
|
379
379
|
- `zigmini consent`: shows the permission notice and records consent.
|
|
380
380
|
- `zigmini update [version]`: updates.
|
|
381
|
+
- `zigmini rotate-main [--restart]`: moves the main conversation to a new session, like "새 세션" (new session). Ask the operator first. See the operator guide.
|
|
381
382
|
|
|
382
383
|
For the full list, see the README's Commands section.
|
|
@@ -98,7 +98,7 @@ function durableReadiness(ctx: ContinuityContext): string | null {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
// Bug 86: only a tool call without its result can still be running. Work that outlives its tool call (task children,
|
|
101
|
-
// detached eval cells, background terminals,
|
|
101
|
+
// detached eval cells, background terminals, open questions) is owned through wake_source_state, which
|
|
102
102
|
// readiness checks before this; a settled call of any tool name leaves nothing behind.
|
|
103
103
|
const entries = ctx.sessionManager.getEntries();
|
|
104
104
|
const results = new Set(
|
|
@@ -162,7 +162,8 @@ export default function continuityControl<Context extends ContinuityContext>(
|
|
|
162
162
|
if (runtime.hasPendingMessages()) return result("native_queue");
|
|
163
163
|
if (runtime.isCompacting?.() !== false) return result("native_compaction");
|
|
164
164
|
if (wakeEvidenceInvalid || !wakes.has("senpi-task")) return result("task_registry_uninitialized");
|
|
165
|
-
|
|
165
|
+
// Bug 153: session shutdown stops terminal monitors; monitoring now belongs to a separate OMO process.
|
|
166
|
+
if ([...wakes.entries()].some(([source, count]) => source !== "terminal-monitors" && count > 0)) return result("native_wake_ownership");
|
|
166
167
|
return result(durable ? durableReadiness(runtime) : null);
|
|
167
168
|
};
|
|
168
169
|
pi.rpc.handle("zgw.continuity_readiness", () => readiness(true));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": 1,
|
|
3
|
+
"revision": 4,
|
|
3
4
|
"upstream": {
|
|
4
5
|
"omo": "5.1.35",
|
|
5
6
|
"senpi": "2026.10.16"
|
|
@@ -9,7 +10,10 @@
|
|
|
9
10
|
"entrypoint": "node_modules/@code-yeongyu/senpi/dist/cli.js",
|
|
10
11
|
"patchRoot": "node_modules",
|
|
11
12
|
"patches": [
|
|
12
|
-
"omo-5.1.35-gateway-port.patch"
|
|
13
|
+
"omo-5.1.35-gateway-port.patch",
|
|
14
|
+
"omo-5.1.35-image-replay.patch",
|
|
15
|
+
"omo-5.1.35-question-capability.patch",
|
|
16
|
+
"omo-5.1.35-phantom-attach.patch"
|
|
13
17
|
],
|
|
14
18
|
"files": [
|
|
15
19
|
{
|
|
@@ -42,6 +46,11 @@
|
|
|
42
46
|
"originalSha256": "f98f1758e934135084d2765f73b20e4c22f6a74dce344569c6dfbfe9dc250cbc",
|
|
43
47
|
"patchedSha256": "218862c77a0df2691a521ae99c95e99acc64a8547dd6d6e627ebd98369d34fe0"
|
|
44
48
|
},
|
|
49
|
+
{
|
|
50
|
+
"path": "node_modules/@code-yeongyu/senpi/dist/core/extensions/builtin/anthropic-subscription/cold-seed-images.js",
|
|
51
|
+
"originalSha256": "f40ff494a945bc5188dca010476cac76ae60f178f67d5391457a04e7f266c16e",
|
|
52
|
+
"patchedSha256": "7c042bc1bfc0240cfd576cdc989f174dd248270219cc834788d22c9df3aa6903"
|
|
53
|
+
},
|
|
45
54
|
{
|
|
46
55
|
"path": "node_modules/@code-yeongyu/senpi/dist/core/extensions/builtin/ask-user/pending.js",
|
|
47
56
|
"originalSha256": "0f8156f0cdd1fbc4bb08486d0b8b6ff5f821b9ac5df4cf5056714689fb2bb0c5",
|
|
@@ -110,12 +119,12 @@
|
|
|
110
119
|
{
|
|
111
120
|
"path": "node_modules/@code-yeongyu/senpi/dist/modes/rpc/connection-handler.js",
|
|
112
121
|
"originalSha256": "20c6ef0ebe92cd4a94b78623bd6ae1144909709c10dd826e7c313932b6c28775",
|
|
113
|
-
"patchedSha256": "
|
|
122
|
+
"patchedSha256": "239dd0f928a7b2c89ca1ebde7bc1ca5f796f303a4430060796c8585099094da3"
|
|
114
123
|
},
|
|
115
124
|
{
|
|
116
125
|
"path": "node_modules/@code-yeongyu/senpi/dist/modes/rpc/connection-question-bridge.js",
|
|
117
126
|
"originalSha256": "0b63f63575df24ebf3f80dc891ffe876a9d908bc1a2264525919e4a8cef061b8",
|
|
118
|
-
"patchedSha256": "
|
|
127
|
+
"patchedSha256": "f88a26b34f127d35debbcc3e6b6a6d26a75b5a32aee852af9c51b6c770956813"
|
|
119
128
|
},
|
|
120
129
|
{
|
|
121
130
|
"path": "node_modules/@code-yeongyu/senpi/dist/modes/rpc/multi-session-host.js",
|
|
@@ -140,7 +149,7 @@
|
|
|
140
149
|
{
|
|
141
150
|
"path": "node_modules/@code-yeongyu/senpi/dist/modes/rpc/session-command-router.js",
|
|
142
151
|
"originalSha256": "3f952631024884c893a6e37549c2861e25b0bddc2387222006b77dcbaad78c9b",
|
|
143
|
-
"patchedSha256": "
|
|
152
|
+
"patchedSha256": "a2ea46300a8d7b4cb46413afd5389ad8df37707c2852f3f4b826011a28eea6ef"
|
|
144
153
|
},
|
|
145
154
|
{
|
|
146
155
|
"path": "node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
diff -ruN a/@code-yeongyu/senpi/dist/core/extensions/builtin/anthropic-subscription/cold-seed-images.js b/@code-yeongyu/senpi/dist/core/extensions/builtin/anthropic-subscription/cold-seed-images.js
|
|
2
|
+
--- a/@code-yeongyu/senpi/dist/core/extensions/builtin/anthropic-subscription/cold-seed-images.js 2026-10-20 08:58:36
|
|
3
|
+
+++ b/@code-yeongyu/senpi/dist/core/extensions/builtin/anthropic-subscription/cold-seed-images.js 2026-10-20 08:58:36
|
|
4
|
+
@@ -21,6 +21,7 @@
|
|
5
|
+
toolOutput: (toolName) => `[image returned by the ${toolName} tool to your own call (tool output, not a user attachment)]`,
|
|
6
|
+
userHistory: "[image the user attached in this earlier turn (history, not part of the current message)]",
|
|
7
|
+
duplicate: (origin, first) => `${subject(origin)}: identical to an image already shown above (${source(first)}); not attached again]`,
|
|
8
|
+
+ userAnswered: "[image the user attached in an earlier turn: you already saw and answered it, so it is not attached again and is not part of the current message; ask the user to attach it again if you need it]",
|
|
9
|
+
answered: (origin) => `[image returned earlier by the ${origin.toolName} tool, id=${origin.toolCallId}: already seen earlier in this conversation; run the tool again if you need it]`,
|
|
10
|
+
capped: (origin) => `${subject(origin)}: omitted from this replay, which re-sends at most ${MAX_REPLAYED_HISTORY_IMAGES} earlier images; re-read the source if you need it]`,
|
|
11
|
+
unreadable: (origin) => `${subject(origin)}: omitted because its image data is missing or unreadable]`,
|
|
12
|
+
@@ -86,14 +87,16 @@
|
|
13
|
+
return new Set(ordered.slice(0, MAX_REPLAYED_HISTORY_IMAGES).map(([hash]) => hash));
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
- * A tool image the assistant already responded to is not re-sent (#2768): a cold
|
|
17
|
+
- * seed packs the whole history into one user message, and replaying earlier tool
|
|
18
|
+
- * reads there made the model answer plain questions as if the user had just sent
|
|
19
|
+
- * those images. The user's own uploads and images still awaiting a response are
|
|
20
|
+
- * replayed.
|
|
21
|
+
+ * An image the assistant already responded to is not re-sent (#2768): a cold
|
|
22
|
+
+ * seed packs the whole history into one user message, and replaying earlier
|
|
23
|
+
+ * images there made the model answer plain questions as if the user had just
|
|
24
|
+
+ * sent those images. zigmini: this covers the user's own earlier uploads too;
|
|
25
|
+
+ * Claude Code saved each replayed upload as a new images/N.png on every model
|
|
26
|
+
+ * call and the model read it as a new attachment. Images still awaiting a
|
|
27
|
+
+ * response (the current turn) are replayed.
|
|
28
|
+
*/
|
|
29
|
+
function isAnsweredToolImage(occurrence) {
|
|
30
|
+
- return occurrence.answered && occurrence.origin.kind === "tool";
|
|
31
|
+
+ return occurrence.answered;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Rewrites the image entries of a cold-seed history (#2490): a tool-result image
|
|
35
|
+
@@ -124,8 +127,8 @@
|
|
36
|
+
if (decoded === undefined || hash === undefined) {
|
|
37
|
+
replacement = [{ type: "text", text: coldSeedImageText.unreadable(origin) }];
|
|
38
|
+
}
|
|
39
|
+
- else if (origin.kind === "tool" && occurrence.answered && shownEarlier === undefined) {
|
|
40
|
+
- replacement = [{ type: "text", text: coldSeedImageText.answered(origin) }];
|
|
41
|
+
+ else if (occurrence.answered && shownEarlier === undefined) {
|
|
42
|
+
+ replacement = [{ type: "text", text: origin.kind === "tool" ? coldSeedImageText.answered(origin) : coldSeedImageText.userAnswered }];
|
|
43
|
+
}
|
|
44
|
+
else if (!replayed.has(hash) && shownEarlier === undefined) {
|
|
45
|
+
replacement = [{ type: "text", text: coldSeedImageText.capped(origin) }];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
diff --git a/@code-yeongyu/senpi/dist/modes/rpc/session-command-router.js b/@code-yeongyu/senpi/dist/modes/rpc/session-command-router.js
|
|
2
|
+
--- a/@code-yeongyu/senpi/dist/modes/rpc/session-command-router.js
|
|
3
|
+
+++ b/@code-yeongyu/senpi/dist/modes/rpc/session-command-router.js
|
|
4
|
+
@@ -584,7 +584,17 @@
|
|
5
|
+
* the failure response otherwise, exactly as an open does.
|
|
6
|
+
*/
|
|
7
|
+
reopenParked(entry) {
|
|
8
|
+
- return this.open(parkedOpenCommand(entry), undefined, { reply: false, defaults: false });
|
|
9
|
+
+ return this.open(parkedOpenCommand(entry), undefined, { reply: false, defaults: false, releaseUnowned: true });
|
|
10
|
+
+ }
|
|
11
|
+
+ /**
|
|
12
|
+
+ * zgw (rotation after a handoff): the registry counts one attachment for every open, and an owner-less reopen has no
|
|
13
|
+
+ * connection that could ever release it, so a later close by the real owner left the session open. Release it as a
|
|
14
|
+
+ * detach: a retained entry stays open at zero attachments, exactly as when its last client leaves.
|
|
15
|
+
+ */
|
|
16
|
+
+ releaseUnownedReopen(sessionId) {
|
|
17
|
+
+ if (this.draining || this.registry.peek(sessionId)?.retainOnDisconnect !== true)
|
|
18
|
+
+ return false;
|
|
19
|
+
+ return this.tryClaimClose(sessionId, { drainAttachments: false, detach: true }) !== undefined;
|
|
20
|
+
}
|
|
21
|
+
async open(command, owner, options) {
|
|
22
|
+
// A parked session's reopen replays what its park recorded; a field it left empty stays empty
|
|
23
|
+
@@ -729,6 +739,8 @@
|
|
24
|
+
this.releaseOwnerAttachment(owner, openedSession.sessionId);
|
|
25
|
+
await this.releaseOwnedSession(openedSession.sessionId);
|
|
26
|
+
}
|
|
27
|
+
+ if (owner === undefined && options?.releaseUnowned === true)
|
|
28
|
+
+ this.releaseUnownedReopen(openedSession.sessionId);
|
|
29
|
+
const replyState = entry.worker?.snapshot?.state ?? (entry.runtime ? buildRpcSessionState(entry.runtime.session) : undefined);
|
|
30
|
+
if (!replyState)
|
|
31
|
+
throw new Error("Session state was not created");
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
diff --git a/@code-yeongyu/senpi/dist/modes/rpc/connection-handler.js b/@code-yeongyu/senpi/dist/modes/rpc/connection-handler.js
|
|
2
|
+
--- a/@code-yeongyu/senpi/dist/modes/rpc/connection-handler.js
|
|
3
|
+
+++ b/@code-yeongyu/senpi/dist/modes/rpc/connection-handler.js
|
|
4
|
+
@@ -313,7 +313,8 @@
|
|
5
|
+
* Create an extension UI context that uses the RPC protocol.
|
|
6
|
+
*/
|
|
7
|
+
const createExtensionUIContext = () => ({
|
|
8
|
+
- question: (request, opts) => clientCapabilities?.includes(QUESTION_CAPABILITY)
|
|
9
|
+
+ // zgw (bug 151): a binding made without a connection (a handoff reopen) asks the session's live connections.
|
|
10
|
+
+ question: (request, opts) => clientCapabilities?.includes(QUESTION_CAPABILITY) || options.clientInfo?.sessionHasCapability?.(QUESTION_CAPABILITY)
|
|
11
|
+
? questions.ask(request, opts)
|
|
12
|
+
: degradeQuestion(createExtensionUIContext(), request, opts),
|
|
13
|
+
select: (title, options, opts) => createDialogPromise(opts, undefined, {
|
|
14
|
+
diff --git a/@code-yeongyu/senpi/dist/modes/rpc/connection-question-bridge.js b/@code-yeongyu/senpi/dist/modes/rpc/connection-question-bridge.js
|
|
15
|
+
--- a/@code-yeongyu/senpi/dist/modes/rpc/connection-question-bridge.js
|
|
16
|
+
+++ b/@code-yeongyu/senpi/dist/modes/rpc/connection-question-bridge.js
|
|
17
|
+
@@ -148,7 +148,8 @@
|
|
18
|
+
else if (selected !== undefined)
|
|
19
|
+
answers[question.id] = { selected: [selected] };
|
|
20
|
+
}
|
|
21
|
+
- const comment = await ui.input("Anything else? (optional)", undefined, opts);
|
|
22
|
+
+ // zgw (bug 151): the optional comment never inherits the question's required flag.
|
|
23
|
+
+ const comment = await ui.input("Anything else? (optional)", undefined, opts && { ...opts, required: false });
|
|
24
|
+
const unanswered = request.questions.filter((q) => !answers[q.id]).map((q) => q.id);
|
|
25
|
+
return {
|
|
26
|
+
status: comment?.trim() ? "comment-submitted" : unanswered.length ? "cancelled" : "answered",
|
|
27
|
+
diff --git a/@code-yeongyu/senpi/dist/modes/rpc/session-command-router.js b/@code-yeongyu/senpi/dist/modes/rpc/session-command-router.js
|
|
28
|
+
--- a/@code-yeongyu/senpi/dist/modes/rpc/session-command-router.js
|
|
29
|
+
+++ b/@code-yeongyu/senpi/dist/modes/rpc/session-command-router.js
|
|
30
|
+
@@ -558,6 +558,27 @@
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
+ * zgw (bug 151): whether the session's clients advertised a capability, read when it is needed. A binding keeps the
|
|
35
|
+
+ * capabilities of the connection that opened it, and a handoff reopen has none. Attached live connections decide;
|
|
36
|
+
+ * with none attached, any live connection that advertised it does, since a question frame is replayed on attach.
|
|
37
|
+
+ */
|
|
38
|
+
+ sessionAdvertises(sessionId, capability) {
|
|
39
|
+
+ let attached = false;
|
|
40
|
+
+ for (const [connection, sessions] of this.sessionsByConnection) {
|
|
41
|
+
+ if (!sessions.has(sessionId) || this.releasedConnections.has(connection))
|
|
42
|
+
+ continue;
|
|
43
|
+
+ attached = true;
|
|
44
|
+
+ if (this.pendingCapabilities.get(connection)?.includes(capability))
|
|
45
|
+
+ return true;
|
|
46
|
+
+ }
|
|
47
|
+
+ if (attached)
|
|
48
|
+
+ return false;
|
|
49
|
+
+ for (const [connection, capabilities] of this.pendingCapabilities)
|
|
50
|
+
+ if (!this.releasedConnections.has(connection) && this.writer.hasRegisteredConnectionCapabilities(connection) && capabilities.includes(capability))
|
|
51
|
+
+ return true;
|
|
52
|
+
+ return false;
|
|
53
|
+
+ }
|
|
54
|
+
+ /**
|
|
55
|
+
* The successor's reopen of a handoff-parked session: the same open as an `open_session`, with no
|
|
56
|
+
* requesting connection and nothing to reply to (senpi#3368). Resolves `undefined` on success and
|
|
57
|
+
* the failure response otherwise, exactly as an open does.
|
|
58
|
+
@@ -675,6 +696,7 @@
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
connectionId: () => this.writer.currentConnection(),
|
|
62
|
+
+ sessionHasCapability: (capability) => this.sessionAdvertises(openedSession.sessionId, capability),
|
|
63
|
+
},
|
|
64
|
+
}));
|
|
65
|
+
if (entry.worker)
|
package/package.json
CHANGED
package/src/cli/doctor.ts
CHANGED
|
@@ -108,10 +108,10 @@ export async function collect(deps: DoctorDeps, ctx: LaunchdContext): Promise<Do
|
|
|
108
108
|
const health = await deps.health(healthUrl(ctx));
|
|
109
109
|
if (health === null) {
|
|
110
110
|
items.push({ id: "gateway.health", label: "게이트웨이", status: "bad", detail: `응답 없음 (${healthUrl(ctx)})` });
|
|
111
|
-
items.push({ id: "gateway.omo", label: "
|
|
111
|
+
items.push({ id: "gateway.omo", label: "엔진 연결", status: "bad", detail: "게이트웨이 응답 없음" });
|
|
112
112
|
} else {
|
|
113
113
|
items.push({ id: "gateway.health", label: "게이트웨이", status: health.status === 200 ? "ok" : "bad", detail: `HTTP ${health.status}` });
|
|
114
|
-
items.push({ id: "gateway.omo", label: "
|
|
114
|
+
items.push({ id: "gateway.omo", label: "엔진 연결", status: health.omo === "ready" ? "ok" : "bad", detail: health.omo === "ready" ? "" : `omo=${health.omo ?? "?"}` });
|
|
115
115
|
items.push(...await voiceItems(deps, ctx, health.voice));
|
|
116
116
|
if (deps.updateStatus !== undefined) items.push(updateItem(await deps.updateStatus(`http://127.0.0.1:${ctx.paths.ports.web}${UPDATE_STATUS_PATH}`)));
|
|
117
117
|
}
|
|
@@ -129,13 +129,13 @@ export async function collect(deps: DoctorDeps, ctx: LaunchdContext): Promise<Do
|
|
|
129
129
|
const pinned = deps.pinnedEngine();
|
|
130
130
|
const global = deps.globalEngine();
|
|
131
131
|
const latest = await deps.latestEngine();
|
|
132
|
-
items.push({ id: "omo.version", label: "
|
|
132
|
+
items.push({ id: "omo.version", label: "엔진 버전", status: pinned === null ? "warn" : "info",
|
|
133
133
|
detail: `고정 ${pinned ?? "없음"}, 전역 ${global ?? "?"}, npm 최신 ${latest ?? "확인 실패"}` });
|
|
134
134
|
const globalWarning = pinned === null ? null : globalEngineWarning(global, pinned);
|
|
135
|
-
if (globalWarning !== null) items.push({ id: "omo.global", label: "전역
|
|
135
|
+
if (globalWarning !== null) items.push({ id: "omo.global", label: "전역 OMO", status: "warn", detail: globalWarning });
|
|
136
136
|
|
|
137
137
|
const rpc = deps.rpcDaemons();
|
|
138
|
-
items.push({ id: "omo.rpc", label: "
|
|
138
|
+
items.push({ id: "omo.rpc", label: "엔진 RPC 호스트 데몬", status: rpc === null ? "warn" : "info", detail: rpc === null ? "확인 실패" : rpc.length === 0 ? "없음" : `${rpc.length}개 (나열만)` });
|
|
139
139
|
|
|
140
140
|
const schema = deps.schema();
|
|
141
141
|
items.push({ id: "schema", label: "thread gateway 스키마", status: schema.state === "too_new" ? "warn" : schema.state === "ok" ? "ok" : "info",
|
package/src/cli/main.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { setupHint } from "./setup-hint.ts";
|
|
|
11
11
|
import { helperCommand, updateCommand } from "../update/cli.ts";
|
|
12
12
|
import { handoffCommand } from "../update/handoff-system.ts";
|
|
13
13
|
import { HANDOFF_COMMAND } from "../update/strategy.ts";
|
|
14
|
+
import { ROTATE_COMMAND, ROTATE_RUN_COMMAND, rotateCommand } from "./rotate-main.ts";
|
|
14
15
|
import { EXIT_USAGE, parseMigrateArgs, parseMigrateRunArgs, runMigrate, runMigrateRun, systemMigrateDeps, systemMigrateRunDeps } from "./migrate.ts";
|
|
15
16
|
|
|
16
17
|
export const HELP = `zigmini - 개인 AI 에이전트 게이트웨이
|
|
@@ -20,7 +21,7 @@ export const HELP = `zigmini - 개인 AI 에이전트 게이트웨이
|
|
|
20
21
|
사용법: zigmini <명령> [옵션]
|
|
21
22
|
|
|
22
23
|
명령:
|
|
23
|
-
setup 설치합니다: 홈 폴더,
|
|
24
|
+
setup 설치합니다: 홈 폴더, 엔진, 설정, launchd 등록과 시작
|
|
24
25
|
start 게이트웨이와 브리지를 launchd에 등록하고 시작합니다
|
|
25
26
|
stop 게이트웨이와 브리지를 멈춥니다
|
|
26
27
|
stop --uninstall 멈추고 이 패키지의 launchd plist를 모두 지웁니다
|
|
@@ -29,6 +30,10 @@ export const HELP = `zigmini - 개인 AI 에이전트 게이트웨이
|
|
|
29
30
|
doctor [--json] [--fix] 전체 상태를 점검합니다(--fix: 이상 있는 launchd 에이전트만 다시 띄움)
|
|
30
31
|
pair 폰 등록 QR(유효 10분, 한 번 사용)을 터미널에 그립니다. 게이트웨이가 실행 중이어야 합니다
|
|
31
32
|
pair --apk 앱(APK) 내려받기 QR(유효 10분)을 터미널에 그립니다
|
|
33
|
+
rotate-main [--restart] [--wait <초>] [--foreground]
|
|
34
|
+
main 대화를 새 엔진 세션으로 교체합니다. main 차례가 끝나길 기다린 뒤 보냅니다(기본 900초).
|
|
35
|
+
기본은 분리 실행이고 결과는 <홈>/logs/rotate-main.log에 남깁니다
|
|
36
|
+
(--restart: 먼저 게이트웨이를 멈추고 다시 켭니다. --foreground: 이 터미널에서 실행)
|
|
32
37
|
consent 풀 액세스 권한 안내를 보이고 동의를 기록합니다(거부하면 멈춤)
|
|
33
38
|
update [버전] [--force] 새 버전으로 업데이트합니다(비우면 latest). DB 백업 뒤 설치·재시작하고, 실패하면 자동으로 되돌립니다
|
|
34
39
|
(--force: 진행 중인 턴이 있어도 합니다. 게이트웨이가 실행 중이어야 합니다)
|
|
@@ -127,6 +132,15 @@ if (import.meta.main) {
|
|
|
127
132
|
}
|
|
128
133
|
if (argv[0] === UPDATER_COMMAND) process.exit(await helperCommand(deps, ctx));
|
|
129
134
|
if (argv[0] === HANDOFF_COMMAND) process.exit(await handoffCommand(deps, ctx, argv.slice(1)));
|
|
135
|
+
if (argv[0] === ROTATE_COMMAND || argv[0] === ROTATE_RUN_COMMAND) {
|
|
136
|
+
const refusal = argv.includes("--restart") ? cliGate("restart", ctx.paths.home, kind, notice) : null;
|
|
137
|
+
if (refusal !== null) {
|
|
138
|
+
deps.err(`${refusal}\n`);
|
|
139
|
+
process.exit(EXIT_DECLINED);
|
|
140
|
+
}
|
|
141
|
+
const args = argv[0] === ROTATE_RUN_COMMAND ? [...argv.slice(1), "--foreground"] : argv.slice(1);
|
|
142
|
+
process.exit(await rotateCommand(args, deps, ctx));
|
|
143
|
+
}
|
|
130
144
|
if (argv[0] === "update") {
|
|
131
145
|
process.exit(await updateCommand(argv.slice(1), { webPort: ctx.paths.ports.web, fetch: (url, init) => fetch(url, init), prompt: stdinPrompt(),
|
|
132
146
|
out: deps.out, err: deps.err, sleep: (ms) => Bun.sleep(ms), now: Date.now }));
|