@tea-agent/loop-agent 0.43.0-next.4 → 0.43.0-next.5
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/CHANGELOG.md +80 -9
- package/dist/application/task-lifecycle/advance.js +17 -4
- package/dist/application/task-lifecycle/observe.js +43 -29
- package/dist/application/task-lifecycle/plan-transitions.js +5 -4
- package/dist/build-stamp.json +3 -3
- package/dist/executors/dag-pi-executor.js +244 -33
- package/dist/executors/pi-executor.js +2 -0
- package/dist/executors/pi-sdk-executor.js +75 -39
- package/dist/task/source-prepare/source-fidelity-pi.js +2 -2
- package/dist/task/source-references.js +48 -23
- package/dist/worker/console/chat/pi-runtime.js +113 -95
- package/dist/worker/console/chat/routes.js +164 -54
- package/dist/worker/console/chat/scm-routes.js +193 -0
- package/dist/worker/console/chat/scm-service.js +253 -0
- package/dist/worker/console/chat/scm-tools.js +111 -0
- package/dist/worker/console/chat/session-catalog.js +1 -0
- package/dist/worker/console/chat/session-mode-view.js +48 -0
- package/dist/worker/console/chat/session-mode.js +218 -0
- package/dist/worker/console/chat/session-store.js +22 -6
- package/dist/worker/console/chat/shortcuts.js +6 -0
- package/dist/worker/console/chat/tools.js +1 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-CCj36aaC.js → abnfDiagram-N423BO3Z-BuyrlONK.js} +1 -1
- package/dist/worker/console/static/assets/{arc-Cl3MIVFh.js → arc-BvCl_wpi.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C_n4kMJS.js → architectureDiagram-T3A2C74G-BkBpBaRq.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DlWYJ7cK.js → blockDiagram-VBNYF7ZC-yve4Mcb_.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Ud5pS7vS.js → c4Diagram-5PPSVZJV-9yLGboBx.js} +1 -1
- package/dist/worker/console/static/assets/channel-BRahIhjw.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CQPwOv_j.js → chunk-2GRJ4B5K-BKP69b8r.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CMzjxrWI.js → chunk-2Q5K7J3B-NWFRnXju.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-pcnXnD6t.js → chunk-5RXB4S5H-BiweqLEc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CEB3AvU2.js → chunk-5VM5RSS4-CP9GHmRY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DSKnbXbp.js → chunk-6Q2QTUOP-BiGDkBtj.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CS6-PjCZ.js → chunk-GF5L2VYU-DkJ4ZjQS.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-f6q8Tnzz.js → chunk-JWPE2WC7-B5jjn7zq.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DkB0QX3q.js → chunk-KBJHAD2P-C6KoK9O0.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BdB4jAv6.js → chunk-RYQCIY6F-BvNIfyn-.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BVm1bnX6.js → chunk-XXDRQBXY-BZ33iX4W.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-GOC80mhq.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-GOC80mhq.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-FV_P-IEp.js → cose-bilkent-JH36ORCC-CP8eIuZd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BM7RtkA2.js → cynefin-VYW2F7L2-DZECjpOe.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-C1BZwTG5.js → cynefinDiagram-MW4NZA55-CvVJoFqf.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C6McNKmY.js → dagre-VZM6K2ZE-D_JTqdAn.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-C0aWRUaE.js → diagram-7IWD3JNH-Db7AQ0z0.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Bfk_SKE8.js → diagram-B4RE2ZJO-AZPVo5CZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-prx1T4qf.js → diagram-LBJQPF4R-BgXqI3E6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DBvty0eU.js → diagram-Q27KOJAE-BYjFn3Ba.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CF0em8e9.js → diagram-UB23O5K3-B4rJgGGO.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CF90b4Ws.js → ebnfDiagram-BXEA7PRR-C6XWZhpw.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-hfunxzof.js → erDiagram-JOGREHBK-1YdQRIQW.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-D-QSy4Nl.js → flowDiagram-UKHOOZJN-l3a0AwN0.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DaBGNcvT.js → ganttDiagram-PKOTCBZU-CQL96F2Z.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BChcubGL.js → gitGraphDiagram-DS77QQ5N-BxCvafRb.js} +1 -1
- package/dist/worker/console/static/assets/index-4wCGsXuU.js +465 -0
- package/dist/worker/console/static/assets/index-CiF9WBoR.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BkC67fIa.js → infoDiagram-6WML65LV-BUipIeDO.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-ibhLczCD.js → ishikawaDiagram-WSZJBQD7-CRfomVMX.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DCRMitay.js → journeyDiagram-NVQOT4AX-_UFvyviX.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DJ_hKaYI.js → kanban-definition-27J2QSJJ-CKIRoqKv.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CIlLsYsA.js → linear-CjttJDCM.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-C-5IQMP_.js → mermaid.core-DPvHX155.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-TCzV5TQT.js → mindmap-definition-FAOFIHXS-Qmj-qwBe.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-bfhd2BQH.js → pegDiagram-VL7TDLO6-Auw6DRgq.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BxFRAm5N.js → pieDiagram-7S7Q4E2Y-CqMrfD4c.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C-m12lxv.js → quadrantDiagram-CIZ2JOQS-BAddeggH.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CwmLiGgx.js → railroadDiagram-AXF67PYL-DG1JuOu2.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-oc0Q1jiD.js → requirementDiagram-LRYGKXZP-DLYGMKlC.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-9Ght6Yur.js → sankeyDiagram-W5VNT64P-g68SbLM9.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-ndm3lyz9.js → sequenceDiagram-SI44F4Z6-CmszLKv8.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-7sPlKVcw.js → sizeCapture-X5ZJPWSS-D2tEKUCj.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Izl21-oS.js → stateDiagram-OKZ733FA-Syc5E_Jf.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CwWwuPI-.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CtO3D65o.js → swimlanes-SLNWSIFB-Ibn0IRCa.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-A7fqemr6.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cyg5cY7z.js → timeline-definition-Z64GVDOM-2ZW9W3iS.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CwtE_TFK.js → vennDiagram-T6HMQDX7-v50HsKUh.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-Dtt9v8OO.js → wardleyDiagram-T6FBY63Y-DqCXEoDH.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-B2Y3ryb6.js → xychartDiagram-ELKLHX3M-vIP8Vo9d.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +20 -4
- package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -7
- package/dist/worker/console/workspace-context.js +6 -0
- package/dist/workflows/dag/frontend-capacity.js +9 -0
- package/dist/workflows/dag/frontend-design-policy.js +2 -1
- package/dist/workflows/dag/frontend-shape-facts.js +12 -2
- package/package.json +1 -1
- package/skills/frontend-plan/SKILL.md +7 -0
- package/dist/worker/console/static/assets/channel-CEMRpNm-.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C9lhaZkR.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C9lhaZkR.js +0 -1
- package/dist/worker/console/static/assets/index-CJkCY1XR.js +0 -464
- package/dist/worker/console/static/assets/index-DUBCbiqv.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CmElLFpP.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DKRbGG8V.js +0 -8
|
@@ -16,6 +16,7 @@ import { randomUUID } from "node:crypto";
|
|
|
16
16
|
import { access } from "node:fs/promises";
|
|
17
17
|
import { parseAssistantContent } from "./assistant-content.js";
|
|
18
18
|
import { OPERATOR_CHAT_ALLOWED_TOOLS, OPERATOR_CHAT_DENIED_TOOLS, OPERATOR_CHAT_DENIED_OPERATOR_ACTIONS, } from "./tools.js";
|
|
19
|
+
import { InvalidChatSessionModeError, SESSION_MODE_ACTION_DENIED_CODE, SESSION_MODE_ACTION_DENIED_MESSAGE, SESSION_MODE_IMMUTABLE_CODE, UnknownChatSessionModeError, isWorkflowSessionMode, parseCreateSessionMode, readStoredSessionMode, shouldAcceptOperatorSnapshot, } from "./session-mode.js";
|
|
19
20
|
import { buildModelCallableToolSchemas } from "./tool-adapter.js";
|
|
20
21
|
import { cleanChatTitle, acceptedOperationIdFromResult, isBlankChatSession } from "./session-store.js";
|
|
21
22
|
import { hashChatTurnPayload, projectChatToolEventPayload, projectCompactSnapshot, } from "./chat-event-store.js";
|
|
@@ -46,11 +47,77 @@ import { isTerminalOperationState } from "../../../infrastructure/console/operat
|
|
|
46
47
|
import { SddDataAlignmentError, } from "./sdd-data-alignment.js";
|
|
47
48
|
import { TerminalSessionRegistry } from "./terminal-sessions.js";
|
|
48
49
|
import { handleTerminalRequest } from "./terminal-routes.js";
|
|
50
|
+
import { handleScmRequest } from "./scm-routes.js";
|
|
49
51
|
function projectBrowserChatSession(record) {
|
|
50
52
|
const { telemetrySessionId, ...projected } = record;
|
|
51
53
|
void telemetrySessionId;
|
|
52
54
|
return projected;
|
|
53
55
|
}
|
|
56
|
+
function reopenInitFromRecord(record) {
|
|
57
|
+
return {
|
|
58
|
+
sessionId: record.sessionId,
|
|
59
|
+
sessionFile: record.sessionFile,
|
|
60
|
+
...(record.modelProvider && record.modelId
|
|
61
|
+
? { model: { provider: record.modelProvider, modelId: record.modelId } }
|
|
62
|
+
: {}),
|
|
63
|
+
sessionMode: record.sessionMode,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async function readChatSessionMode(deps, sessionId) {
|
|
67
|
+
let record;
|
|
68
|
+
try {
|
|
69
|
+
record = await deps.store.get(sessionId);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (error instanceof UnknownChatSessionModeError) {
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
status: 409,
|
|
76
|
+
code: error.code,
|
|
77
|
+
message: error.message,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
if (!record) {
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
status: 404,
|
|
86
|
+
code: "NOT_FOUND",
|
|
87
|
+
message: `chat session not found: ${sessionId}`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// Normalize: the store contract always fills sessionMode, but handler-level
|
|
91
|
+
// fakes / partial records may omit it — treat missing as workflow (the
|
|
92
|
+
// stock-record read rule), while a present-but-unknown value still fails
|
|
93
|
+
// closed in the store layer.
|
|
94
|
+
return {
|
|
95
|
+
ok: true,
|
|
96
|
+
record,
|
|
97
|
+
sessionMode: readStoredSessionMode(record.sessionMode),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async function requireWorkflowSession(res, deps, sessionId) {
|
|
101
|
+
const resolved = await readChatSessionMode(deps, sessionId);
|
|
102
|
+
if (!resolved.ok) {
|
|
103
|
+
sendJson(res, resolved.status, {
|
|
104
|
+
ok: false,
|
|
105
|
+
error: { code: resolved.code, message: resolved.message },
|
|
106
|
+
});
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
if (!isWorkflowSessionMode(resolved.sessionMode)) {
|
|
110
|
+
sendJson(res, 409, {
|
|
111
|
+
ok: false,
|
|
112
|
+
error: {
|
|
113
|
+
code: SESSION_MODE_ACTION_DENIED_CODE,
|
|
114
|
+
message: SESSION_MODE_ACTION_DENIED_MESSAGE,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
return resolved.record;
|
|
120
|
+
}
|
|
54
121
|
/**
|
|
55
122
|
* Reattach the durable event-ring order to session-store rows. The session
|
|
56
123
|
* store remains the content authority; the ring only supplies chronology.
|
|
@@ -567,6 +634,22 @@ workspaceId) {
|
|
|
567
634
|
});
|
|
568
635
|
return;
|
|
569
636
|
}
|
|
637
|
+
let sessionMode;
|
|
638
|
+
try {
|
|
639
|
+
sessionMode = parseCreateSessionMode(body.sessionMode);
|
|
640
|
+
}
|
|
641
|
+
catch (error) {
|
|
642
|
+
sendJson(res, 400, {
|
|
643
|
+
ok: false,
|
|
644
|
+
error: {
|
|
645
|
+
code: error instanceof InvalidChatSessionModeError
|
|
646
|
+
? error.code
|
|
647
|
+
: "INVALID_SESSION_MODE",
|
|
648
|
+
message: error instanceof Error ? error.message : String(error),
|
|
649
|
+
},
|
|
650
|
+
});
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
570
653
|
const provider = typeof body.modelProvider === "string" ? body.modelProvider : undefined;
|
|
571
654
|
const modelId = typeof body.modelId === "string" ? body.modelId : undefined;
|
|
572
655
|
const snapshotInput = body.snapshot;
|
|
@@ -588,6 +671,16 @@ workspaceId) {
|
|
|
588
671
|
let createdSessionId;
|
|
589
672
|
let persisted = false;
|
|
590
673
|
try {
|
|
674
|
+
if (snapshotInput !== undefined && !shouldAcceptOperatorSnapshot(sessionMode)) {
|
|
675
|
+
sendJson(res, 409, {
|
|
676
|
+
ok: false,
|
|
677
|
+
error: {
|
|
678
|
+
code: SESSION_MODE_ACTION_DENIED_CODE,
|
|
679
|
+
message: "Operator snapshots cannot be imported into Pi sessions",
|
|
680
|
+
},
|
|
681
|
+
});
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
591
684
|
const snapshot = snapshotInput === undefined ? undefined : projectCompactSnapshot(snapshotInput);
|
|
592
685
|
// A Console record promises that its Pi JSONL can be reopened after a
|
|
593
686
|
// Console restart. Do not make that promise until Pi has finished
|
|
@@ -595,6 +688,7 @@ workspaceId) {
|
|
|
595
688
|
// future JSONL path, leaving an unrecoverable orphan when init failed or
|
|
596
689
|
// the process restarted in that window.
|
|
597
690
|
const handle = await deps.runtime.createSession({
|
|
691
|
+
sessionMode,
|
|
598
692
|
...(provider && modelId ? { model: { provider, modelId } } : {}),
|
|
599
693
|
...(snapshot
|
|
600
694
|
? {
|
|
@@ -621,6 +715,7 @@ workspaceId) {
|
|
|
621
715
|
sessionFile: handle.sessionFile,
|
|
622
716
|
modelProvider: selection.model?.provider ?? handle.model?.provider,
|
|
623
717
|
modelId: selection.model?.modelId ?? handle.model?.modelId,
|
|
718
|
+
sessionMode,
|
|
624
719
|
// AC-R7-002: explicit parameter from the dispatcher — the runtime
|
|
625
720
|
// store.create wrapper is gone; body workspaceId (if any) only acts as
|
|
626
721
|
// a fallback for direct (non-dispatcher) handler tests.
|
|
@@ -691,6 +786,7 @@ export async function handleForkChatSession(req, res, deps, sourceSessionId) {
|
|
|
691
786
|
try {
|
|
692
787
|
const handle = await deps.runtime.forkSession({
|
|
693
788
|
sourceSessionFile: source.sessionFile ?? "",
|
|
789
|
+
sessionMode: source.sessionMode,
|
|
694
790
|
...(source.modelProvider && source.modelId
|
|
695
791
|
? { model: { provider: source.modelProvider, modelId: source.modelId } }
|
|
696
792
|
: {}),
|
|
@@ -757,10 +853,7 @@ export async function handleBranchChatSession(req, res, deps, sessionId) {
|
|
|
757
853
|
}
|
|
758
854
|
try {
|
|
759
855
|
if (!deps.runtime.hasSession(sessionId)) {
|
|
760
|
-
const reopened = await deps.runtime.reopenSession(
|
|
761
|
-
sessionId,
|
|
762
|
-
sessionFile: record.sessionFile,
|
|
763
|
-
});
|
|
856
|
+
const reopened = await deps.runtime.reopenSession(reopenInitFromRecord(record));
|
|
764
857
|
if (!reopened.ok) {
|
|
765
858
|
const mapped = await mapFailedReopen(deps, sessionId, reopened);
|
|
766
859
|
sendJson(res, mapped.status, {
|
|
@@ -1233,13 +1326,8 @@ export async function handleChatGoal(req, res, deps, sessionId) {
|
|
|
1233
1326
|
});
|
|
1234
1327
|
return;
|
|
1235
1328
|
}
|
|
1236
|
-
if (!(await deps
|
|
1237
|
-
sendJson(res, 404, {
|
|
1238
|
-
ok: false,
|
|
1239
|
-
error: { code: "NOT_FOUND", message: `chat session not found: ${sessionId}` },
|
|
1240
|
-
});
|
|
1329
|
+
if (!(await requireWorkflowSession(res, deps, sessionId)))
|
|
1241
1330
|
return;
|
|
1242
|
-
}
|
|
1243
1331
|
if (req.method === "GET") {
|
|
1244
1332
|
sendJson(res, 200, { ok: true, goal: registry.get(sessionId) });
|
|
1245
1333
|
return;
|
|
@@ -1453,6 +1541,7 @@ export async function listChatSessionSummaries(deps, ownerWorkspaceId) {
|
|
|
1453
1541
|
...(session.thinkingLevel !== undefined
|
|
1454
1542
|
? { thinkingLevel: session.thinkingLevel }
|
|
1455
1543
|
: {}),
|
|
1544
|
+
sessionMode: session.sessionMode,
|
|
1456
1545
|
activeTurnId: active?.turnId,
|
|
1457
1546
|
turnPhase: active?.abortRequestedAt
|
|
1458
1547
|
? "stopping"
|
|
@@ -1476,6 +1565,16 @@ export async function handlePatchChatSession(req, res, deps, sessionId) {
|
|
|
1476
1565
|
}
|
|
1477
1566
|
try {
|
|
1478
1567
|
const body = await readJsonBody(req);
|
|
1568
|
+
if ("sessionMode" in body) {
|
|
1569
|
+
sendJson(res, 409, {
|
|
1570
|
+
ok: false,
|
|
1571
|
+
error: {
|
|
1572
|
+
code: SESSION_MODE_IMMUTABLE_CODE,
|
|
1573
|
+
message: "sessionMode cannot be changed after create",
|
|
1574
|
+
},
|
|
1575
|
+
});
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1479
1578
|
let record;
|
|
1480
1579
|
if (typeof body.title === "string")
|
|
1481
1580
|
record = await deps.store.rename(sessionId, body.title);
|
|
@@ -1534,13 +1633,7 @@ export async function handleReopenChatSession(req, res, deps, sessionId) {
|
|
|
1534
1633
|
});
|
|
1535
1634
|
return;
|
|
1536
1635
|
}
|
|
1537
|
-
const result = await deps.runtime.reopenSession(
|
|
1538
|
-
sessionId,
|
|
1539
|
-
sessionFile: record.sessionFile,
|
|
1540
|
-
...(record.modelProvider && record.modelId
|
|
1541
|
-
? { model: { provider: record.modelProvider, modelId: record.modelId } }
|
|
1542
|
-
: {}),
|
|
1543
|
-
});
|
|
1636
|
+
const result = await deps.runtime.reopenSession(reopenInitFromRecord(record));
|
|
1544
1637
|
if (!result.ok) {
|
|
1545
1638
|
const mapped = await mapFailedReopen(deps, sessionId, result);
|
|
1546
1639
|
sendJson(res, mapped.status, {
|
|
@@ -1620,13 +1713,7 @@ export async function handleChatPrompt(req, res, deps, sessionId) {
|
|
|
1620
1713
|
});
|
|
1621
1714
|
return;
|
|
1622
1715
|
}
|
|
1623
|
-
const reopen = await deps.runtime.reopenSession(
|
|
1624
|
-
sessionId,
|
|
1625
|
-
sessionFile: record.sessionFile,
|
|
1626
|
-
...(record.modelProvider && record.modelId
|
|
1627
|
-
? { model: { provider: record.modelProvider, modelId: record.modelId } }
|
|
1628
|
-
: {}),
|
|
1629
|
-
});
|
|
1716
|
+
const reopen = await deps.runtime.reopenSession(reopenInitFromRecord(record));
|
|
1630
1717
|
if (!reopen.ok) {
|
|
1631
1718
|
const mapped = await mapFailedReopen(deps, sessionId, reopen);
|
|
1632
1719
|
sendJson(res, mapped.status, {
|
|
@@ -1890,13 +1977,7 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
1890
1977
|
});
|
|
1891
1978
|
return;
|
|
1892
1979
|
}
|
|
1893
|
-
const reopen = await deps.runtime.reopenSession(
|
|
1894
|
-
sessionId,
|
|
1895
|
-
sessionFile: record.sessionFile,
|
|
1896
|
-
...(record.modelProvider && record.modelId
|
|
1897
|
-
? { model: { provider: record.modelProvider, modelId: record.modelId } }
|
|
1898
|
-
: {}),
|
|
1899
|
-
});
|
|
1980
|
+
const reopen = await deps.runtime.reopenSession(reopenInitFromRecord(record));
|
|
1900
1981
|
if (!reopen.ok) {
|
|
1901
1982
|
const mapped = await mapFailedReopen(deps, sessionId, reopen);
|
|
1902
1983
|
sendJson(res, mapped.status, {
|
|
@@ -2174,6 +2255,10 @@ export async function driveGoalRound(deps, sessionId) {
|
|
|
2174
2255
|
registry.disarm(sessionId);
|
|
2175
2256
|
return "idle";
|
|
2176
2257
|
}
|
|
2258
|
+
if (!isWorkflowSessionMode(readStoredSessionMode(record.sessionMode))) {
|
|
2259
|
+
registry.disarm(sessionId);
|
|
2260
|
+
return "idle";
|
|
2261
|
+
}
|
|
2177
2262
|
const runtimeFacts = deps.runtime.getSessionBusyFacts(sessionId);
|
|
2178
2263
|
const operationStore = deps.runtime.actionContext?.operations;
|
|
2179
2264
|
const linkedOperationIds = linkedOperationIdsForGoal(deps.events.snapshot(sessionId));
|
|
@@ -2822,6 +2907,7 @@ export async function handleChatCommands(res, deps, sessionId) {
|
|
|
2822
2907
|
const warning = typeof listed.warning === "string" && listed.warning.trim()
|
|
2823
2908
|
? listed.warning.trim().slice(0, 160)
|
|
2824
2909
|
: undefined;
|
|
2910
|
+
const resolvedMode = await readChatSessionMode(deps, sessionId);
|
|
2825
2911
|
res.statusCode = 200;
|
|
2826
2912
|
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
2827
2913
|
res.end(JSON.stringify({
|
|
@@ -2829,18 +2915,25 @@ export async function handleChatCommands(res, deps, sessionId) {
|
|
|
2829
2915
|
commands,
|
|
2830
2916
|
capabilities: {
|
|
2831
2917
|
sddBingo: Boolean(await deps.sddDataAlignment?.isEligible()),
|
|
2918
|
+
...(resolvedMode.ok ? { sessionMode: resolvedMode.sessionMode } : {}),
|
|
2832
2919
|
},
|
|
2833
2920
|
...(warning ? { warning } : {}),
|
|
2834
2921
|
}));
|
|
2835
2922
|
}
|
|
2836
2923
|
catch (error) {
|
|
2837
2924
|
// Whole-list / readiness failures stay fail-soft with empty commands + warning.
|
|
2925
|
+
// Keep the two capability shapes symmetric: the fallback mode mirrors the
|
|
2926
|
+
// client-side prop fallback in useComposer.
|
|
2927
|
+
const fallbackMode = await readChatSessionMode(deps, sessionId).catch(() => undefined);
|
|
2838
2928
|
res.statusCode = 200;
|
|
2839
2929
|
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
2840
2930
|
res.end(JSON.stringify({
|
|
2841
2931
|
ok: true,
|
|
2842
2932
|
commands: [],
|
|
2843
|
-
capabilities: {
|
|
2933
|
+
capabilities: {
|
|
2934
|
+
sddBingo: false,
|
|
2935
|
+
...(fallbackMode?.ok ? { sessionMode: fallbackMode.sessionMode } : {}),
|
|
2936
|
+
},
|
|
2844
2937
|
warning: error instanceof Error ? error.message : "commands unavailable",
|
|
2845
2938
|
}));
|
|
2846
2939
|
}
|
|
@@ -3085,13 +3178,7 @@ export async function ensureActiveSession(deps, sessionId) {
|
|
|
3085
3178
|
message: `chat session not found: ${sessionId}`,
|
|
3086
3179
|
};
|
|
3087
3180
|
}
|
|
3088
|
-
const reopened = await deps.runtime.reopenSession(
|
|
3089
|
-
sessionId,
|
|
3090
|
-
sessionFile: record.sessionFile,
|
|
3091
|
-
...(record.modelProvider && record.modelId
|
|
3092
|
-
? { model: { provider: record.modelProvider, modelId: record.modelId } }
|
|
3093
|
-
: {}),
|
|
3094
|
-
});
|
|
3181
|
+
const reopened = await deps.runtime.reopenSession(reopenInitFromRecord(record));
|
|
3095
3182
|
if (!reopened.ok) {
|
|
3096
3183
|
return { ok: false, ...(await mapFailedReopen(deps, sessionId, reopened)) };
|
|
3097
3184
|
}
|
|
@@ -3420,7 +3507,7 @@ async function handleStartInterview(req, res, deps, sessionId) {
|
|
|
3420
3507
|
});
|
|
3421
3508
|
return;
|
|
3422
3509
|
}
|
|
3423
|
-
if (!(await
|
|
3510
|
+
if (!(await requireWorkflowSession(res, deps, sessionId)))
|
|
3424
3511
|
return;
|
|
3425
3512
|
try {
|
|
3426
3513
|
const body = await readJsonBody(req);
|
|
@@ -3462,7 +3549,7 @@ async function handleAnswerInterview(req, res, deps, sessionId, interviewSession
|
|
|
3462
3549
|
});
|
|
3463
3550
|
return;
|
|
3464
3551
|
}
|
|
3465
|
-
if (!(await
|
|
3552
|
+
if (!(await requireWorkflowSession(res, deps, sessionId)))
|
|
3466
3553
|
return;
|
|
3467
3554
|
try {
|
|
3468
3555
|
const body = await readJsonBody(req);
|
|
@@ -3498,7 +3585,7 @@ async function handleAnswerInterview(req, res, deps, sessionId, interviewSession
|
|
|
3498
3585
|
}
|
|
3499
3586
|
}
|
|
3500
3587
|
async function handleGetInterviewFact(res, deps, sessionId, interviewSessionId, fact) {
|
|
3501
|
-
if (!(await
|
|
3588
|
+
if (!(await requireWorkflowSession(res, deps, sessionId)))
|
|
3502
3589
|
return;
|
|
3503
3590
|
const state = await new ChatInterviewAdapter(deps.appData, deps.events).get(interviewSessionId);
|
|
3504
3591
|
if (!state || state.interview.operatorSessionId !== sessionId) {
|
|
@@ -3596,7 +3683,7 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
|
|
|
3596
3683
|
});
|
|
3597
3684
|
return;
|
|
3598
3685
|
}
|
|
3599
|
-
if (!(await
|
|
3686
|
+
if (!(await requireWorkflowSession(res, deps, sessionId)))
|
|
3600
3687
|
return;
|
|
3601
3688
|
const ctx = deps.runtime.actionContext;
|
|
3602
3689
|
if (!ctx) {
|
|
@@ -3778,7 +3865,7 @@ async function handleDagHumanGate(req, res, deps, sessionId, step) {
|
|
|
3778
3865
|
});
|
|
3779
3866
|
return;
|
|
3780
3867
|
}
|
|
3781
|
-
if (!(await
|
|
3868
|
+
if (!(await requireWorkflowSession(res, deps, sessionId)))
|
|
3782
3869
|
return;
|
|
3783
3870
|
const ctx = deps.runtime.actionContext;
|
|
3784
3871
|
if (!ctx) {
|
|
@@ -3887,7 +3974,7 @@ async function handleMutationHumanGate(req, res, deps, sessionId) {
|
|
|
3887
3974
|
});
|
|
3888
3975
|
return;
|
|
3889
3976
|
}
|
|
3890
|
-
if (!(await
|
|
3977
|
+
if (!(await requireWorkflowSession(res, deps, sessionId)))
|
|
3891
3978
|
return;
|
|
3892
3979
|
const ctx = deps.runtime.actionContext;
|
|
3893
3980
|
if (!ctx) {
|
|
@@ -3976,7 +4063,7 @@ async function handleConfirmTaskKind(req, res, deps, sessionId, interviewSession
|
|
|
3976
4063
|
});
|
|
3977
4064
|
return;
|
|
3978
4065
|
}
|
|
3979
|
-
if (!(await
|
|
4066
|
+
if (!(await requireWorkflowSession(res, deps, sessionId)))
|
|
3980
4067
|
return;
|
|
3981
4068
|
try {
|
|
3982
4069
|
const body = await readJsonBody(req);
|
|
@@ -4032,13 +4119,7 @@ export async function handleChatCompact(req, res, deps, sessionId) {
|
|
|
4032
4119
|
return;
|
|
4033
4120
|
}
|
|
4034
4121
|
if (!deps.runtime.hasSession(sessionId)) {
|
|
4035
|
-
const reopened = await deps.runtime.reopenSession(
|
|
4036
|
-
sessionId,
|
|
4037
|
-
sessionFile: record.sessionFile,
|
|
4038
|
-
...(record.modelProvider && record.modelId
|
|
4039
|
-
? { model: { provider: record.modelProvider, modelId: record.modelId } }
|
|
4040
|
-
: {}),
|
|
4041
|
-
});
|
|
4122
|
+
const reopened = await deps.runtime.reopenSession(reopenInitFromRecord(record));
|
|
4042
4123
|
if (!reopened.ok) {
|
|
4043
4124
|
const mapped = await mapFailedReopen(deps, sessionId, reopened);
|
|
4044
4125
|
sendJson(res, mapped.status, {
|
|
@@ -4062,7 +4143,9 @@ export async function handleChatCompact(req, res, deps, sessionId) {
|
|
|
4062
4143
|
const rawInstructions = typeof body.instructions === "string" ? body.instructions : undefined;
|
|
4063
4144
|
const currentTodos = projectCurrentTodos(deps.events.snapshot(sessionId));
|
|
4064
4145
|
const todoInstructions = buildTodoCompactionInstructions(rawInstructions, currentTodos);
|
|
4065
|
-
const customInstructions =
|
|
4146
|
+
const customInstructions = isWorkflowSessionMode(record.sessionMode)
|
|
4147
|
+
? buildGoalCompactionInstructions(todoInstructions, deps.goalRegistry?.get(sessionId) ?? null)
|
|
4148
|
+
: todoInstructions;
|
|
4066
4149
|
const turnId = deps.events.latestTurnId(sessionId) ?? `${sessionId}:compact`;
|
|
4067
4150
|
const compactionId = `${sessionId}:compaction:${randomUUID()}`;
|
|
4068
4151
|
deps.events.append(sessionId, turnId, {
|
|
@@ -4148,6 +4231,24 @@ export async function handleChatMessages(res, deps, sessionId, url) {
|
|
|
4148
4231
|
export async function handleChatRequest(req, res, deps, pathname, workspaceScope) {
|
|
4149
4232
|
if (!deps)
|
|
4150
4233
|
return false;
|
|
4234
|
+
try {
|
|
4235
|
+
return await dispatchChatRequest(req, res, deps, pathname, workspaceScope);
|
|
4236
|
+
}
|
|
4237
|
+
catch (error) {
|
|
4238
|
+
// Store-level fail-closed for corrupt sessionMode: bounded 409 instead of
|
|
4239
|
+
// a generic 500, for any handler that reads the record without its own
|
|
4240
|
+
// UnknownChatSessionModeError mapping (readChatSessionMode covers the rest).
|
|
4241
|
+
if (error instanceof UnknownChatSessionModeError) {
|
|
4242
|
+
sendJson(res, 409, {
|
|
4243
|
+
ok: false,
|
|
4244
|
+
error: { code: error.code, message: error.message },
|
|
4245
|
+
});
|
|
4246
|
+
return true;
|
|
4247
|
+
}
|
|
4248
|
+
throw error;
|
|
4249
|
+
}
|
|
4250
|
+
}
|
|
4251
|
+
async function dispatchChatRequest(req, res, deps, pathname, workspaceScope) {
|
|
4151
4252
|
// Terminal management API (model + Console UI shared PTY registry).
|
|
4152
4253
|
if (await handleTerminalRequest(req, res, {
|
|
4153
4254
|
terminals: deps.terminals ?? new TerminalSessionRegistry(),
|
|
@@ -4157,6 +4258,15 @@ export async function handleChatRequest(req, res, deps, pathname, workspaceScope
|
|
|
4157
4258
|
}, pathname)) {
|
|
4158
4259
|
return true;
|
|
4159
4260
|
}
|
|
4261
|
+
// Source control API (model + Console UI shared workspace ScmService).
|
|
4262
|
+
if (deps.scm) {
|
|
4263
|
+
if (await handleScmRequest(req, res, {
|
|
4264
|
+
scm: deps.scm,
|
|
4265
|
+
gateMutation: (r) => gateMutation(r, deps),
|
|
4266
|
+
}, pathname)) {
|
|
4267
|
+
return true;
|
|
4268
|
+
}
|
|
4269
|
+
}
|
|
4160
4270
|
if (!pathname.startsWith("/api/operator/v1/chat/") &&
|
|
4161
4271
|
!pathname.startsWith("/api/operator/v1/pi/"))
|
|
4162
4272
|
return false;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operator Chat — source control HTTP API (Console UI side).
|
|
3
|
+
*
|
|
4
|
+
* Endpoints (all under /api/operator/v1/chat):
|
|
5
|
+
* GET /scm/status branch + changes + counts
|
|
6
|
+
* GET /scm/diff?path=&staged= unified diff (one path or --stat)
|
|
7
|
+
* POST /scm/stage { paths: string[] } | { all: true }
|
|
8
|
+
* POST /scm/unstage { paths: string[] }
|
|
9
|
+
* POST /scm/discard { paths: string[] } [dangerous]
|
|
10
|
+
* POST /scm/commit { message }
|
|
11
|
+
* GET /scm/log?limit= recent commits
|
|
12
|
+
* GET /scm/branches branch list + current
|
|
13
|
+
* POST /scm/branches { name, create? } switch or create
|
|
14
|
+
* POST /scm/push push to upstream
|
|
15
|
+
* POST /scm/pull pull from upstream
|
|
16
|
+
*
|
|
17
|
+
* Reads are open (same-origin console); every mutation passes gateMutation.
|
|
18
|
+
* The ScmService is workspace-scoped (repoRoot fixed at construction).
|
|
19
|
+
*/
|
|
20
|
+
function sendJson(res, status, body) {
|
|
21
|
+
res.writeHead(status, {
|
|
22
|
+
"content-type": "application/json; charset=utf-8",
|
|
23
|
+
"cache-control": "no-store",
|
|
24
|
+
});
|
|
25
|
+
res.end(JSON.stringify(body));
|
|
26
|
+
}
|
|
27
|
+
function mapScmError(res, error) {
|
|
28
|
+
const status = error.code === "NO_PATHS" || error.code === "EMPTY_MESSAGE" || error.code === "INVALID_NAME"
|
|
29
|
+
? 400
|
|
30
|
+
: error.code === "GIT_TIMEOUT"
|
|
31
|
+
? 504
|
|
32
|
+
: 500;
|
|
33
|
+
sendJson(res, status, {
|
|
34
|
+
ok: false,
|
|
35
|
+
error: { code: error.code, message: error.message, ...(error.stderr ? { stderr: error.stderr } : {}) },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async function readJsonBody(req, maxBytes = 256 * 1024) {
|
|
39
|
+
const chunks = [];
|
|
40
|
+
let total = 0;
|
|
41
|
+
for await (const chunk of req) {
|
|
42
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
43
|
+
total += buf.length;
|
|
44
|
+
if (total > maxBytes)
|
|
45
|
+
throw new Error("request body too large");
|
|
46
|
+
chunks.push(buf);
|
|
47
|
+
}
|
|
48
|
+
if (chunks.length === 0)
|
|
49
|
+
return {};
|
|
50
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
51
|
+
if (!raw.trim())
|
|
52
|
+
return {};
|
|
53
|
+
return JSON.parse(raw);
|
|
54
|
+
}
|
|
55
|
+
function parsePaths(body) {
|
|
56
|
+
if (!Array.isArray(body.paths))
|
|
57
|
+
return [];
|
|
58
|
+
return body.paths
|
|
59
|
+
.filter((p) => typeof p === "string" && p.length > 0 && !p.includes("\0"))
|
|
60
|
+
.slice(0, 500);
|
|
61
|
+
}
|
|
62
|
+
/** Route dispatcher; returns true when the request was handled. */
|
|
63
|
+
export async function handleScmRequest(req, res, deps, pathname) {
|
|
64
|
+
if (!pathname.startsWith("/api/operator/v1/chat/scm"))
|
|
65
|
+
return false;
|
|
66
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
67
|
+
const rest = pathname.slice("/api/operator/v1/chat/scm".length);
|
|
68
|
+
const parts = rest.split("/").filter(Boolean);
|
|
69
|
+
const sub = parts[0] ?? "";
|
|
70
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
71
|
+
const gateFailed = (gate) => {
|
|
72
|
+
sendJson(res, gate.status, { ok: false, error: { code: gate.code, message: gate.message } });
|
|
73
|
+
return true;
|
|
74
|
+
};
|
|
75
|
+
try {
|
|
76
|
+
if (method === "GET" && sub === "status" && parts.length === 1) {
|
|
77
|
+
const r = await deps.scm.status();
|
|
78
|
+
if (!r.ok)
|
|
79
|
+
return mapScmError(res, r), true;
|
|
80
|
+
sendJson(res, 200, { ok: true, data: r });
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
if (method === "GET" && sub === "diff" && parts.length === 1) {
|
|
84
|
+
const path = url.searchParams.get("path") ?? undefined;
|
|
85
|
+
const staged = url.searchParams.get("staged") === "1";
|
|
86
|
+
const r = await deps.scm.diff({ path, staged });
|
|
87
|
+
if (!r.ok)
|
|
88
|
+
return mapScmError(res, r), true;
|
|
89
|
+
sendJson(res, 200, { ok: true, data: r });
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
if (method === "GET" && sub === "log" && parts.length === 1) {
|
|
93
|
+
const limit = Number(url.searchParams.get("limit") ?? 30);
|
|
94
|
+
const r = await deps.scm.log(Number.isFinite(limit) ? Math.min(Math.max(limit, 1), 200) : 30);
|
|
95
|
+
if (!r.ok)
|
|
96
|
+
return mapScmError(res, r), true;
|
|
97
|
+
sendJson(res, 200, { ok: true, data: r });
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
if (method === "GET" && sub === "branches" && parts.length === 1) {
|
|
101
|
+
const r = await deps.scm.branchList();
|
|
102
|
+
if (!r.ok)
|
|
103
|
+
return mapScmError(res, r), true;
|
|
104
|
+
sendJson(res, 200, { ok: true, data: r });
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
// Everything below mutates → gate first.
|
|
108
|
+
const gate = deps.gateMutation(req);
|
|
109
|
+
if (!gate.ok)
|
|
110
|
+
return gateFailed(gate);
|
|
111
|
+
if (method === "POST" && sub === "stage" && parts.length === 1) {
|
|
112
|
+
const body = await readJsonBody(req);
|
|
113
|
+
const r = body.all === true
|
|
114
|
+
? await deps.scm.stage([], true)
|
|
115
|
+
: await deps.scm.stage(parsePaths(body));
|
|
116
|
+
if (!r.ok)
|
|
117
|
+
return mapScmError(res, r), true;
|
|
118
|
+
sendJson(res, 200, { ok: true });
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
if (method === "POST" && sub === "unstage" && parts.length === 1) {
|
|
122
|
+
const body = await readJsonBody(req);
|
|
123
|
+
const r = await deps.scm.unstage(parsePaths(body));
|
|
124
|
+
if (!r.ok)
|
|
125
|
+
return mapScmError(res, r), true;
|
|
126
|
+
sendJson(res, 200, { ok: true });
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
if (method === "POST" && sub === "discard" && parts.length === 1) {
|
|
130
|
+
const body = await readJsonBody(req);
|
|
131
|
+
const r = await deps.scm.discard(parsePaths(body));
|
|
132
|
+
if (!r.ok)
|
|
133
|
+
return mapScmError(res, r), true;
|
|
134
|
+
sendJson(res, 200, { ok: true });
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
if (method === "POST" && sub === "commit" && parts.length === 1) {
|
|
138
|
+
const body = await readJsonBody(req);
|
|
139
|
+
const message = typeof body.message === "string" ? body.message : "";
|
|
140
|
+
const r = await deps.scm.commit(message);
|
|
141
|
+
if (!r.ok)
|
|
142
|
+
return mapScmError(res, r), true;
|
|
143
|
+
sendJson(res, 200, { ok: true, data: r });
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
if (method === "POST" && sub === "branches" && parts.length === 1) {
|
|
147
|
+
const body = await readJsonBody(req);
|
|
148
|
+
const name = typeof body.name === "string" ? body.name : "";
|
|
149
|
+
if (!name) {
|
|
150
|
+
sendJson(res, 400, { ok: false, error: { code: "INVALID_INPUT", message: "name is required" } });
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
const r = body.create === true
|
|
154
|
+
? await deps.scm.branchCreate(name)
|
|
155
|
+
: await deps.scm.branchSwitch(name);
|
|
156
|
+
if (!r.ok)
|
|
157
|
+
return mapScmError(res, r), true;
|
|
158
|
+
sendJson(res, 200, { ok: true });
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
if (method === "POST" && sub === "push" && parts.length === 1) {
|
|
162
|
+
const r = await deps.scm.push();
|
|
163
|
+
if (!r.ok)
|
|
164
|
+
return mapScmError(res, r), true;
|
|
165
|
+
sendJson(res, 200, { ok: true, data: r });
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
if (method === "POST" && sub === "pull" && parts.length === 1) {
|
|
169
|
+
const r = await deps.scm.pull();
|
|
170
|
+
if (!r.ok)
|
|
171
|
+
return mapScmError(res, r), true;
|
|
172
|
+
sendJson(res, 200, { ok: true, data: r });
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
sendJson(res, 404, { ok: false, error: { code: "NOT_FOUND", message: `unknown scm route: ${method} ${pathname}` } });
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
if (!res.headersSent) {
|
|
180
|
+
sendJson(res, 500, {
|
|
181
|
+
ok: false,
|
|
182
|
+
error: {
|
|
183
|
+
code: "SCM_INTERNAL",
|
|
184
|
+
message: error instanceof Error ? error.message : String(error),
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
res.end();
|
|
190
|
+
}
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
}
|