hanoman 0.1.37 → 0.1.39
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/build-info.json +3 -3
- package/dist/cli.js +66 -0
- package/dist/server.js +157 -1
- package/package.json +1 -1
- package/prisma/migrations/20260815000000_terminal_workspace_user/migration.sql +4 -0
- package/prisma/schema.prisma +5 -0
- package/web/assets/{index-C7l4ixak.js → index-BzBNb9qp.js} +1547 -1547
- package/web/index.html +1 -1
package/dist/build-info.json
CHANGED
package/dist/cli.js
CHANGED
|
@@ -5650,6 +5650,7 @@ var init_api = __esm({
|
|
|
5650
5650
|
// SPEC-233 · drop commit isolasi
|
|
5651
5651
|
fsBrowse: (path) => `${API}/fs/browse${path ? `?path=${encodeURIComponent(path)}` : ""}`,
|
|
5652
5652
|
terminalSessions: `${API}/terminal/sessions`,
|
|
5653
|
+
terminalWorkspace: `${API}/terminal/workspace`,
|
|
5653
5654
|
// SPEC-742 · ADR-0116 · pembersihan worktree yang masih tertunda sesudah sesi ditutup. Di bawah
|
|
5654
5655
|
// prefix /terminal supaya ikut capability `sessions` yang sudah ada (cermin sessionHistory).
|
|
5655
5656
|
terminalCleanups: `${API}/terminal/cleanups`,
|
|
@@ -7121,6 +7122,70 @@ var init_changelog = __esm({
|
|
|
7121
7122
|
}
|
|
7122
7123
|
});
|
|
7123
7124
|
|
|
7125
|
+
// ../shared/src/terminal-workspace.ts
|
|
7126
|
+
var zSessionId, zTerminalGroup, zTerminalWorkspaceV1, zTerminalWorkspaceWrite, zTerminalWorkspaceSnapshot;
|
|
7127
|
+
var init_terminal_workspace = __esm({
|
|
7128
|
+
"../shared/src/terminal-workspace.ts"() {
|
|
7129
|
+
"use strict";
|
|
7130
|
+
init_zod();
|
|
7131
|
+
zSessionId = external_exports.string().trim().min(1).max(256);
|
|
7132
|
+
zTerminalGroup = external_exports.object({
|
|
7133
|
+
id: external_exports.string().trim().min(1).max(128),
|
|
7134
|
+
name: external_exports.string().trim().min(1).max(80),
|
|
7135
|
+
layout: external_exports.object({
|
|
7136
|
+
rows: external_exports.number().int().min(1).max(12),
|
|
7137
|
+
cols: external_exports.number().int().min(1).max(12),
|
|
7138
|
+
cells: external_exports.array(zSessionId.nullable()).max(144)
|
|
7139
|
+
}).strict().superRefine((layout, ctx) => {
|
|
7140
|
+
if (layout.cells.length !== layout.rows * layout.cols) {
|
|
7141
|
+
ctx.addIssue({
|
|
7142
|
+
code: external_exports.ZodIssueCode.custom,
|
|
7143
|
+
path: ["cells"],
|
|
7144
|
+
message: "cells must match rows \xD7 cols"
|
|
7145
|
+
});
|
|
7146
|
+
}
|
|
7147
|
+
})
|
|
7148
|
+
}).strict();
|
|
7149
|
+
zTerminalWorkspaceV1 = external_exports.object({
|
|
7150
|
+
version: external_exports.literal(1),
|
|
7151
|
+
groups: external_exports.array(zTerminalGroup).min(1).max(24)
|
|
7152
|
+
}).strict().superRefine((workspace, ctx) => {
|
|
7153
|
+
const groupIds = /* @__PURE__ */ new Set();
|
|
7154
|
+
const sessionIds = /* @__PURE__ */ new Set();
|
|
7155
|
+
workspace.groups.forEach((group, groupIndex) => {
|
|
7156
|
+
if (groupIds.has(group.id)) {
|
|
7157
|
+
ctx.addIssue({
|
|
7158
|
+
code: external_exports.ZodIssueCode.custom,
|
|
7159
|
+
path: ["groups", groupIndex, "id"],
|
|
7160
|
+
message: "group id must be unique"
|
|
7161
|
+
});
|
|
7162
|
+
}
|
|
7163
|
+
groupIds.add(group.id);
|
|
7164
|
+
group.layout.cells.forEach((sessionId, cellIndex) => {
|
|
7165
|
+
if (sessionId === null) return;
|
|
7166
|
+
if (sessionIds.has(sessionId)) {
|
|
7167
|
+
ctx.addIssue({
|
|
7168
|
+
code: external_exports.ZodIssueCode.custom,
|
|
7169
|
+
path: ["groups", groupIndex, "layout", "cells", cellIndex],
|
|
7170
|
+
message: "sessionId must be unique across the workspace"
|
|
7171
|
+
});
|
|
7172
|
+
}
|
|
7173
|
+
sessionIds.add(sessionId);
|
|
7174
|
+
});
|
|
7175
|
+
});
|
|
7176
|
+
});
|
|
7177
|
+
zTerminalWorkspaceWrite = external_exports.object({
|
|
7178
|
+
baseRevision: external_exports.number().int().min(0),
|
|
7179
|
+
workspace: zTerminalWorkspaceV1
|
|
7180
|
+
}).strict();
|
|
7181
|
+
zTerminalWorkspaceSnapshot = external_exports.object({
|
|
7182
|
+
workspace: zTerminalWorkspaceV1.nullable(),
|
|
7183
|
+
revision: external_exports.number().int().min(0),
|
|
7184
|
+
updatedAt: external_exports.string().datetime().nullable()
|
|
7185
|
+
}).strict();
|
|
7186
|
+
}
|
|
7187
|
+
});
|
|
7188
|
+
|
|
7124
7189
|
// ../shared/src/index.ts
|
|
7125
7190
|
var init_src = __esm({
|
|
7126
7191
|
"../shared/src/index.ts"() {
|
|
@@ -7152,6 +7217,7 @@ var init_src = __esm({
|
|
|
7152
7217
|
init_auto_merge();
|
|
7153
7218
|
init_changelog();
|
|
7154
7219
|
init_cron_expr();
|
|
7220
|
+
init_terminal_workspace();
|
|
7155
7221
|
}
|
|
7156
7222
|
});
|
|
7157
7223
|
|
package/dist/server.js
CHANGED
|
@@ -6092,6 +6092,7 @@ var init_api = __esm({
|
|
|
6092
6092
|
// SPEC-233 · drop commit isolasi
|
|
6093
6093
|
fsBrowse: (path2) => `${API}/fs/browse${path2 ? `?path=${encodeURIComponent(path2)}` : ""}`,
|
|
6094
6094
|
terminalSessions: `${API}/terminal/sessions`,
|
|
6095
|
+
terminalWorkspace: `${API}/terminal/workspace`,
|
|
6095
6096
|
// SPEC-742 · ADR-0116 · pembersihan worktree yang masih tertunda sesudah sesi ditutup. Di bawah
|
|
6096
6097
|
// prefix /terminal supaya ikut capability `sessions` yang sudah ada (cermin sessionHistory).
|
|
6097
6098
|
terminalCleanups: `${API}/terminal/cleanups`,
|
|
@@ -7701,6 +7702,70 @@ var init_changelog = __esm({
|
|
|
7701
7702
|
}
|
|
7702
7703
|
});
|
|
7703
7704
|
|
|
7705
|
+
// ../shared/src/terminal-workspace.ts
|
|
7706
|
+
var zSessionId, zTerminalGroup, zTerminalWorkspaceV1, zTerminalWorkspaceWrite, zTerminalWorkspaceSnapshot;
|
|
7707
|
+
var init_terminal_workspace = __esm({
|
|
7708
|
+
"../shared/src/terminal-workspace.ts"() {
|
|
7709
|
+
"use strict";
|
|
7710
|
+
init_zod();
|
|
7711
|
+
zSessionId = external_exports.string().trim().min(1).max(256);
|
|
7712
|
+
zTerminalGroup = external_exports.object({
|
|
7713
|
+
id: external_exports.string().trim().min(1).max(128),
|
|
7714
|
+
name: external_exports.string().trim().min(1).max(80),
|
|
7715
|
+
layout: external_exports.object({
|
|
7716
|
+
rows: external_exports.number().int().min(1).max(12),
|
|
7717
|
+
cols: external_exports.number().int().min(1).max(12),
|
|
7718
|
+
cells: external_exports.array(zSessionId.nullable()).max(144)
|
|
7719
|
+
}).strict().superRefine((layout, ctx) => {
|
|
7720
|
+
if (layout.cells.length !== layout.rows * layout.cols) {
|
|
7721
|
+
ctx.addIssue({
|
|
7722
|
+
code: external_exports.ZodIssueCode.custom,
|
|
7723
|
+
path: ["cells"],
|
|
7724
|
+
message: "cells must match rows \xD7 cols"
|
|
7725
|
+
});
|
|
7726
|
+
}
|
|
7727
|
+
})
|
|
7728
|
+
}).strict();
|
|
7729
|
+
zTerminalWorkspaceV1 = external_exports.object({
|
|
7730
|
+
version: external_exports.literal(1),
|
|
7731
|
+
groups: external_exports.array(zTerminalGroup).min(1).max(24)
|
|
7732
|
+
}).strict().superRefine((workspace, ctx) => {
|
|
7733
|
+
const groupIds = /* @__PURE__ */ new Set();
|
|
7734
|
+
const sessionIds = /* @__PURE__ */ new Set();
|
|
7735
|
+
workspace.groups.forEach((group2, groupIndex) => {
|
|
7736
|
+
if (groupIds.has(group2.id)) {
|
|
7737
|
+
ctx.addIssue({
|
|
7738
|
+
code: external_exports.ZodIssueCode.custom,
|
|
7739
|
+
path: ["groups", groupIndex, "id"],
|
|
7740
|
+
message: "group id must be unique"
|
|
7741
|
+
});
|
|
7742
|
+
}
|
|
7743
|
+
groupIds.add(group2.id);
|
|
7744
|
+
group2.layout.cells.forEach((sessionId2, cellIndex) => {
|
|
7745
|
+
if (sessionId2 === null) return;
|
|
7746
|
+
if (sessionIds.has(sessionId2)) {
|
|
7747
|
+
ctx.addIssue({
|
|
7748
|
+
code: external_exports.ZodIssueCode.custom,
|
|
7749
|
+
path: ["groups", groupIndex, "layout", "cells", cellIndex],
|
|
7750
|
+
message: "sessionId must be unique across the workspace"
|
|
7751
|
+
});
|
|
7752
|
+
}
|
|
7753
|
+
sessionIds.add(sessionId2);
|
|
7754
|
+
});
|
|
7755
|
+
});
|
|
7756
|
+
});
|
|
7757
|
+
zTerminalWorkspaceWrite = external_exports.object({
|
|
7758
|
+
baseRevision: external_exports.number().int().min(0),
|
|
7759
|
+
workspace: zTerminalWorkspaceV1
|
|
7760
|
+
}).strict();
|
|
7761
|
+
zTerminalWorkspaceSnapshot = external_exports.object({
|
|
7762
|
+
workspace: zTerminalWorkspaceV1.nullable(),
|
|
7763
|
+
revision: external_exports.number().int().min(0),
|
|
7764
|
+
updatedAt: external_exports.string().datetime().nullable()
|
|
7765
|
+
}).strict();
|
|
7766
|
+
}
|
|
7767
|
+
});
|
|
7768
|
+
|
|
7704
7769
|
// ../shared/src/index.ts
|
|
7705
7770
|
var init_src = __esm({
|
|
7706
7771
|
"../shared/src/index.ts"() {
|
|
@@ -7732,6 +7797,7 @@ var init_src = __esm({
|
|
|
7732
7797
|
init_auto_merge();
|
|
7733
7798
|
init_changelog();
|
|
7734
7799
|
init_cron_expr();
|
|
7800
|
+
init_terminal_workspace();
|
|
7735
7801
|
}
|
|
7736
7802
|
});
|
|
7737
7803
|
|
|
@@ -30237,6 +30303,7 @@ async function recordSessionResult(input) {
|
|
|
30237
30303
|
// src/routes/terminal.ts
|
|
30238
30304
|
init_stage_machine();
|
|
30239
30305
|
init_pty();
|
|
30306
|
+
var TERMINAL_WS_MESSAGES_PER_MINUTE = 6e3;
|
|
30240
30307
|
async function advanceStage(specId, repoDir, sessionId2, flow, worktree) {
|
|
30241
30308
|
const next = stageForRun(readPhases(phaseFilePath(repoDir, sessionId2), flow), worktree, specId);
|
|
30242
30309
|
if (!next) return;
|
|
@@ -30580,7 +30647,7 @@ async function terminal_default(app2, opts) {
|
|
|
30580
30647
|
socket2.close(1008, "connection limit");
|
|
30581
30648
|
return;
|
|
30582
30649
|
}
|
|
30583
|
-
const guard = new WsMessageGuard();
|
|
30650
|
+
const guard = new WsMessageGuard({ perWindow: TERMINAL_WS_MESSAGES_PER_MINUTE });
|
|
30584
30651
|
const client = { send: (m) => socket2.send(m), close: () => socket2.close() };
|
|
30585
30652
|
attach(id, client);
|
|
30586
30653
|
socket2.on("message", async (raw2) => {
|
|
@@ -30617,6 +30684,93 @@ async function terminal_default(app2, opts) {
|
|
|
30617
30684
|
});
|
|
30618
30685
|
}
|
|
30619
30686
|
|
|
30687
|
+
// src/routes/terminal-workspace.ts
|
|
30688
|
+
init_src();
|
|
30689
|
+
|
|
30690
|
+
// src/services/terminal-workspace.ts
|
|
30691
|
+
init_src();
|
|
30692
|
+
init_db();
|
|
30693
|
+
import "@prisma/client";
|
|
30694
|
+
var InvalidStoredTerminalWorkspaceError = class extends Error {
|
|
30695
|
+
constructor() {
|
|
30696
|
+
super("stored terminal workspace is invalid");
|
|
30697
|
+
}
|
|
30698
|
+
};
|
|
30699
|
+
async function readTerminalWorkspace(userId) {
|
|
30700
|
+
const user = await prisma.user.findUniqueOrThrow({
|
|
30701
|
+
where: { id: userId },
|
|
30702
|
+
select: {
|
|
30703
|
+
terminalWorkspace: true,
|
|
30704
|
+
terminalWorkspaceRevision: true,
|
|
30705
|
+
terminalWorkspaceUpdatedAt: true
|
|
30706
|
+
}
|
|
30707
|
+
});
|
|
30708
|
+
if (user.terminalWorkspace === null) {
|
|
30709
|
+
return {
|
|
30710
|
+
workspace: null,
|
|
30711
|
+
revision: user.terminalWorkspaceRevision,
|
|
30712
|
+
updatedAt: user.terminalWorkspaceUpdatedAt?.toISOString() ?? null
|
|
30713
|
+
};
|
|
30714
|
+
}
|
|
30715
|
+
const parsed = zTerminalWorkspaceV1.safeParse(user.terminalWorkspace);
|
|
30716
|
+
if (!parsed.success) throw new InvalidStoredTerminalWorkspaceError();
|
|
30717
|
+
return {
|
|
30718
|
+
workspace: parsed.data,
|
|
30719
|
+
revision: user.terminalWorkspaceRevision,
|
|
30720
|
+
updatedAt: user.terminalWorkspaceUpdatedAt?.toISOString() ?? null
|
|
30721
|
+
};
|
|
30722
|
+
}
|
|
30723
|
+
async function writeTerminalWorkspace(userId, input) {
|
|
30724
|
+
await readTerminalWorkspace(userId);
|
|
30725
|
+
const updatedAt = /* @__PURE__ */ new Date();
|
|
30726
|
+
const changed = await prisma.user.updateMany({
|
|
30727
|
+
where: { id: userId, terminalWorkspaceRevision: input.baseRevision },
|
|
30728
|
+
data: {
|
|
30729
|
+
terminalWorkspace: input.workspace,
|
|
30730
|
+
terminalWorkspaceRevision: { increment: 1 },
|
|
30731
|
+
terminalWorkspaceUpdatedAt: updatedAt
|
|
30732
|
+
}
|
|
30733
|
+
});
|
|
30734
|
+
if (changed.count === 1) {
|
|
30735
|
+
return {
|
|
30736
|
+
ok: true,
|
|
30737
|
+
current: {
|
|
30738
|
+
workspace: input.workspace,
|
|
30739
|
+
revision: input.baseRevision + 1,
|
|
30740
|
+
updatedAt: updatedAt.toISOString()
|
|
30741
|
+
}
|
|
30742
|
+
};
|
|
30743
|
+
}
|
|
30744
|
+
return { ok: false, current: await readTerminalWorkspace(userId) };
|
|
30745
|
+
}
|
|
30746
|
+
|
|
30747
|
+
// src/routes/terminal-workspace.ts
|
|
30748
|
+
var storedInvalid = (reply) => reply.code(422).send({ error: "stored terminal workspace is invalid" });
|
|
30749
|
+
async function terminalWorkspaceRoutes(app2) {
|
|
30750
|
+
app2.get("/terminal/workspace", async (request, reply) => {
|
|
30751
|
+
try {
|
|
30752
|
+
return await readTerminalWorkspace(request.user.id);
|
|
30753
|
+
} catch (error) {
|
|
30754
|
+
if (error instanceof InvalidStoredTerminalWorkspaceError) return storedInvalid(reply);
|
|
30755
|
+
throw error;
|
|
30756
|
+
}
|
|
30757
|
+
});
|
|
30758
|
+
app2.put("/terminal/workspace", async (request, reply) => {
|
|
30759
|
+
const parsed = zTerminalWorkspaceWrite.safeParse(request.body);
|
|
30760
|
+
if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
|
|
30761
|
+
try {
|
|
30762
|
+
const result = await writeTerminalWorkspace(request.user.id, parsed.data);
|
|
30763
|
+
if (!result.ok) {
|
|
30764
|
+
return reply.code(409).send({ code: "revision-conflict", current: result.current });
|
|
30765
|
+
}
|
|
30766
|
+
return result.current;
|
|
30767
|
+
} catch (error) {
|
|
30768
|
+
if (error instanceof InvalidStoredTerminalWorkspaceError) return storedInvalid(reply);
|
|
30769
|
+
throw error;
|
|
30770
|
+
}
|
|
30771
|
+
});
|
|
30772
|
+
}
|
|
30773
|
+
|
|
30620
30774
|
// src/routes/vps.ts
|
|
30621
30775
|
init_db();
|
|
30622
30776
|
init_src();
|
|
@@ -44585,6 +44739,7 @@ function capabilityForRoute(method, path2) {
|
|
|
44585
44739
|
if (top === "vps") return rw("vps");
|
|
44586
44740
|
if (top === "prds") return rw("docs");
|
|
44587
44741
|
if (top === "terminal") {
|
|
44742
|
+
if (seg[1] === "workspace") return "COOKIE_ONLY";
|
|
44588
44743
|
if (seg[seg.length - 1] === "ws") return "sessions:write";
|
|
44589
44744
|
return rw("sessions");
|
|
44590
44745
|
}
|
|
@@ -44851,6 +45006,7 @@ function buildApp({ requireAuth = true, agentDocFile, env = process.env } = {})
|
|
|
44851
45006
|
await api.register(fs_default);
|
|
44852
45007
|
const wsOptions = { allowedOrigins: wsControlOrigins(env) };
|
|
44853
45008
|
await api.register(terminal_default, wsOptions);
|
|
45009
|
+
await api.register(terminalWorkspaceRoutes);
|
|
44854
45010
|
await api.register(vps_default);
|
|
44855
45011
|
await api.register(limits);
|
|
44856
45012
|
await api.register(update);
|
package/package.json
CHANGED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
-- SPEC-786 · workspace Terminal kanonik per akun admin, LOCAL-only.
|
|
2
|
+
ALTER TABLE "User" ADD COLUMN "terminalWorkspace" JSONB;
|
|
3
|
+
ALTER TABLE "User" ADD COLUMN "terminalWorkspaceRevision" INTEGER NOT NULL DEFAULT 0;
|
|
4
|
+
ALTER TABLE "User" ADD COLUMN "terminalWorkspaceUpdatedAt" DATETIME;
|
package/prisma/schema.prisma
CHANGED
|
@@ -207,6 +207,11 @@ model User {
|
|
|
207
207
|
// cookie yang sudah terbit tetap hidup sampai 7 hari (pencabutan yang tak mencabut).
|
|
208
208
|
disabled Boolean @default(false)
|
|
209
209
|
createdAt DateTime @default(now())
|
|
210
|
+
// SPEC-786 · LOCAL-only. Mapping Terminal is state operator per akun pada instance ini;
|
|
211
|
+
// fullscreen, focus, dan pilihan grup aktif tetap di browser dan tidak masuk JSON ini.
|
|
212
|
+
terminalWorkspace Json?
|
|
213
|
+
terminalWorkspaceRevision Int @default(0)
|
|
214
|
+
terminalWorkspaceUpdatedAt DateTime?
|
|
210
215
|
sessions Session[]
|
|
211
216
|
deviceTokens DeviceToken[]
|
|
212
217
|
projectAccess ClientProjectAccess[]
|