@minimalcorp/tsunagi 0.3.0 → 0.3.2
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/.next/standalone/apps/web/.next/BUILD_ID +1 -1
- package/.next/standalone/apps/web/.next/build-manifest.json +3 -3
- package/.next/standalone/apps/web/.next/prerender-manifest.json +3 -3
- package/.next/standalone/apps/web/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.html +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.html +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.rsc +2 -2
- package/.next/standalone/apps/web/.next/server/app/settings.segments/_full.segment.rsc +2 -2
- package/.next/standalone/apps/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.segments/_index.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/apps/web/.next/server/app/settings.segments/settings.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/tasks/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/apps/web/.next/server/chunks/ssr/_0g79jqo._.js +1 -1
- package/.next/standalone/apps/web/.next/server/chunks/ssr/_0kflgfj._.js +1 -1
- package/.next/standalone/apps/web/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/apps/web/.next/server/pages/404.html +1 -1
- package/.next/standalone/apps/web/.next/server/pages/500.html +1 -1
- package/.next/standalone/apps/web/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/apps/web/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/apps/web/.next/static/chunks/{0~qk-51yuw4mm.js → 06yhoqamnoyq-.js} +1 -1
- package/.next/standalone/apps/web/.next/static/chunks/{08e.dbdz18nd2.js → 16cbfrtkn0.kw.js} +1 -1
- package/dist/server/index.js +15 -0
- package/dist/server/routes/terminal.js +63 -16
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- /package/.next/standalone/apps/web/.next/static/{jgWo5G4-W0asjpQwCqJWQ → kCD_Mhp9UYT63vnxJPuK4}/_buildManifest.js +0 -0
- /package/.next/standalone/apps/web/.next/static/{jgWo5G4-W0asjpQwCqJWQ → kCD_Mhp9UYT63vnxJPuK4}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/apps/web/.next/static/{jgWo5G4-W0asjpQwCqJWQ → kCD_Mhp9UYT63vnxJPuK4}/_ssgManifest.js +0 -0
|
@@ -27,8 +27,34 @@ function stripTerminalQueries(s) {
|
|
|
27
27
|
.replace(/\x1b\[[?>]?[0-9;]*n/g, '') // DSR: ESC[?6n(主犯) / ESC[6n / ESC[5n など
|
|
28
28
|
.replace(/\x1b\[[?>=]?[0-9;]*c/g, ''); // DA: ESC[c / ESC[0c / ESC[>c / ESC[=c
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* 表示中のクライアントがいなくなった PTY の cols をこの幅に縮める。
|
|
32
|
+
* 一定の幅を下回ると Claude(Ink) が full-frame redraw を行い <Static> を再 emit するという
|
|
33
|
+
* 観測に基づくしきい値。元 cols が既にこの値の場合のみ -1 にして必ず cols を変化させる。
|
|
34
|
+
*
|
|
35
|
+
* 再表示時は replay 後にクライアントが実サイズへ resize するため、必ず cols が変わり
|
|
36
|
+
* SIGWINCH が飛んで Claude が現在の画面を描き直す(同一サイズの resize は no-op で
|
|
37
|
+
* SIGWINCH が飛ばない)。縮める処理を切断時に済ませておくことで、縮めた時の再描画が
|
|
38
|
+
* 再表示より前に scrollback に入り、replay と描き直しの順序がタイミングに依存しない。
|
|
39
|
+
*/
|
|
40
|
+
const REDRAW_BUMP_COLS = 64;
|
|
41
|
+
function bumpColsForRedraw(session) {
|
|
42
|
+
const { pty: ptyProcess } = session;
|
|
43
|
+
const cols = ptyProcess.cols === REDRAW_BUMP_COLS ? REDRAW_BUMP_COLS - 1 : REDRAW_BUMP_COLS;
|
|
44
|
+
try {
|
|
45
|
+
ptyProcess.resize(cols, ptyProcess.rows);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// 終了済みの PTY は無視する
|
|
49
|
+
}
|
|
50
|
+
}
|
|
30
51
|
export async function terminalRoutes(fastify) {
|
|
31
52
|
const io = fastify.io;
|
|
53
|
+
// 作成中のセッション(sessionId → 作成処理)。存在確認から PTY 作成までの間に await を挟むため、
|
|
54
|
+
// 同じ sessionId の作成要求が並行すると両方が存在確認を通過し、後発が "Session already exists" で
|
|
55
|
+
// 失敗する(開発時の StrictMode による effect の二重実行で必ず起きる)。後発は作成中の処理を待って
|
|
56
|
+
// 再利用として返す。
|
|
57
|
+
const pendingCreations = new Map();
|
|
32
58
|
initPtyEnvSync(io);
|
|
33
59
|
// socket.io イベントハンドラ
|
|
34
60
|
io.on('connection', (socket) => {
|
|
@@ -82,16 +108,19 @@ export async function terminalRoutes(fastify) {
|
|
|
82
108
|
ptyManager.setActiveSocket(sessionId, socket.id);
|
|
83
109
|
const { pty: ptyProcess } = session;
|
|
84
110
|
const isReused = session.scrollback.length > 0;
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
111
|
+
// リングバッファの内容を一括送信(画面復元)。初回接続・再接続を問わず join のたびに送る。
|
|
112
|
+
// クライアントは replay を受けたら画面をクリアし、xterm を PTY と同じサイズにしてから
|
|
113
|
+
// 書き込み、完了後に実サイズへ resize する(新規セッションは data が空)。
|
|
114
|
+
// 端末クエリ列(ESC[?6n / ESC[c 等)を除去してから replay する。
|
|
115
|
+
// これを怠ると xterm.js が応答を生成し PTY へ注入され、入力欄に "1;2c64;3R..." が
|
|
116
|
+
// 勝手に入力される(stripTerminalQueries のコメント参照)。
|
|
117
|
+
const buffered = isReused ? stripTerminalQueries(session.scrollback.join('')) : '';
|
|
118
|
+
socket.emit('replay', {
|
|
91
119
|
// 末尾の \r\n / \n / \r をトリムする。
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
120
|
+
data: buffered.replace(/[\r\n]+$/, ''),
|
|
121
|
+
cols: ptyProcess.cols,
|
|
122
|
+
rows: ptyProcess.rows,
|
|
123
|
+
});
|
|
95
124
|
// reused の場合、最初の resize まで onData 出力をバッファリング
|
|
96
125
|
let initialResizeHandled = !isReused;
|
|
97
126
|
const pendingOutput = [];
|
|
@@ -172,6 +201,11 @@ export async function terminalRoutes(fastify) {
|
|
|
172
201
|
ptyManager.clearActiveSocket(boundSessionId, socket.id);
|
|
173
202
|
if (wasOwner) {
|
|
174
203
|
ptyManager.scheduleGc(boundSessionId);
|
|
204
|
+
// 次に表示されたとき Claude に描き直させるため、表示中のクライアントがいない間に
|
|
205
|
+
// cols を縮めておく(REDRAW_BUMP_COLS 参照)
|
|
206
|
+
const session = ptyManager.getSession(boundSessionId);
|
|
207
|
+
if (session)
|
|
208
|
+
bumpColsForRedraw(session);
|
|
175
209
|
}
|
|
176
210
|
}
|
|
177
211
|
});
|
|
@@ -190,14 +224,27 @@ export async function terminalRoutes(fastify) {
|
|
|
190
224
|
fastify.log.info({ sessionId }, 'Reusing existing PTY session');
|
|
191
225
|
return reply.status(200).send({ sessionId, reused: true });
|
|
192
226
|
}
|
|
193
|
-
const
|
|
227
|
+
const pending = pendingCreations.get(sessionId);
|
|
228
|
+
const creation = pending ??
|
|
229
|
+
(async () => {
|
|
230
|
+
const launchRequest = { cwd, env };
|
|
231
|
+
const launch = await resolvePtyLaunch(sessionId, launchRequest);
|
|
232
|
+
await startPtySession(sessionId, launch, {
|
|
233
|
+
request: launchRequest,
|
|
234
|
+
launchClaude: Boolean(claude),
|
|
235
|
+
command,
|
|
236
|
+
});
|
|
237
|
+
})();
|
|
238
|
+
if (!pending) {
|
|
239
|
+
pendingCreations.set(sessionId, creation);
|
|
240
|
+
void creation.catch(() => undefined).finally(() => pendingCreations.delete(sessionId));
|
|
241
|
+
}
|
|
194
242
|
try {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
});
|
|
243
|
+
await creation;
|
|
244
|
+
if (pending) {
|
|
245
|
+
fastify.log.info({ sessionId }, 'Reusing PTY session created by a concurrent request');
|
|
246
|
+
return reply.status(200).send({ sessionId, reused: true });
|
|
247
|
+
}
|
|
201
248
|
return reply.status(201).send({ sessionId, reused: false });
|
|
202
249
|
}
|
|
203
250
|
catch (err) {
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@minimalcorp/tsunagi",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@minimalcorp/tsunagi",
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.2",
|
|
10
10
|
"license": "SEE LICENSE IN LICENSE",
|
|
11
11
|
"os": [
|
|
12
12
|
"darwin",
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|