hanoman 0.1.38 → 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 +155 -0
- 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-BGdNNyED.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
|
|
|
@@ -30618,6 +30684,93 @@ async function terminal_default(app2, opts) {
|
|
|
30618
30684
|
});
|
|
30619
30685
|
}
|
|
30620
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
|
+
|
|
30621
30774
|
// src/routes/vps.ts
|
|
30622
30775
|
init_db();
|
|
30623
30776
|
init_src();
|
|
@@ -44586,6 +44739,7 @@ function capabilityForRoute(method, path2) {
|
|
|
44586
44739
|
if (top === "vps") return rw("vps");
|
|
44587
44740
|
if (top === "prds") return rw("docs");
|
|
44588
44741
|
if (top === "terminal") {
|
|
44742
|
+
if (seg[1] === "workspace") return "COOKIE_ONLY";
|
|
44589
44743
|
if (seg[seg.length - 1] === "ws") return "sessions:write";
|
|
44590
44744
|
return rw("sessions");
|
|
44591
44745
|
}
|
|
@@ -44852,6 +45006,7 @@ function buildApp({ requireAuth = true, agentDocFile, env = process.env } = {})
|
|
|
44852
45006
|
await api.register(fs_default);
|
|
44853
45007
|
const wsOptions = { allowedOrigins: wsControlOrigins(env) };
|
|
44854
45008
|
await api.register(terminal_default, wsOptions);
|
|
45009
|
+
await api.register(terminalWorkspaceRoutes);
|
|
44855
45010
|
await api.register(vps_default);
|
|
44856
45011
|
await api.register(limits);
|
|
44857
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[]
|