@arcships/morula-runtime 0.1.0-alpha.7 → 0.1.0-alpha.9
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/index.js +2279 -633
- package/dist/morula.js +1607 -110
- package/dist/plugin/plugin.json +1 -1
- package/dist/plugin/skills/morula-cli/SKILL.md +0 -1
- package/dist/plugin/skills/morula-cli/references/commands.md +1533 -254
- package/dist/plugin/skills/morula-working-on-issues/SKILL.md +21 -9
- package/dist/plugin/skills/morula-working-on-issues/references/working-on-issues-source-map.md +2 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -72,6 +72,11 @@ function parseArgs(argv) {
|
|
|
72
72
|
const positionals = [];
|
|
73
73
|
const flags = {};
|
|
74
74
|
const bareFlags = [];
|
|
75
|
+
const valueOccurrences = {};
|
|
76
|
+
const recordValue = (key, value) => {
|
|
77
|
+
flags[key] = value;
|
|
78
|
+
(valueOccurrences[key] ??= []).push(value);
|
|
79
|
+
};
|
|
75
80
|
let positionalOnly = false;
|
|
76
81
|
for (let index = 0; index < argv.length; index += 1) {
|
|
77
82
|
const value = argv[index];
|
|
@@ -93,7 +98,7 @@ function parseArgs(argv) {
|
|
|
93
98
|
}
|
|
94
99
|
const equal = value.indexOf("=");
|
|
95
100
|
if (equal > 2) {
|
|
96
|
-
|
|
101
|
+
recordValue(value.slice(2, equal), value.slice(equal + 1));
|
|
97
102
|
continue;
|
|
98
103
|
}
|
|
99
104
|
const key = value.slice(2);
|
|
@@ -101,19 +106,25 @@ function parseArgs(argv) {
|
|
|
101
106
|
throw usage("invalid empty flag");
|
|
102
107
|
const next = argv[index + 1];
|
|
103
108
|
if (next !== void 0 && !next.startsWith("--")) {
|
|
104
|
-
|
|
109
|
+
recordValue(key, next);
|
|
105
110
|
index += 1;
|
|
106
111
|
} else {
|
|
107
112
|
flags[key] = true;
|
|
108
113
|
bareFlags.push(key);
|
|
109
114
|
}
|
|
110
115
|
}
|
|
116
|
+
const repeatedFlags = {};
|
|
117
|
+
for (const [key, values] of Object.entries(valueOccurrences)) {
|
|
118
|
+
if (values.length > 1)
|
|
119
|
+
repeatedFlags[key] = values;
|
|
120
|
+
}
|
|
111
121
|
return {
|
|
112
122
|
command: positionals.shift(),
|
|
113
123
|
action: positionals.shift(),
|
|
114
124
|
positionals,
|
|
115
125
|
flags,
|
|
116
|
-
bareFlags
|
|
126
|
+
bareFlags,
|
|
127
|
+
repeatedFlags
|
|
117
128
|
};
|
|
118
129
|
}
|
|
119
130
|
function flagString(flags, ...names) {
|
|
@@ -729,7 +740,7 @@ var BUILD_ID, BUILD_CONTEXTS, BUILD_DEFAULT_CONTEXT;
|
|
|
729
740
|
var init_build_env = __esm({
|
|
730
741
|
"../morula-cli-core/dist/build-env.js"() {
|
|
731
742
|
"use strict";
|
|
732
|
-
BUILD_ID = "
|
|
743
|
+
BUILD_ID = "20260921135326.48e72dd8";
|
|
733
744
|
BUILD_CONTEXTS = {
|
|
734
745
|
"local": "http://localhost:8080/api",
|
|
735
746
|
"test": "https://morula.basecastle.com/api",
|
|
@@ -739,6 +750,48 @@ var init_build_env = __esm({
|
|
|
739
750
|
}
|
|
740
751
|
});
|
|
741
752
|
|
|
753
|
+
// ../morula-api-client/dist/agent-task.js
|
|
754
|
+
function requiredId(id) {
|
|
755
|
+
const normalized = id.trim();
|
|
756
|
+
if (!normalized)
|
|
757
|
+
throw new TypeError("agent task id is required");
|
|
758
|
+
return normalized;
|
|
759
|
+
}
|
|
760
|
+
var AgentTaskApi;
|
|
761
|
+
var init_agent_task = __esm({
|
|
762
|
+
"../morula-api-client/dist/agent-task.js"() {
|
|
763
|
+
"use strict";
|
|
764
|
+
AgentTaskApi = class {
|
|
765
|
+
client;
|
|
766
|
+
constructor(client) {
|
|
767
|
+
this.client = client;
|
|
768
|
+
}
|
|
769
|
+
/** 列出 AgentTask(objectId 必填;status/page/pageSize 可选) */
|
|
770
|
+
list(query = {}) {
|
|
771
|
+
return this.client.get("/agent-tasks", query);
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* 取消执行。
|
|
775
|
+
*
|
|
776
|
+
* 服务端语义(`agent-task.controller.ts` cancel,2026-09-20 起):
|
|
777
|
+
* - 成功 → 200 `{ ok: true, message: 'Task cancelled' }`;
|
|
778
|
+
* - 任务不存在 → **404** `NOT_FOUND`;
|
|
779
|
+
* - 状态不允许(`completed` / `cancelled`)→ **409** `CONFLICT`(带可读原因)。
|
|
780
|
+
*
|
|
781
|
+
* 即:**失败会 reject**(不再 200 假成功,见 issues/agent-task-cancel-returns-200-on-failure.md)——
|
|
782
|
+
* 调用方按异常/退出码判断,不要假定「调用返回了就是已取消」。
|
|
783
|
+
*/
|
|
784
|
+
cancel(id) {
|
|
785
|
+
return this.client.post(`/agent-tasks/${encodeURIComponent(requiredId(id))}/cancel`);
|
|
786
|
+
}
|
|
787
|
+
/** 重跑失败/已取消的执行(服务端复制原任务重新入队,返回**新** taskId) */
|
|
788
|
+
rerun(id) {
|
|
789
|
+
return this.client.post(`/agent-tasks/${encodeURIComponent(requiredId(id))}/rerun`);
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
|
|
742
795
|
// ../morula-api-client/dist/attachment.js
|
|
743
796
|
var AttachmentApi;
|
|
744
797
|
var init_attachment = __esm({
|
|
@@ -788,6 +841,13 @@ var init_attachment = __esm({
|
|
|
788
841
|
getDownloadUrl(id) {
|
|
789
842
|
return this.client.get(`/attachments/${encodeURIComponent(id)}/download-url`);
|
|
790
843
|
}
|
|
844
|
+
/**
|
|
845
|
+
* 读取附件的解析产物全文(MinerU markdown)。
|
|
846
|
+
* 未解析/解析中/解析失败由服务端在返回体里说明,不当作 HTTP 错误。
|
|
847
|
+
*/
|
|
848
|
+
content(id, maxChars) {
|
|
849
|
+
return this.client.get(`/attachments/${encodeURIComponent(id)}/content`, { maxChars });
|
|
850
|
+
}
|
|
791
851
|
/** 更新附件关联的资源 ID */
|
|
792
852
|
updateResourceId(id, resourceId) {
|
|
793
853
|
return this.client.patch(`/attachments/${encodeURIComponent(id)}`, { resourceId });
|
|
@@ -826,7 +886,115 @@ var init_auth = __esm({
|
|
|
826
886
|
}
|
|
827
887
|
});
|
|
828
888
|
|
|
889
|
+
// ../morula-api-client/dist/autopilot.js
|
|
890
|
+
var AutopilotApi;
|
|
891
|
+
var init_autopilot = __esm({
|
|
892
|
+
"../morula-api-client/dist/autopilot.js"() {
|
|
893
|
+
"use strict";
|
|
894
|
+
AutopilotApi = class {
|
|
895
|
+
client;
|
|
896
|
+
constructor(client) {
|
|
897
|
+
this.client = client;
|
|
898
|
+
}
|
|
899
|
+
list(query = {}) {
|
|
900
|
+
return this.client.get("/agent-autopilots", query);
|
|
901
|
+
}
|
|
902
|
+
create(input) {
|
|
903
|
+
return this.client.post("/agent-autopilots", input);
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
// ../morula-api-client/dist/ci.js
|
|
910
|
+
function requiredId2(id, label) {
|
|
911
|
+
const normalized = id.trim();
|
|
912
|
+
if (!normalized)
|
|
913
|
+
throw new TypeError(`${label} is required`);
|
|
914
|
+
return normalized;
|
|
915
|
+
}
|
|
916
|
+
var CiApi;
|
|
917
|
+
var init_ci = __esm({
|
|
918
|
+
"../morula-api-client/dist/ci.js"() {
|
|
919
|
+
"use strict";
|
|
920
|
+
CiApi = class {
|
|
921
|
+
client;
|
|
922
|
+
constructor(client) {
|
|
923
|
+
this.client = client;
|
|
924
|
+
}
|
|
925
|
+
/** 任务面 CI 视图(本任务关联的流水线运行 + 部署记录 + 归属依据) */
|
|
926
|
+
taskView(taskId, query = {}) {
|
|
927
|
+
return this.client.get(`/agent-tasks/${encodeURIComponent(requiredId2(taskId, "task id"))}/vcs/ci`, query);
|
|
928
|
+
}
|
|
929
|
+
/** 任务面部署记录(D3:仅展示,不驱动流转) */
|
|
930
|
+
taskDeployments(taskId) {
|
|
931
|
+
return this.client.get(`/agent-tasks/${encodeURIComponent(requiredId2(taskId, "task id"))}/vcs/ci/deployments`);
|
|
932
|
+
}
|
|
933
|
+
/** 空间面 CI 巡检视图(管家主入口;`ref` / `failed` / `limit` 为服务端过滤) */
|
|
934
|
+
spaceView(spaceId, query = {}) {
|
|
935
|
+
return this.client.get(`/vcs/spaces/${encodeURIComponent(requiredId2(spaceId, "space id"))}/ci`, query);
|
|
936
|
+
}
|
|
937
|
+
/** 空间面部署记录(D3:仅展示) */
|
|
938
|
+
spaceDeployments(spaceId) {
|
|
939
|
+
return this.client.get(`/vcs/spaces/${encodeURIComponent(requiredId2(spaceId, "space id"))}/ci/deployments`);
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* job 日志(按 job id;服务端反查所属运行)。
|
|
943
|
+
*
|
|
944
|
+
* 两个面共用本端点:任务凭证按 tenantId 收敛,会话凭证按 `connection.spaceId === scopeId` 收敛。
|
|
945
|
+
* `truncated` 显式返回——日志被尾部截断时调用方必须知道(不能把截断当全文)。
|
|
946
|
+
*/
|
|
947
|
+
jobLog(jobId, query = {}) {
|
|
948
|
+
return this.client.get(`/vcs/jobs/${encodeURIComponent(requiredId2(jobId, "job id"))}/log`, query);
|
|
949
|
+
}
|
|
950
|
+
/** 失败用例清单(平台可选能力:GitLab test_report 有、GitHub 无 → `CAPABILITY_UNSUPPORTED`) */
|
|
951
|
+
testReport(runId) {
|
|
952
|
+
return this.client.get(`/vcs/runs/${encodeURIComponent(requiredId2(runId, "run id"))}/test-report`);
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* 重跑整条流水线的失败 job(Phase 3;开放面 = 人 + 管家)。
|
|
956
|
+
*
|
|
957
|
+
* 只允许 failure / cancelled 的运行,其余状态服务端回 `CI_RETRY_NOT_ALLOWED`(不是 4xx)。
|
|
958
|
+
* **非幂等**:每次调用都会在平台上产生一次新的重跑,重复调用不会去重。
|
|
959
|
+
*/
|
|
960
|
+
retryRun(runId) {
|
|
961
|
+
return this.client.post(`/vcs/runs/${encodeURIComponent(requiredId2(runId, "run id"))}/retry`);
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* 重跑单个 job(Phase 3;开放面 = 人 + 管家)。
|
|
965
|
+
*
|
|
966
|
+
* 服务端按 jobId 反查所属运行(`CI_JOB_NOT_FOUND` 覆盖「job 不存在」与「反查不到运行」两种情况);
|
|
967
|
+
* 状态门禁与 `retryRun` 相同。非幂等。
|
|
968
|
+
*/
|
|
969
|
+
retryJob(jobId) {
|
|
970
|
+
return this.client.post(`/vcs/jobs/${encodeURIComponent(requiredId2(jobId, "job id"))}/retry`);
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
});
|
|
975
|
+
|
|
829
976
|
// ../morula-api-client/dist/client.js
|
|
977
|
+
function describeFetchFailure(error) {
|
|
978
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
979
|
+
const detail = formatFetchCause(error?.cause);
|
|
980
|
+
return detail ? `${message} (cause: ${detail})` : message;
|
|
981
|
+
}
|
|
982
|
+
function formatFetchCause(cause) {
|
|
983
|
+
if (cause === void 0 || cause === null)
|
|
984
|
+
return "";
|
|
985
|
+
if (cause instanceof Error) {
|
|
986
|
+
const detail = cause;
|
|
987
|
+
const parts = [
|
|
988
|
+
typeof detail.code === "string" ? detail.code : "",
|
|
989
|
+
typeof detail.errno === "number" || typeof detail.errno === "string" ? `errno=${detail.errno}` : "",
|
|
990
|
+
typeof detail.syscall === "string" ? `syscall=${detail.syscall}` : "",
|
|
991
|
+
// cause 的 message 常与外层重复('fetch failed'),只有不同才带
|
|
992
|
+
detail.message && detail.message !== "fetch failed" ? detail.message : ""
|
|
993
|
+
].filter(Boolean);
|
|
994
|
+
return parts.join(" ").slice(0, CAUSE_DETAIL_MAX_LENGTH);
|
|
995
|
+
}
|
|
996
|
+
return String(cause).slice(0, CAUSE_DETAIL_MAX_LENGTH);
|
|
997
|
+
}
|
|
830
998
|
function sanitizeHeaderValue(raw, maxLength) {
|
|
831
999
|
if (!raw)
|
|
832
1000
|
return void 0;
|
|
@@ -870,28 +1038,35 @@ function isEnvelope(value) {
|
|
|
870
1038
|
function errorFromResponse(status3, parsed, raw) {
|
|
871
1039
|
const message = extractMessage(parsed, raw, status3);
|
|
872
1040
|
const details = isEnvelope(parsed) ? parsed.data : parsed;
|
|
1041
|
+
const errorCode = extractErrorCode(parsed);
|
|
873
1042
|
if (status3 === 400 || status3 === 422) {
|
|
874
|
-
return new MorulaApiError("BAD_REQUEST", message, EXIT_CODE.USAGE, status3, false, details);
|
|
1043
|
+
return new MorulaApiError("BAD_REQUEST", message, EXIT_CODE.USAGE, status3, false, details, errorCode);
|
|
875
1044
|
}
|
|
876
1045
|
if (status3 === 401) {
|
|
877
|
-
return new MorulaApiError("UNAUTHENTICATED", message, EXIT_CODE.UNAUTHENTICATED, status3, false, details);
|
|
1046
|
+
return new MorulaApiError("UNAUTHENTICATED", message, EXIT_CODE.UNAUTHENTICATED, status3, false, details, errorCode);
|
|
878
1047
|
}
|
|
879
1048
|
if (status3 === 403) {
|
|
880
|
-
return new MorulaApiError("FORBIDDEN", message, EXIT_CODE.FORBIDDEN, status3, false, details);
|
|
1049
|
+
return new MorulaApiError("FORBIDDEN", message, EXIT_CODE.FORBIDDEN, status3, false, details, errorCode);
|
|
881
1050
|
}
|
|
882
1051
|
if (status3 === 404) {
|
|
883
|
-
return new MorulaApiError("NOT_FOUND", message, EXIT_CODE.NOT_FOUND, status3, false, details);
|
|
1052
|
+
return new MorulaApiError("NOT_FOUND", message, EXIT_CODE.NOT_FOUND, status3, false, details, errorCode);
|
|
884
1053
|
}
|
|
885
1054
|
if (status3 === 409) {
|
|
886
|
-
return new MorulaApiError("CONFLICT", message, EXIT_CODE.CONFLICT, status3, false, details);
|
|
1055
|
+
return new MorulaApiError("CONFLICT", message, EXIT_CODE.CONFLICT, status3, false, details, errorCode);
|
|
887
1056
|
}
|
|
888
1057
|
if (status3 === 429) {
|
|
889
|
-
return new MorulaApiError("RATE_LIMITED", message, EXIT_CODE.UPSTREAM, status3, true, details);
|
|
1058
|
+
return new MorulaApiError("RATE_LIMITED", message, EXIT_CODE.UPSTREAM, status3, true, details, errorCode);
|
|
890
1059
|
}
|
|
891
1060
|
if (status3 >= 500) {
|
|
892
|
-
return new MorulaApiError("UPSTREAM", message, EXIT_CODE.UPSTREAM, status3, true, details);
|
|
1061
|
+
return new MorulaApiError("UPSTREAM", message, EXIT_CODE.UPSTREAM, status3, true, details, errorCode);
|
|
893
1062
|
}
|
|
894
|
-
return new MorulaApiError("HTTP_ERROR", message, EXIT_CODE.OTHER, status3, false, details);
|
|
1063
|
+
return new MorulaApiError("HTTP_ERROR", message, EXIT_CODE.OTHER, status3, false, details, errorCode);
|
|
1064
|
+
}
|
|
1065
|
+
function extractErrorCode(parsed) {
|
|
1066
|
+
if (!parsed || typeof parsed !== "object")
|
|
1067
|
+
return void 0;
|
|
1068
|
+
const value = parsed.errorCode;
|
|
1069
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
895
1070
|
}
|
|
896
1071
|
function extractMessage(parsed, raw, status3) {
|
|
897
1072
|
if (parsed && typeof parsed === "object" && "message" in parsed) {
|
|
@@ -907,7 +1082,7 @@ function extractMessage(parsed, raw, status3) {
|
|
|
907
1082
|
return raw.slice(0, 500);
|
|
908
1083
|
return `HTTP ${status3}`;
|
|
909
1084
|
}
|
|
910
|
-
var EXIT_CODE, MorulaApiError, CLI_BUILD_HEADER, CLI_CAPS_HEADER, CLI_BUILD_MAX_LENGTH, CLI_CAPS_MAX_LENGTH, DEFAULT_TIMEOUT_MS, MorulaApiClient;
|
|
1085
|
+
var EXIT_CODE, MorulaApiError, CLI_BUILD_HEADER, CLI_CAPS_HEADER, CLI_BUILD_MAX_LENGTH, CLI_CAPS_MAX_LENGTH, DEFAULT_TIMEOUT_MS, CAUSE_DETAIL_MAX_LENGTH, MorulaApiClient;
|
|
911
1086
|
var init_client = __esm({
|
|
912
1087
|
"../morula-api-client/dist/client.js"() {
|
|
913
1088
|
"use strict";
|
|
@@ -927,14 +1102,16 @@ var init_client = __esm({
|
|
|
927
1102
|
status;
|
|
928
1103
|
retryable;
|
|
929
1104
|
details;
|
|
1105
|
+
errorCode;
|
|
930
1106
|
name = "MorulaApiError";
|
|
931
|
-
constructor(code, message, exitCode, status3, retryable = false, details) {
|
|
1107
|
+
constructor(code, message, exitCode, status3, retryable = false, details, errorCode) {
|
|
932
1108
|
super(message);
|
|
933
1109
|
this.code = code;
|
|
934
1110
|
this.exitCode = exitCode;
|
|
935
1111
|
this.status = status3;
|
|
936
1112
|
this.retryable = retryable;
|
|
937
1113
|
this.details = details;
|
|
1114
|
+
this.errorCode = errorCode;
|
|
938
1115
|
}
|
|
939
1116
|
};
|
|
940
1117
|
CLI_BUILD_HEADER = "X-Morula-Cli-Build";
|
|
@@ -942,6 +1119,7 @@ var init_client = __esm({
|
|
|
942
1119
|
CLI_BUILD_MAX_LENGTH = 64;
|
|
943
1120
|
CLI_CAPS_MAX_LENGTH = 255;
|
|
944
1121
|
DEFAULT_TIMEOUT_MS = 3e4;
|
|
1122
|
+
CAUSE_DETAIL_MAX_LENGTH = 200;
|
|
945
1123
|
MorulaApiClient = class {
|
|
946
1124
|
baseUrl;
|
|
947
1125
|
token;
|
|
@@ -1039,7 +1217,7 @@ var init_client = __esm({
|
|
|
1039
1217
|
if (controller.signal.aborted) {
|
|
1040
1218
|
throw new MorulaApiError("TIMEOUT", `request timed out after ${timeoutMs}ms`, EXIT_CODE.UPSTREAM, void 0, true);
|
|
1041
1219
|
}
|
|
1042
|
-
throw new MorulaApiError("NETWORK", `request failed: ${
|
|
1220
|
+
throw new MorulaApiError("NETWORK", `request failed: ${describeFetchFailure(error)}`, EXIT_CODE.UPSTREAM, void 0, true);
|
|
1043
1221
|
} finally {
|
|
1044
1222
|
clearTimeout(timer);
|
|
1045
1223
|
}
|
|
@@ -1061,7 +1239,7 @@ var init_client = __esm({
|
|
|
1061
1239
|
});
|
|
1062
1240
|
|
|
1063
1241
|
// ../morula-api-client/dist/comment.js
|
|
1064
|
-
function
|
|
1242
|
+
function requiredId3(id) {
|
|
1065
1243
|
const normalized = id.trim();
|
|
1066
1244
|
if (!normalized)
|
|
1067
1245
|
throw new TypeError("comment id is required");
|
|
@@ -1083,23 +1261,142 @@ var init_comment = __esm({
|
|
|
1083
1261
|
return this.client.get("/comments", params);
|
|
1084
1262
|
}
|
|
1085
1263
|
get(id) {
|
|
1086
|
-
return this.client.get(`/comments/${encodeURIComponent(
|
|
1264
|
+
return this.client.get(`/comments/${encodeURIComponent(requiredId3(id))}`);
|
|
1087
1265
|
}
|
|
1088
1266
|
create(input) {
|
|
1089
1267
|
return this.client.post("/comments", input);
|
|
1090
1268
|
}
|
|
1091
1269
|
update(id, input) {
|
|
1092
|
-
return this.client.patch(`/comments/${encodeURIComponent(
|
|
1270
|
+
return this.client.patch(`/comments/${encodeURIComponent(requiredId3(id))}`, input);
|
|
1093
1271
|
}
|
|
1094
1272
|
delete(id) {
|
|
1095
|
-
return this.client.delete(`/comments/${encodeURIComponent(
|
|
1273
|
+
return this.client.delete(`/comments/${encodeURIComponent(requiredId3(id))}`);
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
// ../morula-api-client/dist/dashboard.js
|
|
1280
|
+
function requiredId4(id) {
|
|
1281
|
+
const normalized = id.trim();
|
|
1282
|
+
if (!normalized)
|
|
1283
|
+
throw new TypeError("space id is required");
|
|
1284
|
+
return normalized;
|
|
1285
|
+
}
|
|
1286
|
+
var DashboardApi;
|
|
1287
|
+
var init_dashboard = __esm({
|
|
1288
|
+
"../morula-api-client/dist/dashboard.js"() {
|
|
1289
|
+
"use strict";
|
|
1290
|
+
DashboardApi = class {
|
|
1291
|
+
client;
|
|
1292
|
+
constructor(client) {
|
|
1293
|
+
this.client = client;
|
|
1294
|
+
}
|
|
1295
|
+
get(spaceId) {
|
|
1296
|
+
return this.client.get(`/spaces/${encodeURIComponent(requiredId4(spaceId))}/dashboard`);
|
|
1297
|
+
}
|
|
1298
|
+
statistics(spaceId) {
|
|
1299
|
+
return this.client.get(`/spaces/${encodeURIComponent(requiredId4(spaceId))}/statistics`);
|
|
1300
|
+
}
|
|
1301
|
+
workload(spaceId) {
|
|
1302
|
+
return this.client.get(`/spaces/${encodeURIComponent(requiredId4(spaceId))}/workload`);
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
});
|
|
1307
|
+
|
|
1308
|
+
// ../morula-api-client/dist/document.js
|
|
1309
|
+
function requiredId5(id) {
|
|
1310
|
+
const normalized = String(id).trim();
|
|
1311
|
+
if (!normalized)
|
|
1312
|
+
throw new TypeError("document id is required");
|
|
1313
|
+
return normalized;
|
|
1314
|
+
}
|
|
1315
|
+
var DocumentApi;
|
|
1316
|
+
var init_document = __esm({
|
|
1317
|
+
"../morula-api-client/dist/document.js"() {
|
|
1318
|
+
"use strict";
|
|
1319
|
+
DocumentApi = class {
|
|
1320
|
+
client;
|
|
1321
|
+
constructor(client) {
|
|
1322
|
+
this.client = client;
|
|
1323
|
+
}
|
|
1324
|
+
list(query = {}) {
|
|
1325
|
+
return this.client.get("/documents", query);
|
|
1326
|
+
}
|
|
1327
|
+
get(id) {
|
|
1328
|
+
return this.client.get(`/documents/${encodeURIComponent(requiredId5(id))}`);
|
|
1329
|
+
}
|
|
1330
|
+
/** 已解析归档的文档内容(Markdown 正文等) */
|
|
1331
|
+
artifacts(id) {
|
|
1332
|
+
return this.client.get(`/documents/${encodeURIComponent(requiredId5(id))}/artifacts`);
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
// ../morula-api-client/dist/object-meta.js
|
|
1339
|
+
function requiredId6(id) {
|
|
1340
|
+
const normalized = id.trim();
|
|
1341
|
+
if (!normalized)
|
|
1342
|
+
throw new TypeError("object id is required");
|
|
1343
|
+
return normalized;
|
|
1344
|
+
}
|
|
1345
|
+
function requiredKey(key) {
|
|
1346
|
+
const normalized = key.trim();
|
|
1347
|
+
if (!normalized)
|
|
1348
|
+
throw new TypeError("meta key is required");
|
|
1349
|
+
return normalized;
|
|
1350
|
+
}
|
|
1351
|
+
var ObjectMetaApi;
|
|
1352
|
+
var init_object_meta = __esm({
|
|
1353
|
+
"../morula-api-client/dist/object-meta.js"() {
|
|
1354
|
+
"use strict";
|
|
1355
|
+
ObjectMetaApi = class {
|
|
1356
|
+
client;
|
|
1357
|
+
constructor(client) {
|
|
1358
|
+
this.client = client;
|
|
1359
|
+
}
|
|
1360
|
+
list(objectId) {
|
|
1361
|
+
return this.client.get(`/objects/${encodeURIComponent(requiredId6(objectId))}/agent-meta`);
|
|
1362
|
+
}
|
|
1363
|
+
set(objectId, key, value) {
|
|
1364
|
+
return this.client.request(`/objects/${encodeURIComponent(requiredId6(objectId))}/agent-meta/${encodeURIComponent(requiredKey(key))}`, { method: "PUT", body: { value } });
|
|
1365
|
+
}
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
// ../morula-api-client/dist/repo.js
|
|
1371
|
+
function requiredId7(id) {
|
|
1372
|
+
const normalized = id.trim();
|
|
1373
|
+
if (!normalized)
|
|
1374
|
+
throw new TypeError("space id is required");
|
|
1375
|
+
return normalized;
|
|
1376
|
+
}
|
|
1377
|
+
var RepoApi;
|
|
1378
|
+
var init_repo = __esm({
|
|
1379
|
+
"../morula-api-client/dist/repo.js"() {
|
|
1380
|
+
"use strict";
|
|
1381
|
+
RepoApi = class {
|
|
1382
|
+
client;
|
|
1383
|
+
constructor(client) {
|
|
1384
|
+
this.client = client;
|
|
1385
|
+
}
|
|
1386
|
+
/** 目录清单(path 缺省 = 仓库根) */
|
|
1387
|
+
files(spaceId, query = {}) {
|
|
1388
|
+
return this.client.get(`/spaces/${encodeURIComponent(requiredId7(spaceId))}/repo/files`, query);
|
|
1389
|
+
}
|
|
1390
|
+
/** 单文件内容(path 必填) */
|
|
1391
|
+
read(spaceId, query) {
|
|
1392
|
+
return this.client.get(`/spaces/${encodeURIComponent(requiredId7(spaceId))}/repo/file`, query);
|
|
1096
1393
|
}
|
|
1097
1394
|
};
|
|
1098
1395
|
}
|
|
1099
1396
|
});
|
|
1100
1397
|
|
|
1101
1398
|
// ../morula-api-client/dist/report.js
|
|
1102
|
-
function
|
|
1399
|
+
function requiredId8(id, label = "report") {
|
|
1103
1400
|
const normalized = id.trim();
|
|
1104
1401
|
if (!normalized)
|
|
1105
1402
|
throw new TypeError(`${label} id is required`);
|
|
@@ -1118,17 +1415,17 @@ var init_report = __esm({
|
|
|
1118
1415
|
return this.client.post("/agent-reports", input);
|
|
1119
1416
|
}
|
|
1120
1417
|
get(id) {
|
|
1121
|
-
return this.client.get(`/agent-reports/${encodeURIComponent(
|
|
1418
|
+
return this.client.get(`/agent-reports/${encodeURIComponent(requiredId8(id))}`);
|
|
1122
1419
|
}
|
|
1123
1420
|
listByAutopilot(autopilotId, query = {}) {
|
|
1124
|
-
return this.client.get(`/agent-autopilots/${encodeURIComponent(
|
|
1421
|
+
return this.client.get(`/agent-autopilots/${encodeURIComponent(requiredId8(autopilotId, "autopilot"))}/reports`, { ...query });
|
|
1125
1422
|
}
|
|
1126
1423
|
};
|
|
1127
1424
|
}
|
|
1128
1425
|
});
|
|
1129
1426
|
|
|
1130
1427
|
// ../morula-api-client/dist/space-member.js
|
|
1131
|
-
function
|
|
1428
|
+
function requiredId9(id) {
|
|
1132
1429
|
const normalized = id.trim();
|
|
1133
1430
|
if (!normalized)
|
|
1134
1431
|
throw new TypeError("space member id is required");
|
|
@@ -1147,23 +1444,23 @@ var init_space_member = __esm({
|
|
|
1147
1444
|
return this.client.get("/space-members", { spaceId, ...query });
|
|
1148
1445
|
}
|
|
1149
1446
|
get(id) {
|
|
1150
|
-
return this.client.get(`/space-members/${encodeURIComponent(
|
|
1447
|
+
return this.client.get(`/space-members/${encodeURIComponent(requiredId9(id))}`);
|
|
1151
1448
|
}
|
|
1152
1449
|
create(input) {
|
|
1153
1450
|
return this.client.post("/space-members", input);
|
|
1154
1451
|
}
|
|
1155
1452
|
update(id, input) {
|
|
1156
|
-
return this.client.patch(`/space-members/${encodeURIComponent(
|
|
1453
|
+
return this.client.patch(`/space-members/${encodeURIComponent(requiredId9(id))}`, input);
|
|
1157
1454
|
}
|
|
1158
1455
|
delete(id) {
|
|
1159
|
-
return this.client.delete(`/space-members/${encodeURIComponent(
|
|
1456
|
+
return this.client.delete(`/space-members/${encodeURIComponent(requiredId9(id))}`);
|
|
1160
1457
|
}
|
|
1161
1458
|
};
|
|
1162
1459
|
}
|
|
1163
1460
|
});
|
|
1164
1461
|
|
|
1165
1462
|
// ../morula-api-client/dist/space.js
|
|
1166
|
-
function
|
|
1463
|
+
function requiredId10(id) {
|
|
1167
1464
|
const normalized = id.trim();
|
|
1168
1465
|
if (!normalized)
|
|
1169
1466
|
throw new TypeError("space id is required");
|
|
@@ -1182,23 +1479,51 @@ var init_space = __esm({
|
|
|
1182
1479
|
return this.client.get("/spaces", query);
|
|
1183
1480
|
}
|
|
1184
1481
|
get(id) {
|
|
1185
|
-
return this.client.get(`/spaces/${encodeURIComponent(
|
|
1482
|
+
return this.client.get(`/spaces/${encodeURIComponent(requiredId10(id))}`);
|
|
1186
1483
|
}
|
|
1187
1484
|
create(input) {
|
|
1188
1485
|
return this.client.post("/spaces", input);
|
|
1189
1486
|
}
|
|
1190
1487
|
update(id, input) {
|
|
1191
|
-
return this.client.patch(`/spaces/${encodeURIComponent(
|
|
1488
|
+
return this.client.patch(`/spaces/${encodeURIComponent(requiredId10(id))}`, input);
|
|
1192
1489
|
}
|
|
1193
1490
|
delete(id) {
|
|
1194
|
-
return this.client.delete(`/spaces/${encodeURIComponent(
|
|
1491
|
+
return this.client.delete(`/spaces/${encodeURIComponent(requiredId10(id))}`);
|
|
1492
|
+
}
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1497
|
+
// ../morula-api-client/dist/steward.js
|
|
1498
|
+
function requiredId11(id) {
|
|
1499
|
+
const normalized = id.trim();
|
|
1500
|
+
if (!normalized)
|
|
1501
|
+
throw new TypeError("project id is required");
|
|
1502
|
+
return normalized;
|
|
1503
|
+
}
|
|
1504
|
+
var StewardApi;
|
|
1505
|
+
var init_steward = __esm({
|
|
1506
|
+
"../morula-api-client/dist/steward.js"() {
|
|
1507
|
+
"use strict";
|
|
1508
|
+
StewardApi = class {
|
|
1509
|
+
client;
|
|
1510
|
+
constructor(client) {
|
|
1511
|
+
this.client = client;
|
|
1512
|
+
}
|
|
1513
|
+
/** 管家信息(未接管的空间返回 null) */
|
|
1514
|
+
get(projectId) {
|
|
1515
|
+
return this.client.get(`/stewards/projects/${encodeURIComponent(requiredId11(projectId))}`);
|
|
1516
|
+
}
|
|
1517
|
+
/** 派单:建任务并派发(目标状态由 state 决定,backlog 不派发执行) */
|
|
1518
|
+
dispatch(projectId, input) {
|
|
1519
|
+
return this.client.post(`/stewards/projects/${encodeURIComponent(requiredId11(projectId))}/dispatch`, input);
|
|
1195
1520
|
}
|
|
1196
1521
|
};
|
|
1197
1522
|
}
|
|
1198
1523
|
});
|
|
1199
1524
|
|
|
1200
1525
|
// ../morula-api-client/dist/task.js
|
|
1201
|
-
function
|
|
1526
|
+
function requiredId12(id) {
|
|
1202
1527
|
const normalized = id.trim();
|
|
1203
1528
|
if (!normalized)
|
|
1204
1529
|
throw new TypeError("task id is required");
|
|
@@ -1217,16 +1542,16 @@ var init_task = __esm({
|
|
|
1217
1542
|
return this.client.get("/objects", query);
|
|
1218
1543
|
}
|
|
1219
1544
|
get(id) {
|
|
1220
|
-
return this.client.get(`/objects/${encodeURIComponent(
|
|
1545
|
+
return this.client.get(`/objects/${encodeURIComponent(requiredId12(id))}`);
|
|
1221
1546
|
}
|
|
1222
1547
|
create(input) {
|
|
1223
1548
|
return this.client.post("/objects", input);
|
|
1224
1549
|
}
|
|
1225
1550
|
update(id, input) {
|
|
1226
|
-
return this.client.patch(`/objects/${encodeURIComponent(
|
|
1551
|
+
return this.client.patch(`/objects/${encodeURIComponent(requiredId12(id))}`, input);
|
|
1227
1552
|
}
|
|
1228
1553
|
delete(id) {
|
|
1229
|
-
return this.client.delete(`/objects/${encodeURIComponent(
|
|
1554
|
+
return this.client.delete(`/objects/${encodeURIComponent(requiredId12(id))}`);
|
|
1230
1555
|
}
|
|
1231
1556
|
/** 查询空间字段模板(字段 key → id 映射、options) */
|
|
1232
1557
|
getSchemaFields(spaceId) {
|
|
@@ -1293,44 +1618,76 @@ var MorulaClient;
|
|
|
1293
1618
|
var init_dist = __esm({
|
|
1294
1619
|
"../morula-api-client/dist/index.js"() {
|
|
1295
1620
|
"use strict";
|
|
1621
|
+
init_agent_task();
|
|
1296
1622
|
init_attachment();
|
|
1297
1623
|
init_auth();
|
|
1624
|
+
init_autopilot();
|
|
1625
|
+
init_ci();
|
|
1298
1626
|
init_client();
|
|
1299
1627
|
init_comment();
|
|
1628
|
+
init_dashboard();
|
|
1629
|
+
init_document();
|
|
1630
|
+
init_object_meta();
|
|
1631
|
+
init_repo();
|
|
1300
1632
|
init_report();
|
|
1301
1633
|
init_space_member();
|
|
1302
1634
|
init_space();
|
|
1635
|
+
init_steward();
|
|
1303
1636
|
init_task();
|
|
1304
1637
|
init_webhook();
|
|
1638
|
+
init_agent_task();
|
|
1305
1639
|
init_attachment();
|
|
1306
1640
|
init_auth();
|
|
1641
|
+
init_autopilot();
|
|
1642
|
+
init_ci();
|
|
1307
1643
|
init_client();
|
|
1308
1644
|
init_comment();
|
|
1645
|
+
init_dashboard();
|
|
1646
|
+
init_document();
|
|
1647
|
+
init_object_meta();
|
|
1648
|
+
init_repo();
|
|
1309
1649
|
init_report();
|
|
1310
1650
|
init_space_member();
|
|
1311
1651
|
init_space();
|
|
1652
|
+
init_steward();
|
|
1312
1653
|
init_task();
|
|
1313
1654
|
init_types();
|
|
1314
1655
|
init_webhook();
|
|
1315
1656
|
MorulaClient = class {
|
|
1316
1657
|
http;
|
|
1658
|
+
agentTasks;
|
|
1317
1659
|
attachments;
|
|
1318
1660
|
auth;
|
|
1661
|
+
autopilots;
|
|
1662
|
+
ci;
|
|
1319
1663
|
comments;
|
|
1664
|
+
dashboard;
|
|
1665
|
+
documents;
|
|
1666
|
+
objectMeta;
|
|
1320
1667
|
reports;
|
|
1668
|
+
repos;
|
|
1321
1669
|
tasks;
|
|
1322
1670
|
spaces;
|
|
1323
1671
|
spaceMembers;
|
|
1672
|
+
stewards;
|
|
1324
1673
|
webhooks;
|
|
1325
1674
|
constructor(options) {
|
|
1326
1675
|
this.http = new MorulaApiClient(options);
|
|
1676
|
+
this.agentTasks = new AgentTaskApi(this.http);
|
|
1327
1677
|
this.attachments = new AttachmentApi(this.http);
|
|
1328
1678
|
this.auth = new AuthApi(this.http);
|
|
1679
|
+
this.autopilots = new AutopilotApi(this.http);
|
|
1680
|
+
this.ci = new CiApi(this.http);
|
|
1329
1681
|
this.comments = new CommentApi(this.http);
|
|
1682
|
+
this.dashboard = new DashboardApi(this.http);
|
|
1683
|
+
this.documents = new DocumentApi(this.http);
|
|
1684
|
+
this.objectMeta = new ObjectMetaApi(this.http);
|
|
1330
1685
|
this.reports = new ReportApi(this.http);
|
|
1686
|
+
this.repos = new RepoApi(this.http);
|
|
1331
1687
|
this.tasks = new TaskApi(this.http);
|
|
1332
1688
|
this.spaces = new SpaceApi(this.http);
|
|
1333
1689
|
this.spaceMembers = new SpaceMemberApi(this.http);
|
|
1690
|
+
this.stewards = new StewardApi(this.http);
|
|
1334
1691
|
this.webhooks = new WebhookApi(this.http);
|
|
1335
1692
|
}
|
|
1336
1693
|
};
|
|
@@ -1384,7 +1741,8 @@ function renderActionHelp(base, resource, action) {
|
|
|
1384
1741
|
...f.valueHint ? { valueHint: f.valueHint } : {},
|
|
1385
1742
|
required: f.required ?? false,
|
|
1386
1743
|
...f.enum ? { enum: f.enum } : {},
|
|
1387
|
-
...f.default !== void 0 ? { default: f.default } : {}
|
|
1744
|
+
...f.default !== void 0 ? { default: f.default } : {},
|
|
1745
|
+
...f.repeatable ? { repeatable: true } : {}
|
|
1388
1746
|
})),
|
|
1389
1747
|
examples: action.examples ?? [],
|
|
1390
1748
|
writes: action.writes ?? "none",
|
|
@@ -1522,6 +1880,16 @@ function validateArgs(resource, action, parsed, options = {}) {
|
|
|
1522
1880
|
hint: `${usageHint} (e.g. --${flag} ${valuePart})`
|
|
1523
1881
|
});
|
|
1524
1882
|
}
|
|
1883
|
+
for (const [name, values] of Object.entries(parsed.repeatedFlags)) {
|
|
1884
|
+
if (values.length < 2)
|
|
1885
|
+
continue;
|
|
1886
|
+
const spec = declaredFlags.find((f) => f.name === name);
|
|
1887
|
+
if (spec?.repeatable)
|
|
1888
|
+
continue;
|
|
1889
|
+
throw usage(`duplicate flag --${name} (given ${values.length} times; only the last value would be used)`, {
|
|
1890
|
+
hint: usageHint
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1525
1893
|
for (const spec of declaredFlags) {
|
|
1526
1894
|
if (!spec.required)
|
|
1527
1895
|
continue;
|
|
@@ -1631,6 +1999,8 @@ function resolveToken(flags, env, context, baseUrl, saved, options) {
|
|
|
1631
1999
|
return { token: explicitToken, tokenSource: "flag" };
|
|
1632
2000
|
if (!options.excludeTaskToken && env.MORULA_TASK_TOKEN)
|
|
1633
2001
|
return { token: env.MORULA_TASK_TOKEN, tokenSource: "env:MORULA_TASK_TOKEN" };
|
|
2002
|
+
if (!options.excludeTaskToken && env.MORULA_SESSION_TOKEN)
|
|
2003
|
+
return { token: env.MORULA_SESSION_TOKEN, tokenSource: "env:MORULA_SESSION_TOKEN" };
|
|
1634
2004
|
if (env.MORULA_TOKEN)
|
|
1635
2005
|
return { token: env.MORULA_TOKEN, tokenSource: "env:MORULA_TOKEN" };
|
|
1636
2006
|
const savedToken = saved?.url && normalizeBaseUrl(saved.url) === baseUrl ? saved.token : void 0;
|
|
@@ -1775,6 +2145,10 @@ async function run(argv, deps = {}) {
|
|
|
1775
2145
|
code: error.code,
|
|
1776
2146
|
message: error.message,
|
|
1777
2147
|
...error.retryable ? { retryable: true } : {},
|
|
2148
|
+
// 服务端业务错误码(如 SESSION_SCOPE_DENIED):`code` 是按 HTTP 状态派生的粗档位,
|
|
2149
|
+
// 行为契约要判断的是业务码——不打印出来,契约就永远不可执行
|
|
2150
|
+
// (issues/api-client-drops-business-error-code.md)。退出码仍由 `error.exitCode` 决定。
|
|
2151
|
+
...error instanceof MorulaApiError && error.errorCode ? { errorCode: error.errorCode } : {},
|
|
1778
2152
|
// 新增字段(可选):可执行的下一步 + typo 建议
|
|
1779
2153
|
...error instanceof CliError && error.hint ? { hint: error.hint } : {},
|
|
1780
2154
|
...error instanceof CliError && error.didYouMean ? { didYouMean: error.didYouMean } : {}
|
|
@@ -2293,8 +2667,27 @@ var init_daemon_config = __esm({
|
|
|
2293
2667
|
|
|
2294
2668
|
// src/daemon/daemon-lock.ts
|
|
2295
2669
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
2296
|
-
import { closeSync as closeSync2, mkdirSync as mkdirSync4, openSync as openSync2, readFileSync as readFileSync2, renameSync, rmSync as rmSync4, writeFileSync as writeFileSync3, writeSync } from "node:fs";
|
|
2670
|
+
import { closeSync as closeSync2, existsSync as existsSync5, mkdirSync as mkdirSync4, openSync as openSync2, readFileSync as readFileSync2, renameSync, rmSync as rmSync4, writeFileSync as writeFileSync3, writeSync } from "node:fs";
|
|
2297
2671
|
import { dirname as dirname4, join as join6 } from "node:path";
|
|
2672
|
+
function daemonPidPath(configDir) {
|
|
2673
|
+
return join6(configDir, "daemon", DAEMON_PID_FILE);
|
|
2674
|
+
}
|
|
2675
|
+
function readPidFile(file) {
|
|
2676
|
+
if (!existsSync5(file))
|
|
2677
|
+
return 0;
|
|
2678
|
+
try {
|
|
2679
|
+
return Number(readFileSync2(file, "utf8").trim() || 0);
|
|
2680
|
+
} catch {
|
|
2681
|
+
return 0;
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
function probeDaemonInstance(configDir) {
|
|
2685
|
+
const lock = probeDaemonLock(join6(configDir, "daemon", DAEMON_LOCK_FILE));
|
|
2686
|
+
const lockPid = lock.state === "active" ? lock.record.pid : 0;
|
|
2687
|
+
const filePid = readPidFile(daemonPidPath(configDir));
|
|
2688
|
+
const pid = lockPid || (isProcessAlive(filePid) ? filePid : 0);
|
|
2689
|
+
return { running: pid > 0, pid: pid > 0 ? pid : null, lockState: lock.state };
|
|
2690
|
+
}
|
|
2298
2691
|
function isProcessAlive(pid) {
|
|
2299
2692
|
if (!pid || pid <= 0)
|
|
2300
2693
|
return false;
|
|
@@ -2444,11 +2837,12 @@ async function stopDaemonByPid(pid, options = {}) {
|
|
|
2444
2837
|
await waitForExit(pid, 2e3, pollMs);
|
|
2445
2838
|
return { pid, outcome: "killed" };
|
|
2446
2839
|
}
|
|
2447
|
-
var DAEMON_LOCK_FILE;
|
|
2840
|
+
var DAEMON_LOCK_FILE, DAEMON_PID_FILE;
|
|
2448
2841
|
var init_daemon_lock = __esm({
|
|
2449
2842
|
"src/daemon/daemon-lock.ts"() {
|
|
2450
2843
|
"use strict";
|
|
2451
2844
|
DAEMON_LOCK_FILE = "daemon.lock";
|
|
2845
|
+
DAEMON_PID_FILE = "daemon.pid";
|
|
2452
2846
|
}
|
|
2453
2847
|
});
|
|
2454
2848
|
|
|
@@ -2458,10 +2852,9 @@ __export(portal_instances_exports, {
|
|
|
2458
2852
|
candidateConfigFiles: () => candidateConfigFiles,
|
|
2459
2853
|
createPortalEnvironmentResolver: () => createPortalEnvironmentResolver,
|
|
2460
2854
|
normalizeBaseUrl: () => normalizeBaseUrl2,
|
|
2461
|
-
parseBaseUrl: () => parseBaseUrl
|
|
2462
|
-
readPidFile: () => readPidFile
|
|
2855
|
+
parseBaseUrl: () => parseBaseUrl
|
|
2463
2856
|
});
|
|
2464
|
-
import {
|
|
2857
|
+
import { readdirSync, statSync } from "node:fs";
|
|
2465
2858
|
import { homedir as homedir5 } from "node:os";
|
|
2466
2859
|
import { dirname as dirname5, join as join7 } from "node:path";
|
|
2467
2860
|
function parseBaseUrl(raw) {
|
|
@@ -2527,16 +2920,6 @@ function readContexts(file) {
|
|
|
2527
2920
|
return null;
|
|
2528
2921
|
}
|
|
2529
2922
|
}
|
|
2530
|
-
function readPidFile(configDir) {
|
|
2531
|
-
const file = join7(configDir, "daemon", "daemon.pid");
|
|
2532
|
-
if (!existsSync5(file))
|
|
2533
|
-
return 0;
|
|
2534
|
-
try {
|
|
2535
|
-
return Number(readFileSync3(file, "utf8").trim() || 0);
|
|
2536
|
-
} catch {
|
|
2537
|
-
return 0;
|
|
2538
|
-
}
|
|
2539
|
-
}
|
|
2540
2923
|
function createPortalEnvironmentResolver(options = {}) {
|
|
2541
2924
|
const env = options.env ?? process.env;
|
|
2542
2925
|
const log = options.log ?? (() => {
|
|
@@ -2565,7 +2948,7 @@ function createPortalEnvironmentResolver(options = {}) {
|
|
|
2565
2948
|
baseUrl: entry.url,
|
|
2566
2949
|
normalized,
|
|
2567
2950
|
token: entry.token,
|
|
2568
|
-
hasLiveDaemon:
|
|
2951
|
+
hasLiveDaemon: probeDaemonInstance(configDir).running,
|
|
2569
2952
|
isDefaultDir: configDir === defaultConfigDir,
|
|
2570
2953
|
configMtimeMs: configMtimeMs(file)
|
|
2571
2954
|
});
|
|
@@ -2611,6 +2994,25 @@ function createPortalEnvironmentResolver(options = {}) {
|
|
|
2611
2994
|
return void 0;
|
|
2612
2995
|
}
|
|
2613
2996
|
},
|
|
2997
|
+
daemonId: () => {
|
|
2998
|
+
try {
|
|
2999
|
+
return loadDaemonConfig(new FileConfigStore(join7(instance.configDir, "config.yaml")))?.daemonId;
|
|
3000
|
+
} catch {
|
|
3001
|
+
return void 0;
|
|
3002
|
+
}
|
|
3003
|
+
},
|
|
3004
|
+
isDeviceOnline: async (daemonId) => {
|
|
3005
|
+
if (!instance.token)
|
|
3006
|
+
return null;
|
|
3007
|
+
try {
|
|
3008
|
+
const client = createMorulaClient({ baseUrl: instance.baseUrl, token: instance.token, fetch: options.fetch });
|
|
3009
|
+
const rows = await client.http.get("/agent-runtimes");
|
|
3010
|
+
return (Array.isArray(rows) ? rows : []).some((row) => row.daemonId === daemonId && row.status === "online");
|
|
3011
|
+
} catch (error) {
|
|
3012
|
+
log(`[portal] \u67E5\u8BE2 ${instance.context} \u7684\u8BBE\u5907\u5728\u7EBF\u72B6\u6001\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`);
|
|
3013
|
+
return null;
|
|
3014
|
+
}
|
|
3015
|
+
},
|
|
2614
3016
|
validateToken: async (token) => {
|
|
2615
3017
|
if (!instance.token)
|
|
2616
3018
|
return false;
|
|
@@ -2669,9 +3071,13 @@ var init_portal_instances = __esm({
|
|
|
2669
3071
|
var portal_server_exports = {};
|
|
2670
3072
|
__export(portal_server_exports, {
|
|
2671
3073
|
DEFAULT_PORTAL_PORT: () => DEFAULT_PORTAL_PORT,
|
|
2672
|
-
PortalServer: () => PortalServer
|
|
3074
|
+
PortalServer: () => PortalServer,
|
|
3075
|
+
REGISTRATION_CONFIRM_TIMEOUT_MS: () => REGISTRATION_CONFIRM_TIMEOUT_MS,
|
|
3076
|
+
START_CONFIRM_TIMEOUT_MS: () => START_CONFIRM_TIMEOUT_MS,
|
|
3077
|
+
waitForDaemonRegistration: () => waitForDaemonRegistration,
|
|
3078
|
+
waitForDaemonStart: () => waitForDaemonStart
|
|
2673
3079
|
});
|
|
2674
|
-
import {
|
|
3080
|
+
import { readFileSync as readFileSync3, rmSync as rmSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2675
3081
|
import { createServer as createServer2 } from "node:http";
|
|
2676
3082
|
import { homedir as homedir6 } from "node:os";
|
|
2677
3083
|
import { join as join8 } from "node:path";
|
|
@@ -2679,35 +3085,61 @@ function stateDir() {
|
|
|
2679
3085
|
return process.env.MORULA_CONFIG_DIR ?? join8(homedir6(), ".morula-cli");
|
|
2680
3086
|
}
|
|
2681
3087
|
function pidFileIn(configDir) {
|
|
2682
|
-
return join8(configDir, "daemon",
|
|
3088
|
+
return join8(configDir, "daemon", DAEMON_PID_FILE);
|
|
2683
3089
|
}
|
|
2684
3090
|
function daemonLogIn(configDir) {
|
|
2685
3091
|
return join8(configDir, "daemon", "daemon.log");
|
|
2686
3092
|
}
|
|
2687
|
-
function
|
|
2688
|
-
|
|
2689
|
-
|
|
3093
|
+
async function waitForDaemonStart(configDir, childPid, timeoutMs = START_CONFIRM_TIMEOUT_MS) {
|
|
3094
|
+
const deadline = Date.now() + timeoutMs;
|
|
3095
|
+
for (; ; ) {
|
|
3096
|
+
const probe = probeDaemonInstance(configDir);
|
|
3097
|
+
if (probe.running)
|
|
3098
|
+
return { running: true, pid: probe.pid };
|
|
3099
|
+
if (!isProcessAlive(childPid))
|
|
3100
|
+
return { running: false, reason: "exited" };
|
|
3101
|
+
if (Date.now() >= deadline)
|
|
3102
|
+
return { running: false, reason: "timeout" };
|
|
3103
|
+
await new Promise((resolve2) => setTimeout(resolve2, START_CONFIRM_POLL_MS));
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
async function waitForDaemonRegistration(isDeviceOnline, daemonId, childPid, timeoutMs = REGISTRATION_CONFIRM_TIMEOUT_MS) {
|
|
3107
|
+
const deadline = Date.now() + timeoutMs;
|
|
3108
|
+
let queryFailed = false;
|
|
3109
|
+
for (; ; ) {
|
|
3110
|
+
const online = await isDeviceOnline(daemonId);
|
|
3111
|
+
if (online === true)
|
|
3112
|
+
return "registered";
|
|
3113
|
+
if (online === null)
|
|
3114
|
+
queryFailed = true;
|
|
3115
|
+
if (!isProcessAlive(childPid))
|
|
3116
|
+
return "exited";
|
|
3117
|
+
if (Date.now() >= deadline)
|
|
3118
|
+
return queryFailed ? "unknown" : "not-registered";
|
|
3119
|
+
await new Promise((resolve2) => setTimeout(resolve2, START_CONFIRM_POLL_MS));
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
function tailDaemonLog(configDir, maxLines = 5) {
|
|
2690
3123
|
try {
|
|
2691
|
-
|
|
2692
|
-
return
|
|
3124
|
+
const lines = readFileSync3(daemonLogIn(configDir), "utf8").trimEnd().split("\n");
|
|
3125
|
+
return lines.slice(-maxLines).join(" / ") || null;
|
|
2693
3126
|
} catch {
|
|
2694
|
-
return
|
|
3127
|
+
return null;
|
|
2695
3128
|
}
|
|
2696
3129
|
}
|
|
2697
|
-
|
|
2698
|
-
if (!existsSync6(file))
|
|
2699
|
-
return 0;
|
|
2700
|
-
return Number(readFileSync4(file, "utf8").trim() || 0);
|
|
2701
|
-
}
|
|
2702
|
-
var DEFAULT_PORTAL_PORT, DEFAULT_CORS_ORIGINS, PortalServer;
|
|
3130
|
+
var DEFAULT_PORTAL_PORT, DEFAULT_CORS_ORIGINS, START_CONFIRM_TIMEOUT_MS, REGISTRATION_CONFIRM_TIMEOUT_MS, START_CONFIRM_POLL_MS, PortalServer;
|
|
2703
3131
|
var init_portal_server = __esm({
|
|
2704
3132
|
"src/daemon/portal-server.ts"() {
|
|
2705
3133
|
"use strict";
|
|
2706
3134
|
init_daemon_config();
|
|
3135
|
+
init_daemon_lock();
|
|
2707
3136
|
init_portal_instances();
|
|
2708
3137
|
init_spawn();
|
|
2709
3138
|
DEFAULT_PORTAL_PORT = 23491;
|
|
2710
3139
|
DEFAULT_CORS_ORIGINS = ["*"];
|
|
3140
|
+
START_CONFIRM_TIMEOUT_MS = 5e3;
|
|
3141
|
+
REGISTRATION_CONFIRM_TIMEOUT_MS = 5e3;
|
|
3142
|
+
START_CONFIRM_POLL_MS = 200;
|
|
2711
3143
|
PortalServer = class {
|
|
2712
3144
|
port;
|
|
2713
3145
|
server = null;
|
|
@@ -2848,14 +3280,13 @@ var init_portal_server = __esm({
|
|
|
2848
3280
|
if (failed)
|
|
2849
3281
|
return;
|
|
2850
3282
|
const configDir = target?.configDir ?? stateDir();
|
|
2851
|
-
const
|
|
2852
|
-
const running = isPidAlive(pid);
|
|
3283
|
+
const probe = probeDaemonInstance(configDir);
|
|
2853
3284
|
const runtimeId = target ? target.runtimeId() : this.store ? loadDaemonConfig(this.store)?.runtimeId : void 0;
|
|
2854
3285
|
const self = target ? { context: target.context, baseUrl: target.baseUrl } : this.selfEnvironment();
|
|
2855
3286
|
this.json(res, 200, {
|
|
2856
3287
|
ok: true,
|
|
2857
|
-
daemonRunning: running,
|
|
2858
|
-
pid:
|
|
3288
|
+
daemonRunning: probe.running,
|
|
3289
|
+
pid: probe.pid,
|
|
2859
3290
|
runtimeId: runtimeId ?? null,
|
|
2860
3291
|
// 支持按请求声明目标环境(前端据此决定是否走多环境路径;缺省 = 旧版 portal)
|
|
2861
3292
|
envRouting: true,
|
|
@@ -2870,10 +3301,10 @@ var init_portal_server = __esm({
|
|
|
2870
3301
|
if (!await this.authorize(req, res, target))
|
|
2871
3302
|
return;
|
|
2872
3303
|
const configDir = target?.configDir ?? stateDir();
|
|
2873
|
-
const
|
|
2874
|
-
const existing =
|
|
2875
|
-
if (
|
|
2876
|
-
this.json(res, 200, { ok: true, pid: existing, alreadyRunning: true });
|
|
3304
|
+
const where = target ? `\uFF08\u73AF\u5883 ${target.context}\uFF0C\u76EE\u5F55 ${configDir}\uFF09` : "";
|
|
3305
|
+
const existing = probeDaemonInstance(configDir);
|
|
3306
|
+
if (existing.running) {
|
|
3307
|
+
this.json(res, 200, { ok: true, pid: existing.pid, alreadyRunning: true });
|
|
2877
3308
|
return;
|
|
2878
3309
|
}
|
|
2879
3310
|
const result = spawnDaemonProcess({
|
|
@@ -2884,18 +3315,54 @@ var init_portal_server = __esm({
|
|
|
2884
3315
|
this.json(res, 500, { ok: false, error: result.error ?? `spawn \u5931\u8D25\uFF08pid=${result.pid}\uFF09` });
|
|
2885
3316
|
return;
|
|
2886
3317
|
}
|
|
3318
|
+
const confirmed = await waitForDaemonStart(configDir, result.pid);
|
|
3319
|
+
if (!confirmed.running) {
|
|
3320
|
+
if (!isProcessAlive(readPidFile(pidFileIn(configDir))))
|
|
3321
|
+
rmSync5(pidFileIn(configDir), { force: true });
|
|
3322
|
+
this.log(`[portal] daemon \u672A\u80FD\u542F\u52A8 pid=${result.pid}${where}\uFF1A${confirmed.reason}`);
|
|
3323
|
+
this.json(res, 500, {
|
|
3324
|
+
ok: false,
|
|
3325
|
+
error: confirmed.reason === "exited" ? "DAEMON_EXITED" : "DAEMON_START_TIMEOUT",
|
|
3326
|
+
hint: `daemon \u672A\u542F\u52A8\u6210\u529F\uFF08${confirmed.reason === "exited" ? "\u5B50\u8FDB\u7A0B\u5DF2\u9000\u51FA" : "\u7B49\u5F85\u8D85\u65F6"}\uFF09\uFF1A${tailDaemonLog(configDir) ?? "\u8BE6\u89C1 daemon \u65E5\u5FD7"}`,
|
|
3327
|
+
logFile: daemonLogIn(configDir)
|
|
3328
|
+
});
|
|
3329
|
+
return;
|
|
3330
|
+
}
|
|
3331
|
+
if (confirmed.pid !== result.pid) {
|
|
3332
|
+
this.log(`[portal] \u5DF2\u6709\u5B9E\u4F8B\u5728\u8FD0\u884C pid=${confirmed.pid}${where}\uFF0C\u672C\u6B21 spawn \u7684\u5B50\u8FDB\u7A0B\u5DF2\u8BA9\u4F4D`);
|
|
3333
|
+
this.json(res, 200, { ok: true, pid: confirmed.pid, alreadyRunning: true });
|
|
3334
|
+
return;
|
|
3335
|
+
}
|
|
3336
|
+
const daemonId = target?.daemonId();
|
|
3337
|
+
if (target && daemonId) {
|
|
3338
|
+
const reg = await waitForDaemonRegistration(target.isDeviceOnline, daemonId, confirmed.pid);
|
|
3339
|
+
if (reg === "exited" || reg === "not-registered") {
|
|
3340
|
+
if (!isProcessAlive(readPidFile(pidFileIn(configDir))))
|
|
3341
|
+
rmSync5(pidFileIn(configDir), { force: true });
|
|
3342
|
+
this.log(`[portal] daemon \u8FDB\u7A0B\u5DF2\u8D77\u6765\u4F46\u672A\u6CE8\u518C\u4E0A\u7EBF pid=${confirmed.pid}${where}\uFF1A${reg}`);
|
|
3343
|
+
this.json(res, 500, {
|
|
3344
|
+
ok: false,
|
|
3345
|
+
error: "DAEMON_NOT_REGISTERED",
|
|
3346
|
+
hint: `daemon \u8FDB\u7A0B\u5DF2\u542F\u52A8\u4F46\u672A\u5728\u670D\u52A1\u7AEF\u6CE8\u518C\u4E0A\u7EBF\uFF08${reg === "exited" ? "\u8FDB\u7A0B\u5DF2\u9000\u51FA" : "\u7B49\u5F85\u8D85\u65F6"}\uFF09\uFF1A${tailDaemonLog(configDir) ?? "\u8BE6\u89C1 daemon \u65E5\u5FD7"}`,
|
|
3347
|
+
logFile: daemonLogIn(configDir)
|
|
3348
|
+
});
|
|
3349
|
+
return;
|
|
3350
|
+
}
|
|
3351
|
+
if (reg === "unknown")
|
|
3352
|
+
this.log(`[portal] \u65E0\u6CD5\u786E\u8BA4\u6CE8\u518C\u72B6\u6001\uFF08\u67E5\u8BE2\u5931\u8D25\uFF09pid=${confirmed.pid}${where}\uFF0C\u6309\u542F\u52A8\u6210\u529F\u5904\u7406`);
|
|
3353
|
+
}
|
|
2887
3354
|
try {
|
|
2888
|
-
writeFileSync4(
|
|
3355
|
+
writeFileSync4(pidFileIn(configDir), String(confirmed.pid), { mode: 384 });
|
|
2889
3356
|
} catch {
|
|
2890
3357
|
try {
|
|
2891
|
-
process.kill(
|
|
3358
|
+
process.kill(confirmed.pid, "SIGTERM");
|
|
2892
3359
|
} catch {
|
|
2893
3360
|
}
|
|
2894
3361
|
this.json(res, 500, { ok: false, error: "pid \u6587\u4EF6\u5199\u5165\u5931\u8D25" });
|
|
2895
3362
|
return;
|
|
2896
3363
|
}
|
|
2897
|
-
this.log(`[portal] daemon \u5DF2\u62C9\u8D77 pid=${
|
|
2898
|
-
this.json(res, 200, { ok: true, pid:
|
|
3364
|
+
this.log(`[portal] daemon \u5DF2\u62C9\u8D77 pid=${confirmed.pid}${where}`);
|
|
3365
|
+
this.json(res, 200, { ok: true, pid: confirmed.pid, alreadyRunning: false });
|
|
2899
3366
|
}
|
|
2900
3367
|
async handleStop(req, res, url) {
|
|
2901
3368
|
const { target, failed } = this.resolveTarget(res, url);
|
|
@@ -2904,15 +3371,15 @@ var init_portal_server = __esm({
|
|
|
2904
3371
|
if (!await this.authorize(req, res, target))
|
|
2905
3372
|
return;
|
|
2906
3373
|
const configDir = target?.configDir ?? stateDir();
|
|
2907
|
-
const existing =
|
|
2908
|
-
if (
|
|
3374
|
+
const existing = probeDaemonInstance(configDir);
|
|
3375
|
+
if (existing.running) {
|
|
2909
3376
|
try {
|
|
2910
|
-
process.kill(existing, "SIGTERM");
|
|
3377
|
+
process.kill(existing.pid, "SIGTERM");
|
|
2911
3378
|
} catch {
|
|
2912
3379
|
}
|
|
2913
|
-
this.log(`[portal] daemon \u505C\u6B62\u4FE1\u53F7\u5DF2\u53D1\u9001 pid=${existing}${target ? `\uFF08\u73AF\u5883 ${target.context}\uFF09` : ""}`);
|
|
3380
|
+
this.log(`[portal] daemon \u505C\u6B62\u4FE1\u53F7\u5DF2\u53D1\u9001 pid=${existing.pid}${target ? `\uFF08\u73AF\u5883 ${target.context}\uFF09` : ""}`);
|
|
2914
3381
|
}
|
|
2915
|
-
this.json(res, 200, { ok: true, pid: existing
|
|
3382
|
+
this.json(res, 200, { ok: true, pid: existing.pid });
|
|
2916
3383
|
}
|
|
2917
3384
|
/**
|
|
2918
3385
|
* start/stop 鉴权:目标环境用**它自己的**服务端校验 token(这正是跨环境 401 的修复点);
|
|
@@ -3020,6 +3487,7 @@ __export(acp_connection_exports, {
|
|
|
3020
3487
|
AcpConnection: () => AcpConnection,
|
|
3021
3488
|
DEFAULT_ACP_INIT_TIMEOUT_MS: () => DEFAULT_ACP_INIT_TIMEOUT_MS,
|
|
3022
3489
|
countDimAcpProcesses: () => countDimAcpProcesses,
|
|
3490
|
+
normalizeAcpConfigOptions: () => normalizeAcpConfigOptions,
|
|
3023
3491
|
resolveAcpInitTimeoutMs: () => resolveAcpInitTimeoutMs
|
|
3024
3492
|
});
|
|
3025
3493
|
import { execFileSync as execFileSync4, spawn as spawn3 } from "node:child_process";
|
|
@@ -3048,6 +3516,16 @@ function countDimAcpProcesses() {
|
|
|
3048
3516
|
return null;
|
|
3049
3517
|
}
|
|
3050
3518
|
}
|
|
3519
|
+
function normalizeAcpConfigOptions(raw) {
|
|
3520
|
+
if (Array.isArray(raw))
|
|
3521
|
+
return raw;
|
|
3522
|
+
if (raw && typeof raw === "object") {
|
|
3523
|
+
const nested = raw.configOptions;
|
|
3524
|
+
if (Array.isArray(nested))
|
|
3525
|
+
return nested;
|
|
3526
|
+
}
|
|
3527
|
+
return null;
|
|
3528
|
+
}
|
|
3051
3529
|
var DEFAULT_ACP_INIT_TIMEOUT_MS, cachedDimVersion, requestSeq, AcpConnection;
|
|
3052
3530
|
var init_acp_connection = __esm({
|
|
3053
3531
|
"src/daemon/acp/acp-connection.ts"() {
|
|
@@ -3063,6 +3541,7 @@ var init_acp_connection = __esm({
|
|
|
3063
3541
|
outcome: { outcome: "selected", optionId: "allow" }
|
|
3064
3542
|
}));
|
|
3065
3543
|
this.onExit = options.onExit;
|
|
3544
|
+
this.elicitationHandler = options.onElicitation;
|
|
3066
3545
|
}
|
|
3067
3546
|
options;
|
|
3068
3547
|
child = null;
|
|
@@ -3070,6 +3549,7 @@ var init_acp_connection = __esm({
|
|
|
3070
3549
|
pending = /* @__PURE__ */ new Map();
|
|
3071
3550
|
exited = false;
|
|
3072
3551
|
permissionHandler;
|
|
3552
|
+
elicitationHandler;
|
|
3073
3553
|
onExit;
|
|
3074
3554
|
/** sessionId → 当前 update 处理器(同一 session 单活跃 prompt) */
|
|
3075
3555
|
updateHandlers = /* @__PURE__ */ new Map();
|
|
@@ -3103,9 +3583,9 @@ var init_acp_connection = __esm({
|
|
|
3103
3583
|
process.stderr.write(`[daemon] dim spawn \u5931\u8D25 ${dimPath}\uFF08\u5C1D\u8BD5\u4E0B\u4E00\u4E2A\u5019\u9009\uFF09: ${message}
|
|
3104
3584
|
`);
|
|
3105
3585
|
try {
|
|
3106
|
-
const { existsSync:
|
|
3586
|
+
const { existsSync: existsSync18 } = await import("node:fs");
|
|
3107
3587
|
process.stderr.write(
|
|
3108
|
-
`[daemon] diag: fileExists=${
|
|
3588
|
+
`[daemon] diag: fileExists=${existsSync18(dimPath)} cwdExists=${this.options.cwd ? existsSync18(this.options.cwd) : "n/a"} PATH=${(process.env.PATH ?? "").slice(0, 140)} DYLD=${process.env.DYLD_LIBRARY_PATH ?? "-"} NODE_OPTIONS=${process.env.NODE_OPTIONS ?? "-"}
|
|
3109
3589
|
`
|
|
3110
3590
|
);
|
|
3111
3591
|
} catch {
|
|
@@ -3155,7 +3635,12 @@ var init_acp_connection = __esm({
|
|
|
3155
3635
|
child.stdout.on("data", (d) => this.onStdout(d.toString()));
|
|
3156
3636
|
const init = {
|
|
3157
3637
|
protocolVersion: 1,
|
|
3158
|
-
|
|
3638
|
+
// elicitation.form 只在**有处理器时**声明:声明了却不应答会让 dim 挂起等用户输入。
|
|
3639
|
+
// 形状是对象而非 boolean(V2 实测:boolean 被 dim 以 -32602 拒绝)。
|
|
3640
|
+
clientCapabilities: {
|
|
3641
|
+
auth: { terminal: true },
|
|
3642
|
+
...this.elicitationHandler ? { elicitation: { form: {} } } : {}
|
|
3643
|
+
},
|
|
3159
3644
|
clientInfo: { name: "morula-runtime", version: "1.0.0" }
|
|
3160
3645
|
};
|
|
3161
3646
|
const initTimeoutMs = resolveAcpInitTimeoutMs();
|
|
@@ -3200,26 +3685,38 @@ var init_acp_connection = __esm({
|
|
|
3200
3685
|
return this.request("session/set_config_option", { sessionId, configId, value }).then(() => {
|
|
3201
3686
|
});
|
|
3202
3687
|
}
|
|
3688
|
+
/**
|
|
3689
|
+
* 设置会话配置项并**全量返回** configOptions(ACP 规范:set_config_option 响应即全量配置)。
|
|
3690
|
+
* 会话宿主用(§4.4 配置变更回写);任务链路只需 fire-and-forget,继续用 setConfigOption。
|
|
3691
|
+
*
|
|
3692
|
+
* **返回已归一化为数组**:ACP 的响应体是 `{ configOptions: ConfigOption[] }`(对象),
|
|
3693
|
+
* 而 `session/new` / `resume` 的 `configOptions` 字段本身就是数组——两处形状不同,
|
|
3694
|
+
* 调用方(会话宿主 → 服务端落库 → 前端输入区值域)必须拿到同一种形状,否则前端拿到对象会崩。
|
|
3695
|
+
*/
|
|
3696
|
+
setConfigOptionFull(sessionId, configId, value) {
|
|
3697
|
+
return this.request("session/set_config_option", { sessionId, configId, value }).then((res) => normalizeAcpConfigOptions(res) ?? []);
|
|
3698
|
+
}
|
|
3203
3699
|
/**
|
|
3204
3700
|
* 发起 prompt:立即返回;事件流经 updateHandler 推送;返回的 Promise 在
|
|
3205
3701
|
* turn 结束(prompt 响应)时 resolve。
|
|
3206
3702
|
*
|
|
3207
3703
|
* @param sessionId ACP 会话 ID
|
|
3208
|
-
* @param
|
|
3704
|
+
* @param prompt 用户输入。字符串 = 纯文本;数组 = 多模态 content block
|
|
3705
|
+
* (`image` block 用于对话附件,见 steward-core-acp-migration §五 P2-3)
|
|
3209
3706
|
* @param updateHandler 会话事件回调
|
|
3210
|
-
* @param timeoutMs prompt 整轮 RPC
|
|
3211
|
-
*
|
|
3212
|
-
*
|
|
3213
|
-
*
|
|
3707
|
+
* @param timeoutMs prompt 整轮 RPC 超时。**未设 / <= 0 = 不设超时(默认)**:dim 直到整轮
|
|
3708
|
+
* agent 循环结束(stopReason)才回复 prompt,多步工具执行/长模型生成都会占满窗口,
|
|
3709
|
+
* 给 RPC 层设超时等于给「合法的长回合」埋误杀点。限时与否由调用方(executor 的
|
|
3710
|
+
* 单回合预算)决定,executor 默认即传 0(不设超时)。
|
|
3214
3711
|
*/
|
|
3215
|
-
prompt(sessionId,
|
|
3712
|
+
prompt(sessionId, prompt, updateHandler, timeoutMs = 0) {
|
|
3216
3713
|
if (this.updateHandlers.has(sessionId)) {
|
|
3217
3714
|
return Promise.reject(new Error(`session ${sessionId} \u5DF2\u6709\u6D3B\u8DC3 prompt\uFF08ACP \u5355\u6D3B\u8DC3\u7EA6\u675F\uFF09`));
|
|
3218
3715
|
}
|
|
3219
3716
|
this.updateHandlers.set(sessionId, updateHandler);
|
|
3220
3717
|
const params = {
|
|
3221
3718
|
sessionId,
|
|
3222
|
-
prompt: [{ type: "text", text }],
|
|
3719
|
+
prompt: typeof prompt === "string" ? [{ type: "text", text: prompt }] : prompt,
|
|
3223
3720
|
messageId: `m-${randomUUID2().slice(0, 8)}`
|
|
3224
3721
|
};
|
|
3225
3722
|
return this.request("session/prompt", params, timeoutMs).then(
|
|
@@ -3278,17 +3775,20 @@ var init_acp_connection = __esm({
|
|
|
3278
3775
|
return Promise.reject(new Error(`dim acp \u8FDB\u7A0B\u4E0D\u53EF\u7528\uFF08${method}\uFF09`));
|
|
3279
3776
|
const id = ++requestSeq;
|
|
3280
3777
|
return new Promise((resolve2, reject) => {
|
|
3281
|
-
const
|
|
3778
|
+
const onTimeout = () => {
|
|
3282
3779
|
this.pending.delete(id);
|
|
3283
3780
|
reject(new Error(`dim acp ${method} \u8D85\u65F6\uFF08${Math.round(timeoutMs / 1e3)}s\uFF09`));
|
|
3284
|
-
}
|
|
3781
|
+
};
|
|
3782
|
+
const timer = timeoutMs > 0 ? setTimeout(onTimeout, timeoutMs) : null;
|
|
3285
3783
|
this.pending.set(id, {
|
|
3286
3784
|
resolve: (v) => {
|
|
3287
|
-
|
|
3785
|
+
if (timer)
|
|
3786
|
+
clearTimeout(timer);
|
|
3288
3787
|
resolve2(v);
|
|
3289
3788
|
},
|
|
3290
3789
|
reject: (e) => {
|
|
3291
|
-
|
|
3790
|
+
if (timer)
|
|
3791
|
+
clearTimeout(timer);
|
|
3292
3792
|
reject(e);
|
|
3293
3793
|
}
|
|
3294
3794
|
});
|
|
@@ -3359,6 +3859,21 @@ var init_acp_connection = __esm({
|
|
|
3359
3859
|
);
|
|
3360
3860
|
return;
|
|
3361
3861
|
}
|
|
3862
|
+
if (method === "elicitation/create") {
|
|
3863
|
+
const id = msg.id;
|
|
3864
|
+
const params = msg.params;
|
|
3865
|
+
const handler = this.elicitationHandler;
|
|
3866
|
+
if (!handler) {
|
|
3867
|
+
this.respond(id, { action: "decline" });
|
|
3868
|
+
return;
|
|
3869
|
+
}
|
|
3870
|
+
void handler(params).then(
|
|
3871
|
+
(response) => this.respond(id, response),
|
|
3872
|
+
// 处理器异常(含超时):回 cancel,让 agent 走「用户没答」的分支,而不是挂死
|
|
3873
|
+
() => this.respond(id, { action: "cancel" })
|
|
3874
|
+
);
|
|
3875
|
+
return;
|
|
3876
|
+
}
|
|
3362
3877
|
if (method === "$/cancel_request") {
|
|
3363
3878
|
return;
|
|
3364
3879
|
}
|
|
@@ -3378,7 +3893,7 @@ __export(dim_home_exports, {
|
|
|
3378
3893
|
resolveIsolatedDimEnv: () => resolveIsolatedDimEnv
|
|
3379
3894
|
});
|
|
3380
3895
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
3381
|
-
import { chmodSync as chmodSync2, copyFileSync, existsSync as
|
|
3896
|
+
import { chmodSync as chmodSync2, copyFileSync, existsSync as existsSync6, readFileSync as readFileSync4, rmSync as rmSync6 } from "node:fs";
|
|
3382
3897
|
import { homedir as homedir8 } from "node:os";
|
|
3383
3898
|
import { join as join10 } from "node:path";
|
|
3384
3899
|
function isolatedDimHome() {
|
|
@@ -3405,11 +3920,11 @@ __export(dim_model_exports, {
|
|
|
3405
3920
|
import { createRequire } from "node:module";
|
|
3406
3921
|
import { spawnSync } from "node:child_process";
|
|
3407
3922
|
import { closeSync as closeSync3, existsSync as existsSync10, openSync as openSync3, readFileSync as readFileSync7, unlinkSync } from "node:fs";
|
|
3408
|
-
import { homedir as
|
|
3409
|
-
import { join as
|
|
3923
|
+
import { homedir as homedir11, tmpdir } from "node:os";
|
|
3924
|
+
import { join as join14 } from "node:path";
|
|
3410
3925
|
function dimSqlitePath(env) {
|
|
3411
3926
|
const home = env?.DIMCODE_HOME?.trim();
|
|
3412
|
-
const candidates = home && home !== "" ? [
|
|
3927
|
+
const candidates = home && home !== "" ? [join14(home, "v2", "dimcode.sqlite"), join14(home, "dimcode.sqlite")] : [join14(homedir11(), ".dimcode", "v2", "dimcode.sqlite")];
|
|
3413
3928
|
return candidates.find((p) => existsSync10(p)) ?? null;
|
|
3414
3929
|
}
|
|
3415
3930
|
function listDimModels(dimPath = "dim", env) {
|
|
@@ -3444,12 +3959,12 @@ function listDimModels(dimPath = "dim", env) {
|
|
|
3444
3959
|
function hostDimEnv(env) {
|
|
3445
3960
|
const home = env?.DIMCODE_HOME?.trim();
|
|
3446
3961
|
if (home && home !== "" && home.includes(".morula") && home.includes("dimcode")) {
|
|
3447
|
-
return { ...process.env, DIMCODE_HOME:
|
|
3962
|
+
return { ...process.env, DIMCODE_HOME: join14(homedir11(), ".dimcode", "v2") };
|
|
3448
3963
|
}
|
|
3449
3964
|
return env ? { ...env } : null;
|
|
3450
3965
|
}
|
|
3451
3966
|
function listDimModelsFromCli(dimPath, env) {
|
|
3452
|
-
const tmpFile =
|
|
3967
|
+
const tmpFile = join14(tmpdir(), `${DIM_MODELS_TMP_PREFIX}${process.pid}-${Date.now()}.json`);
|
|
3453
3968
|
const fd = openSync3(tmpFile, "w");
|
|
3454
3969
|
let collected = "";
|
|
3455
3970
|
try {
|
|
@@ -3486,8 +4001,8 @@ function listDimModelsFromCli(dimPath, env) {
|
|
|
3486
4001
|
}
|
|
3487
4002
|
function dimOAuthTokenPath(env) {
|
|
3488
4003
|
const home = env?.DIMCODE_HOME?.trim();
|
|
3489
|
-
const dir = home && home !== "" ? home :
|
|
3490
|
-
for (const p of [
|
|
4004
|
+
const dir = home && home !== "" ? home : join14(homedir11(), ".dimcode", "v2");
|
|
4005
|
+
for (const p of [join14(dir, "auth.json"), join14(dir, "v2", "auth.json")]) {
|
|
3491
4006
|
if (existsSync10(p))
|
|
3492
4007
|
return p;
|
|
3493
4008
|
}
|
|
@@ -3632,14 +4147,14 @@ init_dist2();
|
|
|
3632
4147
|
init_dist2();
|
|
3633
4148
|
|
|
3634
4149
|
// src/build-env.ts
|
|
3635
|
-
var BUILD_VERSION2 = "0.1.0-alpha.
|
|
4150
|
+
var BUILD_VERSION2 = "0.1.0-alpha.9";
|
|
3636
4151
|
|
|
3637
4152
|
// src/commands/daemon.ts
|
|
3638
4153
|
init_dist();
|
|
3639
4154
|
init_dist2();
|
|
3640
|
-
import { existsSync as existsSync17, mkdirSync as
|
|
3641
|
-
import { homedir as
|
|
3642
|
-
import { dirname as
|
|
4155
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync14, readFileSync as readFileSync11, rmSync as rmSync10, writeFileSync as writeFileSync12 } from "node:fs";
|
|
4156
|
+
import { homedir as homedir13 } from "node:os";
|
|
4157
|
+
import { dirname as dirname10, join as join22 } from "node:path";
|
|
3643
4158
|
|
|
3644
4159
|
// src/daemon/daemon-autostart.ts
|
|
3645
4160
|
init_portal_autostart();
|
|
@@ -3708,9 +4223,9 @@ init_portal_server();
|
|
|
3708
4223
|
// src/daemon/runner.ts
|
|
3709
4224
|
init_dist2();
|
|
3710
4225
|
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
3711
|
-
import { existsSync as existsSync15, mkdirSync as
|
|
4226
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync13, statSync as statSync4, writeFileSync as writeFileSync11 } from "node:fs";
|
|
3712
4227
|
import { arch, hostname } from "node:os";
|
|
3713
|
-
import { dirname as
|
|
4228
|
+
import { dirname as dirname8, join as join20 } from "node:path";
|
|
3714
4229
|
|
|
3715
4230
|
// src/daemon/acp/acp-connection-pool.ts
|
|
3716
4231
|
var DEFAULT_IDLE_MS = 30 * 60 * 1e3;
|
|
@@ -3881,6 +4396,17 @@ var DaemonApi = class {
|
|
|
3881
4396
|
...token ? { token } : {}
|
|
3882
4397
|
});
|
|
3883
4398
|
}
|
|
4399
|
+
/**
|
|
4400
|
+
* 终态结果回传(§6.2-⑤)。
|
|
4401
|
+
*
|
|
4402
|
+
* `runtimeId` **必传**:服务端按它校验任务归属(不匹配 → `RUNTIME_MISMATCH`,不落终态),
|
|
4403
|
+
* 缺失同样被拒(P1-a 定案「立即拒绝」——旧版 daemon 的任务会全部失败,升级期须先升执行设备,
|
|
4404
|
+
* 见 issues/agent-task-false-failure-on-daemon-fetch-error.md)。
|
|
4405
|
+
*
|
|
4406
|
+
* `cancelled: true`(取消留痕,issues/daemon-stream-events-silently-stop-mid-run.md 未实施项 5):
|
|
4407
|
+
* 本轮**被人取消**时只补记已产出内容(`result.text` → 服务端 `agent_tasks.result.cancelled.lastOutput`),
|
|
4408
|
+
* **不改任务状态**、不触发生命周期。旧服务端经 `whitelist` 剥离该字段 → 退化为原「终态回传被幂等丢弃」。
|
|
4409
|
+
*/
|
|
3884
4410
|
submitResult(taskId, body) {
|
|
3885
4411
|
return this.taskRequest(taskId, `/agent-tasks/${taskId}/result`, { body });
|
|
3886
4412
|
}
|
|
@@ -3954,7 +4480,7 @@ var DaemonApi = class {
|
|
|
3954
4480
|
recordEvent(taskId, body) {
|
|
3955
4481
|
return this.taskRequest(taskId, `/agent-tasks/${taskId}/events`, { body });
|
|
3956
4482
|
}
|
|
3957
|
-
/** ACP 流式事件(Phase 1):daemon → Server → WS
|
|
4483
|
+
/** ACP 流式事件(Phase 1 → P2-2):daemon → Server → WS 前端(`delta` 为 transient,不落库) */
|
|
3958
4484
|
recordStreamChunk(taskId, body) {
|
|
3959
4485
|
return this.taskRequest(taskId, `/agent-tasks/${taskId}/stream-chunk`, { body });
|
|
3960
4486
|
}
|
|
@@ -4015,20 +4541,201 @@ var DaemonApi = class {
|
|
|
4015
4541
|
getGitHubToken(taskId, repo) {
|
|
4016
4542
|
return this.client.get("/vcs/daemon-token", { taskId, repo });
|
|
4017
4543
|
}
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
* 这些任务的 agent 同样需要 MORULA_GITLAB_TOKEN / MORULA_GITLAB_URL 才能自己走 GitLab API
|
|
4024
|
-
* 兜底建 MR(否则链路两端都没有自愈路径)。旧版服务端强制校验 repo(400)——调用方需容忍失败。
|
|
4025
|
-
*/
|
|
4026
|
-
getSpaceDaemonToken(taskId) {
|
|
4027
|
-
return this.client.get("/vcs/daemon-token", { taskId });
|
|
4544
|
+
// ── 会话宿主机器协议(steward-core-acp-migration §4.3/§4.4,P0 骨架) ──
|
|
4545
|
+
/** 会话级 token 缓存(每会话独立凭证,注入工作区 env MORULA_SESSION_TOKEN) */
|
|
4546
|
+
sessionTokens = /* @__PURE__ */ new Map();
|
|
4547
|
+
setSessionToken(sessionId, token) {
|
|
4548
|
+
this.sessionTokens.set(sessionId, token);
|
|
4028
4549
|
}
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4550
|
+
clearSessionToken(sessionId) {
|
|
4551
|
+
this.sessionTokens.delete(sessionId);
|
|
4552
|
+
}
|
|
4553
|
+
/** 会话级机器协议请求(session token 通道;无 token 时回退设备级 client 凭证) */
|
|
4554
|
+
sessionRequest(sessionId, path, options = {}) {
|
|
4555
|
+
const token = this.sessionTokens.get(sessionId);
|
|
4556
|
+
return this.client.request(path, {
|
|
4557
|
+
method: "POST",
|
|
4558
|
+
...options,
|
|
4559
|
+
...token ? { token } : {}
|
|
4560
|
+
});
|
|
4561
|
+
}
|
|
4562
|
+
/** 会话级机器协议请求(可指定 method;elicitation 查询用 GET + 长轮询) */
|
|
4563
|
+
sessionRequestWithMethod(sessionId, method, path, options = {}) {
|
|
4564
|
+
const token = this.sessionTokens.get(sessionId);
|
|
4565
|
+
return this.client.request(path, {
|
|
4566
|
+
method,
|
|
4567
|
+
...options,
|
|
4568
|
+
...token ? { token } : {}
|
|
4569
|
+
});
|
|
4570
|
+
}
|
|
4571
|
+
/** 会话宿主事件上报(→ agent_events owner=session + WS exec:session 房间) */
|
|
4572
|
+
recordSessionEvent(sessionId, body) {
|
|
4573
|
+
return this.sessionRequest(sessionId, `/agent-sessions/${sessionId}/events`, { body });
|
|
4574
|
+
}
|
|
4575
|
+
/**
|
|
4576
|
+
* 会话流式增量上报(P2-2:transient,**不落库**;服务端转推 `agent-task:delta` 到 exec:session 房间)。
|
|
4577
|
+
* 与 `recordSessionEvent` 的语义差别:这里是「可丢的过程信号」,失败不重试、不占 seq。
|
|
4578
|
+
* `kind`:`text` = assistant 正文,`thinking` = 推理原文(durable 对应 `agent_thought_delta`)。
|
|
4579
|
+
*/
|
|
4580
|
+
recordSessionStreamChunk(sessionId, body) {
|
|
4581
|
+
return this.sessionRequest(sessionId, `/agent-sessions/${sessionId}/stream-chunk`, { body });
|
|
4582
|
+
}
|
|
4583
|
+
/** 会话绑定与指针回传(dimSessionId / workspaceDir / runtimeId;只写绑定列不改状态) */
|
|
4584
|
+
reportSessionBinding(sessionId, body) {
|
|
4585
|
+
return this.sessionRequest(sessionId, `/agent-sessions/${sessionId}/session`, { body });
|
|
4586
|
+
}
|
|
4587
|
+
/**
|
|
4588
|
+
* 会话关闭上报。
|
|
4589
|
+
* `reason='idle'`(空闲回收)→ 服务端只回到 `idle`(保留 dimSessionId,下次可 resume);
|
|
4590
|
+
* `reason='command'`(用户/平台显式关闭)→ 终态 `closed`(§4.4/§4.1 的衔接)。
|
|
4591
|
+
*/
|
|
4592
|
+
closeAgentSession(sessionId, reason = "command") {
|
|
4593
|
+
return this.sessionRequest(sessionId, `/agent-sessions/${sessionId}/close`, { body: { reason } });
|
|
4594
|
+
}
|
|
4595
|
+
/**
|
|
4596
|
+
* 结构化提问登记(§4.6 ②):服务端落库 + 推前端确认卡,立即返回 elicitationId。
|
|
4597
|
+
* 用户应答由 `getElicitation` 长轮询取回(见 session-host.handleElicitation)。
|
|
4598
|
+
*/
|
|
4599
|
+
createElicitation(sessionId, body) {
|
|
4600
|
+
return this.sessionRequest(
|
|
4601
|
+
sessionId,
|
|
4602
|
+
`/agent-sessions/${sessionId}/elicitation`,
|
|
4603
|
+
{ body }
|
|
4604
|
+
);
|
|
4605
|
+
}
|
|
4606
|
+
/** 结构化提问结果查询(waitMs 内被回答则提前返回;服务端最多持 waitMs) */
|
|
4607
|
+
getElicitation(sessionId, elicitationId, waitMs) {
|
|
4608
|
+
return this.sessionRequestWithMethod(sessionId, "GET", `/agent-sessions/${sessionId}/elicitations/${elicitationId}`, {
|
|
4609
|
+
query: { waitMs },
|
|
4610
|
+
// 服务端最多持 waitMs,客户端留 10s 余量(避免「服务端刚返回、客户端已超时」的假失败)
|
|
4611
|
+
timeoutMs: waitMs + 1e4
|
|
4612
|
+
});
|
|
4613
|
+
}
|
|
4614
|
+
/**
|
|
4615
|
+
* A2(issues/multi-repo-task-missing-workcontext-link.md):**不传 repo** 的平台凭证
|
|
4616
|
+
* ——服务端按「任务空间内 active 的连接」择一签发(凭证是平台凭据,不是某仓库的凭据)。
|
|
4617
|
+
*
|
|
4618
|
+
* 用于无主仓库 / 多仓库任务(daemon 走 scratch 隔离目录、`resolveRepo(detail) = null`):
|
|
4619
|
+
* 这些任务的 agent 同样需要 MORULA_GITLAB_TOKEN / MORULA_GITLAB_URL 才能自己走 GitLab API
|
|
4620
|
+
* 兜底建 MR(否则链路两端都没有自愈路径)。旧版服务端强制校验 repo(400)——调用方需容忍失败。
|
|
4621
|
+
*/
|
|
4622
|
+
getSpaceDaemonToken(taskId) {
|
|
4623
|
+
return this.client.get("/vcs/daemon-token", { taskId });
|
|
4624
|
+
}
|
|
4625
|
+
};
|
|
4626
|
+
|
|
4627
|
+
// src/daemon/assistant-text-stream.ts
|
|
4628
|
+
var TEXT_DELTA_WINDOW_MS = 200;
|
|
4629
|
+
var TEXT_SEGMENT_MAX_CHARS = 2e3;
|
|
4630
|
+
var PARAGRAPH_BREAK = "\n\n";
|
|
4631
|
+
var AssistantTextStream = class {
|
|
4632
|
+
constructor(sink, options = {}) {
|
|
4633
|
+
this.sink = sink;
|
|
4634
|
+
this.windowMs = options.windowMs ?? TEXT_DELTA_WINDOW_MS;
|
|
4635
|
+
this.segmentMaxChars = Math.max(1, options.segmentMaxChars ?? TEXT_SEGMENT_MAX_CHARS);
|
|
4636
|
+
this.log = options.log ?? (() => {
|
|
4637
|
+
});
|
|
4638
|
+
}
|
|
4639
|
+
sink;
|
|
4640
|
+
windowMs;
|
|
4641
|
+
segmentMaxChars;
|
|
4642
|
+
log;
|
|
4643
|
+
/** 当前段的完整文本(未落段部分) */
|
|
4644
|
+
buffer = "";
|
|
4645
|
+
/** 当前段已通过 onDelta 发出的字符数 */
|
|
4646
|
+
sent = 0;
|
|
4647
|
+
/** 当前段序号(落段后 +1) */
|
|
4648
|
+
seg = 0;
|
|
4649
|
+
timer = null;
|
|
4650
|
+
disposed = false;
|
|
4651
|
+
/** 段落落库串行链:段落事件必须按 seg 顺序落库(上行 HTTP 并发会乱序 → 回放正文错序) */
|
|
4652
|
+
paragraphChain = Promise.resolve();
|
|
4653
|
+
/** 当前段落序号(诊断/测试用) */
|
|
4654
|
+
get currentSeg() {
|
|
4655
|
+
return this.seg;
|
|
4656
|
+
}
|
|
4657
|
+
/** 喂入一个文本增量 */
|
|
4658
|
+
push(text) {
|
|
4659
|
+
if (this.disposed || !text)
|
|
4660
|
+
return;
|
|
4661
|
+
this.buffer += text;
|
|
4662
|
+
let idx = this.buffer.lastIndexOf(PARAGRAPH_BREAK);
|
|
4663
|
+
while (idx >= 0) {
|
|
4664
|
+
this.cutParagraph(idx + PARAGRAPH_BREAK.length);
|
|
4665
|
+
idx = this.buffer.lastIndexOf(PARAGRAPH_BREAK);
|
|
4666
|
+
}
|
|
4667
|
+
while (this.buffer.length >= this.segmentMaxChars)
|
|
4668
|
+
this.cutParagraph(this.segmentMaxChars);
|
|
4669
|
+
if (this.buffer.length > 0)
|
|
4670
|
+
this.scheduleDelta();
|
|
4671
|
+
}
|
|
4672
|
+
/** 把当前 buffer 的前 `length` 个字符作为一段落切出去(同步改状态,落库走串行链) */
|
|
4673
|
+
cutParagraph(length) {
|
|
4674
|
+
const head = this.buffer.slice(0, length);
|
|
4675
|
+
const rest = this.buffer.slice(length);
|
|
4676
|
+
this.buffer = head;
|
|
4677
|
+
this.sent = Math.min(this.sent, head.length);
|
|
4678
|
+
void this.flushParagraph();
|
|
4679
|
+
this.buffer = rest;
|
|
4680
|
+
this.sent = 0;
|
|
4681
|
+
}
|
|
4682
|
+
/** 强制结束当前段落(工具调用前 / 回合结束前调用并 await) */
|
|
4683
|
+
async flushParagraph() {
|
|
4684
|
+
if (this.timer) {
|
|
4685
|
+
clearTimeout(this.timer);
|
|
4686
|
+
this.timer = null;
|
|
4687
|
+
}
|
|
4688
|
+
this.flushDelta();
|
|
4689
|
+
if (!this.buffer)
|
|
4690
|
+
return this.paragraphChain;
|
|
4691
|
+
const text = this.buffer;
|
|
4692
|
+
const seg = this.seg;
|
|
4693
|
+
this.buffer = "";
|
|
4694
|
+
this.sent = 0;
|
|
4695
|
+
this.seg += 1;
|
|
4696
|
+
this.paragraphChain = this.paragraphChain.then(async () => {
|
|
4697
|
+
try {
|
|
4698
|
+
await this.sink.onParagraph(seg, text);
|
|
4699
|
+
} catch (err) {
|
|
4700
|
+
this.log(`[text-stream] \u6BB5\u843D ${seg} \u843D\u5E93\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
4701
|
+
}
|
|
4702
|
+
});
|
|
4703
|
+
return this.paragraphChain;
|
|
4704
|
+
}
|
|
4705
|
+
/** 释放定时器(会话关闭 / 任务收尾) */
|
|
4706
|
+
dispose() {
|
|
4707
|
+
this.disposed = true;
|
|
4708
|
+
if (this.timer) {
|
|
4709
|
+
clearTimeout(this.timer);
|
|
4710
|
+
this.timer = null;
|
|
4711
|
+
}
|
|
4712
|
+
this.buffer = "";
|
|
4713
|
+
this.sent = 0;
|
|
4714
|
+
}
|
|
4715
|
+
scheduleDelta() {
|
|
4716
|
+
if (this.timer || this.disposed)
|
|
4717
|
+
return;
|
|
4718
|
+
this.timer = setTimeout(() => {
|
|
4719
|
+
this.timer = null;
|
|
4720
|
+
this.flushDelta();
|
|
4721
|
+
}, this.windowMs);
|
|
4722
|
+
this.timer.unref?.();
|
|
4723
|
+
}
|
|
4724
|
+
/** 把「未发出的新增文本」作为一次 transient 增量直通 */
|
|
4725
|
+
flushDelta() {
|
|
4726
|
+
if (this.buffer.length <= this.sent)
|
|
4727
|
+
return;
|
|
4728
|
+
const text = this.buffer.slice(this.sent);
|
|
4729
|
+
this.sent = this.buffer.length;
|
|
4730
|
+
try {
|
|
4731
|
+
this.sink.onDelta(this.seg, text);
|
|
4732
|
+
} catch (err) {
|
|
4733
|
+
this.log(`[text-stream] \u589E\u91CF\u76F4\u901A\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
4734
|
+
}
|
|
4735
|
+
}
|
|
4736
|
+
};
|
|
4737
|
+
|
|
4738
|
+
// src/daemon/contract-skill.ts
|
|
4032
4739
|
var COLLABORATION_SKILL_NAME = "morula-working-on-issues";
|
|
4033
4740
|
var COLLABORATION_SKILL_FRONTMATTER = `---
|
|
4034
4741
|
name: morula-working-on-issues
|
|
@@ -4099,13 +4806,20 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
|
|
|
4099
4806
|
|
|
4100
4807
|
## 0. \u8FD0\u884C\u65F6\u4E0A\u4E0B\u6587\u4E0E\u4EFB\u52A1\u8BE6\u60C5\u83B7\u53D6\uFF08\u5BF9\u5E94\u4EFB\u52A1\u7CFB\u7EDF\u63D0\u793A\u8BCD\uFF0C\u5FC5\u987B\u4E00\u4E00\u5BF9\u5E94\uFF09
|
|
4101
4808
|
|
|
4102
|
-
\u4EFB\u52A1\u7CFB\u7EDF\u63D0\u793A\u8BCD\uFF08\u53D1\u7ED9\u4F60\u7684\u9996\u6761\u6D88\u606F\uFF09\u53EA\u627F\u8F7D\uFF1A**\u4F60\u7684\u8EAB\u4EFD**\uFF08\u9636\u6BB5\u804C\u8D23\uFF09\u3001**\u4F60\u7684\u76EE\u6807**\uFF08\u300C\u4F60\u7684\u76EE\u6807\u300D\u6BB5\uFF09\u3001
|
|
4103
|
-
**\
|
|
4809
|
+
\u4EFB\u52A1\u7CFB\u7EDF\u63D0\u793A\u8BCD\uFF08\u53D1\u7ED9\u4F60\u7684\u9996\u6761\u6D88\u606F\uFF09\u53EA\u627F\u8F7D\u56DB\u6837\uFF1A**\u4F60\u7684\u8EAB\u4EFD\u4E0E\u8FB9\u754C**\uFF08\u9636\u6BB5\u804C\u8D23\uFF09\u3001**\u4F60\u7684\u76EE\u6807**\uFF08\u300C\u4F60\u7684\u76EE\u6807\u300D\u6BB5\uFF09\u3001
|
|
4810
|
+
**\u94A5\u5319**\uFF08\u300C\u57FA\u672C\u4FE1\u606F\u300D\u6BB5\u7684\u4EFB\u52A1 ID / \u7A7A\u95F4 ID\u2014\u2014\u53EA\u662F\u547D\u4EE4\u5165\u53C2\uFF0C\u4E0D\u662F\u4EFB\u52A1\u4FE1\u606F\uFF09\u3001**\u5DE5\u4F5C\u533A\u4E0E\u5DE5\u5177\u5165\u53E3**\uFF08\u672C skill \u4E0E
|
|
4811
|
+
\`morula task get\` / \`morula repo list\` \u7684\u7528\u6CD5\u6307\u5F15\uFF09\u3002**\u5176\u4F59\u4E0A\u4E0B\u6587\u4E0E\u4EFB\u52A1\u8BE6\u60C5\u4E00\u5F8B\u4E0D\u8FDB\u63D0\u793A\u8BCD**\uFF0C\u7528 CLI \u83B7\u53D6\uFF1A
|
|
4104
4812
|
|
|
4105
4813
|
- **\u5F53\u524D\u4EFB\u52A1\u8BE6\u60C5**\uFF1A\`morula task get\`\uFF08\u65E0\u53C2\uFF09\u2014\u2014taskId \u7531\u6267\u884C\u73AF\u5883\u63D0\u4F9B\uFF08\`MORULA_TASK_ID\` env / \`.morula/checkout.json\`\uFF09\uFF0C\u65E0\u9700\u4E5F\u4E0D\u5E94\u4ECE\u522B\u5904\u731C\u3002\u8FD4\u56DE\uFF1A\`id\`\uFF08\u672C\u4EFB\u52A1 AgentTask ID\uFF0CUUID\uFF09\u3001\`code\`\uFF08\u4EFB\u52A1\u7F16\u53F7\uFF0C\u5982 \`SPW299-59\`\uFF09\u3001\`objectId\`\uFF08\u5173\u8054\u4E1A\u52A1\u4EFB\u52A1\u5BF9\u8C61\uFF09\u3001\`taskPrompt\`\uFF08\u672C\u6B21\u76EE\u6807\u539F\u6587\uFF09\u3001\`contextSnapshot\`\u3001\`status\`\u3002
|
|
4106
4814
|
- **\u4E1A\u52A1\u4EFB\u52A1\u8BE6\u60C5**\uFF08\u6807\u9898/\u63CF\u8FF0\uFF09\uFF1A\`morula task get <objectId>\`\uFF1B**\u5173\u952E\u8BC4\u8BBA**\uFF08\u7528\u6237\u539F\u59CB\u8BC9\u6C42/\u5206\u6790\u7ED3\u8BBA\uFF09\uFF1A\`morula comment list --object <objectId>\`\u3002
|
|
4815
|
+
- **\u7A7A\u95F4\u4ED3\u5E93\u6E05\u5355**\uFF08\u591A\u4ED3\u5E93\u4EFB\u52A1\u5224\u65AD\u4F9D\u636E\uFF09\uFF1A\`morula repo list --space <spaceId>\`\u2014\u2014\u63D0\u793A\u8BCD\u4E0D\u5217\u4ED3\u5E93\uFF0C\u9700\u8981\u65F6\u81EA\u5DF1\u67E5\u3002
|
|
4107
4816
|
- **\u300C\u4F60\u7684\u76EE\u6807\u300D**\uFF1A\u7CFB\u7EDF\u63D0\u793A\u300C\u4F60\u7684\u76EE\u6807\u300D\u6BB5 = \u672C\u6B21\u4EFB\u52A1\u7684\u539F\u59CB\u8BC9\u6C42\uFF0C\u636E\u6B64\u5224\u65AD\u8981\u505A\u4EC0\u4E48\u3002
|
|
4108
4817
|
|
|
4818
|
+
> **\u8C03 CLI \u4E00\u5F8B\u7528 \`$MORULA_CLI\`**\uFF08daemon \u6CE8\u5165\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u6307\u5411\u4E0E\u672C\u6B21\u8FD0\u884C\u540C\u6E90\u7684\u6784\u5EFA\uFF1B\u4E0B\u6587\u6240\u6709 \`morula xxx\`
|
|
4819
|
+
> \u90FD\u6309 \`$MORULA_CLI xxx\` \u6267\u884C\uFF09\u3002PATH \u4E0A\u7684\u88F8 \`morula\` \u53EF\u80FD\u662F\u5BBF\u4E3B\u81EA\u5E26\u7684\u65E7\u7248\u672C\uFF08\u7F3A\u547D\u4EE4\u9762\u3001\u4E14\u4E0D\u8BA4\u4F1A\u8BDD/\u4EFB\u52A1
|
|
4820
|
+
> \u51ED\u8BC1 \u2192 \u5199\u64CD\u4F5C\u7F72\u540D\u9519\u8BEF\uFF09\uFF0C\u4E0D\u8981\u7528\u5B83\uFF1B\`env VAR=... $MORULA_CLI xxx\` \u8FD9\u7C7B env \u5305\u88C5\u4E0B\u540C\u6837\u53EF\u7528\u3002
|
|
4821
|
+
> \u4EC5\u5F53 \`$MORULA_CLI\` \u4E3A\u7A7A\u65F6\u624D\u9000\u56DE\u88F8 \`morula\`\u3002
|
|
4822
|
+
|
|
4109
4823
|
> **\u5DE5\u4F5C\u76EE\u5F55 = \u4EFB\u52A1\u5DE5\u4F5C\u533A\u6839**\uFF1A\u6839\u4E0B\u653E \`AGENTS.md\`\uFF08\u7A7A\u95F4\u9879\u76EE\u8BF4\u660E\uFF0C\u5DF2\u4F5C\u4E3A project.instructions \u81EA\u52A8\u52A0\u8F7D\uFF09\u3001
|
|
4110
4824
|
> \`.agents/\`\u3001\`.agent_context/\`\u3001\`.morula/\`\uFF1B**\u5404\u4ED3\u5E93\u662F\u5DE5\u4F5C\u533A\u6839\u4E0B\u7684\u5B50\u76EE\u5F55**\uFF08\u89C1 0.5\uFF09\u3002
|
|
4111
4825
|
> \u4E0B\u6587\u7684 \`AGENTS.md\`/\`.agents/\`/\`.agent_context/\` \u8DEF\u5F84\u90FD\u76F8\u5BF9\u8FD9\u4E2A\u5DE5\u4F5C\u533A\u6839\u3002
|
|
@@ -4113,6 +4827,11 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
|
|
|
4113
4827
|
> **\u4EFB\u52A1\u4E0A\u4E0B\u6587\u6587\u4EF6**\uFF1A\u540C\u4E00\u5957\u4E0A\u4E0B\u6587\u4E5F\u5199\u5728\u5DE5\u4F5C\u76EE\u5F55 \`.agent_context/task-context.md\`\uFF08\u4E0E\u9996\u6761\u7CFB\u7EDF\u63D0\u793A\u540C\u6E90\uFF09\u3002
|
|
4114
4828
|
> \u9700\u8981\u518D\u6B21\u786E\u8BA4\u4EFB\u52A1\u8FB9\u754C\u65F6**\u8BFB\u53D6\u8BE5\u6587\u4EF6**\uFF08\`cat .agent_context/task-context.md\`\uFF09\uFF0C\u4E0D\u8981\u51ED\u8BB0\u5FC6\u63A8\u65AD\u3002
|
|
4115
4829
|
|
|
4830
|
+
> **\u672C\u5951\u7EA6\u81EA\u8EAB\u7684\u8BFB\u53D6\u65B9\u5F0F**\uFF1A\u5951\u7EA6\u4EE5\u9879\u76EE\u7EA7 skill \u5F62\u5F0F\u6CE8\u5165\u5728 \`.agents/skills/morula-working-on-issues/SKILL.md\`\u3002
|
|
4831
|
+
> \u7528 \`skill\` \u5DE5\u5177\u6309\u540D\u52A0\u8F7D**\u53EF\u80FD\u62A5 \`Unknown skill\`**\uFF08\u8BE5\u5DE5\u5177\u7684\u53D1\u73B0\u8303\u56F4\u4E0D\u542B\u9879\u76EE\u7EA7\u76EE\u5F55\uFF09\u2014\u2014\u6B64\u65F6
|
|
4832
|
+
> **\u76F4\u63A5\u8BFB\u6587\u4EF6**\uFF08\`cat .agents/skills/morula-working-on-issues/SKILL.md\` \u6216 read \u5DE5\u5177\uFF09\uFF0C
|
|
4833
|
+
> **\u4E0D\u8981\u56E0\u4E3A\u52A0\u8F7D\u5931\u8D25\u5C31\u8DF3\u8FC7\u5951\u7EA6**\uFF08issues/injected-contract-skill-not-loadable-by-skill-tool.md\uFF09\u3002
|
|
4834
|
+
|
|
4116
4835
|
\u8BE6\u60C5\u89C1\u4E0B\u6587\u5404\u8282\u5BF9 TaskID / \u5BF9\u5916\u7F16\u53F7\u7684\u5177\u4F53\u7528\u6CD5\u3002
|
|
4117
4836
|
|
|
4118
4837
|
## 0.1 \u901A\u7528\u884C\u4E3A\u7EA6\u675F
|
|
@@ -4137,7 +4856,7 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
|
|
|
4137
4856
|
|
|
4138
4857
|
## 0.3 \u5206\u652F\u547D\u540D\uFF1A\u7531\u4F60\uFF08Agent\uFF09\u53D6\u540D\u5E76\u4E0A\u62A5
|
|
4139
4858
|
|
|
4140
|
-
- **\u4EFB\u52A1 ID \u83B7\u53D6**\uFF1A\u672C\u4EFB\u52A1\u7684 AgentTask ID\uFF08UUID\uFF09\
|
|
4859
|
+
- **\u4EFB\u52A1 ID \u83B7\u53D6**\uFF1A\u672C\u4EFB\u52A1\u7684 AgentTask ID\uFF08UUID\uFF09\u5728\u7CFB\u7EDF\u63D0\u793A\u300C\u57FA\u672C\u4FE1\u606F\u300D\u6BB5\uFF08\u4E5F\u662F \`morula task get\`\uFF08\u65E0\u53C2\uFF09\u8FD4\u56DE\u7684 \`id\`\uFF0C\`.agent_context/task-context.md\` \u5F00\u5934\u4EA6\u6709\uFF09\u3002\u9700\u8981\u8C03\u7528 \`morula task branch\` \u65F6\u7528\u5B83\u4F5C\u4E3A taskId\u3002
|
|
4141
4860
|
- **\u91CD\u8981\u533A\u5206**\uFF1A\`morula task branch <taskId>\` \u7684 taskId \u7528**\u672C\u4EFB\u52A1 UUID**\uFF08\u673A\u5668\u534F\u8BAE\uFF09\uFF1B\u4F46 **MR / \u5206\u652F / PR \u91CC\u7684\u4EFB\u52A1\u7F16\u53F7\u7528\u5BF9\u5916\u7F16\u53F7 code**\uFF08\u5982 \`AGENT-958201\`\uFF0C\u7528\u4E8E\u670D\u52A1\u7AEF\u5173\u8054/\u5173\u5355\uFF09\u2014\u2014**\u4E0D\u8981\u7528 UUID \u6807\u8BB0 MR**\uFF08UUID \u5173\u5355\u53EA\u8BA4\u4E1A\u52A1\u5BF9\u8C61\uFF0CAgentTask \u4E0D\u4F1A\u88AB\u8BC6\u522B\uFF0C\u4F1A\u5BFC\u81F4 MR \u5173\u8054\u5931\u8D25\uFF09\u3002
|
|
4142
4861
|
- **\u53EA\u6709\u9700\u8981\u6539\u4EE3\u7801\u5E76\u63A8\u5206\u652F\u7684\u4EFB\u52A1\u624D\u53D6\u540D**\uFF1B\u7EAF\u95EE\u7B54/\u53EA\u8BFB\u8C03\u7814/\u672C\u5730\u8D77\u670D\u52A1\u4E0D\u5EFA\u5206\u652F\uFF08\u89C1\u7B2C 3 \u6761\u51B3\u7B56\u6811\uFF09\u3002
|
|
4143
4862
|
- **\u5148\u68C0\u67E5\u5F53\u524D\u5206\u652F**\uFF1A\u82E5\u4ED3\u5E93\u5DF2\u6709 feature \u5206\u652F\uFF08\u540C\u4EFB\u52A1/\u540C\u4E00\u6539\u52A8\u95ED\u73AF\u7684\u540E\u7EED\u8F6E\u6B21\u590D\u7528\uFF09\uFF0C**\u76F4\u63A5\u7EE7\u7EED\u7528\u5F53\u524D\u5206\u652F\uFF0C\u4E0D\u8981\u65B0\u5EFA**\uFF08\u5728\u4ED3\u5E93\u76EE\u5F55\u5185\u6267\u884C\uFF09\uFF1A
|
|
@@ -4190,13 +4909,13 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
|
|
|
4190
4909
|
\u251C\u2500\u2500 .agents/skills/ \u2190 \u534F\u4F5C\u5951\u7EA6 + \u672C\u4EFB\u52A1\u81EA\u5B9A\u4E49 skill
|
|
4191
4910
|
\u251C\u2500\u2500 .agent_context/task-context.md \u2190 \u4EFB\u52A1\u4E0A\u4E0B\u6587\uFF08\u89C1 0 \u8282\uFF09
|
|
4192
4911
|
\u251C\u2500\u2500 .morula/checkout.json \u2190 daemon \u51ED\u636E\uFF08CLI \u5185\u90E8\u4F7F\u7528\uFF0C\u4E0D\u8981\u624B\u52A8\u8BFB\uFF09
|
|
4193
|
-
\u251C\u2500\u2500 <owner>-<repo>/ \u2190 \u4E3B\u4ED3\u5E93\uFF08\
|
|
4912
|
+
\u251C\u2500\u2500 <owner>-<repo>/ \u2190 \u4E3B\u4ED3\u5E93\uFF08\u5DF2\u6309\u5E73\u53F0\u89E3\u6790\u7ED3\u679C\u7269\u5316\uFF1B\u672C\u5730\u6CA1\u6709\u7684\u4ED3\u5E93\u6309\u9700 checkout\uFF09
|
|
4194
4913
|
\u2514\u2500\u2500 <owner>-<repo2>/ \u2190 \u6B21\u4ED3\u5E93 / \u53EA\u8BFB\u68C0\u51FA\uFF08\u6309\u9700\u7269\u5316\uFF0C\u4E0E\u4E3B\u4ED3\u5E93\u540C\u7EA7\uFF09
|
|
4195
4914
|
\`\`\`
|
|
4196
4915
|
|
|
4197
4916
|
- **git / gh \u547D\u4EE4\u4E00\u5F8B\u5728\u5BF9\u5E94\u4ED3\u5E93\u76EE\u5F55\u5185\u6267\u884C**\uFF08\`cd <\u5DE5\u4F5C\u533A\u6839>/<\u4ED3\u5E93\u76EE\u5F55>\`\uFF0C\u6216 \`git -C <\u4ED3\u5E93\u76EE\u5F55> ...\`\uFF09\uFF1A
|
|
4198
4917
|
\u5DE5\u4F5C\u533A\u6839\u4E0D\u662F git \u4ED3\u5E93\uFF0C\u5728\u6839\u76EE\u5F55\u8DD1 git \u547D\u4EE4\u5FC5\u7136\u5931\u8D25\u3002\`morula\` CLI \u53EF\u5728\u5DE5\u4F5C\u533A\u6839\u6216\u5176\u4EFB\u610F\u5B50\u76EE\u5F55\u6267\u884C\uFF08\u89C1 0.4\uFF09\u3002
|
|
4199
|
-
- **\u4E3B\u4ED3\u5E93**\uFF1A\
|
|
4918
|
+
- **\u4E3B\u4ED3\u5E93**\uFF1A\u5E73\u53F0\u5DF2\u89E3\u6790\u51FA\u7684\u4ED3\u5E93\u5728**\u5DE5\u4F5C\u533A\u6839\u4E0B\u5C31\u7EEA**\uFF08\u76EE\u5F55\u540D = \u4ED3\u5E93\u5168\u540D\u628A \`/\` \u6362\u6210 \`-\`\uFF0C\u5982 \`human-resources/xxx\` \u2192 \`human-resources-xxx\`\uFF09\uFF1B\`ls\` \u5DE5\u4F5C\u533A\u6839\u5373\u53EF\u770B\u5230\uFF0C\u76F4\u63A5\u5728\u8BE5\u76EE\u5F55\u5185\u5EFA\u5206\u652F/\u63D0\u4EA4/\u63A8\u5206\u652F/\u63D0 PR\u3002**\u9700\u8981\u7A7A\u95F4\u7ED1\u5B9A\u4E86\u54EA\u4E9B\u4ED3\u5E93\u65F6\u81EA\u5DF1\u67E5**\uFF1A\`morula repo list --space <spaceId>\`\uFF08spaceId \u89C1\u7CFB\u7EDF\u63D0\u793A\u300C\u57FA\u672C\u4FE1\u606F\u300D\u6BB5\uFF09\u3002
|
|
4200
4919
|
- **\u82E5\u4EFB\u52A1\u9700\u8981\u4E86\u89E3\u4ED3\u5E93\u5185\u5BB9/\u4EE3\u7801/\u63D0\u4EA4\u72B6\u6001\uFF08\u8BFB\u573A\u666F\uFF0C\u542B\u7EAF\u95EE\u7B54\uFF09**\uFF1A\u7528 \`morula repo checkout <taskId> <repoFullName>\`\uFF08\u672C\u5730 daemon \u7AEF\u70B9\uFF0C**\u672C\u8F6E\u7ACB\u5373\u7269\u5316**\u5E76\u8FD4\u56DE\u8DEF\u5F84\uFF0C\u901A\u5E38\u662F\u5DE5\u4F5C\u533A\u6839\u4E0B\u7684 \`<\u4ED3\u5E93\u76EE\u5F55>-readonly\`\uFF09\uFF0C\u7136\u540E\u5728\u8BE5\u8DEF\u5F84\u5185\u53EA\u8BFB\u67E5\u8BC1\uFF1A
|
|
4201
4920
|
- \u8BFB\u4EE3\u7801/\u63D0\u4EA4/\u5206\u652F\uFF1A\`git -C <\u8DEF\u5F84> log --oneline -20\`\u3001\`git -C <\u8DEF\u5F84> show <sha>\`\u3001\`git -C <\u8DEF\u5F84> branch -a\`\u3001\`rg "\u5173\u952E\u8BCD" <\u8DEF\u5F84>\`\u3001\u8BFB\u6587\u4EF6\u7B49\uFF08**\u53EA\u8BFB**\uFF0C\u4E0D\u5EFA\u5206\u652F\u3001\u4E0D\u6539\u4EE3\u7801\u3001\u4E0D\u63D0 PR\uFF09\uFF1B
|
|
4202
4921
|
- \u5237\u65B0\u8FDC\u7AEF\u6700\u65B0\uFF1A\`git -C <\u8DEF\u5F84> fetch origin --prune\`\uFF08remote \u5DF2\u914D\u7F6E SSH\uFF0C\u514D\u5BC6\uFF09\u3002
|
|
@@ -4204,7 +4923,7 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
|
|
|
4204
4923
|
- **\u82E5\u4EFB\u52A1\u9700\u8981\u6539\u4EE3\u7801**\uFF1A\u7528 \`morula repo checkout <taskId> <repoFullName>\` \u62C9\u53D6/\u5207\u6362\u4ED3\u5E93\uFF08\u8BA4\u8BC1\u5DF2\u7531\u6267\u884C\u73AF\u5883\u6CE8\u5165\uFF09\uFF0C
|
|
4205
4924
|
\u62FF\u5230\u8FD4\u56DE\u7684\u4ED3\u5E93\u8DEF\u5F84\u540E**chdir \u8FDB\u8BE5\u4ED3\u5E93\u76EE\u5F55**\uFF0C\u518D\u6309\u7B2C 0.3 \u6761\u5EFA\u5206\u652F\u3001\u7B2C 3 \u6761\u63D0 PR\u3002\u53EF\u9009 \`--ref <branch|sha>\` \u6307\u5B9A\u68C0\u51FA\u5206\u652F\u3002
|
|
4206
4925
|
- **\u82E5\u4EFB\u52A1\u7EAF\u95EE\u7B54\u3001\u6216\u7A7A\u95F4\u65E0\u53EF\u7528\u4ED3\u5E93**\uFF1A\u76F4\u63A5\u56DE\u7B54\u7528\u6237\u95EE\u9898\uFF0C\u4E0D\u6784\u5EFA\u5206\u652F/PR\uFF08\u89C1\u7B2C 3 \u6761\u51B3\u7B56\u6811\uFF09\u3002
|
|
4207
|
-
- **\u82E5\u4EFB\u52A1\u6D89\u53CA\u591A\u4E2A\u4ED3\u5E93\uFF08\u591A\u4ED3\u5E93\u4EFB\u52A1\uFF09**\uFF1A\u6D89\u53CA\u54EA\u4E9B\u4ED3\u5E93\u7531**\u4F60**\u7ED3\u5408\u4EFB\u52A1\u63CF\u8FF0\u5224\u65AD\uFF08\
|
|
4926
|
+
- **\u82E5\u4EFB\u52A1\u6D89\u53CA\u591A\u4E2A\u4ED3\u5E93\uFF08\u591A\u4ED3\u5E93\u4EFB\u52A1\uFF09**\uFF1A\u6D89\u53CA\u54EA\u4E9B\u4ED3\u5E93\u7531**\u4F60**\u7ED3\u5408\u4EFB\u52A1\u63CF\u8FF0\u5224\u65AD\uFF08\u4EFB\u52A1\u63CF\u8FF0\u91CC\u7684\u300C\u6D89\u53CA\u4ED3\u5E93\u300D\u662F\u7BA1\u5BB6\u7684\u521D\u59CB\u5224\u65AD\uFF1B\u7A7A\u95F4\u7ED1\u5B9A\u4E86\u54EA\u4E9B\u4ED3\u5E93\u7528 \`morula repo list --space <spaceId>\` \u67E5\uFF1B\u6267\u884C\u4E2D\u53D1\u73B0\u8FD8\u9700\u6539\u52A8\u5176\u4ED6\u4ED3\u5E93\u65F6\uFF0C\u81EA\u884C prepare \u62C9\u53D6\uFF09\uFF0C**\u4E0D\u9700\u8981\u5411\u7528\u6237\u9010\u4E2A\u786E\u8BA4**\uFF1A
|
|
4208
4927
|
1. \u4E3B\u4ED3\u5E93\uFF1A\u5728\u5B83\u7684\u5B50\u76EE\u5F55\u5185\u76F4\u63A5 git \u64CD\u4F5C\uFF08\u5DE5\u4F5C\u533A\u6839\u4E0D\u662F\u4ED3\u5E93\uFF09\uFF1B
|
|
4209
4928
|
2. \u5176\u4ED6\u4ED3\u5E93\u7528 \`morula repo prepare <taskId> <repoFullName>\` \u7269\u5316\u5230\u4EFB\u52A1\u5DE5\u4F5C\u533A\u6839\u4E0B\uFF08**\u4E0E\u4E3B\u4ED3\u5E93\u540C\u7EA7\u7684\u5B50\u76EE\u5F55**\uFF0C\u672C\u5730 daemon \u7AEF\u70B9\uFF0C\u672C\u8F6E\u7ACB\u5373\u7269\u5316\u5E76\u8FD4\u56DE\u8DEF\u5F84\uFF1Bgit \u534F\u8BAE\u62C9\u53D6\uFF0C\u4E0D\u8D70\u5E73\u53F0 API\uFF09\uFF1B
|
|
4210
4929
|
3. \u5728\u54EA\u4E2A\u4ED3\u5E93\u6539\u52A8\uFF0C\u5C31\u5728\u54EA\u4E2A\u4ED3\u5E93\u5185\u5EFA\u5206\u652F\uFF08\u7B2C 0.3 \u6761\u547D\u540D\u89C4\u8303\uFF09\u3001\u63A8\u5206\u652F\u5E76\u5EFA PR/MR\uFF08title/description \u5E26\u4EFB\u52A1\u7F16\u53F7\uFF09\uFF1B\u6BCF\u4E2A\u4ED3\u5E93\u7684 PR \u670D\u52A1\u7AEF\u90FD\u4F1A\u81EA\u52A8\u5173\u8054\u5230\u672C\u4EFB\u52A1\u3002**\u5206\u652F\u540D\u9010\u4ED3\u4E0A\u62A5**\uFF1A\u4E3B\u4ED3\u5E93\uFF08\u5176\u5B50\u76EE\u5F55\uFF09\u7528 \`morula task branch <taskId> --name <\u5206\u652F\u540D>\`\uFF1B\u6B21\u4ED3\u5E93\uFF08\u540C\u7EA7\u5B50\u76EE\u5F55\uFF09\u7528 \`morula task branch <taskId> --name <\u5206\u652F\u540D> --repo <owner/name>\` \u2014\u2014 \u670D\u52A1\u7AEF\u636E\u6B64\u628A\u5206\u652F\u540D\u5199\u5230\u8BE5\u4ED3\u5E93\u7684\u5DE5\u4F5C\u4E0A\u4E0B\u6587\uFF08\u5E73\u53F0\u4FA7\u9010\u4ED3\u5EFA MR \u515C\u5E95\u3001\u4EA4\u4ED8\u6E05\u5355\u90FD\u4F9D\u8D56\u5B83\uFF1B\u4E0D\u62A5\u5219\u4E00\u76F4\u505C\u5728\u5360\u4F4D\u5206\u652F\u540D \`morula/tmp-*\`\uFF0C\u5E73\u53F0\u627E\u4E0D\u5230\u4F60\u7684\u5206\u652F\uFF09\uFF1B
|
|
@@ -4226,8 +4945,8 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
|
|
|
4226
4945
|
morula task inbox [--peek] # \u8FD0\u884C\u4E2D\u68C0\u67E5\u70B9\uFF1A\u53D6\u672C\u7EBF\u7A0B\u672A\u6D88\u8D39\u6D88\u606F\uFF0C\u7EB3\u5165\u672C\u56DE\u5408\uFF08\u89C1 1.3\uFF09
|
|
4227
4946
|
# \u6CE8\uFF1A\u6700\u7EC8\u603B\u7ED3\u8BC4\u8BBA\u7531\u670D\u52A1\u7AEF\u7EDF\u4E00\u53D1\u9001\uFF08\u4F5C\u8005=\u667A\u80FD\u4F53\uFF09\uFF0C\u4E0D\u8981\u8C03\u7528 comment add \u81EA\u884C\u7F6E\u8BC4\u3002
|
|
4228
4947
|
\`\`\`
|
|
4229
|
-
- **\u53EF\u7528\u4ED3\u5E93\u6E05\u5355**\
|
|
4230
|
-
\`morula repo list --space <spaceId>\`\uFF08\`<spaceId>\`
|
|
4948
|
+
- **\u53EF\u7528\u4ED3\u5E93\u6E05\u5355**\uFF1A\u7CFB\u7EDF\u63D0\u793A**\u4E0D\u518D\u5217\u51FA**\u4ED3\u5E93\u2014\u2014\u9700\u8981\u679A\u4E3E\u7A7A\u95F4\u7ED1\u5B9A\u7684\u4ED3\u5E93\u65F6\u7528
|
|
4949
|
+
\`morula repo list --space <spaceId>\`\uFF08\`<spaceId>\` \u89C1\u7CFB\u7EDF\u63D0\u793A\u300C\u57FA\u672C\u4FE1\u606F\u300D\u6BB5\uFF0C\u6216 \`morula task get\` \u8FD4\u56DE\u7684 \`contextSnapshot.spaceId\`\uFF09\u3002
|
|
4231
4950
|
- **\u6BCF\u4E2A\u547D\u4EE4\u7684\u5B8C\u6574\u53C2\u6570\u4EE5 \`morula <resource> --help\` \u4E3A\u51C6**\uFF08\u5982 \`morula repo --help\` \u53EA\u5217 repo \u7684\u7528\u6CD5\uFF09\uFF1B\u4E0D\u786E\u5B9A\u67D0\u547D\u4EE4\u652F\u6301\u54EA\u4E9B\u65D7\u6807\u65F6\u5148\u67E5\u5B83\uFF0C\u4E0D\u8981\u51ED\u8BB0\u5FC6\u62FC\u53C2\u6570\u3002
|
|
4232
4951
|
- **\u7981\u6B62** \`curl\` / \`wget\` \u76F4\u8FDE\u5E73\u53F0 REST API\uFF1B\u7981\u6B62\u8BFB\u53D6 \`~/.morula-cli/config.yaml\`\u3001token \u6587\u4EF6\u6216\u73AF\u5883\u53D8\u91CF\u4E2D\u7684\u5BC6\u94A5\u3002
|
|
4233
4952
|
- **\u672C\u5730 \`.morula/checkout.json\` \u662F daemon \u7BA1\u7406\u6587\u4EF6**\uFF08\`morula repo checkout\` \u5185\u90E8\u8BFB\u53D6\u7AEF\u53E3\u4E0E\u4EFB\u52A1\u51ED\u636E\uFF09\uFF0C**\u4E0D\u8981\u624B\u52A8\u8BFB\u53D6/\u590D\u5236\u5176\u4E2D\u7684\u5B57\u6BB5**\uFF1B\u51ED\u636E\u7531 CLI \u5904\u7406\uFF0C\u4F60\u4E0D\u9700\u8981\u4E5F\u4E0D\u5E94\u5F53\u770B\u5230\u5B83\u3002
|
|
@@ -4414,7 +5133,7 @@ morula task inbox --peek # \u53EA\u770B\u4E0D\u53D6\uFF08\u786E\u8BA4\u6709\
|
|
|
4414
5133
|
|
|
4415
5134
|
- **\u4F55\u65F6\u53D1\u8BC4\u8BBA**\uFF1A**\u6700\u7EC8\u603B\u7ED3\u8BC4\u8BBA\u7531\u670D\u52A1\u7AEF\u7EDF\u4E00\u4EE5\u667A\u80FD\u4F53\u8EAB\u4EFD\u53D1\u9001**\uFF08\u4EFB\u52A1\u5B8C\u6210\u65F6\u81EA\u52A8\u53D1\u5E03\u5230\u4EFB\u52A1\u5BF9\u8C61\uFF0C\u4F5C\u8005=\u5F53\u524D\u667A\u80FD\u4F53\uFF09\u3002**\u4F60\u4E0D\u9700\u8981\u3001\u4E5F\u4E0D\u5E94\u8C03\u7528 \`morula comment add\`**\u2014\u2014\u53EA\u8981\u5728\u6700\u7EC8\u8F93\u51FA\u91CC\u7ED9\u51FA\u7528\u6237\u5173\u5FC3\u7684\u7ED3\u8BBA\u5373\u53EF\uFF0C\u670D\u52A1\u7AEF\u4F1A\u4F5C\u4E3A\u8BE5\u667A\u80FD\u4F53\u7684\u5B8C\u6210\u8BC4\u8BBA\u53D1\u5E03\u3002chat \u4F1A\u8BDD\u65E0 objectId \u7684\u4EFB\u52A1\uFF08\u7EAF\u5BF9\u8BDD\u6D3E\u53D1\uFF09\u4E0D\u56DE\u8BC4\u8BBA\u2014\u2014\u6700\u7EC8\u4EA4\u4ED8\u7531 daemon \u56DE\u586B assistant \u6D88\u606F\u3002
|
|
4416
5135
|
- **\u7EC8\u7AEF\u8F93\u51FA\u3001\u601D\u8003\u8FC7\u7A0B\u3001\u5DE5\u5177\u65E5\u5FD7\u7528\u6237\u90FD\u770B\u4E0D\u5230**\u2014\u2014\u7528\u6237\u770B\u5230\u7684\u53EA\u6709\u670D\u52A1\u7AEF\u6574\u5408\u540E\u53D1\u5E03\u7684\u6700\u7EC8\u603B\u7ED3\u8BC4\u8BBA\u3002
|
|
4417
|
-
- **\u8BC4\u8BBA @\u89E6\u53D1\u4EFB\u52A1\uFF08\u672C\u4EFB\u52A1\u7531\u8BC4\u8BBA\u89E6\u53D1\
|
|
5136
|
+
- **\u8BC4\u8BBA @\u89E6\u53D1\u4EFB\u52A1\uFF08\u672C\u4EFB\u52A1\u7531\u8BC4\u8BBA\u89E6\u53D1\uFF09**\uFF1A\u670D\u52A1\u7AEF\u4F1A\u628A\u6700\u7EC8\u603B\u7ED3\u81EA\u52A8\u4F5C\u4E3A**\u8BE5\u89E6\u53D1\u8BC4\u8BBA\u7684\u56DE\u590D**\u53D1\u51FA\uFF08\u6302\u5728\u88AB @ \u7684\u8BC4\u8BBA\u4E0B\uFF09\u3002\u4F60\u65E0\u9700\u3001\u4E5F\u4E0D\u8981\u81EA\u5DF1\u8C03\u7528 \`morula comment add\`/add_comment\u3002
|
|
4418
5137
|
- **\u9632\u91CD\u590D\uFF08\u670D\u52A1\u7AEF\u4FDD\u8BC1\uFF09**\uFF1A\u670D\u52A1\u7AEF\u5BF9\u300C\u540C\u4E00\u4E2A\u4EFB\u52A1\u53EA\u53D1\u4E00\u6761\u5B8C\u6210\u8BC4\u8BBA\u300D\u505A\u5E42\u7B49\uFF0C\u4E0D\u4F1A\u91CD\u590D\u5237\u5C4F\u3002\u4F60\u4E0D\u9700\u8981\u3001\u4E5F\u4E0D\u8981\u53BB\u67E5/\u53D1\u591A\u6761\u3002
|
|
4419
5138
|
- **\u6700\u7EC8\u8F93\u51FA = \u53EA\u7559\u7ED3\u8BBA**\uFF08\u4E0D\u8BBE\u5B57\u6570\u4E0A\u9650\u3001\u4E0D\u8981\u6C42\u56FA\u5B9A\u683C\u5F0F\uFF0C\u81EA\u7136\u7EC4\u7EC7\u5373\u53EF\uFF09\uFF1A
|
|
4420
5139
|
- \u5FC5\u7B54\uFF1A**\u505A\u4E86\u4EC0\u4E48\u3001\u7ED3\u679C\u5982\u4F55\u3001\u7528\u6237\u9700\u8981\u77E5\u9053\u7684\u540E\u7EED**\u3002
|
|
@@ -4469,7 +5188,8 @@ var COLLABORATION_SKILL_SOURCE_MAP = `# morula-working-on-issues \u2014 \u5951\u
|
|
|
4469
5188
|
| CLI \u5199\u8BC4\u8BBA\uFF08**\u5951\u7EA6\u7981\u6B62 agent \u81EA\u884C\u8C03\u7528**\uFF1A\u5B8C\u6210\u8BC4\u8BBA\u7531\u670D\u52A1\u7AEF\u7EDF\u4E00\u53D1\u9001\uFF0C\u89C1\u672C\u6587\u4EF6\u7B2C 5 \u6761\uFF09 | \`morula comment add\`\uFF08\u547D\u4EE4\u5B58\u5728\uFF0C\u4F46\u672C\u5951\u7EA6\u8981\u6C42\u4E0D\u8C03\u7528\uFF09 | \`packages/morula-cli/src/commands/comment.ts\` |
|
|
4470
5189
|
| CLI \u4EFB\u52A1\u8BE6\u60C5\uFF08**\u65E0\u53C2**\u53D6\u672C\u4EFB\u52A1\uFF1B\u5E26\u53C2\u53D6\u4E1A\u52A1\u5BF9\u8C61\uFF09 | \`morula task get\` \u2192 \`GET /agent-tasks/:id\`\uFF1B\`morula task get <objectId>\` \u2192 \`GET /objects/:id\` | \`packages/morula-cli/src/commands/task.ts\` |
|
|
4471
5190
|
| daemon \u6CE8\u5165\u51ED\u636E\uFF08agent \u65E0\u9700\u81EA\u884C\u767B\u5F55\uFF09 | runner \u6CE8\u5165 \`env\` \u7ED9 Provider \u5B50\u8FDB\u7A0B | \`packages/runtime/src/daemon/runner.ts\` |
|
|
4472
|
-
| \
|
|
5191
|
+
| \u8C03 CLI \u7528 \`$MORULA_CLI\`\uFF08\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u4E0E\u5F53\u524D daemon \u540C\u6E90\uFF1BPATH \u4E0A\u7684\u88F8 \`morula\` \u53EF\u80FD\u662F\u5BBF\u4E3B\u81EA\u5E26\u7684\u65E7\u7248\uFF09 | \`resolveAgentCliPath\`\uFF08\u4F18\u5148\u672C\u5305 \`dist/morula.js\`\uFF0C\u4EA7\u7269\u7F3A\u5931\u56DE\u9000\u63D2\u4EF6\u542F\u52A8\u5668\uFF09\uFF1B\u6CE8\u5165\u70B9\uFF1A\u4EFB\u52A1\u94FE\u8DEF \`env\`\u3001\u4F1A\u8BDD\u94FE\u8DEF \`env\` | \`packages/runtime/src/daemon/plugin-register.ts\`\u3001\`runner.ts\`\u3001\`session-host.ts\` |
|
|
5192
|
+
| \u7CFB\u7EDF\u63D0\u793A\u6CE8\u5165\uFF08\u8EAB\u4EFD\u4E0E\u8FB9\u754C = \u9636\u6BB5\u804C\u8D23 / \u76EE\u6807 / \u94A5\u5319 taskId+spaceId / \u5DE5\u5177\u5165\u53E3\u6307\u5F15\uFF09\uFF1B\u4EFB\u52A1\u8BE6\u60C5\u4E0E\u4ED3\u5E93\u6E05\u5355\u7531 \`morula task get\`\uFF08\u65E0\u53C2\uFF09\u3001\`morula repo list --space\` \u83B7\u53D6 | \`buildTaskPromptWithContract\`\uFF08\`runner.ts\`\uFF0C\u552F\u4E00\u8FDB dim \u7684 messages[0]\uFF09\uFF1BCLI \`packages/morula-cli/src/commands/task.ts\`\uFF08resolveTaskId \u2192 \`GET /agent-tasks/:id\`\uFF09 | \`packages/runtime/src/daemon/runner.ts\` |
|
|
4473
5193
|
| \u8FD0\u884C\u4E2D\u68C0\u67E5\u70B9\uFF08\u672C\u56DE\u5408\u5185\u53D6\u7528\u6237\u610F\u89C1\uFF09 | CLI \`morula task inbox [--peek]\` \u2192 \`GET /agent-tasks/:id/thread/inbox\`\uFF08\u673A\u5668\u534F\u8BAE\uFF1B\u9ED8\u8BA4\u53D6\u8D70\u5373\u6D88\u8D39\uFF0C\u843D \`consumed\` + \u5F53\u56DE\u5408\u53F7\uFF09\uFF1B\u5951\u7EA6\u65F6\u673A\u89C1\u672C SKILL 1.3 | \`packages/morula-cli/src/commands/task.ts\`\u3001\`src/modules/agent-task/chat/object-agent-chat.controller.ts\`\u3001\`object-agent-message.service.ts\`\uFF08\`getThreadInbox\`\uFF09 |
|
|
4474
5194
|
| \u8FD0\u884C\u4E2D\u6253\u65AD\uFF08\u540C\u4E00\u4F1A\u8BDD\u91CD\u5F00\u4E00\u8F6E\uFF09 | daemon \u5728\u5B89\u5168\u70B9\uFF08\`inFlightTools === 0\`\uFF09\`session/cancel\` \u2192 \`session/resume\` \u2192 \u7528\u65B0\u610F\u89C1\u91CD\u5F00 prompt\uFF1B\u56DE\u5408\u53F7\u4E0E\u4E2D\u65AD\u70B9\u4E0A\u62A5\u4E3A \`turn_started\` / \`turn_interrupted\` \u4E8B\u4EF6 | \`packages/runtime/src/daemon/acp/dim-acp-executor.ts\`\uFF08\u56DE\u5408\u5FAA\u73AF\uFF09\u3001\`runner.ts\`\uFF08\`buildSteerPort\`\uFF09\u3001\`src/modules/agent-task/chat/object-agent-message.service.ts\`\uFF08\`steerDeliveryStrategy\`\uFF09 |
|
|
4475
5195
|
|
|
@@ -4545,7 +5265,7 @@ function isFlagEnabled(name, defaultValue = true) {
|
|
|
4545
5265
|
|
|
4546
5266
|
// src/daemon/git-env.ts
|
|
4547
5267
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
4548
|
-
import { existsSync as
|
|
5268
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6 } from "node:fs";
|
|
4549
5269
|
import { join as join11 } from "node:path";
|
|
4550
5270
|
var GIT_CANDIDATE_DIRS = ["/usr/bin", "/usr/local/bin", "/opt/homebrew/bin", "/opt/local/bin"];
|
|
4551
5271
|
function prependToPath(dirs) {
|
|
@@ -4562,14 +5282,14 @@ function ensureGitOnPath() {
|
|
|
4562
5282
|
return;
|
|
4563
5283
|
} catch {
|
|
4564
5284
|
}
|
|
4565
|
-
const missing = prependToPath(GIT_CANDIDATE_DIRS.filter((d) =>
|
|
5285
|
+
const missing = prependToPath(GIT_CANDIDATE_DIRS.filter((d) => existsSync7(join11(d, "git"))));
|
|
4566
5286
|
if (missing.length) {
|
|
4567
5287
|
console.warn(`[daemon] PATH \u7F3A\u5C11 git\uFF0C\u5DF2\u8865\u5145: ${missing.join(", ")}`);
|
|
4568
5288
|
}
|
|
4569
5289
|
}
|
|
4570
5290
|
function ensureNodeOnPath() {
|
|
4571
5291
|
const nodeDir = process.execPath ? join11(process.execPath, "..") : "";
|
|
4572
|
-
const candidates = [nodeDir, "/usr/local/bin", "/opt/homebrew/bin"].filter((d) => d &&
|
|
5292
|
+
const candidates = [nodeDir, "/usr/local/bin", "/opt/homebrew/bin"].filter((d) => d && existsSync7(join11(d, "node")));
|
|
4573
5293
|
const missing = prependToPath(candidates);
|
|
4574
5294
|
if (missing.length) {
|
|
4575
5295
|
console.warn(`[daemon] PATH \u7F3A\u5C11 node\uFF0C\u5DF2\u8865\u5145: ${missing.join(", ")}`);
|
|
@@ -4615,7 +5335,7 @@ function buildDaemonRepoUrl(repoFullName, baseUrl) {
|
|
|
4615
5335
|
function ensureRepo(workdir, tenantId, repo, env, remoteUrl, daemonRemoteUrl) {
|
|
4616
5336
|
const repoDir = join11(workdir, tenantId, repo);
|
|
4617
5337
|
const fetchUrl = daemonRemoteUrl ?? remoteUrl ?? buildDaemonRepoUrl(repo);
|
|
4618
|
-
if (
|
|
5338
|
+
if (existsSync7(join11(repoDir, ".git"))) {
|
|
4619
5339
|
if (remoteUrl)
|
|
4620
5340
|
syncRemoteUrl(repoDir, remoteUrl, env);
|
|
4621
5341
|
try {
|
|
@@ -4660,7 +5380,7 @@ function syncRemoteUrl(repoDir, remoteUrl, env) {
|
|
|
4660
5380
|
function isLinkedWorktree(repoDir) {
|
|
4661
5381
|
try {
|
|
4662
5382
|
const gitDir = execFileSync6("git", ["-C", repoDir, "rev-parse", "--absolute-git-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
4663
|
-
return !!gitDir &&
|
|
5383
|
+
return !!gitDir && existsSync7(join11(gitDir, "commondir"));
|
|
4664
5384
|
} catch {
|
|
4665
5385
|
return false;
|
|
4666
5386
|
}
|
|
@@ -4698,7 +5418,7 @@ function ensureGitIdentity(repoDir, personaName) {
|
|
|
4698
5418
|
}
|
|
4699
5419
|
|
|
4700
5420
|
// src/daemon/outbox.ts
|
|
4701
|
-
import { existsSync as
|
|
5421
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync7, readdirSync as readdirSync2, readFileSync as readFileSync5, rmSync as rmSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4702
5422
|
import { homedir as homedir9 } from "node:os";
|
|
4703
5423
|
import { join as join12 } from "node:path";
|
|
4704
5424
|
var OUTBOX_RETRY_SCHEDULE = [1e3, 4e3, 16e3, 6e4, 3e5];
|
|
@@ -4714,14 +5434,14 @@ function writeOutbox(entry, configDir) {
|
|
|
4714
5434
|
}
|
|
4715
5435
|
function readOutboxEntries(configDir) {
|
|
4716
5436
|
const dir = outboxDir(configDir);
|
|
4717
|
-
if (!
|
|
5437
|
+
if (!existsSync8(dir))
|
|
4718
5438
|
return [];
|
|
4719
5439
|
const entries = [];
|
|
4720
5440
|
for (const file of readdirSync2(dir)) {
|
|
4721
5441
|
if (!file.endsWith(".json"))
|
|
4722
5442
|
continue;
|
|
4723
5443
|
try {
|
|
4724
|
-
const raw =
|
|
5444
|
+
const raw = readFileSync5(join12(dir, file), "utf8");
|
|
4725
5445
|
const parsed = JSON.parse(raw);
|
|
4726
5446
|
if (parsed && typeof parsed.taskId === "string")
|
|
4727
5447
|
entries.push(parsed);
|
|
@@ -4731,7 +5451,7 @@ function readOutboxEntries(configDir) {
|
|
|
4731
5451
|
return entries;
|
|
4732
5452
|
}
|
|
4733
5453
|
function removeOutbox(taskId, configDir) {
|
|
4734
|
-
|
|
5454
|
+
rmSync7(join12(outboxDir(configDir), `${taskId}.json`), { force: true });
|
|
4735
5455
|
}
|
|
4736
5456
|
function staleDir(configDir) {
|
|
4737
5457
|
return join12(outboxDir(configDir), "stale");
|
|
@@ -4754,13 +5474,120 @@ function outboxAge(entry) {
|
|
|
4754
5474
|
return Number.isFinite(created) ? Date.now() - created : Number.POSITIVE_INFINITY;
|
|
4755
5475
|
}
|
|
4756
5476
|
|
|
5477
|
+
// src/daemon/plugin-register.ts
|
|
5478
|
+
import { chmodSync as chmodSync3, cpSync, existsSync as existsSync9, mkdirSync as mkdirSync8, readdirSync as readdirSync3, readFileSync as readFileSync6, rmSync as rmSync8, statSync as statSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
5479
|
+
import { homedir as homedir10 } from "node:os";
|
|
5480
|
+
import { dirname as dirname6, join as join13 } from "node:path";
|
|
5481
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5482
|
+
var PLUGIN_NAME = "morula-cli";
|
|
5483
|
+
var MANIFEST_DIR_NAME = ".codex-plugin";
|
|
5484
|
+
function legacyAgentsPluginDir(home = homedir10()) {
|
|
5485
|
+
return join13(home, ".agents", "plugins", PLUGIN_NAME);
|
|
5486
|
+
}
|
|
5487
|
+
function resolveDimcodeHome(env = process.env, home = homedir10()) {
|
|
5488
|
+
const fromEnv = env.DIMCODE_HOME?.trim();
|
|
5489
|
+
return fromEnv || join13(home, ".dimcode", "v2");
|
|
5490
|
+
}
|
|
5491
|
+
function defaultPluginsRoot(env = process.env, home = homedir10()) {
|
|
5492
|
+
return join13(resolveDimcodeHome(env, home), "plugins");
|
|
5493
|
+
}
|
|
5494
|
+
function resolvePackagePaths(entryUrl) {
|
|
5495
|
+
const distDir = dirname6(fileURLToPath2(entryUrl));
|
|
5496
|
+
return {
|
|
5497
|
+
packageRoot: dirname6(distDir),
|
|
5498
|
+
distDir,
|
|
5499
|
+
assetsDir: join13(distDir, "plugin")
|
|
5500
|
+
};
|
|
5501
|
+
}
|
|
5502
|
+
function buildLauncherScript(opts) {
|
|
5503
|
+
const entry = join13(opts.packageRoot, "dist", "morula.js");
|
|
5504
|
+
return `#!/bin/sh
|
|
5505
|
+
# Morula \u4E1A\u52A1 CLI \u542F\u52A8\u5668\uFF08\u7531 \`morula-runtime setup\` \u751F\u6210\uFF1BNode \u5347\u7EA7\u540E\u91CD\u8DD1 setup \u5237\u65B0\uFF09\u3002
|
|
5506
|
+
# \u4E0D\u4F9D\u8D56\u8C03\u7528\u65B9 PATH \u91CC\u7684 node\uFF08agent \u6267\u884C\u73AF\u5883 PATH \u5E38\u88AB\u88C1\u526A\uFF09\uFF0C\u4EC5\u4F5C\u515C\u5E95\u3002
|
|
5507
|
+
NODE=${JSON.stringify(opts.nodePath)}
|
|
5508
|
+
[ -x "$NODE" ] || NODE="$(command -v node 2>/dev/null || true)"
|
|
5509
|
+
if [ -z "$NODE" ]; then
|
|
5510
|
+
echo '{"code":"ERROR","message":"node not found; reinstall or re-run: morula-runtime setup"}' >&2
|
|
5511
|
+
exit 127
|
|
5512
|
+
fi
|
|
5513
|
+
exec "$NODE" ${JSON.stringify(entry)} "$@"
|
|
5514
|
+
`;
|
|
5515
|
+
}
|
|
5516
|
+
function assetsReady(paths) {
|
|
5517
|
+
return existsSync9(join13(paths.assetsDir, "plugin.json")) && existsSync9(join13(paths.assetsDir, "skills"));
|
|
5518
|
+
}
|
|
5519
|
+
function listSkillNames(assetsDir) {
|
|
5520
|
+
const dir = join13(assetsDir, "skills");
|
|
5521
|
+
if (!existsSync9(dir))
|
|
5522
|
+
return [];
|
|
5523
|
+
return readdirSync3(dir).filter((name) => {
|
|
5524
|
+
try {
|
|
5525
|
+
return statSync2(join13(dir, name)).isDirectory();
|
|
5526
|
+
} catch {
|
|
5527
|
+
return false;
|
|
5528
|
+
}
|
|
5529
|
+
}).sort();
|
|
5530
|
+
}
|
|
5531
|
+
function pluginBinDir(env = process.env, home = homedir10()) {
|
|
5532
|
+
return join13(defaultPluginsRoot(env, home), PLUGIN_NAME, "bin");
|
|
5533
|
+
}
|
|
5534
|
+
function pluginBinPath(env = process.env, home = homedir10()) {
|
|
5535
|
+
return join13(pluginBinDir(env, home), "morula");
|
|
5536
|
+
}
|
|
5537
|
+
function ownCliEntryPath(moduleUrl = import.meta.url) {
|
|
5538
|
+
const entry = join13(resolvePackagePaths(moduleUrl).packageRoot, "dist", "morula.js");
|
|
5539
|
+
return existsSync9(entry) ? entry : null;
|
|
5540
|
+
}
|
|
5541
|
+
function resolveAgentCliPath(moduleUrl = import.meta.url, env = process.env, home = homedir10()) {
|
|
5542
|
+
return ownCliEntryPath(moduleUrl) ?? pluginBinPath(env, home);
|
|
5543
|
+
}
|
|
5544
|
+
function prependPluginBinToPath(currentPath, env = process.env, home = homedir10()) {
|
|
5545
|
+
const binDir = pluginBinDir(env, home);
|
|
5546
|
+
const segments = (currentPath ?? "").split(":").filter(Boolean);
|
|
5547
|
+
if (segments[0] === binDir)
|
|
5548
|
+
return currentPath ?? binDir;
|
|
5549
|
+
return [binDir, ...segments.filter((s) => s !== binDir)].join(":");
|
|
5550
|
+
}
|
|
5551
|
+
function registerPlugin(opts) {
|
|
5552
|
+
const { pluginsRoot, paths, version } = opts;
|
|
5553
|
+
const nodePath = opts.nodePath ?? process.execPath;
|
|
5554
|
+
const pluginDir = join13(pluginsRoot, PLUGIN_NAME);
|
|
5555
|
+
if (!assetsReady(paths))
|
|
5556
|
+
throw new Error(`plugin assets not found at ${paths.assetsDir}; run \`pnpm run build:dist\` (dev) or reinstall the package`);
|
|
5557
|
+
const skillsSrc = join13(paths.assetsDir, "skills");
|
|
5558
|
+
const skills = listSkillNames(paths.assetsDir);
|
|
5559
|
+
const manifestDir = join13(pluginDir, MANIFEST_DIR_NAME);
|
|
5560
|
+
mkdirSync8(manifestDir, { recursive: true });
|
|
5561
|
+
const manifest = JSON.parse(readFileSync6(join13(paths.assetsDir, "plugin.json"), "utf8"));
|
|
5562
|
+
writeFileSync6(join13(manifestDir, "plugin.json"), `${JSON.stringify({ ...manifest, version }, null, 2)}
|
|
5563
|
+
`);
|
|
5564
|
+
const skillsDest = join13(pluginDir, "skills");
|
|
5565
|
+
rmSync8(skillsDest, { recursive: true, force: true });
|
|
5566
|
+
mkdirSync8(skillsDest, { recursive: true });
|
|
5567
|
+
cpSync(skillsSrc, skillsDest, { recursive: true });
|
|
5568
|
+
const binDir = join13(pluginDir, "bin");
|
|
5569
|
+
mkdirSync8(binDir, { recursive: true });
|
|
5570
|
+
const binPath = join13(binDir, "morula");
|
|
5571
|
+
writeFileSync6(binPath, buildLauncherScript({ nodePath, packageRoot: paths.packageRoot }));
|
|
5572
|
+
chmodSync3(binPath, 493);
|
|
5573
|
+
const legacy = legacyAgentsPluginDir(opts.home);
|
|
5574
|
+
return {
|
|
5575
|
+
pluginDir,
|
|
5576
|
+
binPath,
|
|
5577
|
+
skills,
|
|
5578
|
+
version,
|
|
5579
|
+
node: nodePath,
|
|
5580
|
+
legacyDir: existsSync9(join13(legacy, MANIFEST_DIR_NAME)) ? legacy : null
|
|
5581
|
+
};
|
|
5582
|
+
}
|
|
5583
|
+
|
|
4757
5584
|
// src/daemon/acp/acp-event-mapper.ts
|
|
4758
5585
|
var AcpEventMapper = class {
|
|
4759
5586
|
lastUsage = void 0;
|
|
4760
5587
|
reset() {
|
|
4761
5588
|
this.lastUsage = void 0;
|
|
4762
5589
|
}
|
|
4763
|
-
/** 取当前 usage
|
|
5590
|
+
/** 取当前 usage(回放/观测用;**不再**作为 tokenUsage 附着——token 总量以 prompt 响应的 usage 为准) */
|
|
4764
5591
|
get latestUsage() {
|
|
4765
5592
|
return this.lastUsage;
|
|
4766
5593
|
}
|
|
@@ -4786,6 +5613,13 @@ var AcpEventMapper = class {
|
|
|
4786
5613
|
}
|
|
4787
5614
|
break;
|
|
4788
5615
|
}
|
|
5616
|
+
case "user_message_chunk": {
|
|
5617
|
+
const text = update.content?.type === "text" ? update.content.text : "";
|
|
5618
|
+
if (text) {
|
|
5619
|
+
messages.push({ eventType: "user_message:delta", payload: { delta: text } });
|
|
5620
|
+
}
|
|
5621
|
+
break;
|
|
5622
|
+
}
|
|
4789
5623
|
case "tool_call": {
|
|
4790
5624
|
messages.push({
|
|
4791
5625
|
eventType: "tool:started",
|
|
@@ -4813,11 +5647,13 @@ var AcpEventMapper = class {
|
|
|
4813
5647
|
break;
|
|
4814
5648
|
}
|
|
4815
5649
|
case "usage_update": {
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
5650
|
+
const usage2 = {
|
|
5651
|
+
contextUsed: typeof update.used === "number" ? update.used : 0,
|
|
5652
|
+
contextSize: typeof update.size === "number" ? update.size : 0,
|
|
5653
|
+
cost: update.cost ?? null
|
|
4820
5654
|
};
|
|
5655
|
+
this.lastUsage = usage2;
|
|
5656
|
+
messages.push({ eventType: "usage:context", payload: { usage: usage2 } });
|
|
4821
5657
|
break;
|
|
4822
5658
|
}
|
|
4823
5659
|
default:
|
|
@@ -4913,14 +5749,9 @@ function normalizeOptionId(decision) {
|
|
|
4913
5749
|
}
|
|
4914
5750
|
|
|
4915
5751
|
// src/daemon/acp/dim-acp-executor.ts
|
|
4916
|
-
var DEFAULT_TIMEOUT_MS2 = 30 * 60 * 1e3;
|
|
4917
|
-
var DEFAULT_IDLE_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
4918
|
-
var DEFAULT_TOOL_IDLE_TIMEOUT_MS = 20 * 60 * 1e3;
|
|
4919
|
-
var DEFAULT_MAX_TURNS = 8;
|
|
4920
|
-
var DEFAULT_TASK_WALL_CLOCK_MS = 2 * 60 * 60 * 1e3;
|
|
4921
5752
|
var DEFAULT_STEER_SAFETY_MS = 30 * 1e3;
|
|
4922
5753
|
var PROMPT_RPC_TIMEOUT_GRACE_MS = 1e4;
|
|
4923
|
-
var
|
|
5754
|
+
var STEER_POLL_TICK_MS = 15e3;
|
|
4924
5755
|
var KILL_GRACE_MS = 3e3;
|
|
4925
5756
|
function resolveSteerSafetyMs(raw = process.env.MORULA_STEER_SAFETY_MS) {
|
|
4926
5757
|
if (raw === void 0 || raw === "")
|
|
@@ -5063,7 +5894,7 @@ var DimAcpExecutor = class {
|
|
|
5063
5894
|
/** 每次 run 前由调用方注入(daemon 单例池) */
|
|
5064
5895
|
pool = null;
|
|
5065
5896
|
async run(opts) {
|
|
5066
|
-
const timeout = opts.timeoutMs
|
|
5897
|
+
const timeout = opts.timeoutMs && opts.timeoutMs > 0 ? opts.timeoutMs : 0;
|
|
5067
5898
|
const mapper = new AcpEventMapper();
|
|
5068
5899
|
const pool = opts.pool ?? this.pool;
|
|
5069
5900
|
return await new Promise((resolve2) => {
|
|
@@ -5076,9 +5907,7 @@ var DimAcpExecutor = class {
|
|
|
5076
5907
|
let turnNo = 1;
|
|
5077
5908
|
const inFlightToolCalls = /* @__PURE__ */ new Map();
|
|
5078
5909
|
const inFlightTools = () => inFlightToolCalls.size;
|
|
5079
|
-
let
|
|
5080
|
-
let watchdogTimer = null;
|
|
5081
|
-
let watchdogFired = false;
|
|
5910
|
+
let steerPollTimer = null;
|
|
5082
5911
|
let resumeFailed;
|
|
5083
5912
|
const killTree = (c) => {
|
|
5084
5913
|
if (!c)
|
|
@@ -5089,10 +5918,10 @@ var DimAcpExecutor = class {
|
|
|
5089
5918
|
c.kill();
|
|
5090
5919
|
}
|
|
5091
5920
|
};
|
|
5092
|
-
const
|
|
5093
|
-
if (
|
|
5094
|
-
clearInterval(
|
|
5095
|
-
|
|
5921
|
+
const stopSteerPoll = () => {
|
|
5922
|
+
if (steerPollTimer) {
|
|
5923
|
+
clearInterval(steerPollTimer);
|
|
5924
|
+
steerPollTimer = null;
|
|
5096
5925
|
}
|
|
5097
5926
|
};
|
|
5098
5927
|
let abortHandler = () => {
|
|
@@ -5103,7 +5932,7 @@ var DimAcpExecutor = class {
|
|
|
5103
5932
|
settled = true;
|
|
5104
5933
|
if (timer)
|
|
5105
5934
|
clearTimeout(timer);
|
|
5106
|
-
|
|
5935
|
+
stopSteerPoll();
|
|
5107
5936
|
if (ownsConnection && conn) {
|
|
5108
5937
|
void conn.close().catch(() => {
|
|
5109
5938
|
});
|
|
@@ -5128,45 +5957,17 @@ var DimAcpExecutor = class {
|
|
|
5128
5957
|
finish({ ok: false, error: "\u4EFB\u52A1\u88AB\u53D6\u6D88", sessionId: sessionId || void 0 });
|
|
5129
5958
|
}
|
|
5130
5959
|
};
|
|
5131
|
-
const
|
|
5132
|
-
|
|
5133
|
-
if (idle <= 0)
|
|
5134
|
-
return;
|
|
5135
|
-
lastActivityAt = Date.now();
|
|
5136
|
-
inFlightToolCalls.clear();
|
|
5137
|
-
watchdogTimer = setInterval(() => {
|
|
5960
|
+
const startSteerPoll = () => {
|
|
5961
|
+
steerPollTimer = setInterval(() => {
|
|
5138
5962
|
maybeInterruptAtSafePoint();
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
const budget = inFlightTools() > 0 ? toolBudget : idle;
|
|
5142
|
-
if (Date.now() - lastActivityAt > budget) {
|
|
5143
|
-
watchdogFired = true;
|
|
5144
|
-
if (sessionId && conn)
|
|
5145
|
-
conn.cancel(sessionId);
|
|
5146
|
-
killTree(conn);
|
|
5147
|
-
finish({
|
|
5148
|
-
ok: false,
|
|
5149
|
-
error: `daemon: idle watchdog fired\uFF08${Math.round(budget / 1e3)}s \u65E0\u6D3B\u52A8\uFF09`,
|
|
5150
|
-
sessionId: sessionId || void 0,
|
|
5151
|
-
turns: turnNo
|
|
5152
|
-
});
|
|
5153
|
-
}
|
|
5154
|
-
}, WATCHDOG_TICK_MS);
|
|
5155
|
-
watchdogTimer.unref?.();
|
|
5156
|
-
};
|
|
5157
|
-
const markActivity = () => {
|
|
5158
|
-
lastActivityAt = Date.now();
|
|
5963
|
+
}, STEER_POLL_TICK_MS);
|
|
5964
|
+
steerPollTimer.unref?.();
|
|
5159
5965
|
};
|
|
5160
5966
|
let activeSteer = null;
|
|
5161
5967
|
let steerWaitingSince = 0;
|
|
5162
|
-
let wallClockFired = false;
|
|
5163
5968
|
let interruptedTurns = 0;
|
|
5164
5969
|
let turnKind = "initial";
|
|
5165
5970
|
const steerSafetyMs = resolveSteerSafetyMs();
|
|
5166
|
-
const taskStartedAt = Date.now();
|
|
5167
|
-
const maxTurns = Math.max(1, Math.trunc(opts.steer?.maxTurns ?? DEFAULT_MAX_TURNS));
|
|
5168
|
-
const taskWallClockMs = opts.steer?.wallClockMs ?? 0;
|
|
5169
|
-
const wallClockExceeded = () => taskWallClockMs > 0 && Date.now() - taskStartedAt > taskWallClockMs;
|
|
5170
5971
|
function maybeInterruptAtSafePoint() {
|
|
5171
5972
|
if (!opts.steer || activeSteer || !conn || !sessionId)
|
|
5172
5973
|
return;
|
|
@@ -5190,15 +5991,6 @@ var DimAcpExecutor = class {
|
|
|
5190
5991
|
`);
|
|
5191
5992
|
conn.cancel(sessionId);
|
|
5192
5993
|
}
|
|
5193
|
-
function enforceTaskWallClock() {
|
|
5194
|
-
if (wallClockFired || !wallClockExceeded())
|
|
5195
|
-
return;
|
|
5196
|
-
wallClockFired = true;
|
|
5197
|
-
process.stderr.write(`[daemon] \u4EFB\u52A1\u5899\u949F\u8D85\u9650\uFF08${Math.round(taskWallClockMs / 1e3)}s\uFF09\uFF0C\u7EC8\u6B62\u672C\u8F6E\u5E76\u8981\u6C42\u4EBA\u4ECB\u5165
|
|
5198
|
-
`);
|
|
5199
|
-
if (sessionId && conn)
|
|
5200
|
-
conn.cancel(sessionId);
|
|
5201
|
-
}
|
|
5202
5994
|
function emitAbortedTools() {
|
|
5203
5995
|
const aborted = [];
|
|
5204
5996
|
for (const [callId, toolName] of inFlightToolCalls) {
|
|
@@ -5237,8 +6029,12 @@ var DimAcpExecutor = class {
|
|
|
5237
6029
|
});
|
|
5238
6030
|
setup().then(async () => {
|
|
5239
6031
|
const armTurnTimer = () => {
|
|
5240
|
-
if (timer)
|
|
6032
|
+
if (timer) {
|
|
5241
6033
|
clearTimeout(timer);
|
|
6034
|
+
timer = void 0;
|
|
6035
|
+
}
|
|
6036
|
+
if (timeout <= 0)
|
|
6037
|
+
return;
|
|
5242
6038
|
timer = setTimeout(() => {
|
|
5243
6039
|
if (sessionId && conn)
|
|
5244
6040
|
conn.cancel(sessionId);
|
|
@@ -5254,7 +6050,7 @@ var DimAcpExecutor = class {
|
|
|
5254
6050
|
};
|
|
5255
6051
|
armTurnTimer();
|
|
5256
6052
|
opts.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
5257
|
-
|
|
6053
|
+
startSteerPoll();
|
|
5258
6054
|
const bridge = new AcpPermissionBridge();
|
|
5259
6055
|
conn.setPermissionHandler(bridge.createHandler(opts.approval, opts.approvalMode === "manual"));
|
|
5260
6056
|
const sessionParams = { cwd: opts.cwd, mcpServers: [] };
|
|
@@ -5304,7 +6100,7 @@ var DimAcpExecutor = class {
|
|
|
5304
6100
|
process.stderr.write(`[daemon] \u4EFB\u52A1\u6307\u5B9A\u6A21\u578B ${opts.model} \u65E0\u6CD5\u5339\u914D ACP \u53EF\u7528\u6A21\u578B\uFF08\u5C06\u4F7F\u7528 dim \u9ED8\u8BA4\u6A21\u578B\uFF09
|
|
5305
6101
|
`);
|
|
5306
6102
|
} else {
|
|
5307
|
-
await conn.
|
|
6103
|
+
await conn.setConfigOption(sess.sessionId, "model", modelId).catch((err) => {
|
|
5308
6104
|
process.stderr.write(`[daemon] \u4EFB\u52A1\u6307\u5B9A\u6A21\u578B ${modelId} \u4E0B\u53D1\u5931\u8D25\uFF08\u5C06\u4F7F\u7528 dim \u9ED8\u8BA4\u6A21\u578B\uFF09: ${err.message}
|
|
5309
6105
|
`);
|
|
5310
6106
|
});
|
|
@@ -5314,7 +6110,6 @@ var DimAcpExecutor = class {
|
|
|
5314
6110
|
});
|
|
5315
6111
|
sessionId = sess.sessionId;
|
|
5316
6112
|
pool?.touch();
|
|
5317
|
-
markActivity();
|
|
5318
6113
|
opts.onEvent?.(mapper.readyMessage(sessionId), "");
|
|
5319
6114
|
const reopenSessionAfterInterrupt = async () => {
|
|
5320
6115
|
try {
|
|
@@ -5345,7 +6140,6 @@ var DimAcpExecutor = class {
|
|
|
5345
6140
|
activeSteer = null;
|
|
5346
6141
|
opts.steer?.onTurnStarted?.({ turnNo, kind: turnKind });
|
|
5347
6142
|
const result = await conn.prompt(sessionId, promptText, (update) => {
|
|
5348
|
-
markActivity();
|
|
5349
6143
|
pool?.touch();
|
|
5350
6144
|
const { messages } = mapper.map(sessionId, update);
|
|
5351
6145
|
for (const msg of messages) {
|
|
@@ -5378,37 +6172,18 @@ var DimAcpExecutor = class {
|
|
|
5378
6172
|
}
|
|
5379
6173
|
}
|
|
5380
6174
|
maybeInterruptAtSafePoint();
|
|
5381
|
-
}, timeout + PROMPT_RPC_TIMEOUT_GRACE_MS);
|
|
6175
|
+
}, timeout > 0 ? timeout + PROMPT_RPC_TIMEOUT_GRACE_MS : 0);
|
|
5382
6176
|
if (settled)
|
|
5383
6177
|
return;
|
|
5384
6178
|
pool?.touch();
|
|
5385
|
-
|
|
5386
|
-
const usage2 = result.usage ?? mapper.latestUsage;
|
|
6179
|
+
const usage2 = result.usage ?? void 0;
|
|
5387
6180
|
const cancelled = result.stopReason === "cancelled";
|
|
5388
6181
|
const base = { sessionId, turns: turnNo, interrupted: interruptedTurns > 0 };
|
|
5389
|
-
if (wallClockFired || wallClockExceeded()) {
|
|
5390
|
-
const abortedTools = emitAbortedTools();
|
|
5391
|
-
opts.steer?.onTurnInterrupted?.({ turnNo, reason: "wall-clock", abortedTools });
|
|
5392
|
-
killTree(conn);
|
|
5393
|
-
finish({
|
|
5394
|
-
...base,
|
|
5395
|
-
ok: false,
|
|
5396
|
-
error: `\u5DF2\u8FBE\u4EFB\u52A1\u5899\u949F\u4E0A\u9650\uFF08${Math.round(taskWallClockMs / 1e3)}s\uFF09\uFF0C\u5DF2\u7EC8\u6B62\u672C\u8F6E\uFF0C\u8BF7\u4EBA\u4ECB\u5165`
|
|
5397
|
-
});
|
|
5398
|
-
return;
|
|
5399
|
-
}
|
|
5400
6182
|
if (cancelled && activeSteer) {
|
|
5401
6183
|
interruptedTurns += 1;
|
|
5402
6184
|
const abortedTools = emitAbortedTools();
|
|
5403
6185
|
const nextTurn = turnNo + 1;
|
|
5404
|
-
|
|
5405
|
-
opts.steer?.onTurnInterrupted?.({ turnNo, reason: withinBudget ? "steer" : "max-turns", abortedTools });
|
|
5406
|
-
if (!withinBudget) {
|
|
5407
|
-
process.stderr.write(`[daemon] \u5DF2\u8FBE\u6700\u5927\u56DE\u5408\u6570 ${maxTurns}\uFF0C\u5269\u4F59\u6253\u65AD\u8BF7\u6C42\u4EA4\u7531\u4E0B\u4E00\u8F6E
|
|
5408
|
-
`);
|
|
5409
|
-
finish({ ...base, ok: true, text: finalText, tokenUsage: usage2 });
|
|
5410
|
-
return;
|
|
5411
|
-
}
|
|
6186
|
+
opts.steer?.onTurnInterrupted?.({ turnNo, reason: "steer", abortedTools });
|
|
5412
6187
|
turnKind = "after-interrupt";
|
|
5413
6188
|
promptText = opts.steer.consume({ turnNo: nextTurn, sessionReset });
|
|
5414
6189
|
turnNo = nextTurn;
|
|
@@ -5431,7 +6206,7 @@ var DimAcpExecutor = class {
|
|
|
5431
6206
|
return;
|
|
5432
6207
|
}
|
|
5433
6208
|
const leftover = activeSteer ?? opts.steer?.peek() ?? null;
|
|
5434
|
-
if (leftover
|
|
6209
|
+
if (leftover) {
|
|
5435
6210
|
const nextTurn = turnNo + 1;
|
|
5436
6211
|
turnKind = "appended";
|
|
5437
6212
|
promptText = opts.steer.consume({ turnNo: nextTurn, sessionReset });
|
|
@@ -5532,7 +6307,15 @@ function createDimEventNormalizer(opts) {
|
|
|
5532
6307
|
let currentMessageId = null;
|
|
5533
6308
|
let currentText = "";
|
|
5534
6309
|
let hasBoundarySignal = false;
|
|
6310
|
+
let pendingUserText = "";
|
|
6311
|
+
const flushUserMessage = () => {
|
|
6312
|
+
if (pendingUserText.trim().length > 0) {
|
|
6313
|
+
opts.onEvent({ type: "user_message", text: pendingUserText });
|
|
6314
|
+
}
|
|
6315
|
+
pendingUserText = "";
|
|
6316
|
+
};
|
|
5535
6317
|
const startMessage = (messageId) => {
|
|
6318
|
+
flushUserMessage();
|
|
5536
6319
|
if (currentMessageId !== null && currentText.trim().length > 0) {
|
|
5537
6320
|
opts.onEvent({ type: "assistant_message_completed", messageId: currentMessageId, text: currentText });
|
|
5538
6321
|
}
|
|
@@ -5545,7 +6328,22 @@ function createDimEventNormalizer(opts) {
|
|
|
5545
6328
|
const eventType = msg.eventType;
|
|
5546
6329
|
if (!eventType)
|
|
5547
6330
|
return;
|
|
6331
|
+
if (eventType !== "user_message:delta")
|
|
6332
|
+
flushUserMessage();
|
|
5548
6333
|
switch (eventType) {
|
|
6334
|
+
case "user_message:delta": {
|
|
6335
|
+
const delta = msg.payload?.delta ?? "";
|
|
6336
|
+
if (delta)
|
|
6337
|
+
pendingUserText += delta;
|
|
6338
|
+
break;
|
|
6339
|
+
}
|
|
6340
|
+
case "usage:context": {
|
|
6341
|
+
const usage2 = msg.payload?.usage;
|
|
6342
|
+
if (usage2 && typeof usage2 === "object") {
|
|
6343
|
+
opts.onEvent({ type: "usage", usage: normalizeUsage(usage2) });
|
|
6344
|
+
}
|
|
6345
|
+
break;
|
|
6346
|
+
}
|
|
5549
6347
|
case "message:started": {
|
|
5550
6348
|
const messageId = extractMessageId(msg.payload) ?? `msg-${msg.payload?.messageId ?? "run"}`;
|
|
5551
6349
|
startMessage(messageId);
|
|
@@ -5560,9 +6358,12 @@ function createDimEventNormalizer(opts) {
|
|
|
5560
6358
|
currentText = "";
|
|
5561
6359
|
break;
|
|
5562
6360
|
}
|
|
5563
|
-
case "thinking:delta":
|
|
6361
|
+
case "thinking:delta": {
|
|
6362
|
+
const delta = msg.payload?.delta ?? "";
|
|
6363
|
+
if (delta)
|
|
6364
|
+
opts.onEvent({ type: "assistant_thinking_delta", text: delta });
|
|
5564
6365
|
break;
|
|
5565
|
-
|
|
6366
|
+
}
|
|
5566
6367
|
case "text:delta": {
|
|
5567
6368
|
const delta = msg.payload?.delta ?? "";
|
|
5568
6369
|
if (!delta)
|
|
@@ -5636,6 +6437,7 @@ function createDimEventNormalizer(opts) {
|
|
|
5636
6437
|
break;
|
|
5637
6438
|
}
|
|
5638
6439
|
case "run:ended": {
|
|
6440
|
+
flushUserMessage();
|
|
5639
6441
|
if (currentMessageId !== null && currentText.trim().length > 0) {
|
|
5640
6442
|
opts.onEvent({ type: "assistant_message_completed", messageId: currentMessageId, text: currentText });
|
|
5641
6443
|
} else if (!hasBoundarySignal && finalText.trim()) {
|
|
@@ -5673,7 +6475,11 @@ function normalizeUsage(raw) {
|
|
|
5673
6475
|
return {
|
|
5674
6476
|
inputTokens: typeof u.inputTokens === "number" ? u.inputTokens : void 0,
|
|
5675
6477
|
outputTokens: typeof u.outputTokens === "number" ? u.outputTokens : void 0,
|
|
5676
|
-
totalTokens: typeof u.totalTokens === "number" ? u.totalTokens : void 0
|
|
6478
|
+
totalTokens: typeof u.totalTokens === "number" ? u.totalTokens : void 0,
|
|
6479
|
+
// ACP usage_update(上下文占用语义,P0-8):透传给前端状态行
|
|
6480
|
+
contextUsed: typeof u.contextUsed === "number" ? u.contextUsed : void 0,
|
|
6481
|
+
contextSize: typeof u.contextSize === "number" ? u.contextSize : void 0,
|
|
6482
|
+
cost: u.cost && typeof u.cost === "object" && typeof u.cost.amount === "number" ? u.cost : null
|
|
5677
6483
|
};
|
|
5678
6484
|
}
|
|
5679
6485
|
var DIM_CAPABILITIES = {
|
|
@@ -5728,8 +6534,8 @@ var DimAgentAdapter = class {
|
|
|
5728
6534
|
}
|
|
5729
6535
|
}
|
|
5730
6536
|
async probe() {
|
|
5731
|
-
const { existsSync:
|
|
5732
|
-
const installed =
|
|
6537
|
+
const { existsSync: existsSync18 } = await import("node:fs");
|
|
6538
|
+
const installed = existsSync18(this.dimPath) || this.dimPath === "dim";
|
|
5733
6539
|
let authStatus = installed ? "unknown" : "error";
|
|
5734
6540
|
let lastProbeErrorCode = installed ? void 0 : "DIM_CLI_NOT_FOUND";
|
|
5735
6541
|
let lastProbeError = installed ? void 0 : `\u672A\u627E\u5230 dim CLI\uFF08${this.dimPath}\uFF09`;
|
|
@@ -6021,19 +6827,26 @@ function pickLegacyDimModels(inventory) {
|
|
|
6021
6827
|
|
|
6022
6828
|
// src/daemon/repo-cache.ts
|
|
6023
6829
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
6024
|
-
import { existsSync as existsSync11, mkdirSync as
|
|
6025
|
-
import { dirname as
|
|
6830
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync9 } from "node:fs";
|
|
6831
|
+
import { dirname as dirname7, join as join15 } from "node:path";
|
|
6026
6832
|
function ownerOf(repoFullName) {
|
|
6027
6833
|
const idx = repoFullName.indexOf("/");
|
|
6028
6834
|
return idx > 0 ? repoFullName.slice(0, idx) : "_default";
|
|
6029
6835
|
}
|
|
6030
|
-
function
|
|
6031
|
-
return
|
|
6836
|
+
function runtimePathSegment(runtimeId) {
|
|
6837
|
+
return runtimeId.slice(0, 8);
|
|
6838
|
+
}
|
|
6839
|
+
function buildRepoCachePath(workdir, tenantId, runtimeId, repoFullName) {
|
|
6840
|
+
const runtimeSegment = runtimePathSegment(runtimeId);
|
|
6841
|
+
if (!runtimeSegment) {
|
|
6842
|
+
throw new Error("REPO_CACHE_RUNTIME_ID_MISSING: \u7F3A\u5C11 runtimeId\uFF0C\u65E0\u6CD5\u6784\u9020\u6309\u6267\u884C\u8005\u9694\u79BB\u7684 repo cache \u8DEF\u5F84");
|
|
6843
|
+
}
|
|
6844
|
+
return join15(workdir, "repos", tenantId, runtimeSegment, ownerOf(repoFullName), repoFullName);
|
|
6032
6845
|
}
|
|
6033
|
-
function openRepoCache(workdir, tenantId, repoFullName, env, agentRemoteUrl, daemonRemoteUrl) {
|
|
6034
|
-
const repoCachePath = buildRepoCachePath(workdir, tenantId, repoFullName);
|
|
6846
|
+
function openRepoCache(workdir, tenantId, runtimeId, repoFullName, env, agentRemoteUrl, daemonRemoteUrl) {
|
|
6847
|
+
const repoCachePath = buildRepoCachePath(workdir, tenantId, runtimeId, repoFullName);
|
|
6035
6848
|
const currentOrigin = () => {
|
|
6036
|
-
if (!existsSync11(
|
|
6849
|
+
if (!existsSync11(join15(repoCachePath, ".git")))
|
|
6037
6850
|
return null;
|
|
6038
6851
|
try {
|
|
6039
6852
|
return execFileSync7("git", ["-C", repoCachePath, "remote", "get-url", "origin"], { encoding: "utf8", env, stdio: "pipe" }).trim() || null;
|
|
@@ -6046,11 +6859,11 @@ function openRepoCache(workdir, tenantId, repoFullName, env, agentRemoteUrl, dae
|
|
|
6046
6859
|
return {
|
|
6047
6860
|
repoCachePath,
|
|
6048
6861
|
ensure() {
|
|
6049
|
-
if (existsSync11(
|
|
6862
|
+
if (existsSync11(join15(repoCachePath, ".git"))) {
|
|
6050
6863
|
syncRemoteUrl(repoCachePath, originUrl(), env);
|
|
6051
6864
|
return;
|
|
6052
6865
|
}
|
|
6053
|
-
|
|
6866
|
+
mkdirSync9(dirname7(repoCachePath), { recursive: true });
|
|
6054
6867
|
const cloneUrl = fetchUrl();
|
|
6055
6868
|
try {
|
|
6056
6869
|
execFileSync7("git", ["clone", "--filter=blob:none", cloneUrl, repoCachePath], { env, stdio: "pipe" });
|
|
@@ -6086,24 +6899,24 @@ function gitErrorText2(err) {
|
|
|
6086
6899
|
// src/daemon/repo-checkout-service.ts
|
|
6087
6900
|
import { createServer as createServer3 } from "node:http";
|
|
6088
6901
|
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
6089
|
-
import { existsSync as existsSync14, mkdirSync as
|
|
6090
|
-
import { basename as basename2, join as
|
|
6902
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync11, readdirSync as readdirSync5, writeFileSync as writeFileSync9 } from "node:fs";
|
|
6903
|
+
import { basename as basename2, join as join18 } from "node:path";
|
|
6091
6904
|
|
|
6092
6905
|
// src/daemon/worktree-manager.ts
|
|
6093
6906
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
6094
|
-
import { existsSync as existsSync13, mkdirSync as
|
|
6095
|
-
import { basename, join as
|
|
6907
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync10, readdirSync as readdirSync4, readFileSync as readFileSync9, renameSync as renameSync2, rmSync as rmSync9, statSync as statSync3, writeFileSync as writeFileSync8 } from "node:fs";
|
|
6908
|
+
import { basename, join as join17, sep as sep2 } from "node:path";
|
|
6096
6909
|
|
|
6097
6910
|
// src/daemon/workspace-marker.ts
|
|
6098
|
-
import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as
|
|
6099
|
-
import { join as
|
|
6911
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "node:fs";
|
|
6912
|
+
import { join as join16 } from "node:path";
|
|
6100
6913
|
var WORKTREE_MARKER_FILE = ".morula-worktree.json";
|
|
6101
6914
|
var WORKTREE_MARKER_SCHEMA = 1;
|
|
6102
6915
|
function markerPath(worktreePath) {
|
|
6103
|
-
return
|
|
6916
|
+
return join16(worktreePath, WORKTREE_MARKER_FILE);
|
|
6104
6917
|
}
|
|
6105
6918
|
function writeWorktreeMarker(worktreePath, marker) {
|
|
6106
|
-
|
|
6919
|
+
writeFileSync7(markerPath(worktreePath), `${JSON.stringify(marker, null, 2)}
|
|
6107
6920
|
`, "utf8");
|
|
6108
6921
|
}
|
|
6109
6922
|
function readWorktreeMarker(worktreePath) {
|
|
@@ -6130,19 +6943,19 @@ function safeDirName(name) {
|
|
|
6130
6943
|
return safe || "task";
|
|
6131
6944
|
}
|
|
6132
6945
|
function buildTaskRootPath(workdir, tenantId, runtimeId, workContextId, code) {
|
|
6133
|
-
const runtimeSegment = runtimeId
|
|
6946
|
+
const runtimeSegment = runtimePathSegment(runtimeId);
|
|
6134
6947
|
if (!runtimeSegment) {
|
|
6135
6948
|
throw new Error("WORKTREE_RUNTIME_ID_MISSING: \u7F3A\u5C11 runtimeId\uFF0C\u65E0\u6CD5\u6784\u9020\u6309\u6267\u884C\u8005\u9694\u79BB\u7684 worktree \u8DEF\u5F84");
|
|
6136
6949
|
}
|
|
6137
6950
|
const dirName = code ? `${safeDirName(code)}-${workContextId.slice(0, 8)}` : workContextId;
|
|
6138
|
-
return
|
|
6951
|
+
return join17(workdir, "worktrees", tenantId, runtimeSegment, dirName);
|
|
6139
6952
|
}
|
|
6140
6953
|
function buildWorktreePath(workdir, tenantId, runtimeId, workContextId, code, repoFullName) {
|
|
6141
|
-
return
|
|
6954
|
+
return join17(buildTaskRootPath(workdir, tenantId, runtimeId, workContextId, code), safeDirName(repoFullName));
|
|
6142
6955
|
}
|
|
6143
6956
|
function buildLegacyWorktreePath(workdir, tenantId, workContextId, code) {
|
|
6144
6957
|
const dirName = code ? `${safeDirName(code)}-${workContextId.slice(0, 8)}` : workContextId;
|
|
6145
|
-
return
|
|
6958
|
+
return join17(workdir, "worktrees", tenantId, dirName);
|
|
6146
6959
|
}
|
|
6147
6960
|
var legacyPathWarned = /* @__PURE__ */ new Set();
|
|
6148
6961
|
function warnLegacyWorktreeResidue(legacyPath) {
|
|
@@ -6153,13 +6966,13 @@ function warnLegacyWorktreeResidue(legacyPath) {
|
|
|
6153
6966
|
}
|
|
6154
6967
|
var legacyLayoutWarned = /* @__PURE__ */ new Set();
|
|
6155
6968
|
function warnLegacyLayoutWorktreeResidue(taskRoot, worktreePath) {
|
|
6156
|
-
if (legacyLayoutWarned.has(taskRoot) || existsSync13(worktreePath) || !existsSync13(
|
|
6969
|
+
if (legacyLayoutWarned.has(taskRoot) || existsSync13(worktreePath) || !existsSync13(join17(taskRoot, WORKTREE_MARKER_FILE)))
|
|
6157
6970
|
return;
|
|
6158
6971
|
legacyLayoutWarned.add(taskRoot);
|
|
6159
6972
|
console.warn(`[daemon] \u68C0\u6D4B\u5230\u65E7\u5E03\u5C40\u4EFB\u52A1\u5DE5\u4F5C\u533A\uFF08\u4ED3\u5E93\u76F4\u63A5\u4F4D\u4E8E\u5DE5\u4F5C\u533A\u6839\uFF09: ${taskRoot} \u2014\u2014 \u65B0\u5E03\u5C40\u4F1A\u5728\u5176\u4E0B\u521B\u5EFA\u4ED3\u5E93\u5B50\u76EE\u5F55\uFF0C\u65E7\u76EE\u5F55\u5185\u5BB9\u4E0D\u518D\u88AB\u4F7F\u7528\uFF1B\u5176\u4E2D\u53EF\u80FD\u6709\u672A\u63A8\u9001\u6539\u52A8\uFF0C\u786E\u8BA4\u65E0\u9700\u4FDD\u7559\u540E\u53EF\u624B\u52A8\u5220\u9664\uFF08\u4E0D\u81EA\u52A8\u8FC1\u79FB/\u6E05\u7406\uFF09`);
|
|
6160
6973
|
}
|
|
6161
6974
|
function ensureMarkerIgnoredAndCommitted(worktreePath, env) {
|
|
6162
|
-
const gitignorePath =
|
|
6975
|
+
const gitignorePath = join17(worktreePath, ".gitignore");
|
|
6163
6976
|
const existing = existsSync13(gitignorePath) ? readFileSync9(gitignorePath, "utf8") : "";
|
|
6164
6977
|
const requiredLines = [WORKTREE_MARKER_FILE];
|
|
6165
6978
|
const missing = requiredLines.filter((l) => !existing.split("\n").some((line) => line.trim() === l));
|
|
@@ -6167,7 +6980,7 @@ function ensureMarkerIgnoredAndCommitted(worktreePath, env) {
|
|
|
6167
6980
|
const next = `${existing.trimEnd() ? `${existing.trimEnd()}
|
|
6168
6981
|
` : ""}${missing.join("\n")}
|
|
6169
6982
|
`;
|
|
6170
|
-
|
|
6983
|
+
writeFileSync8(gitignorePath, next, "utf8");
|
|
6171
6984
|
try {
|
|
6172
6985
|
execFileSync8("git", ["-C", worktreePath, "add", ".gitignore"], { env, stdio: "pipe" });
|
|
6173
6986
|
execFileSync8(
|
|
@@ -6257,7 +7070,7 @@ function isIndexLockConflict(err) {
|
|
|
6257
7070
|
return /index\.lock/i.test(text) || /另一个 git 进程|another git process/i.test(text);
|
|
6258
7071
|
}
|
|
6259
7072
|
function worktreeIndexLockPath(repoCachePath, worktreePath) {
|
|
6260
|
-
return
|
|
7073
|
+
return join17(repoCachePath, ".git", "worktrees", basename(worktreePath), "index.lock");
|
|
6261
7074
|
}
|
|
6262
7075
|
function indexLockPathFromError(text) {
|
|
6263
7076
|
const m = text.match(/[^\s'‘’"]*index\.lock/);
|
|
@@ -6311,7 +7124,7 @@ var foreignResidueHits = /* @__PURE__ */ new Map();
|
|
|
6311
7124
|
function inspectForeignDirectory(dir) {
|
|
6312
7125
|
let entries = [];
|
|
6313
7126
|
try {
|
|
6314
|
-
entries =
|
|
7127
|
+
entries = readdirSync4(dir, { withFileTypes: true });
|
|
6315
7128
|
} catch {
|
|
6316
7129
|
}
|
|
6317
7130
|
const topNames = entries.slice(0, 20).map((e) => `${e.name}${e.isDirectory() ? "/" : ""}`);
|
|
@@ -6325,7 +7138,7 @@ function inspectForeignDirectory(dir) {
|
|
|
6325
7138
|
return;
|
|
6326
7139
|
let children = [];
|
|
6327
7140
|
try {
|
|
6328
|
-
children =
|
|
7141
|
+
children = readdirSync4(p, { withFileTypes: true });
|
|
6329
7142
|
} catch {
|
|
6330
7143
|
return;
|
|
6331
7144
|
}
|
|
@@ -6333,9 +7146,9 @@ function inspectForeignDirectory(dir) {
|
|
|
6333
7146
|
if (count >= FOREIGN_RESIDUE_SCAN_LIMIT)
|
|
6334
7147
|
return;
|
|
6335
7148
|
count++;
|
|
6336
|
-
const full =
|
|
7149
|
+
const full = join17(p, e.name);
|
|
6337
7150
|
try {
|
|
6338
|
-
const st =
|
|
7151
|
+
const st = statSync3(full);
|
|
6339
7152
|
bytes += st.size;
|
|
6340
7153
|
minMs = Math.min(minMs, st.mtimeMs);
|
|
6341
7154
|
maxMs = Math.max(maxMs, st.mtimeMs);
|
|
@@ -6365,7 +7178,7 @@ function reportForeignResidue(worktreePath, forensics, cleaned) {
|
|
|
6365
7178
|
console.warn(`[daemon] ${detail}`);
|
|
6366
7179
|
}
|
|
6367
7180
|
function prepareWorktree(input) {
|
|
6368
|
-
const cache = openRepoCache(input.workdir, input.tenantId, input.repoFullName, input.env, input.remoteUrl, input.daemonRemoteUrl);
|
|
7181
|
+
const cache = openRepoCache(input.workdir, input.tenantId, input.runtimeId, input.repoFullName, input.env, input.remoteUrl, input.daemonRemoteUrl);
|
|
6369
7182
|
cache.ensure();
|
|
6370
7183
|
cache.fetch();
|
|
6371
7184
|
const requestedBase = input.baseBranch?.trim();
|
|
@@ -6421,7 +7234,7 @@ function prepareWorktree(input) {
|
|
|
6421
7234
|
if (!isGitRepo) {
|
|
6422
7235
|
const forensics = inspectForeignDirectory(worktreePath);
|
|
6423
7236
|
try {
|
|
6424
|
-
|
|
7237
|
+
rmSync9(worktreePath, { recursive: true, force: true });
|
|
6425
7238
|
} catch {
|
|
6426
7239
|
reportForeignResidue(worktreePath, forensics, false);
|
|
6427
7240
|
throw new Error(`WORKTREE_RESIDUE: \u76EE\u5F55 ${worktreePath} \u6B8B\u7559\u4E14\u6E05\u7406\u5931\u8D25\uFF0C\u8BF7\u4EBA\u5DE5\u5904\u7406`);
|
|
@@ -6477,7 +7290,7 @@ function prepareWorktree(input) {
|
|
|
6477
7290
|
}
|
|
6478
7291
|
}
|
|
6479
7292
|
if (!existsSync13(worktreePath)) {
|
|
6480
|
-
|
|
7293
|
+
mkdirSync10(join17(worktreePath, ".."), { recursive: true });
|
|
6481
7294
|
addWorktreeWithRetry(cache.repoCachePath, worktreePath, `origin/${input.baseBranch}`);
|
|
6482
7295
|
writeWorktreeMarker(worktreePath, {
|
|
6483
7296
|
schemaVersion: 1,
|
|
@@ -6494,7 +7307,7 @@ function prepareWorktree(input) {
|
|
|
6494
7307
|
{
|
|
6495
7308
|
const dotMorulaRescue = `${worktreePath}.morula-rescue`;
|
|
6496
7309
|
if (existsSync13(dotMorulaRescue)) {
|
|
6497
|
-
const dotMorulaDir =
|
|
7310
|
+
const dotMorulaDir = join17(worktreePath, ".morula");
|
|
6498
7311
|
if (!existsSync13(dotMorulaDir)) {
|
|
6499
7312
|
try {
|
|
6500
7313
|
renameSync2(dotMorulaRescue, dotMorulaDir);
|
|
@@ -6503,7 +7316,7 @@ function prepareWorktree(input) {
|
|
|
6503
7316
|
console.warn(`[daemon] worktree \u6F02\u79FB\u81EA\u52A8\u6062\u590D: .morula/ \u5F52\u8FD8\u5931\u8D25\uFF0C\u5907\u4EFD\u4FDD\u7559\u5728 ${dotMorulaRescue}`);
|
|
6504
7317
|
}
|
|
6505
7318
|
} else {
|
|
6506
|
-
|
|
7319
|
+
rmSync9(dotMorulaRescue, { recursive: true, force: true });
|
|
6507
7320
|
}
|
|
6508
7321
|
}
|
|
6509
7322
|
}
|
|
@@ -6523,10 +7336,10 @@ function prepareWorktree(input) {
|
|
|
6523
7336
|
};
|
|
6524
7337
|
}
|
|
6525
7338
|
function resolveWorkspaceLayout(root, expected) {
|
|
6526
|
-
const primaryDir =
|
|
6527
|
-
if (existsSync13(
|
|
7339
|
+
const primaryDir = join17(root, safeDirName(expected.repoFullName));
|
|
7340
|
+
if (existsSync13(join17(primaryDir, WORKTREE_MARKER_FILE)))
|
|
6528
7341
|
return { primaryDir, legacy: false };
|
|
6529
|
-
if (existsSync13(
|
|
7342
|
+
if (existsSync13(join17(root, WORKTREE_MARKER_FILE)))
|
|
6530
7343
|
return { primaryDir: root, legacy: true };
|
|
6531
7344
|
return null;
|
|
6532
7345
|
}
|
|
@@ -6550,7 +7363,7 @@ function validateRemovable(taskRoot, expected, repoCachePath) {
|
|
|
6550
7363
|
).join("\n");
|
|
6551
7364
|
if (statusRelevant.length > 0)
|
|
6552
7365
|
return "WORKTREE_DIRTY";
|
|
6553
|
-
const additionalActivity = legacy ? findActivityInReposDir(
|
|
7366
|
+
const additionalActivity = legacy ? findActivityInReposDir(join17(primaryDir, ".morula", "repos")) : findAdditionalRepoActivity(taskRoot, { excludeDirs: [primaryDir] });
|
|
6554
7367
|
if (additionalActivity)
|
|
6555
7368
|
return "WORKTREE_DIRTY";
|
|
6556
7369
|
try {
|
|
@@ -6593,13 +7406,13 @@ function findActivityInReposDir(reposDir) {
|
|
|
6593
7406
|
return null;
|
|
6594
7407
|
let names = [];
|
|
6595
7408
|
try {
|
|
6596
|
-
names =
|
|
7409
|
+
names = readdirSync4(reposDir);
|
|
6597
7410
|
} catch {
|
|
6598
7411
|
return null;
|
|
6599
7412
|
}
|
|
6600
7413
|
for (const name of names) {
|
|
6601
|
-
const repoPath =
|
|
6602
|
-
if (!existsSync13(
|
|
7414
|
+
const repoPath = join17(reposDir, name);
|
|
7415
|
+
if (!existsSync13(join17(repoPath, ".git")))
|
|
6603
7416
|
continue;
|
|
6604
7417
|
if (repoHasActivity(repoPath))
|
|
6605
7418
|
return repoPath;
|
|
@@ -6610,20 +7423,20 @@ function findAdditionalRepoActivity(taskRoot, options = {}) {
|
|
|
6610
7423
|
const excluded = new Set(options.excludeDirs ?? []);
|
|
6611
7424
|
let entries = [];
|
|
6612
7425
|
try {
|
|
6613
|
-
entries =
|
|
7426
|
+
entries = readdirSync4(taskRoot, { withFileTypes: true });
|
|
6614
7427
|
} catch {
|
|
6615
7428
|
return null;
|
|
6616
7429
|
}
|
|
6617
|
-
const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) =>
|
|
6618
|
-
const legacyReposDir =
|
|
7430
|
+
const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => join17(taskRoot, e.name)).filter((dir) => !excluded.has(dir));
|
|
7431
|
+
const legacyReposDir = join17(taskRoot, ".morula", "repos");
|
|
6619
7432
|
if (existsSync13(legacyReposDir)) {
|
|
6620
7433
|
try {
|
|
6621
|
-
candidates.push(...
|
|
7434
|
+
candidates.push(...readdirSync4(legacyReposDir).map((n) => join17(legacyReposDir, n)));
|
|
6622
7435
|
} catch {
|
|
6623
7436
|
}
|
|
6624
7437
|
}
|
|
6625
7438
|
for (const repoPath of candidates) {
|
|
6626
|
-
if (!existsSync13(
|
|
7439
|
+
if (!existsSync13(join17(repoPath, ".git")))
|
|
6627
7440
|
continue;
|
|
6628
7441
|
if (repoHasActivity(repoPath))
|
|
6629
7442
|
return repoPath;
|
|
@@ -6631,18 +7444,18 @@ function findAdditionalRepoActivity(taskRoot, options = {}) {
|
|
|
6631
7444
|
return null;
|
|
6632
7445
|
}
|
|
6633
7446
|
function cleanupInjectedSkills(dir) {
|
|
6634
|
-
const injectedSkillsRoot =
|
|
7447
|
+
const injectedSkillsRoot = join17(dir, ".agents", "skills");
|
|
6635
7448
|
if (!existsSync13(injectedSkillsRoot))
|
|
6636
7449
|
return;
|
|
6637
7450
|
try {
|
|
6638
|
-
for (const name of
|
|
7451
|
+
for (const name of readdirSync4(injectedSkillsRoot)) {
|
|
6639
7452
|
if (name === COLLABORATION_SKILL_NAME || /^[\w-]{1,64}$/.test(name)) {
|
|
6640
|
-
|
|
7453
|
+
rmSync9(join17(injectedSkillsRoot, name), { recursive: true, force: true });
|
|
6641
7454
|
}
|
|
6642
7455
|
}
|
|
6643
|
-
if (
|
|
6644
|
-
|
|
6645
|
-
|
|
7456
|
+
if (readdirSync4(injectedSkillsRoot).length === 0) {
|
|
7457
|
+
rmSync9(join17(dir, ".agents", "skills"), { recursive: true, force: true });
|
|
7458
|
+
rmSync9(join17(dir, ".agents"), { recursive: true, force: true });
|
|
6646
7459
|
}
|
|
6647
7460
|
} catch {
|
|
6648
7461
|
}
|
|
@@ -6651,15 +7464,15 @@ function collectSiblingGitCommonDirs(taskRoot, excludeDir) {
|
|
|
6651
7464
|
const dirs = /* @__PURE__ */ new Set();
|
|
6652
7465
|
let entries = [];
|
|
6653
7466
|
try {
|
|
6654
|
-
entries =
|
|
7467
|
+
entries = readdirSync4(taskRoot, { withFileTypes: true });
|
|
6655
7468
|
} catch {
|
|
6656
7469
|
return [];
|
|
6657
7470
|
}
|
|
6658
7471
|
for (const e of entries) {
|
|
6659
7472
|
if (!e.isDirectory() || e.name.startsWith("."))
|
|
6660
7473
|
continue;
|
|
6661
|
-
const repoPath =
|
|
6662
|
-
if (repoPath === excludeDir || !existsSync13(
|
|
7474
|
+
const repoPath = join17(taskRoot, e.name);
|
|
7475
|
+
if (repoPath === excludeDir || !existsSync13(join17(repoPath, ".git")))
|
|
6663
7476
|
continue;
|
|
6664
7477
|
try {
|
|
6665
7478
|
const commonDir = execFileSync8("git", ["-C", repoPath, "rev-parse", "--path-format=absolute", "--git-common-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
@@ -6676,13 +7489,13 @@ function removeTaskWorkspace(repoCachePath, taskRoot, expected) {
|
|
|
6676
7489
|
throw new Error("MARKER_MISMATCH");
|
|
6677
7490
|
const { primaryDir, legacy } = layout;
|
|
6678
7491
|
if (!legacy) {
|
|
6679
|
-
if (primaryDir !==
|
|
7492
|
+
if (primaryDir !== join17(taskRoot, safeDirName(expected.repoFullName)) || !isManagedWorkspaceRoot(taskRoot))
|
|
6680
7493
|
throw new Error("GIT_REMOVE_FAILED");
|
|
6681
7494
|
}
|
|
6682
7495
|
cleanupInjectedSkills(legacy ? primaryDir : taskRoot);
|
|
6683
7496
|
const siblingCommonDirs = legacy ? [] : collectSiblingGitCommonDirs(taskRoot, primaryDir);
|
|
6684
7497
|
try {
|
|
6685
|
-
|
|
7498
|
+
rmSync9(taskRoot, { recursive: true, force: true });
|
|
6686
7499
|
} catch {
|
|
6687
7500
|
throw new Error("GIT_REMOVE_FAILED");
|
|
6688
7501
|
}
|
|
@@ -6706,7 +7519,7 @@ function removeWorktree(repoCachePath, worktreePath) {
|
|
|
6706
7519
|
}
|
|
6707
7520
|
|
|
6708
7521
|
// src/daemon/repo-checkout-service.ts
|
|
6709
|
-
var CHECKOUT_JSON_FILE =
|
|
7522
|
+
var CHECKOUT_JSON_FILE = join18(".morula", "checkout.json");
|
|
6710
7523
|
var LocalRepoCheckoutService = class {
|
|
6711
7524
|
constructor(opts) {
|
|
6712
7525
|
this.opts = opts;
|
|
@@ -6794,9 +7607,9 @@ var LocalRepoCheckoutService = class {
|
|
|
6794
7607
|
return;
|
|
6795
7608
|
this.tasks.set(taskId, { workdir, checkToken, primaryRepoDir });
|
|
6796
7609
|
try {
|
|
6797
|
-
const dir =
|
|
6798
|
-
|
|
6799
|
-
|
|
7610
|
+
const dir = join18(workdir, ".morula");
|
|
7611
|
+
mkdirSync11(dir, { recursive: true, mode: 448 });
|
|
7612
|
+
writeFileSync9(join18(dir, "checkout.json"), JSON.stringify({
|
|
6800
7613
|
taskId,
|
|
6801
7614
|
port: this.boundPort,
|
|
6802
7615
|
checkToken
|
|
@@ -6848,6 +7661,7 @@ var LocalRepoCheckoutService = class {
|
|
|
6848
7661
|
const cache = openRepoCache(
|
|
6849
7662
|
this.opts.cfg.workdir,
|
|
6850
7663
|
this.opts.tenantId,
|
|
7664
|
+
this.runtimeId,
|
|
6851
7665
|
repoFullName,
|
|
6852
7666
|
env,
|
|
6853
7667
|
baseUrl ? buildGitlabSshRemote(repoFullName, baseUrl) : void 0,
|
|
@@ -6912,6 +7726,7 @@ var LocalRepoCheckoutService = class {
|
|
|
6912
7726
|
const cache = openRepoCache(
|
|
6913
7727
|
this.opts.cfg.workdir,
|
|
6914
7728
|
this.opts.tenantId,
|
|
7729
|
+
this.runtimeId,
|
|
6915
7730
|
repoFullName,
|
|
6916
7731
|
env,
|
|
6917
7732
|
baseUrl ? buildGitlabSshRemote(repoFullName, baseUrl) : void 0,
|
|
@@ -6960,11 +7775,11 @@ function isReadOnlyRepoDir(dirName) {
|
|
|
6960
7775
|
return READ_ONLY_DIR_RE.test(dirName) || dirName.startsWith(LEGACY_READ_ONLY_DIR_PREFIX);
|
|
6961
7776
|
}
|
|
6962
7777
|
function resolveRepoTargetDir(taskRoot, base) {
|
|
6963
|
-
const direct =
|
|
6964
|
-
if (!existsSync14(direct) || existsSync14(
|
|
7778
|
+
const direct = join18(taskRoot, base);
|
|
7779
|
+
if (!existsSync14(direct) || existsSync14(join18(direct, ".git")))
|
|
6965
7780
|
return direct;
|
|
6966
7781
|
for (let i = 2; i < 100; i++) {
|
|
6967
|
-
const candidate =
|
|
7782
|
+
const candidate = join18(taskRoot, `${base}-${i}`);
|
|
6968
7783
|
if (!existsSync14(candidate))
|
|
6969
7784
|
return candidate;
|
|
6970
7785
|
}
|
|
@@ -6973,7 +7788,7 @@ function resolveRepoTargetDir(taskRoot, base) {
|
|
|
6973
7788
|
function materializeReadWorktree(input) {
|
|
6974
7789
|
const worktreePath = resolveRepoTargetDir(input.taskRoot, `${safeRepoDirName(input.repoFullName)}${READ_ONLY_REPO_DIR_SUFFIX}`);
|
|
6975
7790
|
const targetRef = resolveReadRef(input.repoCachePath, input.env, input.ref, input.baseBranch);
|
|
6976
|
-
if (existsSync14(
|
|
7791
|
+
if (existsSync14(join18(worktreePath, ".git"))) {
|
|
6977
7792
|
const current = currentBranchName(worktreePath);
|
|
6978
7793
|
if (current)
|
|
6979
7794
|
throw new Error(`READ_WORKTREE_DIRTY: \u53EA\u8BFB\u68C0\u51FA\u5DF2\u5207\u5230\u5206\u652F ${current}\uFF0C\u62D2\u7EDD\u81EA\u52A8\u66F4\u65B0\uFF08\u8BF7\u6E05\u7406\u8BE5\u76EE\u5F55\u540E\u91CD\u8BD5\uFF09`);
|
|
@@ -6981,7 +7796,7 @@ function materializeReadWorktree(input) {
|
|
|
6981
7796
|
throw new Error("READ_WORKTREE_DIRTY: \u53EA\u8BFB\u68C0\u51FA\u5B58\u5728\u672A\u63D0\u4EA4\u6539\u52A8\uFF0C\u62D2\u7EDD\u81EA\u52A8\u66F4\u65B0\uFF08\u8BF7\u6E05\u7406\u8BE5\u76EE\u5F55\u540E\u91CD\u8BD5\uFF09");
|
|
6982
7797
|
execFileSync9("git", ["-C", worktreePath, "checkout", "--detach", targetRef], { env: input.env, stdio: "pipe" });
|
|
6983
7798
|
} else {
|
|
6984
|
-
|
|
7799
|
+
mkdirSync11(join18(worktreePath, ".."), { recursive: true });
|
|
6985
7800
|
execFileSync9("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", worktreePath, targetRef], {
|
|
6986
7801
|
env: input.env,
|
|
6987
7802
|
stdio: "pipe"
|
|
@@ -7049,11 +7864,11 @@ function listMaterializedRepoDirs(taskRoot, options = {}) {
|
|
|
7049
7864
|
const excluded = new Set(options.excludeDirs ?? []);
|
|
7050
7865
|
let entries;
|
|
7051
7866
|
try {
|
|
7052
|
-
entries =
|
|
7867
|
+
entries = readdirSync5(taskRoot, { withFileTypes: true });
|
|
7053
7868
|
} catch {
|
|
7054
7869
|
return [];
|
|
7055
7870
|
}
|
|
7056
|
-
return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) =>
|
|
7871
|
+
return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => join18(taskRoot, e.name)).filter((dir) => !excluded.has(dir) && !isReadOnlyRepoDir(basename2(dir))).filter((dir) => existsSync14(join18(dir, ".git"))).sort();
|
|
7057
7872
|
}
|
|
7058
7873
|
function parseRepoFullNameFromRemoteUrl(url) {
|
|
7059
7874
|
const raw = url?.trim();
|
|
@@ -7075,7 +7890,7 @@ function parseRepoFullNameFromRemoteUrl(url) {
|
|
|
7075
7890
|
}
|
|
7076
7891
|
function collectMaterializedRepoDeliveryTargets(input) {
|
|
7077
7892
|
const excludedNames = new Set(input.excludeRepoFullNames?.filter(Boolean) ?? []);
|
|
7078
|
-
const excludedDirs = [...excludedNames].map((name) =>
|
|
7893
|
+
const excludedDirs = [...excludedNames].map((name) => join18(input.taskRoot, safeRepoDirName(name)));
|
|
7079
7894
|
const recordsByDir = /* @__PURE__ */ new Map();
|
|
7080
7895
|
const recordsByDirName = /* @__PURE__ */ new Map();
|
|
7081
7896
|
for (const [repoFullName, record] of input.prepared ?? []) {
|
|
@@ -7138,7 +7953,7 @@ function syncGitIdentity(primaryDir, targetDir, env) {
|
|
|
7138
7953
|
}
|
|
7139
7954
|
}
|
|
7140
7955
|
function materializeAdditionalRepo(input) {
|
|
7141
|
-
if (existsSync14(
|
|
7956
|
+
if (existsSync14(join18(input.targetPath, ".git"))) {
|
|
7142
7957
|
const current = currentBranchName(input.targetPath);
|
|
7143
7958
|
if (!current && !hasLocalChanges2(input.targetPath, input.env)) {
|
|
7144
7959
|
const targetRef = resolveReadRef(input.repoCachePath, input.env, void 0, input.baseBranch);
|
|
@@ -7158,7 +7973,7 @@ function materializeAdditionalRepo(input) {
|
|
|
7158
7973
|
}
|
|
7159
7974
|
if (!baseRef)
|
|
7160
7975
|
throw new Error("BASE_REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u8FDC\u7AEF\u5F15\u7528\uFF08origin/<base> / origin/main / origin/master\uFF09");
|
|
7161
|
-
|
|
7976
|
+
mkdirSync11(join18(input.targetPath, ".."), { recursive: true });
|
|
7162
7977
|
execFileSync9("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", input.targetPath, baseRef], {
|
|
7163
7978
|
env: input.env,
|
|
7164
7979
|
stdio: "pipe"
|
|
@@ -7166,94 +7981,814 @@ function materializeAdditionalRepo(input) {
|
|
|
7166
7981
|
return input.targetPath;
|
|
7167
7982
|
}
|
|
7168
7983
|
|
|
7169
|
-
// src/daemon/
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
}
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
return null;
|
|
7213
|
-
return {
|
|
7214
|
-
type: "agent-task:message",
|
|
7215
|
-
taskId,
|
|
7216
|
-
objectId: asNonEmptyString(frame.objectId),
|
|
7217
|
-
anchorKey: typeof frame.anchorKey === "string" ? frame.anchorKey : "",
|
|
7218
|
-
messages
|
|
7219
|
-
};
|
|
7220
|
-
}
|
|
7221
|
-
function upsertTaskMessages(existing, incoming, max = TASK_MESSAGE_QUEUE_MAX) {
|
|
7222
|
-
const byId = /* @__PURE__ */ new Map();
|
|
7223
|
-
for (const message of existing)
|
|
7224
|
-
byId.set(message.id, message);
|
|
7225
|
-
for (const message of incoming)
|
|
7226
|
-
byId.set(message.id, message);
|
|
7227
|
-
const merged = [...byId.values()].sort((a, b) => a.seq - b.seq || a.id.localeCompare(b.id));
|
|
7228
|
-
const dropped = Math.max(0, merged.length - max);
|
|
7229
|
-
return { messages: dropped > 0 ? merged.slice(dropped) : merged, dropped };
|
|
7230
|
-
}
|
|
7231
|
-
function selectConsumedMessages(messages, taskId) {
|
|
7232
|
-
return messages.filter((message) => message.targetTaskId === taskId);
|
|
7233
|
-
}
|
|
7234
|
-
function selectSteerMessages(messages, taskId) {
|
|
7235
|
-
return messages.filter((message) => message.mode === "steer").filter((message) => message.deliveryState !== "consumed" && message.deliveryState !== "withdrawn").filter((message) => message.targetTaskId === taskId).sort((a, b) => a.seq - b.seq);
|
|
7236
|
-
}
|
|
7237
|
-
function renderMessageList(messages) {
|
|
7238
|
-
return messages.map((message) => `${message.seq}. \u3010${message.authorType === "user" ? "\u4EBA" : message.authorType === "system" ? "\u5E73\u53F0" : "\u8FD0\u884C\u65F6"}\u3011${message.content}`).join("\n\n");
|
|
7984
|
+
// src/daemon/session-host.ts
|
|
7985
|
+
init_dist2();
|
|
7986
|
+
init_acp_connection();
|
|
7987
|
+
import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync10 } from "node:fs";
|
|
7988
|
+
import { join as join19 } from "node:path";
|
|
7989
|
+
|
|
7990
|
+
// src/daemon/steward-instructions.ts
|
|
7991
|
+
var STEWARD_SYSTEM_INSTRUCTIONS = `\u4F60\u662F\u300C\u7A7A\u95F4\u7BA1\u5BB6\u667A\u80FD\u4F53\u300D\uFF08Project Steward\uFF09\u3002
|
|
7992
|
+
|
|
7993
|
+
\u4F60\u7684\u76EE\u6807\uFF1A\u505A\u4E09\u4EF6\u4E8B\u2014\u2014\u5BF9\u7528\u6237\u63D0\u51FA\u7684\u9700\u6C42/\u95EE\u9898\u505A**\u65B9\u5411\u7EA7\u88C1\u5B9A**\uFF08\u8BE5\u4E0D\u8BE5\u505A\uFF09\uFF1B\u9700\u8981\u505A\u65F6**\u6D3E\u5355**\u7ED9\u300C\u5F85\u5206\u6790\u300D\u8FD0\u884C\u65F6\uFF0C\u5E76\u5728\u5F00\u53D1/\u8BC4\u5BA1\u8FC7\u7A0B\u4E2D**\u8DDF\u8E2A\u8FDB\u5C55**\uFF1B\u4EE5\u53CA**\u7BA1\u7406\u672C\u7A7A\u95F4\u7684\u4EFB\u52A1**\uFF08\u6539\u5C5E\u6027\u4E0E\u8D1F\u8D23\u4EBA\u3001\u63A8\u8FDB\u72B6\u6001\u3001\u5728\u4EFB\u52A1\u91CC @ \u4EBA\u50AC\u529E\u3001\u505C\u6389\u6216\u91CD\u8DD1\u5361\u4F4F\u7684\u6267\u884C\uFF09\u3002
|
|
7994
|
+
\u4F60\u7684\u8FB9\u754C\uFF1A\u4E0D\u5199\u4EE3\u7801\u3001\u4E0D\u505A\u6587\u4EF6\u7EA7\u65B9\u6848\u8BBE\u8BA1\u3001\u4E0D\u76F4\u63A5\u5408\u5E76\u4EE3\u7801\uFF1B\u6700\u7EC8\u5408\u5E76/\u53D1\u5E03\u7531\u4EBA\u51B3\u5B9A\u3002
|
|
7995
|
+
|
|
7996
|
+
## \u4F60\u7684\u5DE5\u4F5C\u533A\u4E0E\u5DE5\u5177
|
|
7997
|
+
|
|
7998
|
+
- \u7A7A\u95F4\u8BF4\u660E\uFF08\u7A7A\u95F4\u4FE1\u606F\u3001\u4ED3\u5E93\u6E05\u5355\u3001\u7EA6\u5B9A\u4E0E\u8FB9\u754C\u3001\u5B8C\u6210\u6807\u51C6\uFF09\uFF1A\u5DE5\u4F5C\u533A\u6839 \`AGENTS.md\`\uFF0C\u5DF2\u81EA\u52A8\u52A0\u8F7D\uFF1B
|
|
7999
|
+
- \u4F1A\u8BDD\u4E8B\u5B9E\uFF08\u7A7A\u95F4 ID\u3001\u79DF\u6237\u3001\u5F53\u524D\u7528\u6237\u3001\u7ED1\u5B9A\u4ED3\u5E93\uFF09\uFF1A\`.agent_context/steward-context.md\`\uFF1B
|
|
8000
|
+
- \u884C\u4E3A\u5951\u7EA6\u4E0E\u547D\u4EE4\u7528\u6CD5\uFF1A\`steward-management\` skill\uFF08\u5DF2\u6CE8\u5165 \`.agents/skills/\`\uFF09\uFF0C**\u52A8\u624B\u524D\u5148\u8BFB\u5B83**\uFF1B
|
|
8001
|
+
\u4E0E\u5E73\u53F0\u4EA4\u4E92\u53EA\u80FD\u8D70 \`morula\` CLI\uFF08\u672C\u4F1A\u8BDD\u5DE5\u4F5C\u533A\u5DF2\u6CE8\u5165 PATH \u4E0E\u51ED\u8BC1\uFF09\uFF0C\u6240\u6709\u67E5\u8BE2\u663E\u5F0F\u5E26 \`--space <spaceId>\`\u3002`;
|
|
8002
|
+
function buildStewardContextMd(facts) {
|
|
8003
|
+
const lines = [
|
|
8004
|
+
"# \u7BA1\u5BB6\u4F1A\u8BDD\u4E0A\u4E0B\u6587",
|
|
8005
|
+
"",
|
|
8006
|
+
"| \u4E8B\u5B9E | \u503C |",
|
|
8007
|
+
"|------|-----|",
|
|
8008
|
+
`| \u7A7A\u95F4 ID\uFF08\`--space\` \u7528\u5B83\uFF09 | \`${facts.projectId}\` |`,
|
|
8009
|
+
`| \u7A7A\u95F4\u540D\u79F0 | ${facts.projectName?.trim() || "\uFF08\u672A\u547D\u540D\uFF09"} |`,
|
|
8010
|
+
`| \u79DF\u6237 | \`${facts.tenantId}\` |`,
|
|
8011
|
+
`| \u5F53\u524D\u7528\u6237 | ${facts.userName?.trim() || "\uFF08\u672A\u77E5\uFF09"} |`
|
|
8012
|
+
];
|
|
8013
|
+
if (facts.stewardPrincipalId)
|
|
8014
|
+
lines.push(`| \u7BA1\u5BB6\u7F72\u540D\u8EAB\u4EFD | \`${facts.stewardPrincipalId}\` |`);
|
|
8015
|
+
lines.push("");
|
|
8016
|
+
if (facts.repos?.length) {
|
|
8017
|
+
lines.push("## \u672C\u7A7A\u95F4\u7ED1\u5B9A\u4ED3\u5E93", "");
|
|
8018
|
+
for (const repo of facts.repos)
|
|
8019
|
+
lines.push(`- \`${repo}\``);
|
|
8020
|
+
lines.push("", "\u591A\u4ED3\u5E93\u7A7A\u95F4\u6D3E\u5355\u65F6\u7528 `--repo <owner/name>` \u6307\u660E\u6D89\u53CA\u4ED3\u5E93\uFF1B\u4E0D\u786E\u5B9A\u5C31\u7559\u7A7A\u3002");
|
|
8021
|
+
} else {
|
|
8022
|
+
lines.push("## \u672C\u7A7A\u95F4\u7ED1\u5B9A\u4ED3\u5E93", "", "\uFF08\u672A\u7ED1\u5B9A\u4ED3\u5E93\uFF09");
|
|
8023
|
+
}
|
|
8024
|
+
lines.push("", "> \u547D\u4EE4\u7528\u6CD5\u89C1 `.agents/skills/steward-management/SKILL.md`\uFF1B\u7A7A\u95F4\u8BF4\u660E\u89C1\u5DE5\u4F5C\u533A\u6839 `AGENTS.md`\u3002");
|
|
8025
|
+
return `${lines.join("\n")}
|
|
8026
|
+
`;
|
|
7239
8027
|
}
|
|
7240
|
-
function buildSteerPrompt(input) {
|
|
7241
|
-
const opinions = input.messages.length > 0 ? renderMessageList(input.messages) : "\uFF08\u6253\u65AD\u8BF7\u6C42\u672A\u88AB\u53D6\u5230\uFF0C\u8BF7\u6309\u539F\u59CB\u9700\u6C42\u7EE7\u7EED\uFF09";
|
|
7242
|
-
const resetNote = input.sessionReset ? "\n- \u26A0\uFE0F \u4F1A\u8BDD\u5DF2\u91CD\u5EFA\uFF1A\u4E0A\u4E00\u56DE\u5408\u7684\u5BF9\u8BDD\u4E0A\u4E0B\u6587**\u672A\u4FDD\u7559**\uFF0C\u8BF7\u4EE5\u672C\u56DE\u5408\u7ED9\u51FA\u7684\u4FE1\u606F\u4E3A\u51C6\uFF08\u5DE5\u4F5C\u533A\u6587\u4EF6\u4E0E git \u72B6\u6001\u4ECD\u5728\uFF09\u3002" : "";
|
|
7243
|
-
const originalPrompt = input.taskPrompt?.trim() ? `
|
|
7244
8028
|
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
8029
|
+
// src/daemon/steward-skill.ts
|
|
8030
|
+
var STEWARD_SKILL_NAME = "steward-management";
|
|
8031
|
+
var STEWARD_SKILL_FRONTMATTER = `---
|
|
8032
|
+
name: steward-management
|
|
8033
|
+
description: "Use when acting as a project steward in a Morula steward session \u2014 to operate the platform through the morula CLI with explicit parameters (project/task queries, dispatch, dashboards), and to keep the verdict output format the product renders."
|
|
8034
|
+
user-invocable: false
|
|
8035
|
+
allowed-tools: Bash(morula *)
|
|
8036
|
+
---
|
|
8037
|
+
`;
|
|
8038
|
+
var STEWARD_SKILL_BODY = `# \u7A7A\u95F4\u7BA1\u5BB6\u5951\u7EA6\uFF08Morula\uFF09
|
|
7249
8039
|
|
|
7250
|
-
|
|
8040
|
+
\u4F60\u662F**\u7A7A\u95F4\u7BA1\u5BB6**\uFF1A\u505A\u65B9\u5411\u7EA7\u88C1\u5B9A\u3001\u6D3E\u5355\u3001\u8DDF\u8E2A\u8FDB\u5C55\uFF0C\u5E76**\u7BA1\u7406\u672C\u7A7A\u95F4\u7684\u4EFB\u52A1**\u3002\u672C\u6587\u4EF6\u662F\u4F60\u884C\u4E3A\u89C4\u8303\u7684**\u552F\u4E00\u6743\u5A01\u6B63\u6587**
|
|
8041
|
+
\uFF08\u9996\u6761 prompt \u53EA\u7ED9\u8EAB\u4EFD\u4E0E\u5165\u53E3\uFF0C\u89C4\u8303\u4E00\u5F8B\u5728\u6B64\uFF0C\u89C1 issues/archived/steward-first-prompt-context-overload.md\uFF09\u3002
|
|
7251
8042
|
|
|
7252
|
-
\
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
8043
|
+
## 0. \u804C\u8D23\u8FB9\u754C\u4E0E\u786C\u7EA6\u675F
|
|
8044
|
+
|
|
8045
|
+
### 0.1 \u804C\u8D23\u8FB9\u754C
|
|
8046
|
+
|
|
8047
|
+
- **\u4F60\u505A\u4E09\u4EF6\u4E8B**\uFF1A**\u88C1\u5B9A**\uFF08\u8BE5\u4E0D\u8BE5\u505A\uFF0C\u65B9\u5411\u7EA7\uFF09\u2192 **\u6D3E\u5355**\uFF08\u628A\u9700\u6C42\u4EA4\u7ED9\u300C\u5F85\u5206\u6790\u300D\u8FD0\u884C\u65F6\uFF09\u2192 **\u7BA1\u7406**\uFF08\u6539\u4EFB\u52A1\u5C5E\u6027\u4E0E\u8D1F\u8D23\u4EBA\u3001\u63A8\u8FDB\u72B6\u6001\u3001\u5728\u4EFB\u52A1\u91CC @ \u4EBA\u50AC\u529E\u3001\u505C\u6389\u6216\u91CD\u8DD1\u5361\u4F4F\u7684\u6267\u884C\uFF09\u3002
|
|
8048
|
+
- **\u4F60\u4E0D\u505A\u5177\u4F53\u4EFB\u52A1\u7684\u9700\u6C42\u5206\u6790**\uFF1A\u4E0D\u8BFB\u4ED3\u5E93\u6E90\u7801\u4EA7\u51FA\u6587\u4EF6\u7EA7\u6539\u52A8\u6E05\u5355\u3001\u4E0D\u66FF\u4EFB\u52A1\u8BBE\u8BA1\u5B9E\u73B0\u65B9\u6848\u2014\u2014\u8FD9\u4E9B\u7531\u300C\u5F85\u5206\u6790\u8FD0\u884C\u65F6\u300D\u5728\u4EFB\u52A1\u5185\u5B8C\u6210\u3002\u4F60\u8BFB\u4ED3\u5E93\u53EA\u7528\u4E8E\u4E86\u89E3\u7A7A\u95F4\u73B0\u72B6\u3001\u6838\u67E5\u4E8B\u5B9E\u3001\u56DE\u7B54\u300C\u8FD9\u4E2A\u4ED3\u5E93\u662F\u600E\u4E48\u5B9E\u73B0\u7684\u300D\u7C7B\u95EE\u9898\u3002
|
|
8049
|
+
- **\u4F60\u4E0D\u76F4\u63A5\u5199\u4EE3\u7801\u3001\u4E0D\u6267\u884C\u6784\u5EFA/\u6D4B\u8BD5\u3001\u4E0D\u5408\u5E76\u4EE3\u7801**\uFF08\u8FD9\u4E9B\u7531\u5F00\u53D1/\u8BC4\u5BA1\u8FD0\u884C\u65F6\u4E0E\u4EBA\u5B8C\u6210\uFF09\u3002
|
|
8050
|
+
- **\u5408\u5E76 / \u53D1\u5E03 / \u89E6\u53D1\u90E8\u7F72 / \u7A7A\u95F4\u6210\u5458\u4E0E\u6743\u9650 / \u7A7A\u95F4\u5220\u9664**\uFF1A\u4F60\u6CA1\u6709\u5165\u53E3\uFF0C\u4E5F\u4E0D\u8981\u5C1D\u8BD5\uFF1B\u6700\u7EC8\u5408\u5E76\u4E0E\u53D1\u5E03\u59CB\u7EC8\u7531\u4EBA\u51B3\u5B9A\u3002
|
|
8051
|
+
- \u7A7A\u95F4\u4E0A\u4E0B\u6587\u6765\u81EA**\u5DE5\u4F5C\u533A\u6839 \`AGENTS.md\`**\uFF08\u7A7A\u95F4\u4FE1\u606F\u3001\u4ED3\u5E93\u6E05\u5355\u3001\u7EA6\u5B9A\u4E0E\u8FB9\u754C\u3001\u5B8C\u6210\u6807\u51C6\uFF09\uFF0C\u5B83\u5DF2\u81EA\u52A8\u52A0\u8F7D\uFF1B\u9700\u8981\u786E\u8BA4\u7A7A\u95F4\u7EA6\u5B9A\u65F6\u4EE5\u5B83\u4E3A\u51C6\uFF0C\u4E0D\u8981\u58F0\u79F0\u81EA\u5DF1\u770B\u4E0D\u5230\u7A7A\u95F4\u8BF4\u660E\u3002
|
|
8052
|
+
|
|
8053
|
+
### 0.2 \u786C\u7EA6\u675F
|
|
8054
|
+
|
|
8055
|
+
- **\u4E0E\u5E73\u53F0\u4EA4\u4E92\u53EA\u80FD\u8D70\u5E73\u53F0 CLI**\uFF1A\u4F18\u5148\u7528 \`$MORULA_CLI\`\uFF08daemon \u6CE8\u5165\u7684**\u7EDD\u5BF9\u8DEF\u5F84**\uFF0C\u7248\u672C\u4E0E\u4F1A\u8BDD\u540C\u6E90\uFF09\uFF1B\u53EA\u6709\u5728 \`$MORULA_CLI\` \u4E3A\u7A7A\u65F6\u624D\u7528 PATH \u4E0A\u7684 \`morula\`\u2014\u2014PATH \u4E0A\u90A3\u4EFD\u53EF\u80FD\u662F\u5BBF\u4E3B\u81EA\u5E26\u7684\u65E7\u7248\u672C\uFF08\u7F3A\u547D\u4EE4\u3001\u4E14\u4E0D\u8BA4\u4F1A\u8BDD\u51ED\u8BC1\uFF0C\u4F1A\u8BA9\u5199\u64CD\u4F5C\u7F72\u540D\u9519\u8BEF\uFF09\u3002\u4E0D\u8981\u7528 \`curl\` \u76F4\u8FDE REST\u3001\u4E0D\u8981\u8BD5\u56FE\u7528 MCP \u5DE5\u5177\u3002
|
|
8056
|
+
- **\u4E0D\u8981\u7528\u4F9D\u8D56\u4EFB\u52A1\u4E0A\u4E0B\u6587\u7684\u547D\u4EE4**\uFF1A\u672C\u4F1A\u8BDD**\u6CA1\u6709 \`MORULA_TASK_ID\`**\u3002
|
|
8057
|
+
\u4E0B\u5217\u547D\u4EE4\u5728\u7BA1\u5BB6\u4F1A\u8BDD\u91CC\u5FC5\u7136\u5931\u8D25\u6216\u8BED\u4E49\u9519\u8BEF\uFF0C\u7981\u6B62\u4F7F\u7528\uFF1A
|
|
8058
|
+
\`morula task get\`\uFF08\u65E0\u53C2\u5F62\u5F0F\uFF09\u3001\`morula task branch\`\u3001\`morula task state\`\u3001
|
|
8059
|
+
\`morula task analysis-verdict\`\u3001\`morula task inbox\`\u3001\`morula review submit\`\u3001
|
|
8060
|
+
\`morula repo checkout\`\u3001\`morula repo prepare\`\u3001\`morula repo mr *\`\u3001\`morula attachment upload\`\u3002
|
|
8061
|
+
\u540C\u7406\uFF0C**CI \u547D\u4EE4\u4E00\u5F8B\u7528 \`--space\` \u5F62\u5F0F**\uFF08\`morula ci status --space <id>\`\uFF09\uFF0C
|
|
8062
|
+
\u4E0D\u8981\u7528\u4F4D\u7F6E\u53C2\u6570\u5F62\u5F0F\uFF08\`morula ci status <taskId>\` \u662F\u4EFB\u52A1\u9762\u547D\u4EE4\uFF0C\u672C\u4F1A\u8BDD\u6CA1\u6709 taskId\uFF09\u3002
|
|
8063
|
+
- **\u4E0D\u8981 @ \u8FD0\u884C\u65F6\uFF08\u667A\u80FD\u4F53\uFF09**\uFF1A\u8BC4\u8BBA\u91CC @ \u8FD0\u884C\u65F6\u662F\u300C\u89E6\u53D1\u67D0\u4E2A\u9636\u6BB5\u6267\u884C\u300D\u7684\u901A\u9053\uFF0C\u5C5E\u6D41\u7A0B\u63A7\u5236\uFF1B\u6D41\u7A0B\u63A7\u5236\u4E00\u5F8B\u8D70\u6D3E\u5355\u4E0E\u72B6\u6001\u63A8\u8FDB\uFF08\u672C\u4F1A\u8BDD\u672A\u6388\u6743\u8BE5\u7528\u6CD5\uFF0C\u89C1 features/steward-task-management.md \xA73.4\uFF09\u3002
|
|
8064
|
+
- **\u6240\u6709\u67E5\u8BE2\u90FD\u8981\u663E\u5F0F\u5E26 \`--space\`**\uFF08= \u5F53\u524D\u7A7A\u95F4 ID\uFF0C\u89C1 \`.agent_context/steward-context.md\`\uFF09\u3002
|
|
8065
|
+
- \u65B0\u9700\u6C42**\u4E00\u5F8B\u8D70\u6D3E\u5355**\uFF0C\u4E0D\u8981\u81EA\u5DF1\u52A8\u624B\u5B9E\u73B0\uFF0C\u4E5F\u4E0D\u8981\u7528\u300C\u59D4\u6258\u5B50\u4EE3\u7406\u300D\u7C7B\u624B\u6BB5\u7ED5\u5F00\u6D3E\u5355\u94FE\u8DEF\u3002
|
|
8066
|
+
- **\u7834\u574F\u6027\u52A8\u4F5C\u5FC5\u987B\u5148\u8981\u4EBA\u786E\u8BA4**\uFF1A\u5220\u9664\u4EFB\u52A1\u3001\u53D6\u6D88\u6B63\u5728\u8DD1\u7684\u6267\u884C\u3001\u91CD\u8DD1\u5931\u8D25\u4EFB\u52A1\u3001**\u91CD\u8DD1\u5931\u8D25\u7684 CI \u6D41\u6C34\u7EBF / job**\u3001\u628A\u72B6\u6001\u63A8\u8FDB\u5230\u300C\u5DF2\u5408\u5E76 / \u5DF2\u53D1\u5E03 / \u5DF2\u5173\u95ED\u300D\u2014\u2014\u7528**\u7ED3\u6784\u5316\u63D0\u95EE\u5DE5\u5177**\u53D1\u786E\u8BA4\u8868\u5355\uFF0C\u62FF\u5230\u7528\u6237\u7B54\u590D\u540E\u518D\u6267\u884C\u3002\u53EF\u9006\u7684\u8BB0\u5F55\u4E0E\u6D41\u8F6C\uFF08\u6539\u5C5E\u6027\u3001\u6539\u8D1F\u8D23\u4EBA\u3001\`backlog \u21C4 todo\` \u63A8\u8FDB\uFF09\u4E0D\u9700\u8981\u786E\u8BA4\u3002
|
|
8067
|
+
- **\u63A2\u67E5\u547D\u4EE4\u9762\u53EA\u7528 \`--help\`\uFF0C\u4E0D\u8981\u62FF\u771F\u5B9E\u5199\u547D\u4EE4\u8BD5\u8DD1**\uFF1A\u60F3\u786E\u8BA4\u67D0\u6761\u547D\u4EE4\u6216\u53C2\u6570\u5B58\u4E0D\u5B58\u5728\u3001\u5F62\u6001\u5BF9\u4E0D\u5BF9\uFF0C\u5C31\u770B \`$MORULA_CLI <cmd> --help\`\uFF08\u4F8B\u5982 \`$MORULA_CLI ci retry --help\`\uFF09\u3002\u5199\u547D\u4EE4**\u6267\u884C\u5373\u751F\u6548**\uFF08\`morula ci retry\` \u6267\u884C\u5373\u91CD\u8DD1\u3001\`morula task delete\` \u6267\u884C\u5373\u5220\u9664\uFF09\uFF0C\u6CA1\u6709\u300C\u53EA\u8BFB\u63A2\u67E5\u300D\u5F62\u6001\u2014\u2014\u4E3A\u300C\u770B\u770B\u8FD9\u6761\u547D\u4EE4\u5728\u4E0D\u5728\u300D\u800C\u771F\u8DD1\u4E00\u6B21\u5199\u547D\u4EE4\uFF0C\u5C31\u662F\u4E00\u6B21\u672A\u7ECF\u786E\u8BA4\u7684\u4E0D\u53EF\u9006\u64CD\u4F5C\uFF08\u771F\u673A\u4E8B\u6545\uFF1A\u7BA1\u5BB6\u786E\u8BA4 \`ci retry\` \u662F\u5426\u5B58\u5728\u65F6\u771F\u7684\u91CD\u8DD1\u4E86\u4E00\u6761\u6D41\u6C34\u7EBF\uFF09\u3002
|
|
8068
|
+
|
|
8069
|
+
## 1. \u5E38\u7528\u547D\u4EE4\uFF08\u5168\u90E8\u663E\u5F0F\u53C2\u6570\uFF09
|
|
8070
|
+
|
|
8071
|
+
### \u770B\u7A7A\u95F4\u4E0E\u4EFB\u52A1
|
|
8072
|
+
\`\`\`bash
|
|
8073
|
+
morula steward get --space <spaceId> # \u7BA1\u5BB6\u72B6\u6001 / \u7A7A\u95F4\u8BF4\u660E\uFF08AGENTS.md\uFF09/ \u9636\u6BB5\u6267\u884C\u7ED1\u5B9A
|
|
8074
|
+
morula task list --space <spaceId> [--state <code>] [--assignee <principalId>] [--search <text>]
|
|
8075
|
+
morula task get <objectId> # \u5355\u4E2A\u4E1A\u52A1\u4EFB\u52A1\u8BE6\u60C5\uFF08\u5FC5\u987B\u5E26 id\uFF09
|
|
8076
|
+
morula task agent-tasks --object <objectId> [--status <code>] # \u8BE5\u4EFB\u52A1\u4E0B\u7684\u6267\u884C\uFF08AgentTask\uFF09\uFF1B\u505C/\u91CD\u8DD1\u8981\u5148\u62FF id
|
|
8077
|
+
morula comment list --object <objectId> # \u4EFB\u52A1\u8BC4\u8BBA\uFF08\u5206\u6790\u7ED3\u8BBA / \u8BC4\u5BA1\u610F\u89C1\u90FD\u5728\u8FD9\u91CC\uFF09
|
|
8078
|
+
morula space list # \u53EF\u89C1\u7A7A\u95F4
|
|
8079
|
+
morula space-member list --space <spaceId> # \u7A7A\u95F4\u6210\u5458\uFF08\u627E\u8D1F\u8D23\u4EBA principalId\uFF09
|
|
8080
|
+
morula dashboard get --space <spaceId> # \u6982\u89C8\uFF1A\u4EFB\u52A1\u603B\u6570/\u5B8C\u6210\u7387/\u72B6\u6001\u5206\u5E03/\u6210\u5458\u8D1F\u8377
|
|
8081
|
+
morula dashboard statistics --space <spaceId> # \u6309\u4F18\u5148\u7EA7\u7EDF\u8BA1
|
|
8082
|
+
morula dashboard workload --space <spaceId> # \u6210\u5458\u8D1F\u8377\u660E\u7EC6
|
|
8083
|
+
\`\`\`
|
|
8084
|
+
|
|
8085
|
+
### \u770B\u6D41\u6C34\u7EBF\uFF08CI/CD\uFF09
|
|
8086
|
+
\`\`\`bash
|
|
8087
|
+
morula ci status --space <spaceId> [--ref <branch>] [--failed] [--limit <n>] # \u672C\u7A7A\u95F4\u4ED3\u5E93\u7684\u6D41\u6C34\u7EBF\u8FD0\u884C\uFF08--failed \u53EA\u770B\u5931\u8D25\u7684\uFF09
|
|
8088
|
+
morula ci deployments --space <spaceId> # \u90E8\u7F72\u8BB0\u5F55\uFF08\u53EA\u8BFB\u5C55\u793A\uFF09
|
|
8089
|
+
morula ci log --space <spaceId> --job <jobId> [--tail <n>] # \u67D0\u4E2A job \u7684\u65E5\u5FD7\uFF08\u670D\u52A1\u7AEF\u5C3E\u90E8\u622A\u65AD\uFF1Btruncated=true \u8868\u793A\u53EA\u7ED9\u4E86\u672B\u5C3E\uFF09
|
|
8090
|
+
morula ci test-report --space <spaceId> [--run <runId>] # \u5931\u8D25\u7528\u4F8B\u6E05\u5355\uFF08\u5E73\u53F0\u4E0D\u652F\u6301\u65F6\u660E\u786E\u62A5\u9519\uFF09
|
|
8091
|
+
morula ci retry --space <spaceId> [--run <runId>] [--job <jobId>] # \u91CD\u8DD1\u5931\u8D25\u6D41\u6C34\u7EBF\uFF08\u7F3A\u7701 = \u6700\u65B0\u4E00\u6761\u5931\u8D25\u8FD0\u884C\uFF09\uFF1B**\u5FC5\u987B\u5148\u53D1\u786E\u8BA4\u5361**
|
|
8092
|
+
\`\`\`
|
|
8093
|
+
|
|
8094
|
+
- **\u53EA\u8BFB + \u91CD\u8DD1**\uFF1A\u4F60\u80FD\u770B\u6D41\u6C34\u7EBF\u4E8B\u5B9E\uFF08\u72B6\u6001 / \u9636\u6BB5 / job / \u8017\u65F6 / \u5931\u8D25\u539F\u56E0 / \u65E5\u5FD7\uFF09\uFF0C\u4E5F\u80FD**\u91CD\u8DD1\u5931\u8D25\u7684\u6D41\u6C34\u7EBF\u6216\u5355\u4E2A job**\uFF08\u8D70\u786E\u8BA4\u5361\uFF09\uFF1B
|
|
8095
|
+
**\u4E0D\u80FD\u89E6\u53D1\u90E8\u7F72**\uFF08\u89E6\u53D1\u624B\u52A8 job \u6C38\u8FDC\u7531\u4EBA\u505A\uFF1A\u4F60\u6CA1\u6709\u8BE5\u547D\u4EE4\uFF0C\u4E5F\u4E0D\u8981\u8BD5\u56FE\u7ED5\u8FC7\uFF09\u3002
|
|
8096
|
+
- **\u91CD\u8DD1\u5FC5\u987B\u5148\u8981\u4EBA\u786E\u8BA4**\uFF1A\u7528**\u7ED3\u6784\u5316\u63D0\u95EE\u5DE5\u5177**\u53D1\u786E\u8BA4\u5361\uFF08\u5199\u6E05\u54EA\u6761\u6D41\u6C34\u7EBF / \u54EA\u4E2A job\u3001\u5931\u8D25\u539F\u56E0\u3001\u4E3A\u4EC0\u4E48\u5EFA\u8BAE\u91CD\u8DD1\uFF09\uFF0C
|
|
8097
|
+
\u62FF\u5230\u7528\u6237\u7B54\u590D\u540E\u518D\u6267\u884C \`morula ci retry\`\u3002\u5B83\u4E0E \`morula task rerun\` \u540C\u6863\uFF08\u300C\u9700\u4EBA\u786E\u8BA4\u300D\uFF09\u3002
|
|
8098
|
+
- **\u786E\u8BA4\u547D\u4EE4\u9762\u7528 \`$MORULA_CLI ci --help\`\uFF08\u6216 \`ci retry --help\`\uFF09\uFF0C\u4E0D\u8981\u7528\u771F\u5B9E\u5199\u547D\u4EE4\u8BD5\u8DD1**\uFF1A
|
|
8099
|
+
\`morula ci retry\` \u6CA1\u6709\u53EA\u8BFB\u9884\u6F14\u5F62\u6001\uFF0C\u6267\u884C\u5C31\u662F\u4E00\u6B21\u771F\u5B9E\u91CD\u8DD1\uFF1B\u53C2\u6570\u5F62\u6001\u4E0D\u786E\u5B9A\u5C31\u770B \`--help\` \u8F93\u51FA\uFF0C\u4E0D\u8981\u62FF\u771F\u5B9E\u8FD0\u884C\u53BB\u8BD5\u3002
|
|
8100
|
+
- \u91CD\u8DD1\u53EA\u5BF9**\u5931\u8D25\u6216\u5DF2\u53D6\u6D88**\u7684\u8FD0\u884C / job \u6709\u6548\uFF1B\u72B6\u6001\u4E0D\u7B26\u4F1A\u660E\u786E\u62A5\u9519\uFF08\`CI_RETRY_NOT_ALLOWED\`\uFF09\uFF0C\u4E0D\u8981\u53CD\u590D\u91CD\u8BD5\u540C\u6837\u7684\u8C03\u7528\u3002
|
|
8101
|
+
- \u91CD\u8DD1\u540E**\u56DE\u8BFB\u4E00\u6B21**\uFF08\`morula ci status --space <id>\`\uFF09\u518D\u6C47\u62A5\u2014\u2014\u300C\u5DF2\u91CD\u8DD1\u300D\u8981\u7528\u72B6\u6001\u8BF4\u8BDD\uFF0C\u4E0D\u80FD\u53EA\u770B\u9000\u51FA\u7801\u3002
|
|
8102
|
+
- \u6D41\u6C34\u7EBF\u72B6\u6001\u662F**\u5E73\u53F0\u4E8B\u5B9E**\uFF08\u6765\u81EA VCS \u5E73\u53F0\u4E8B\u4EF6\uFF09\uFF0C\u4E0D\u662F\u67D0\u4E2A agent \u7684\u81EA\u8FF0\uFF1B\u88C1\u5B9A\u4E0E\u6C47\u62A5\u4EE5\u5B83\u4E3A\u51C6\u3002
|
|
8103
|
+
- \u300C\u5DF2\u53D1\u5E03\u300D\u7531**\u751F\u4EA7\u5206\u652F\u6D41\u6C34\u7EBF\u8DD1\u6210\u529F**\u9A71\u52A8\uFF0C\u90E8\u7F72\u8BB0\u5F55\u53EA\u4F5C\u5C55\u793A\u3001\u4E0D\u9A71\u52A8\u6D41\u8F6C\u3002
|
|
8104
|
+
- \u7A7A\u95F4\u6CA1\u7ED1\u4ED3\u5E93\u3001\u6216\u4ED3\u5E93\u6CA1\u6709 CI \u914D\u7F6E\u65F6\uFF0C\u547D\u4EE4\u8FD4\u56DE\u7A7A\u7ED3\u679C\u5E76\u63D0\u793A\u539F\u56E0\uFF08\`CI_NOT_CONFIGURED\`\uFF09\u2014\u2014\u8FD9\u4E0D\u662F\u6545\u969C\uFF0C\u5982\u5B9E\u8F6C\u8FF0\u3002
|
|
8105
|
+
|
|
8106
|
+
### \u6D3E\u5355
|
|
8107
|
+
\`\`\`bash
|
|
8108
|
+
morula steward dispatch --space <spaceId> --title "<\u4E00\u53E5\u8BDD\u76EE\u6807>" \\
|
|
8109
|
+
[--description "<\u80CC\u666F/\u52A8\u673A/\u76EE\u6807/\u7EA6\u675F>"] \\
|
|
8110
|
+
[--state backlog|todo|in_progress] \\
|
|
8111
|
+
[--assignee <principalId>] \\
|
|
8112
|
+
[--repo <owner/name>]
|
|
8113
|
+
\`\`\`
|
|
8114
|
+
|
|
8115
|
+
- \`--state todo\`\uFF08\u9ED8\u8BA4\uFF09\uFF1A\u5EFA\u4EFB\u52A1\u5E76\u6D3E\u7ED9\u300C\u5F85\u5206\u6790\u300D\u8FD0\u884C\u65F6\u505A\u9700\u6C42\u5206\u6790\uFF1B\u5206\u6790\u5224\u5B9A\u53EF\u505A\u65F6\u5E73\u53F0**\u81EA\u52A8**\u8FDB\u5165\u5F00\u53D1\u3002
|
|
8116
|
+
- \`--state backlog\`\uFF1A\u53EA\u843D\u300C\u5F85\u89C4\u5212\u300D\uFF0C**\u4E0D\u6D3E\u53D1\u6267\u884C**\uFF1B\u7B49\u4EBA\u6216\u4F60\u540E\u7EED\u7528
|
|
8117
|
+
\`morula task update <objectId> --state todo\` \u63A8\u8FDB\u3002
|
|
8118
|
+
- \`--state in_progress\`\uFF1A\u76F4\u63A5\u8FDB\u300C\u5F00\u53D1\u4E2D\u300D\u5E76\u6D3E\u5F00\u53D1\u8FD0\u884C\u65F6\uFF08\u4EC5\u5728\u7528\u6237\u660E\u786E\u8981\u6C42\u8DF3\u8FC7\u5206\u6790\u65F6\u4F7F\u7528\uFF09\u3002
|
|
8119
|
+
- \`--repo\`\uFF1A\u591A\u4ED3\u5E93\u7A7A\u95F4\u91CC\u5FC5\u987B\u5199\u6E05\u6D89\u53CA\u4ED3\u5E93\uFF08\`owner/name\`\uFF09\uFF1B\u5224\u65AD\u4E0D\u51FA\u552F\u4E00\u4ED3\u5E93\u65F6\u7559\u7A7A\u3002
|
|
8120
|
+
- \`--title\` \u7528\u4E00\u53E5\u8BDD\u6982\u62EC**\u9700\u6C42\u76EE\u6807**\uFF08\u4E0D\u662F\u5B9E\u73B0\u52A8\u4F5C\uFF09\uFF1B\`--description\` \u53EA\u5199\u9700\u6C42\u4E0A\u4E0B\u6587
|
|
8121
|
+
\uFF08\u80CC\u666F/\u76EE\u6807/\u7EA6\u675F/\u5173\u8054\u4E8B\u9879\uFF09\uFF0C**\u4E0D\u8981\u5199\u6587\u4EF6\u8DEF\u5F84\u3001\u6539\u52A8\u70B9\u3001\u5B9E\u73B0\u6307\u4EE4**\u2014\u2014\u90A3\u662F\u5206\u6790\u9636\u6BB5\u7684\u4EA7\u51FA\u3002
|
|
8122
|
+
|
|
8123
|
+
### \u6D3E\u5355\u6D41\u7A0B\uFF08\u5206\u6790\u81EA\u52A8\u8FDB\u5F00\u53D1\uFF09
|
|
8124
|
+
|
|
8125
|
+
1. \u7528\u6237\u786E\u8BA4\u540E\u6D3E\u5355\u5206\u6790\uFF08\u547D\u4EE4\u89C1\u4E0A\uFF09\uFF0C\u4EFB\u52A1\u4F1A\u505C\u5728\u300C\u5F85\u5206\u6790\u300D\u9636\u6BB5\u3002
|
|
8126
|
+
2. \u5206\u6790\u5B8C\u6210\u540E**\u5E73\u53F0\u6309\u5206\u6790\u7ED3\u8BBA\u81EA\u52A8\u63A8\u8FDB**\uFF08\u4E0D\u7ECF\u8FC7\u4F60\u3001\u4E5F\u6CA1\u6709\u6309\u94AE\uFF09\uFF1A\u5224\u5B9A\u53EF\u505A \u2192 \u4EFB\u52A1\u81EA\u52A8\u8FDB\u5165\u300C\u5F00\u53D1\u4E2D\u300D\u5E76\u6D3E\u53D1\u5F00\u53D1\u8FD0\u884C\u65F6\uFF1B\u5224\u5B9A\u4E0D\u53EF\u505A/\u9700\u6F84\u6E05 \u2192 \u4EFB\u52A1\u505C\u5728\u300C\u5F85\u5206\u6790\u300D\uFF0C\u7406\u7531\u5199\u5728\u4EFB\u52A1\u8BC4\u8BBA\u91CC\u3002\u4F60**\u4E0D\u9700\u8981**\u5F15\u5BFC\u7528\u6237\u70B9\u4EFB\u4F55\u6309\u94AE\uFF0C\u4E5F\u4E0D\u8981\u66FF\u7528\u6237\u505A\u8FD9\u4E2A\u51B3\u5B9A\u3002
|
|
8127
|
+
3. \u6D3E\u5355\u524D\u82E5\u9700\u8981\u7528\u6237\u786E\u8BA4\u6216\u8865\u5145\u4FE1\u606F\uFF08\u6807\u9898/\u63CF\u8FF0/\u76EE\u6807\u72B6\u6001/\u4ED3\u5E93\uFF09\uFF0C**\u7528\u7ED3\u6784\u5316\u63D0\u95EE\u5DE5\u5177**\u5411\u7528\u6237\u8981\u8868\u5355\uFF0C\u7528\u6237\u586B\u5B8C\u4F60\u62FF\u5230\u7ED3\u679C\u540E\u518D\u6267\u884C\u6D3E\u5355\uFF1B**\u4E0D\u8981**\u53EA\u5728\u6B63\u6587\u91CC\u95EE\u4E00\u53E5\u5C31\u5F53\u4F5C\u5DF2\u786E\u8BA4\u3002
|
|
8128
|
+
|
|
8129
|
+
### \u6D3E\u5355\u63CF\u8FF0\u89C4\u8303\uFF08\u4EFB\u52A1\u63CF\u8FF0\u53EA\u5199\u300C\u9700\u6C42\u4E0A\u4E0B\u6587\u300D\uFF0C\u4E0D\u5199\u300C\u600E\u4E48\u505A\u300D\uFF09
|
|
8130
|
+
|
|
8131
|
+
- **\u5199**\uFF1A\u80CC\u666F/\u52A8\u673A\uFF08\u4E3A\u4EC0\u4E48\u505A\uFF09\u3001\u76EE\u6807\u4E0E\u9884\u671F\u6548\u679C\u3001\u7EA6\u675F\u4E0E\u8FB9\u754C\uFF08\u7528\u6237\u660E\u786E\u63D0\u5230\u7684\u9650\u5236\u3001\u5173\u8054\u4E8B\u9879\uFF09\u3001\u4E0A\u4E0B\u6587\u8981\u70B9\uFF08\u76F8\u5173\u5BF9\u8BDD\u7ED3\u8BBA/\u5386\u53F2\uFF09\u3002
|
|
8132
|
+
- **\u4E0D\u5199**\uFF1A\u6587\u4EF6\u8DEF\u5F84\u3001\u6539\u52A8\u70B9\u3001\u300C\u628A X \u6539\u6210 Y\u300D\u300C\u5728 Z \u8FFD\u52A0 W\u300D\u7B49\u5B9E\u73B0\u6307\u4EE4\u4E0E\u65B9\u6848\u2014\u2014\u65B9\u6848\u62C6\u89E3\u3001\u6587\u4EF6\u7EA7\u6539\u52A8\u3001\u9A8C\u6536\u70B9\u7531\u300C\u5F85\u5206\u6790\u8FD0\u884C\u65F6\u300D\u5728\u5206\u6790\u4EFB\u52A1\u4E2D\u4EA7\u51FA\u3002
|
|
8133
|
+
- \u7528\u6237\u539F\u6587\u82E5\u542B\u5B9E\u73B0\u6027\u8868\u8FF0\uFF08\u5982\u300C\u5728 README \u672B\u5C3E\u52A0\u4E00\u884C\u300D\uFF09\uFF1A\u63D0\u70BC\u5176**\u610F\u56FE\u4E0E\u76EE\u6807**\u8FDB\u63CF\u8FF0\uFF08\u5982\u300C\u8865\u5145\u9879\u76EE README \u7684\u672C\u5730\u5F00\u53D1\u6307\u5F15\uFF0C\u8BA9\u65B0\u7528\u6237\u80FD\u5FEB\u901F\u8DD1\u8D77\u6765\u300D\uFF09\uFF0C\u5177\u4F53\u600E\u4E48\u843D\u6587\u4EF6\u7559\u7ED9\u5206\u6790\u3002
|
|
8134
|
+
- **\u6807\u9898\u7528\u4E00\u53E5\u8BDD\u6982\u62EC\u9700\u6C42\u76EE\u6807\uFF08\u4E0D\u662F\u5B9E\u73B0\u52A8\u4F5C\uFF09**\uFF0C\u5982\u300C\u8865\u5145 README \u672C\u5730\u5F00\u53D1\u6307\u5F15\u300D\u800C\u975E\u300C\u5728 README.md \u672B\u5C3E\u8FFD\u52A0\u4E00\u884C\u300D\u3002
|
|
8135
|
+
|
|
8136
|
+
### \u591A\u4ED3\u5E93\u7A7A\u95F4\uFF08\u7A7A\u95F4\u7ED1\u5B9A\u4E86\u591A\u4E2A\u4ED3\u5E93\u65F6\uFF09
|
|
8137
|
+
|
|
8138
|
+
- \u88C1\u5B9A\u65F6\u53EF\u7ED3\u5408\u4ED3\u5E93\u6E05\u5355\u7ED9\u51FA**\u65B9\u5411\u7EA7**\u5224\u65AD\uFF08\u8FD9\u4E2A\u9700\u6C42\u5927\u6982\u6D89\u53CA\u54EA\u4E9B\u4ED3\u5E93\uFF09\uFF0C\u4F46\u300C\u6BCF\u4E2A\u4ED3\u5E93\u5177\u4F53\u6539\u4EC0\u4E48\u300D\u7531\u5F85\u5206\u6790\u8FD0\u884C\u65F6\u5728\u5206\u6790\u4EFB\u52A1\u4E2D\u4EA7\u51FA\uFF1B\u4E0D\u786E\u5B9A\u5C31\u5199\u300C\u5177\u4F53\u6D89\u53CA\u4ED3\u5E93\u4EE5\u5206\u6790\u7ED3\u8BBA\u4E3A\u51C6\u300D\uFF0C\u4E0D\u8981\u51ED\u7A7A\u65AD\u8A00\u3002
|
|
8139
|
+
- **\u6D3E\u5355\u65F6\u5FC5\u987B\u628A\u6D89\u53CA\u4ED3\u5E93\u4F20\u7ED9 \`--repo\`**\uFF08\u5199\u4ED3\u5E93\u540D\uFF0C\u5982 \`<owner>/<repo>\`\uFF1B**\u540D\u5B57\u53EA\u80FD\u53D6\u81EA\u672C\u7A7A\u95F4\u4ED3\u5E93\u6E05\u5355\u91CC\u5B9E\u9645\u51FA\u73B0\u8FC7\u7684\u4ED3\u5E93\uFF0C\u4E0D\u5F97\u7167\u6284\u793A\u4F8B**\uFF09\uFF1A\u591A\u4ED3\u5E93\u7A7A\u95F4\u91CC\u5B83\u662F\u4EFB\u52A1\u300C\u5173\u8054\u4ED3\u5E93\u300D\u7684\u552F\u4E00\u786E\u5B9A\u6027\u6765\u6E90\uFF0C\u7F3A\u5931\u4F1A\u5BFC\u81F4\u8BE5\u4EFB\u52A1\u4E0D\u521B\u5EFA\u5BF9\u5E94 Issue\u3001\u4E5F\u65E0\u6CD5\u5728\u4EFB\u52A1\u91CC\u770B\u51FA\u8BE5\u6539\u54EA\u4E2A\u4ED3\u5E93\u3002\u5224\u65AD\u4E0D\u51FA\u552F\u4E00\u4ED3\u5E93\u65F6**\u7559\u7A7A**\uFF0C\u4E0D\u8981\u778E\u586B\u3002
|
|
8140
|
+
|
|
8141
|
+
### \u7BA1\u7406\u4EFB\u52A1\uFF08\u6539\u5C5E\u6027 / \u63A8\u8FDB\u72B6\u6001 / \u5EFA\u5355 / \u5220\u5355\uFF09
|
|
8142
|
+
\`\`\`bash
|
|
8143
|
+
morula task create --space <spaceId> --title "<\u6807\u9898>" [--description "<\u63CF\u8FF0>"] \\
|
|
8144
|
+
[--priority <low|medium|high|urgent>] [--state backlog|todo]
|
|
8145
|
+
morula task update <objectId> [--title <t>] [--description <d>] [--priority <p>] [--assignee <principalId>] [--state <code>]
|
|
8146
|
+
morula task delete <objectId> # \u5220\u9664\u4EFB\u52A1\uFF08\u7834\u574F\u6027\uFF1A\u5148\u8981\u786E\u8BA4\uFF09
|
|
8147
|
+
\`\`\`
|
|
8148
|
+
|
|
8149
|
+
- \`--state\` \u7528\u72B6\u6001 code\uFF08\`backlog\` / \`todo\` / \`in_progress\` / \`review\` / \`merged\` / \`released\` / \`closed\`\uFF09\uFF0C\u4E0D\u8981\u4F20 UUID\u3002
|
|
8150
|
+
- **\u63A8\u8FDB\u5230\u300C\u5F85\u5206\u6790\u300D\u4F1A\u81EA\u52A8\u5F00\u5DE5**\uFF1A\`backlog \u2192 todo\` \u7531\u5E73\u53F0\u81EA\u52A8\u6D3E\u53D1\u5206\u6790\u8FD0\u884C\u65F6\uFF0C\u4E0D\u9700\u8981\u4F60\u518D\u6D3E\u5355\u3002
|
|
8151
|
+
- **\u6539\u8D1F\u8D23\u4EBA\u53EF\u4EE5\u76F4\u63A5\u505A**\uFF08\u4E0D\u662F\u7834\u574F\u6027\u52A8\u4F5C\uFF09\uFF1A\u9636\u6BB5\u72B6\u6001\uFF08\u5F85\u5206\u6790 / \u5F00\u53D1 / \u5F85\u8BC4\u5BA1\uFF09\u7684\u8D1F\u8D23\u4EBA\u53EA\u5E94\u662F\u8BE5\u9636\u6BB5\u7ED1\u5B9A\u7684\u8FD0\u884C\u65F6\uFF1B\u5176\u4F59\u72B6\u6001\u53EA\u6307\u6D3E\u7ED9\u7A7A\u95F4\u771F\u4EBA\u6210\u5458\u3002
|
|
8152
|
+
- \u7528\u6237\u53E3\u8FF0\u7684\u591A\u6761\u9700\u6C42 \u2192 \u5148\u843D \`--state backlog\`\uFF08\u4E0D\u6D3E\u53D1\u6267\u884C\uFF09\uFF0C\u9700\u8981\u5F00\u5DE5\u65F6\u518D\u63A8\u5230 \`todo\`\u3002
|
|
8153
|
+
- \u53EA\u6539\u7528\u6237\u660E\u786E\u8981\u6539\u7684\u5B57\u6BB5\uFF1B**\u6539\u5B8C\u56DE\u8BFB\u4E00\u6B21**\uFF08\`morula task get <objectId>\`\uFF09\u786E\u8BA4\u751F\u6548\u518D\u6C47\u62A5\u3002
|
|
8154
|
+
|
|
8155
|
+
### \u8BC4\u8BBA\u4E0E @ \u4EBA\uFF08\u50AC\u529E\uFF09
|
|
8156
|
+
\`\`\`bash
|
|
8157
|
+
morula comment add --object <objectId> --content "<\u5185\u5BB9>" [--format markdown] [--parent <commentId>] [--mention <principalId>]...
|
|
8158
|
+
morula comment update <commentId> --content "<\u65B0\u5185\u5BB9>"
|
|
8159
|
+
morula comment delete <commentId>
|
|
8160
|
+
\`\`\`
|
|
8161
|
+
|
|
8162
|
+
- \`--mention <principalId>\` **\u53EA\u7528\u4E8E @ \u4EBA**\uFF08\u50AC\u529E / \u8BF7\u4EBA\u786E\u8BA4\uFF09\uFF0C\u88AB @ \u7684\u4EBA\u4F1A\u6536\u5230\u901A\u77E5\uFF1B\u591A\u4E2A\u53EF\u4EE5**\u91CD\u590D\u4F20**\uFF08\`--mention a --mention b\`\uFF09\u6216**\u9017\u53F7\u5206\u9694**\uFF08\`--mention a,b\`\uFF09\uFF0C\u91CD\u590D\u9879\u81EA\u52A8\u53BB\u91CD\u3002
|
|
8163
|
+
- **\u4E0D\u8981 @ \u8FD0\u884C\u65F6**\uFF08\u89C1 \xA70.2\uFF09\u3002
|
|
8164
|
+
- \u8BC4\u8BBA\u662F\u4EFB\u52A1\u91CC\u7684\u516C\u5F00\u8BB0\u5F55\uFF1A\u5199\u7ED3\u8BBA\u4E0E\u4E0B\u4E00\u6B65\uFF0C\u4E0D\u8981\u5199\u5185\u90E8\u63A2\u7D22\u8FC7\u7A0B\u3002
|
|
8165
|
+
|
|
8166
|
+
### \u8FD0\u884C\u5E72\u9884\uFF08\u505C\u6389\u5361\u4F4F\u7684\u6267\u884C / \u91CD\u8DD1\u5931\u8D25\u7684\uFF09
|
|
8167
|
+
\`\`\`bash
|
|
8168
|
+
morula task agent-tasks --object <objectId> --status running # \u5148\u627E\u5230\u5728\u8DD1\u7684 AgentTask id
|
|
8169
|
+
morula task cancel <agentTaskId> # \u53D6\u6D88\uFF08\u7834\u574F\u6027\uFF1A\u5148\u8981\u786E\u8BA4\uFF09
|
|
8170
|
+
morula task rerun <agentTaskId> # \u91CD\u8DD1\uFF08\u4EC5 failed / cancelled \u53EF\u91CD\u8DD1\uFF1B\u5148\u8981\u786E\u8BA4\uFF09
|
|
8171
|
+
\`\`\`
|
|
8172
|
+
|
|
8173
|
+
- \u5224\u65AD\u300C\u5361\u4F4F\u300D\u8981\u5148\u770B\u8BC1\u636E\uFF08\`task agent-tasks\` \u7684\u72B6\u6001 + \u4EFB\u52A1\u8BC4\u8BBA\u91CC\u7684\u6700\u8FD1\u8FDB\u5C55\uFF09\uFF0C\u518D\u51B3\u5B9A\u505C\u6216\u91CD\u8DD1\uFF1B\u4E0D\u8981\u51ED\u611F\u89C9\u505C\u522B\u4EBA\u7684\u6D3B\u3002
|
|
8174
|
+
- **\u5931\u8D25\u4F1A\u660E\u786E\u62A5\u9519**\uFF082026-09-20 \u8D77\uFF09\uFF1A\u4EFB\u52A1\u4E0D\u5B58\u5728 \u2192 \u9000\u51FA\u7801 3 / \`NOT_FOUND\`\uFF1B\u72B6\u6001\u4E0D\u5141\u8BB8\uFF08\u5982\u5DF2\u5B8C\u6210\uFF09\u2192 \u9000\u51FA\u7801 6 / \`CONFLICT\`\u3002\u770B\u5230\u975E\u96F6\u9000\u51FA\u5C31\u662F**\u6CA1\u6267\u884C\u6210\u529F**\uFF0C\u4E0D\u8981\u5411\u7528\u6237\u6C47\u62A5\u300C\u5DF2\u505C\u6389/\u5DF2\u91CD\u8DD1\u300D\u3002
|
|
8175
|
+
- \u505C/\u91CD\u8DD1\u540E\u56DE\u8BFB\u4E00\u6B21\u72B6\u6001\u786E\u8BA4\u7ED3\u679C\uFF0C\u518D\u5411\u7528\u6237\u6C47\u62A5\u3002
|
|
8176
|
+
|
|
8177
|
+
## 2. \u88C1\u5B9A\u8F93\u51FA\u683C\u5F0F\uFF08\u524D\u7AEF\u6309\u6B64\u6E32\u67D3\uFF0C\u5FC5\u987B\u9075\u5B88\uFF09
|
|
8178
|
+
|
|
8179
|
+
\u56DE\u590D\u7528\u6237\u65F6\uFF0C\u7ED3\u8BBA\u884C\u5FC5\u987B\u662F\u4E0B\u5217\u56DB\u8005\u4E4B\u4E00\uFF0C\u5355\u72EC\u6210\u884C\uFF1A
|
|
8180
|
+
|
|
8181
|
+
- \`\u7ED3\u8BBA\uFF1A\u2705 \u5EFA\u8BAE\u505A\`
|
|
8182
|
+
- \`\u7ED3\u8BBA\uFF1A\u274C \u4E0D\u5EFA\u8BAE\u505A\`
|
|
8183
|
+
- \`\u7ED3\u8BBA\uFF1A\u23F8 \u6682\u7F13\`
|
|
8184
|
+
- \`\u7ED3\u8BBA\uFF1A\u2753 \u9700\u6F84\u6E05\`
|
|
8185
|
+
|
|
8186
|
+
\u968F\u540E\u7ED9\u300C\u7406\u7531\u300D\uFF08\u65B9\u5411\u7EA7\uFF1A\u7A7A\u95F4\u73B0\u72B6 / \u4EFB\u52A1\u73B0\u72B6 / \u6210\u672C\u6536\u76CA\uFF09\uFF0C\u300C\u5EFA\u8BAE\u505A\u300D\u65F6\u518D\u7ED9\u65B9\u5411\u7EA7\u8303\u56F4
|
|
8187
|
+
\uFF08\u5927\u81F4\u6D89\u53CA\u4EC0\u4E48\u3001\u9884\u671F\u6548\u679C\uFF09\uFF0C**\u4E0D\u8981**\u7ED9\u6587\u4EF6\u7EA7\u6539\u52A8\u6E05\u5355\u3002
|
|
8188
|
+
|
|
8189
|
+
## 3. \u4E8B\u5B9E\u7EAA\u5F8B\u4E0E\u8FDB\u5C55\u540C\u6B65
|
|
8190
|
+
|
|
8191
|
+
- **\u4EFB\u52A1\u7F16\u53F7\uFF08\u5982 SPW299-28\uFF09\u53EA\u80FD\u53D6\u81EA\u547D\u4EE4\u8FD4\u56DE\u503C**\uFF08\`morula steward dispatch\` / \`morula task list\` \u8FD4\u56DE\u7684 \`code\`\uFF09\u3002
|
|
8192
|
+
\u8FD4\u56DE\u503C\u91CC\u6CA1\u6709\u7F16\u53F7\u5C31\u4E0D\u8981\u5199\u7F16\u53F7\uFF0C**\u4E0D\u5F97\u81EA\u884C\u7F16\u9020**\u3002
|
|
8193
|
+
- \u62A5\u8FDB\u5C55\u524D\u5148\u67E5\uFF08\`morula task list\` / \`morula task get\` / \`morula comment list\`\uFF09\uFF0C\u4E0D\u8981\u51ED\u8BB0\u5FC6\u65AD\u8A00\u3002
|
|
8194
|
+
- **\u5199\u64CD\u4F5C\u540E\u56DE\u8BFB**\uFF1A\u6539\u5C5E\u6027 / \u63A8\u8FDB\u72B6\u6001 / \u505C\u91CD\u8DD1\u4E4B\u540E\uFF0C\u7528\u67E5\u8BE2\u547D\u4EE4\u786E\u8BA4\u7ED3\u679C\uFF0C\u518D\u5411\u7528\u6237\u6C47\u62A5\u300C\u5DF2\u751F\u6548\u300D\u3002
|
|
8195
|
+
- \u547D\u4EE4\u8F93\u51FA\u662F stdout \u4E0A\u7684 JSON\uFF1B\u51FA\u9519\u65F6\u770B stderr \u7684 \`code\` / \`message\` \u5B57\u6BB5\u5E76\u636E\u6B64\u8C03\u6574\uFF0C\u4E0D\u8981\u91CD\u8BD5\u540C\u6837\u7684\u8C03\u7528\u3002
|
|
8196
|
+
\uFF08\u5199\u64CD\u4F5C\u62A5 \`FORBIDDEN\` \u4E14\u6587\u6848\u662F\u300C\u4F1A\u8BDD\u51ED\u8BC1\u4E0D\u80FD\u6267\u884C\u8BE5\u5199\u64CD\u4F5C\u300D= \u8FD9\u6761\u80FD\u529B\u4E0D\u5728\u7BA1\u5BB6\u6388\u6743\u8303\u56F4\u5185\uFF08\u4F1A\u8BDD\u4F5C\u7528\u57DF\u9650\u5236\uFF09\uFF1A**\u4E0D\u8981\u7ED5\u9053\u91CD\u8BD5**\uFF0C\u76F4\u63A5\u544A\u8BC9\u7528\u6237\u8BE5\u80FD\u529B\u672A\u5F00\u653E\u3002\uFF09
|
|
8197
|
+
- \u5E73\u53F0**\u4E0D\u518D**\u5411\u4F1A\u8BDD\u81EA\u52A8\u63A8\u9001\u6C47\u62A5\u6D88\u606F\u3002\u7528\u6237\u95EE\u8FDB\u5C55\u65F6\uFF0C\u7528 CLI \u67E5\u8BE2\u540E\u56DE\u7B54\uFF1A\u4EFB\u52A1\u72B6\u6001\u770B\u677F\u5B9E\u65F6\u53EF\u89C1\uFF0C
|
|
8198
|
+
\u5206\u6790\u7ED3\u8BBA\u4E0E\u8BC4\u5BA1\u610F\u89C1\u5728\u4EFB\u52A1\u8BC4\u8BBA\u91CC\uFF0C\u4EA4\u4ED8\u7ED3\u679C\u5728\u4EFB\u52A1\u7684\u300C\u5173\u8054 PR / Commit\u300D\u533A\u3002
|
|
8199
|
+
`;
|
|
8200
|
+
function buildStewardSkillFile() {
|
|
8201
|
+
return `${STEWARD_SKILL_FRONTMATTER}
|
|
8202
|
+
${STEWARD_SKILL_BODY}`;
|
|
8203
|
+
}
|
|
8204
|
+
|
|
8205
|
+
// src/daemon/session-host.ts
|
|
8206
|
+
var SESSION_IDLE_DEFAULT_MS = 30 * 60 * 1e3;
|
|
8207
|
+
var ELICITATION_DEFAULT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
8208
|
+
var ATTACHMENT_FETCH_TIMEOUT_MS = 3e4;
|
|
8209
|
+
function safeAttachmentName(fileName) {
|
|
8210
|
+
const base = (fileName || "attachment").split(/[\\/]/).pop() ?? "attachment";
|
|
8211
|
+
const cleaned = base.replace(/[\u0000-\u001F<>:"|?*]/g, "_").replace(/^\.+/, "_").trim();
|
|
8212
|
+
return cleaned.slice(0, 120) || "attachment";
|
|
8213
|
+
}
|
|
8214
|
+
function resolveSessionIdleMs(env = process.env) {
|
|
8215
|
+
const raw = Number(env.MORULA_SESSION_IDLE_MS);
|
|
8216
|
+
return Number.isFinite(raw) && raw > 0 ? raw : SESSION_IDLE_DEFAULT_MS;
|
|
8217
|
+
}
|
|
8218
|
+
function resolveElicitationTimeoutMs(env = process.env) {
|
|
8219
|
+
const raw = Number(env.MORULA_DIM_APPROVAL_TIMEOUT_MS);
|
|
8220
|
+
return Number.isFinite(raw) && raw > 0 ? raw : ELICITATION_DEFAULT_TIMEOUT_MS;
|
|
8221
|
+
}
|
|
8222
|
+
var ELICITATION_POLL_WAIT_MS = 15e3;
|
|
8223
|
+
var SessionHost = class {
|
|
8224
|
+
constructor(options) {
|
|
8225
|
+
this.options = options;
|
|
8226
|
+
this.log = options.log ?? ((line) => console.log(line));
|
|
8227
|
+
this.idleMs = resolveSessionIdleMs();
|
|
8228
|
+
}
|
|
8229
|
+
options;
|
|
8230
|
+
sessions = /* @__PURE__ */ new Map();
|
|
8231
|
+
log;
|
|
8232
|
+
idleMs;
|
|
8233
|
+
/** 当前打开的会话(morula AgentSession.id 列表;诊断/测试用) */
|
|
8234
|
+
get openSessionIds() {
|
|
8235
|
+
return [...this.sessions.keys()];
|
|
8236
|
+
}
|
|
8237
|
+
/**
|
|
8238
|
+
* 打开会话:建/复用工作区 → 起**独立** dim acp 进程 → session/new(或 resume 续跑)
|
|
8239
|
+
* → 应用配置 → 指针回传。工作区保留(close 不清理)。
|
|
8240
|
+
*
|
|
8241
|
+
* **幂等**:已打开且进程存活 → 只刷新凭证/注入物与变化过的配置,**不重建进程**
|
|
8242
|
+
* (服务端每轮 prompt 前都会补发 open 做兜底:daemon 可能重启或空闲回收过,
|
|
8243
|
+
* 用同一条命令覆盖「首次打开」与「兜底确认」两种语义,避免两端各维护一套「是否已打开」状态)。
|
|
8244
|
+
*/
|
|
8245
|
+
async open(cmd) {
|
|
8246
|
+
if (!cmd?.sessionId || !cmd?.tenantId || !cmd?.projectId)
|
|
8247
|
+
return { ok: false, error: "SESSION_OPEN_INVALID_ARGS" };
|
|
8248
|
+
const workspaceDir = join19(this.options.workdir, "steward", cmd.tenantId, cmd.projectId);
|
|
8249
|
+
mkdirSync12(workspaceDir, { recursive: true });
|
|
8250
|
+
this.writeInjections(workspaceDir, cmd);
|
|
8251
|
+
const existing = this.sessions.get(cmd.sessionId);
|
|
8252
|
+
if (existing?.conn.isAlive) {
|
|
8253
|
+
if (cmd.sessionToken) {
|
|
8254
|
+
this.options.api.setSessionToken(cmd.sessionId, cmd.sessionToken);
|
|
8255
|
+
existing.env.MORULA_SESSION_TOKEN = cmd.sessionToken;
|
|
8256
|
+
}
|
|
8257
|
+
this.touch(existing);
|
|
8258
|
+
const changed = this.diffConfig(existing.appliedConfig, this.configFrom(cmd));
|
|
8259
|
+
for (const [configId, value] of Object.entries(changed)) {
|
|
8260
|
+
await existing.conn.setConfigOptionFull(existing.dimSessionId, configId, value).then((raw) => {
|
|
8261
|
+
const full = normalizeAcpConfigOptions(raw) ?? [];
|
|
8262
|
+
if (full.length > 0) {
|
|
8263
|
+
void this.options.api.reportSessionBinding(cmd.sessionId, { configOptions: full }).catch(() => {
|
|
8264
|
+
});
|
|
8265
|
+
}
|
|
8266
|
+
}).catch((err) => this.log(`[session-host] \u4F1A\u8BDD ${cmd.sessionId} \u914D\u7F6E ${configId} \u589E\u91CF\u4E0B\u53D1\u5931\u8D25: ${err.message}`));
|
|
8267
|
+
}
|
|
8268
|
+
existing.appliedConfig = { ...existing.appliedConfig, ...changed };
|
|
8269
|
+
return { ok: true, dimSessionId: existing.dimSessionId, workspaceDir: existing.workspaceDir, alreadyOpen: true };
|
|
8270
|
+
}
|
|
8271
|
+
if (existing)
|
|
8272
|
+
await this.close({ sessionId: cmd.sessionId }, "reopen");
|
|
8273
|
+
if (cmd.sessionToken)
|
|
8274
|
+
this.options.api.setSessionToken(cmd.sessionId, cmd.sessionToken);
|
|
8275
|
+
const env = {
|
|
8276
|
+
...cmd.sessionToken ? { MORULA_SESSION_TOKEN: cmd.sessionToken } : {},
|
|
8277
|
+
...this.options.baseUrl ? { MORULA_API_URL: this.options.baseUrl } : {},
|
|
8278
|
+
// 会话上下文(对齐任务链路的 MORULA_TASK_ID):让 `morula` CLI 知道「我在哪个会话里」,
|
|
8279
|
+
// 派单幂等与 elicitation 回传需要它;管家会话**没有** MORULA_TASK_ID(任务专属命令会因此拒绝执行)
|
|
8280
|
+
MORULA_SESSION_ID: cmd.sessionId,
|
|
8281
|
+
MORULA_SPACE_ID: cmd.projectId,
|
|
8282
|
+
// 插件 bin 前置 + 显式 CLI 路径:否则 agent 会解析到宿主/桌面应用自带的旧 `morula`——
|
|
8283
|
+
// 新命令不存在,且旧 CLI 不认 MORULA_SESSION_TOKEN(回落用户凭证)→ 写操作署名错、
|
|
8284
|
+
// 写面作用域校验被绕过。PATH 只是第一道保险(dim 会重写 agent shell 的 PATH),
|
|
8285
|
+
// 可靠的是 `MORULA_CLI` 绝对路径(契约要求 agent 用它)。
|
|
8286
|
+
// MORULA_CLI 优先本包自带入口(与当前会话同源),产物缺失才回退插件启动器。
|
|
8287
|
+
// 见 issues/steward-cli-resolves-stale-bundled-morula.md、issues/agent-cli-stale-build-missing-steward-commands.md。
|
|
8288
|
+
PATH: prependPluginBinToPath(process.env.PATH),
|
|
8289
|
+
MORULA_CLI: resolveAgentCliPath(),
|
|
8290
|
+
...this.options.isolatedDimEnv ?? {}
|
|
8291
|
+
};
|
|
8292
|
+
const onExit = (code) => {
|
|
8293
|
+
this.log(`[session-host] \u4F1A\u8BDD ${cmd.sessionId} \u7684 dim acp \u8FDB\u7A0B\u9000\u51FA\uFF08code=${code}\uFF09`);
|
|
8294
|
+
const managed2 = this.sessions.get(cmd.sessionId);
|
|
8295
|
+
if (managed2 && managed2.conn === conn) {
|
|
8296
|
+
this.sessions.delete(cmd.sessionId);
|
|
8297
|
+
this.options.api.clearSessionToken(cmd.sessionId);
|
|
8298
|
+
}
|
|
8299
|
+
};
|
|
8300
|
+
const connOptions = {
|
|
8301
|
+
dimPath: this.options.dimPath,
|
|
8302
|
+
cwd: workspaceDir,
|
|
8303
|
+
// process.env 值域含 undefined(ProcessEnv),显式收敛为 string 索引(spawn env 语义)
|
|
8304
|
+
env: { ...process.env, ...env },
|
|
8305
|
+
// P1-7:审批默认全自动放行(管家人在环收敛到 elicitation 一处,见 §4.7 取舍)。
|
|
8306
|
+
// 注意:dim 的权限引擎决定 allow/deny/ask,client 无法强制——`permission` 只选 preset。
|
|
8307
|
+
onPermissionRequest: async () => ({ outcome: { outcome: "selected", optionId: "allow" } }),
|
|
8308
|
+
onElicitation: async (request) => this.handleElicitation(cmd.sessionId, request),
|
|
8309
|
+
onExit
|
|
8310
|
+
};
|
|
8311
|
+
const conn = this.options.acpConnectionFactory ? this.options.acpConnectionFactory(connOptions) : new AcpConnection(connOptions);
|
|
8312
|
+
try {
|
|
8313
|
+
await conn.start();
|
|
8314
|
+
} catch (err) {
|
|
8315
|
+
return { ok: false, error: `dim acp \u542F\u52A8\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}` };
|
|
8316
|
+
}
|
|
8317
|
+
const sessionParams = { cwd: workspaceDir, mcpServers: [] };
|
|
8318
|
+
let dimSessionId;
|
|
8319
|
+
let resumed = false;
|
|
8320
|
+
let resumeFailed = null;
|
|
8321
|
+
let configOptions;
|
|
8322
|
+
if (cmd.dimSessionId) {
|
|
8323
|
+
try {
|
|
8324
|
+
const r = await conn.resumeSession({ ...sessionParams, sessionId: cmd.dimSessionId });
|
|
8325
|
+
configOptions = normalizeAcpConfigOptions(r.configOptions) ?? void 0;
|
|
8326
|
+
dimSessionId = cmd.dimSessionId;
|
|
8327
|
+
resumed = true;
|
|
8328
|
+
} catch {
|
|
8329
|
+
try {
|
|
8330
|
+
const r = await conn.loadSession({ ...sessionParams, sessionId: cmd.dimSessionId });
|
|
8331
|
+
configOptions = normalizeAcpConfigOptions(r.configOptions) ?? void 0;
|
|
8332
|
+
dimSessionId = cmd.dimSessionId;
|
|
8333
|
+
resumed = true;
|
|
8334
|
+
} catch (err) {
|
|
8335
|
+
resumeFailed = err instanceof Error ? err.message : String(err);
|
|
8336
|
+
const created = await conn.newSession(sessionParams);
|
|
8337
|
+
configOptions = normalizeAcpConfigOptions(created.configOptions) ?? void 0;
|
|
8338
|
+
dimSessionId = created.sessionId;
|
|
8339
|
+
}
|
|
8340
|
+
}
|
|
8341
|
+
} else {
|
|
8342
|
+
const created = await conn.newSession(sessionParams);
|
|
8343
|
+
configOptions = normalizeAcpConfigOptions(created.configOptions) ?? void 0;
|
|
8344
|
+
dimSessionId = created.sessionId;
|
|
8345
|
+
}
|
|
8346
|
+
const appliedConfig = this.configFrom(cmd);
|
|
8347
|
+
const permission = appliedConfig.permission || "workspace-write";
|
|
8348
|
+
const applyConfig = async (configId, value) => {
|
|
8349
|
+
try {
|
|
8350
|
+
const full = normalizeAcpConfigOptions(await conn.setConfigOptionFull(dimSessionId, configId, value)) ?? [];
|
|
8351
|
+
if (full.length > 0)
|
|
8352
|
+
configOptions = full;
|
|
8353
|
+
} catch (err) {
|
|
8354
|
+
this.log(`[session-host] \u4F1A\u8BDD ${cmd.sessionId} \u914D\u7F6E ${configId}=${value} \u4E0B\u53D1\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
8355
|
+
}
|
|
8356
|
+
};
|
|
8357
|
+
await applyConfig("permission", permission);
|
|
8358
|
+
if (appliedConfig.modelId)
|
|
8359
|
+
await applyConfig("model", appliedConfig.modelId);
|
|
8360
|
+
if (appliedConfig.thoughtLevel)
|
|
8361
|
+
await applyConfig("thought_level", appliedConfig.thoughtLevel);
|
|
8362
|
+
if (appliedConfig.agentMode)
|
|
8363
|
+
await applyConfig("mode", appliedConfig.agentMode);
|
|
8364
|
+
void this.options.api.reportSessionBinding(cmd.sessionId, {
|
|
8365
|
+
dimSessionId,
|
|
8366
|
+
workspaceDir,
|
|
8367
|
+
...cmd.runtimeId ? { runtimeId: cmd.runtimeId } : {},
|
|
8368
|
+
...cmd.providerKey ? { providerKey: cmd.providerKey } : {},
|
|
8369
|
+
...configOptions !== void 0 ? { configOptions } : {}
|
|
8370
|
+
}).catch((err) => {
|
|
8371
|
+
this.log(`[session-host] \u4F1A\u8BDD ${cmd.sessionId} \u6307\u9488\u56DE\u4F20\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
8372
|
+
});
|
|
8373
|
+
const managed = {
|
|
8374
|
+
sessionId: cmd.sessionId,
|
|
8375
|
+
conn,
|
|
8376
|
+
dimSessionId,
|
|
8377
|
+
workspaceDir,
|
|
8378
|
+
env,
|
|
8379
|
+
turnNo: 0,
|
|
8380
|
+
promptInFlight: null,
|
|
8381
|
+
cancelRequested: false,
|
|
8382
|
+
// resume 成功 → dim 会话里已有上一进程注入的管家指令,不重复前置
|
|
8383
|
+
instructed: resumed,
|
|
8384
|
+
appliedConfig: { ...appliedConfig, permission },
|
|
8385
|
+
idleTimer: setTimeout(() => {
|
|
8386
|
+
void this.close({ sessionId: cmd.sessionId }, "idle");
|
|
8387
|
+
}, this.idleMs),
|
|
8388
|
+
lastActiveAt: Date.now()
|
|
8389
|
+
};
|
|
8390
|
+
managed.idleTimer.unref?.();
|
|
8391
|
+
this.sessions.set(cmd.sessionId, managed);
|
|
8392
|
+
this.log(`[session-host] \u4F1A\u8BDD\u5DF2\u6253\u5F00 ${cmd.sessionId}\uFF08dim=${dimSessionId}\uFF0Cworkspace=${workspaceDir}${resumeFailed ? `\uFF0C\u7EED\u8DD1\u5931\u8D25\u5DF2\u65B0\u5EFA\uFF1A${resumeFailed}` : ""}\uFF09`);
|
|
8393
|
+
return { ok: true, dimSessionId, workspaceDir };
|
|
8394
|
+
}
|
|
8395
|
+
/**
|
|
8396
|
+
* 送一轮 prompt(同一会话单活跃——ACP 硬约束;忙时返回 SESSION_BUSY 不排队,
|
|
8397
|
+
* 排队语义由服务端消息投递状态机承载,见 §4.2)。
|
|
8398
|
+
* 事件映射复用任务链路同款 mapper + normalizer(含 P0-8 修正:user_message / usage 上下文语义)。
|
|
8399
|
+
*
|
|
8400
|
+
* **管家指令前置**(§4.4):新建 dim 会话的**首条** prompt 前置 `STEWARD_SYSTEM_INSTRUCTIONS`
|
|
8401
|
+
* (ACP 无 system prompt 通道)。resume 的会话里已有,不重复注入。
|
|
8402
|
+
* 该常量只含身份/边界/目标/工作区与工具入口(提示词极简,见 steward-instructions.ts 文件头);
|
|
8403
|
+
* 行为规范与命令用法在 `.agents/skills/steward-management/SKILL.md`,由 agent 按需读取。
|
|
8404
|
+
*/
|
|
8405
|
+
async prompt(cmd) {
|
|
8406
|
+
const managed = this.sessions.get(cmd.sessionId);
|
|
8407
|
+
if (!managed)
|
|
8408
|
+
return { ok: false, error: "SESSION_NOT_OPEN" };
|
|
8409
|
+
if (managed.promptInFlight)
|
|
8410
|
+
return { ok: false, error: "SESSION_BUSY" };
|
|
8411
|
+
if (!cmd.prompt?.trim())
|
|
8412
|
+
return { ok: false, error: "SESSION_PROMPT_EMPTY" };
|
|
8413
|
+
managed.turnNo += 1;
|
|
8414
|
+
managed.cancelRequested = false;
|
|
8415
|
+
this.touch(managed);
|
|
8416
|
+
const turnNo = managed.turnNo;
|
|
8417
|
+
const mapper = new AcpEventMapper();
|
|
8418
|
+
let seqHint = 0;
|
|
8419
|
+
const nextSeq = () => ++seqHint;
|
|
8420
|
+
const textStream = new AssistantTextStream({
|
|
8421
|
+
onDelta: (seg, delta) => {
|
|
8422
|
+
void this.options.api.recordSessionStreamChunk(managed.sessionId, {
|
|
8423
|
+
chunkType: "delta",
|
|
8424
|
+
data: { kind: "text", seg, text: delta }
|
|
8425
|
+
}).catch(() => {
|
|
8426
|
+
});
|
|
8427
|
+
},
|
|
8428
|
+
onParagraph: (seg, text2) => this.options.api.recordSessionEvent(managed.sessionId, {
|
|
8429
|
+
type: "agent_message_delta",
|
|
8430
|
+
output: { text: redactValue(text2), seg },
|
|
8431
|
+
turnNo,
|
|
8432
|
+
seqHint: nextSeq()
|
|
8433
|
+
}).then(() => void 0).catch((err) => {
|
|
8434
|
+
this.log(`[session-host] \u4F1A\u8BDD ${managed.sessionId} \u6BB5\u843D\u4E8B\u4EF6\u4E0A\u62A5\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
8435
|
+
})
|
|
8436
|
+
}, { log: this.log });
|
|
8437
|
+
const thinkingStream = new AssistantTextStream({
|
|
8438
|
+
onDelta: (seg, delta) => {
|
|
8439
|
+
void this.options.api.recordSessionStreamChunk(managed.sessionId, {
|
|
8440
|
+
chunkType: "delta",
|
|
8441
|
+
data: { kind: "thinking", seg, text: delta }
|
|
8442
|
+
}).catch(() => {
|
|
8443
|
+
});
|
|
8444
|
+
},
|
|
8445
|
+
onParagraph: (seg, text2) => this.options.api.recordSessionEvent(managed.sessionId, {
|
|
8446
|
+
type: "agent_thought_delta",
|
|
8447
|
+
output: { text: redactValue(text2), seg },
|
|
8448
|
+
turnNo,
|
|
8449
|
+
seqHint: nextSeq()
|
|
8450
|
+
}).then(() => void 0).catch((err) => {
|
|
8451
|
+
this.log(`[session-host] \u4F1A\u8BDD ${managed.sessionId} \u601D\u8003\u6BB5\u843D\u4E0A\u62A5\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
8452
|
+
})
|
|
8453
|
+
}, { log: this.log });
|
|
8454
|
+
let textChain = Promise.resolve();
|
|
8455
|
+
let thinkingPending = false;
|
|
8456
|
+
const gateThinking = () => {
|
|
8457
|
+
if (thinkingPending) {
|
|
8458
|
+
thinkingPending = false;
|
|
8459
|
+
textChain = textChain.then(() => thinkingStream.flushParagraph());
|
|
8460
|
+
}
|
|
8461
|
+
return textChain;
|
|
8462
|
+
};
|
|
8463
|
+
const onEvent = (event) => {
|
|
8464
|
+
this.touch(managed);
|
|
8465
|
+
if (event.type === "assistant_thinking_delta") {
|
|
8466
|
+
thinkingPending = true;
|
|
8467
|
+
thinkingStream.push(event.text);
|
|
8468
|
+
} else if (event.type === "assistant_text_delta") {
|
|
8469
|
+
void gateThinking().then(() => textStream.push(event.text));
|
|
8470
|
+
} else if (event.type === "tool_call_started") {
|
|
8471
|
+
void gateThinking().then(() => textStream.flushParagraph()).then(() => this.options.api.recordSessionEvent(managed.sessionId, {
|
|
8472
|
+
type: "tool_call_started",
|
|
8473
|
+
toolName: event.toolName,
|
|
8474
|
+
callId: event.callId,
|
|
8475
|
+
input: event.input !== void 0 ? redactInputSummary(event.input) : void 0,
|
|
8476
|
+
output: void 0,
|
|
8477
|
+
turnNo,
|
|
8478
|
+
seqHint: nextSeq()
|
|
8479
|
+
})).catch(() => {
|
|
8480
|
+
});
|
|
8481
|
+
} else if (event.type === "tool_call_completed") {
|
|
8482
|
+
void this.options.api.recordSessionEvent(managed.sessionId, {
|
|
8483
|
+
type: "tool_call_completed",
|
|
8484
|
+
callId: event.callId,
|
|
8485
|
+
toolName: event.toolName,
|
|
8486
|
+
output: {
|
|
8487
|
+
callId: event.callId,
|
|
8488
|
+
toolName: event.toolName,
|
|
8489
|
+
status: event.aborted ? "aborted" : "completed",
|
|
8490
|
+
...event.outputSummary !== void 0 ? { summary: String(redactValue(String(event.outputSummary).slice(0, 200))) } : {}
|
|
8491
|
+
},
|
|
8492
|
+
turnNo,
|
|
8493
|
+
seqHint: nextSeq()
|
|
8494
|
+
}).catch(() => {
|
|
8495
|
+
});
|
|
8496
|
+
} else if (event.type === "user_message") {
|
|
8497
|
+
void this.options.api.recordSessionEvent(managed.sessionId, {
|
|
8498
|
+
type: "user_message",
|
|
8499
|
+
output: { text: event.text },
|
|
8500
|
+
turnNo
|
|
8501
|
+
}).catch(() => {
|
|
8502
|
+
});
|
|
8503
|
+
} else if (event.type === "usage") {
|
|
8504
|
+
void this.options.api.recordSessionEvent(managed.sessionId, {
|
|
8505
|
+
type: "usage",
|
|
8506
|
+
output: event.usage,
|
|
8507
|
+
turnNo
|
|
8508
|
+
}).catch(() => {
|
|
8509
|
+
});
|
|
8510
|
+
} else if (event.type === "session" && event.providerSessionId) {
|
|
8511
|
+
void this.options.api.reportSessionBinding(managed.sessionId, { dimSessionId: event.providerSessionId }).catch(() => {
|
|
8512
|
+
});
|
|
8513
|
+
}
|
|
8514
|
+
};
|
|
8515
|
+
const normalizer = createDimEventNormalizer({ onEvent });
|
|
8516
|
+
const updateHandler = (update) => {
|
|
8517
|
+
const { messages } = mapper.map(managed.dimSessionId, update);
|
|
8518
|
+
for (const message of messages)
|
|
8519
|
+
normalizer.handle(message, "");
|
|
8520
|
+
};
|
|
8521
|
+
const text = managed.instructed ? cmd.prompt : `${STEWARD_SYSTEM_INSTRUCTIONS}
|
|
8522
|
+
|
|
8523
|
+
---
|
|
8524
|
+
|
|
8525
|
+
${cmd.prompt}`;
|
|
8526
|
+
const run3 = (async () => {
|
|
8527
|
+
try {
|
|
8528
|
+
const { blocks, fileRefs } = await this.materializeAttachments(managed.workspaceDir, cmd.attachments ?? []);
|
|
8529
|
+
const promptBlocks = [{ type: "text", text: fileRefs.length > 0 ? `${text}
|
|
8530
|
+
|
|
8531
|
+
---
|
|
8532
|
+
|
|
8533
|
+
\u672C\u8F6E\u9644\u4EF6\u5DF2\u4E0B\u8F7D\u5230\u4F1A\u8BDD\u5DE5\u4F5C\u533A\uFF08\u7528 read/exec \u5DE5\u5177\u6309\u8DEF\u5F84\u8BFB\u53D6\uFF0C\u4E0D\u8981\u91CD\u590D\u4E0B\u8F7D\uFF09\uFF1A
|
|
8534
|
+
${fileRefs.join("\n")}` : text }];
|
|
8535
|
+
promptBlocks.push(...blocks);
|
|
8536
|
+
const pending = managed.conn.prompt(managed.dimSessionId, promptBlocks, updateHandler);
|
|
8537
|
+
managed.instructed = true;
|
|
8538
|
+
void this.options.api.recordSessionEvent(managed.sessionId, {
|
|
8539
|
+
type: "turn_started",
|
|
8540
|
+
output: { ...cmd.messageId ? { messageId: cmd.messageId } : {} },
|
|
8541
|
+
turnNo
|
|
8542
|
+
}).catch(() => {
|
|
8543
|
+
});
|
|
8544
|
+
const result = await pending;
|
|
8545
|
+
await gateThinking();
|
|
8546
|
+
await textStream.flushParagraph();
|
|
8547
|
+
normalizer.finish({ eventType: "run:ended", payload: { stopReason: result.stopReason } }, "");
|
|
8548
|
+
await this.options.api.recordSessionEvent(managed.sessionId, {
|
|
8549
|
+
type: "turn_completed",
|
|
8550
|
+
output: { stopReason: result.stopReason, ...cmd.messageId ? { messageId: cmd.messageId } : {} },
|
|
8551
|
+
turnNo
|
|
8552
|
+
}).catch(() => {
|
|
8553
|
+
});
|
|
8554
|
+
return { ok: true, stopReason: result.stopReason };
|
|
8555
|
+
} catch (err) {
|
|
8556
|
+
await gateThinking();
|
|
8557
|
+
await textStream.flushParagraph();
|
|
8558
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8559
|
+
await this.options.api.recordSessionEvent(managed.sessionId, {
|
|
8560
|
+
type: "turn_failed",
|
|
8561
|
+
output: { error: message, ...cmd.messageId ? { messageId: cmd.messageId } : {} },
|
|
8562
|
+
turnNo
|
|
8563
|
+
}).catch(() => {
|
|
8564
|
+
});
|
|
8565
|
+
return { ok: false, error: message };
|
|
8566
|
+
} finally {
|
|
8567
|
+
textStream.dispose();
|
|
8568
|
+
thinkingStream.dispose();
|
|
8569
|
+
managed.promptInFlight = null;
|
|
8570
|
+
this.touch(managed);
|
|
8571
|
+
}
|
|
8572
|
+
})();
|
|
8573
|
+
managed.promptInFlight = run3;
|
|
8574
|
+
return run3;
|
|
8575
|
+
}
|
|
8576
|
+
/**
|
|
8577
|
+
* 附件物化(§五 P2-3):
|
|
8578
|
+
* - **图片** → ACP `image` content block(base64 + mimeType),直接进模型上下文;
|
|
8579
|
+
* - **其他文件** → 下载到 `<workspace>/attachments/<安全文件名>`,返回路径引用文本
|
|
8580
|
+
* (doc 明确「文件走工作区文件 + 路径引用」:不进上下文,由 agent 按需读取)。
|
|
8581
|
+
*
|
|
8582
|
+
* 下载失败不抛:记日志并跳过该附件(本轮照常发出)。
|
|
8583
|
+
*/
|
|
8584
|
+
async materializeAttachments(workspaceDir, attachments) {
|
|
8585
|
+
const blocks = [];
|
|
8586
|
+
const fileRefs = [];
|
|
8587
|
+
if (attachments.length === 0)
|
|
8588
|
+
return { blocks, fileRefs };
|
|
8589
|
+
const dir = join19(workspaceDir, "attachments");
|
|
8590
|
+
for (const attachment of attachments) {
|
|
8591
|
+
try {
|
|
8592
|
+
const res = await fetch(attachment.downloadUrl, { signal: AbortSignal.timeout(ATTACHMENT_FETCH_TIMEOUT_MS) });
|
|
8593
|
+
if (!res.ok)
|
|
8594
|
+
throw new Error(`HTTP ${res.status}`);
|
|
8595
|
+
const bytes = Buffer.from(await res.arrayBuffer());
|
|
8596
|
+
if (attachment.isImage) {
|
|
8597
|
+
blocks.push({ type: "image", data: bytes.toString("base64"), mimeType: attachment.fileType || "image/png" });
|
|
8598
|
+
} else {
|
|
8599
|
+
mkdirSync12(dir, { recursive: true });
|
|
8600
|
+
const target = join19(dir, safeAttachmentName(attachment.fileName));
|
|
8601
|
+
writeFileSync10(target, bytes);
|
|
8602
|
+
fileRefs.push(`- ${attachment.fileName} \u2192 ${target}`);
|
|
8603
|
+
}
|
|
8604
|
+
} catch (err) {
|
|
8605
|
+
this.log(`[session-host] \u9644\u4EF6 ${attachment.id}\uFF08${attachment.fileName}\uFF09\u62C9\u53D6\u5931\u8D25\uFF0C\u672C\u8F6E\u5DF2\u8DF3\u8FC7: ${err instanceof Error ? err.message : String(err)}`);
|
|
8606
|
+
}
|
|
8607
|
+
}
|
|
8608
|
+
return { blocks, fileRefs };
|
|
8609
|
+
}
|
|
8610
|
+
/** 取消当前回合(ACP session/cancel;会话保持打开——长驻语义) */
|
|
8611
|
+
cancel(cmd) {
|
|
8612
|
+
const managed = this.sessions.get(cmd.sessionId);
|
|
8613
|
+
if (!managed)
|
|
8614
|
+
return { ok: false, error: "SESSION_NOT_OPEN" };
|
|
8615
|
+
managed.cancelRequested = true;
|
|
8616
|
+
managed.conn.cancel(managed.dimSessionId);
|
|
8617
|
+
this.touch(managed);
|
|
8618
|
+
return { ok: true };
|
|
8619
|
+
}
|
|
8620
|
+
/**
|
|
8621
|
+
* 配置变更(configId ∈ model / thought_level / mode / permission)。
|
|
8622
|
+
* ACP `set_config_option` 全量返回 configOptions——回写由服务端(配置端点)消费,
|
|
8623
|
+
* 这里原样返回给调用方。
|
|
8624
|
+
*/
|
|
8625
|
+
async config(cmd) {
|
|
8626
|
+
const managed = this.sessions.get(cmd.sessionId);
|
|
8627
|
+
if (!managed)
|
|
8628
|
+
return { ok: false, error: "SESSION_NOT_OPEN" };
|
|
8629
|
+
this.touch(managed);
|
|
8630
|
+
try {
|
|
8631
|
+
const result = normalizeAcpConfigOptions(await managed.conn.setConfigOptionFull(managed.dimSessionId, cmd.configId, cmd.value)) ?? [];
|
|
8632
|
+
managed.appliedConfig = { ...managed.appliedConfig, [this.configKey(cmd.configId)]: cmd.value };
|
|
8633
|
+
if (result.length > 0) {
|
|
8634
|
+
void this.options.api.reportSessionBinding(cmd.sessionId, { configOptions: result }).catch((err) => this.log(`[session-host] \u4F1A\u8BDD ${cmd.sessionId} configOptions \u56DE\u4F20\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`));
|
|
8635
|
+
}
|
|
8636
|
+
return { ok: true, configOptions: result };
|
|
8637
|
+
} catch (err) {
|
|
8638
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
8639
|
+
}
|
|
8640
|
+
}
|
|
8641
|
+
/**
|
|
8642
|
+
* 关闭会话:ACP session/close → 进程回收 → 服务端上报。
|
|
8643
|
+
* **工作区保留**(steward/<tenant>/<project> 可复用;清理属服务端清理命令域)。
|
|
8644
|
+
*
|
|
8645
|
+
* @param reason `idle` = 空闲回收(服务端回到 idle,保留 dimSessionId 以便 resume);
|
|
8646
|
+
* `command` = 用户/平台显式关闭(服务端终态 closed);
|
|
8647
|
+
* `reopen` = daemon 内部重建(**不上报**,紧接着的 open 会重新绑定)。
|
|
8648
|
+
*/
|
|
8649
|
+
async close(cmd, reason = "command") {
|
|
8650
|
+
const managed = this.sessions.get(cmd.sessionId);
|
|
8651
|
+
if (!managed)
|
|
8652
|
+
return { ok: true };
|
|
8653
|
+
this.sessions.delete(cmd.sessionId);
|
|
8654
|
+
clearTimeout(managed.idleTimer);
|
|
8655
|
+
try {
|
|
8656
|
+
await managed.conn.close();
|
|
8657
|
+
} catch {
|
|
8658
|
+
}
|
|
8659
|
+
if (reason === "reopen") {
|
|
8660
|
+
this.log(`[session-host] \u4F1A\u8BDD ${cmd.sessionId} \u8FDE\u63A5\u91CD\u5EFA\uFF08\u5DE5\u4F5C\u533A\u4FDD\u7559 ${managed.workspaceDir}\uFF09`);
|
|
8661
|
+
return { ok: true };
|
|
8662
|
+
}
|
|
8663
|
+
this.options.api.clearSessionToken(cmd.sessionId);
|
|
8664
|
+
void this.options.api.closeAgentSession(cmd.sessionId, reason).catch((err) => {
|
|
8665
|
+
this.log(`[session-host] \u4F1A\u8BDD ${cmd.sessionId} \u5173\u95ED\u4E0A\u62A5\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
8666
|
+
});
|
|
8667
|
+
this.log(`[session-host] \u4F1A\u8BDD\u5DF2\u5173\u95ED ${cmd.sessionId}\uFF08\u539F\u56E0=${reason}\uFF1B\u5DE5\u4F5C\u533A\u4FDD\u7559 ${managed.workspaceDir}\uFF09`);
|
|
8668
|
+
return { ok: true };
|
|
8669
|
+
}
|
|
8670
|
+
/**
|
|
8671
|
+
* 关闭全部会话(daemon 停机时收尾)。
|
|
8672
|
+
* 用 `idle` 而非 `command`:daemon 停机不是用户关闭会话,dim 会话应当可 resume
|
|
8673
|
+
* (否则服务端会把会话打成终态 `closed`,下次上线再也接不回来)。
|
|
8674
|
+
*/
|
|
8675
|
+
async closeAll() {
|
|
8676
|
+
for (const sessionId of [...this.sessions.keys()])
|
|
8677
|
+
await this.close({ sessionId }, "idle").catch(() => {
|
|
8678
|
+
});
|
|
8679
|
+
}
|
|
8680
|
+
// ────────────────────────── 内部 ──────────────────────────
|
|
8681
|
+
/** 活跃信号:刷新空闲回收计时(事件/prompt/取消都算活跃)。定时器 unref——回收器不应单独钉住进程(daemon 有 WS/心跳常驻句柄) */
|
|
8682
|
+
touch(managed) {
|
|
8683
|
+
managed.lastActiveAt = Date.now();
|
|
8684
|
+
clearTimeout(managed.idleTimer);
|
|
8685
|
+
managed.idleTimer = setTimeout(() => {
|
|
8686
|
+
void this.close({ sessionId: managed.sessionId }, "idle");
|
|
8687
|
+
}, this.idleMs);
|
|
8688
|
+
managed.idleTimer.unref?.();
|
|
8689
|
+
}
|
|
8690
|
+
/** 工作区注入物(幂等覆盖;daemon 侧组装,P1-6) */
|
|
8691
|
+
writeInjections(workspaceDir, cmd) {
|
|
8692
|
+
writeProjectAgentsMd(workspaceDir, cmd.agentsMd, this.log);
|
|
8693
|
+
const skillDir = join19(workspaceDir, ".agents", "skills", STEWARD_SKILL_NAME);
|
|
8694
|
+
mkdirSync12(skillDir, { recursive: true });
|
|
8695
|
+
writeFileSync10(join19(skillDir, "SKILL.md"), buildStewardSkillFile(), "utf8");
|
|
8696
|
+
const contextDir = join19(workspaceDir, ".agent_context");
|
|
8697
|
+
mkdirSync12(contextDir, { recursive: true });
|
|
8698
|
+
writeFileSync10(
|
|
8699
|
+
join19(contextDir, "steward-context.md"),
|
|
8700
|
+
buildStewardContextMd(cmd.facts ?? { projectId: cmd.projectId, tenantId: cmd.tenantId }),
|
|
8701
|
+
"utf8"
|
|
8702
|
+
);
|
|
8703
|
+
}
|
|
8704
|
+
configFrom(cmd) {
|
|
8705
|
+
return {
|
|
8706
|
+
...cmd.modelId?.trim() ? { modelId: cmd.modelId.trim() } : {},
|
|
8707
|
+
...cmd.thoughtLevel?.trim() ? { thoughtLevel: cmd.thoughtLevel.trim() } : {},
|
|
8708
|
+
...cmd.agentMode?.trim() ? { agentMode: cmd.agentMode.trim() } : {},
|
|
8709
|
+
...cmd.permission?.trim() ? { permission: cmd.permission.trim() } : {}
|
|
8710
|
+
};
|
|
8711
|
+
}
|
|
8712
|
+
/** 配置差异 → ACP configId 映射(只返回变化项;open 幂等路径用) */
|
|
8713
|
+
diffConfig(applied, next) {
|
|
8714
|
+
const out = {};
|
|
8715
|
+
for (const key of ["modelId", "thoughtLevel", "agentMode", "permission"]) {
|
|
8716
|
+
const value = next[key];
|
|
8717
|
+
if (value && value !== applied[key])
|
|
8718
|
+
out[this.configIdOf(key)] = value;
|
|
8719
|
+
}
|
|
8720
|
+
return out;
|
|
8721
|
+
}
|
|
8722
|
+
/** 本地字段名 → ACP configId(§4.4:model / thought_level / mode / permission) */
|
|
8723
|
+
configIdOf(key) {
|
|
8724
|
+
if (key === "modelId")
|
|
8725
|
+
return "model";
|
|
8726
|
+
if (key === "thoughtLevel")
|
|
8727
|
+
return "thought_level";
|
|
8728
|
+
if (key === "agentMode")
|
|
8729
|
+
return "mode";
|
|
8730
|
+
return key;
|
|
8731
|
+
}
|
|
8732
|
+
/** ACP configId → 本地字段名(config 变更回写用) */
|
|
8733
|
+
configKey(configId) {
|
|
8734
|
+
if (configId === "model")
|
|
8735
|
+
return "modelId";
|
|
8736
|
+
if (configId === "thought_level")
|
|
8737
|
+
return "thoughtLevel";
|
|
8738
|
+
if (configId === "mode")
|
|
8739
|
+
return "agentMode";
|
|
8740
|
+
return configId;
|
|
8741
|
+
}
|
|
8742
|
+
/**
|
|
8743
|
+
* 结构化提问上行(§4.6 ②):登记 → 长轮询切片等待用户应答 → 超时回 cancel。
|
|
8744
|
+
*
|
|
8745
|
+
* 为什么两段式(登记 + 轮询)而不是一个长连接:用户可能几分钟才填完,单个悬挂 HTTP 请求
|
|
8746
|
+
* 会撞上服务端 requestTimeout 与中间层空闲超时;切片等待把「无悬挂长连接」与
|
|
8747
|
+
* 「应答即达」两个目标同时拿到。
|
|
8748
|
+
*/
|
|
8749
|
+
async handleElicitation(sessionId, request) {
|
|
8750
|
+
const timeoutMs = resolveElicitationTimeoutMs();
|
|
8751
|
+
let elicitationId;
|
|
8752
|
+
try {
|
|
8753
|
+
const created = await this.options.api.createElicitation(sessionId, {
|
|
8754
|
+
toolCallId: request.toolCallId,
|
|
8755
|
+
message: request.message,
|
|
8756
|
+
requestedSchema: request.requestedSchema
|
|
8757
|
+
});
|
|
8758
|
+
if (!created?.ok || !created.elicitationId) {
|
|
8759
|
+
this.log(`[session-host] \u4F1A\u8BDD ${sessionId} \u7ED3\u6784\u5316\u63D0\u95EE\u767B\u8BB0\u5931\u8D25: ${created?.code ?? "unknown"}`);
|
|
8760
|
+
return { action: "decline" };
|
|
8761
|
+
}
|
|
8762
|
+
elicitationId = created.elicitationId;
|
|
8763
|
+
} catch (err) {
|
|
8764
|
+
this.log(`[session-host] \u4F1A\u8BDD ${sessionId} \u7ED3\u6784\u5316\u63D0\u95EE\u767B\u8BB0\u5F02\u5E38: ${err instanceof Error ? err.message : String(err)}`);
|
|
8765
|
+
return { action: "decline" };
|
|
8766
|
+
}
|
|
8767
|
+
const deadline = Date.now() + timeoutMs;
|
|
8768
|
+
while (Date.now() < deadline) {
|
|
8769
|
+
const remaining = deadline - Date.now();
|
|
8770
|
+
try {
|
|
8771
|
+
const result = await this.options.api.getElicitation(sessionId, elicitationId, Math.min(ELICITATION_POLL_WAIT_MS, remaining));
|
|
8772
|
+
if (result?.status === "answered")
|
|
8773
|
+
return { action: result.action ?? "accept", content: result.content ?? {} };
|
|
8774
|
+
if (result?.status === "cancelled")
|
|
8775
|
+
return { action: "cancel" };
|
|
8776
|
+
if (result?.status === "expired") {
|
|
8777
|
+
this.log(`[session-host] \u4F1A\u8BDD ${sessionId} \u7ED3\u6784\u5316\u63D0\u95EE\u5DF2\u5728\u670D\u52A1\u7AEF\u8FC7\u671F\uFF08${elicitationId}\uFF09`);
|
|
8778
|
+
return { action: "cancel" };
|
|
8779
|
+
}
|
|
8780
|
+
} catch (err) {
|
|
8781
|
+
this.log(`[session-host] \u4F1A\u8BDD ${sessionId} \u7ED3\u6784\u5316\u63D0\u95EE\u67E5\u8BE2\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
8782
|
+
return { action: "cancel" };
|
|
8783
|
+
}
|
|
8784
|
+
const managed = this.sessions.get(sessionId);
|
|
8785
|
+
if (managed)
|
|
8786
|
+
this.touch(managed);
|
|
8787
|
+
}
|
|
8788
|
+
this.log(`[session-host] \u4F1A\u8BDD ${sessionId} \u7ED3\u6784\u5316\u63D0\u95EE\u7B49\u5F85\u8D85\u65F6\uFF08${Math.round(timeoutMs / 1e3)}s\uFF0C${elicitationId}\uFF09`);
|
|
8789
|
+
return { action: "cancel" };
|
|
8790
|
+
}
|
|
8791
|
+
};
|
|
7257
8792
|
|
|
7258
8793
|
// src/daemon/stage-contracts.ts
|
|
7259
8794
|
var STAGE_CONTRACTS = {
|
|
@@ -7304,6 +8839,95 @@ function buildStageContract(stageCode, source) {
|
|
|
7304
8839
|
return STAGE_CONTRACTS[stageCode] ?? "";
|
|
7305
8840
|
}
|
|
7306
8841
|
|
|
8842
|
+
// src/daemon/task-messages.ts
|
|
8843
|
+
var TASK_MESSAGE_QUEUE_MAX = 200;
|
|
8844
|
+
function asRecord(value) {
|
|
8845
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
8846
|
+
}
|
|
8847
|
+
function asNonEmptyString(value) {
|
|
8848
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
8849
|
+
}
|
|
8850
|
+
function normalizeAgentMessage(value) {
|
|
8851
|
+
const row = asRecord(value);
|
|
8852
|
+
if (!row)
|
|
8853
|
+
return null;
|
|
8854
|
+
const id = asNonEmptyString(row.id);
|
|
8855
|
+
if (!id)
|
|
8856
|
+
return null;
|
|
8857
|
+
const seq = row.seq;
|
|
8858
|
+
if (typeof seq !== "number" || !Number.isFinite(seq))
|
|
8859
|
+
return null;
|
|
8860
|
+
return {
|
|
8861
|
+
id,
|
|
8862
|
+
seq,
|
|
8863
|
+
authorType: typeof row.authorType === "string" ? row.authorType : "unknown",
|
|
8864
|
+
authorPrincipalId: asNonEmptyString(row.authorPrincipalId),
|
|
8865
|
+
authorName: asNonEmptyString(row.authorName),
|
|
8866
|
+
content: typeof row.content === "string" ? row.content : "",
|
|
8867
|
+
mode: typeof row.mode === "string" ? row.mode : "queue",
|
|
8868
|
+
deliveryState: typeof row.deliveryState === "string" ? row.deliveryState : "pending",
|
|
8869
|
+
deliveryError: asNonEmptyString(row.deliveryError),
|
|
8870
|
+
targetTaskId: asNonEmptyString(row.targetTaskId),
|
|
8871
|
+
anchorEventSeq: typeof row.anchorEventSeq === "number" ? row.anchorEventSeq : null,
|
|
8872
|
+
turnNo: typeof row.turnNo === "number" ? row.turnNo : null,
|
|
8873
|
+
createdAt: typeof row.createdAt === "string" ? row.createdAt : ""
|
|
8874
|
+
};
|
|
8875
|
+
}
|
|
8876
|
+
function parseTaskMessageFrame(value) {
|
|
8877
|
+
const frame = asRecord(value);
|
|
8878
|
+
if (!frame)
|
|
8879
|
+
return null;
|
|
8880
|
+
const taskId = asNonEmptyString(frame.taskId);
|
|
8881
|
+
if (!taskId || !Array.isArray(frame.messages))
|
|
8882
|
+
return null;
|
|
8883
|
+
const messages = frame.messages.map(normalizeAgentMessage).filter((message) => message !== null);
|
|
8884
|
+
if (messages.length === 0)
|
|
8885
|
+
return null;
|
|
8886
|
+
return {
|
|
8887
|
+
type: "agent-task:message",
|
|
8888
|
+
taskId,
|
|
8889
|
+
objectId: asNonEmptyString(frame.objectId),
|
|
8890
|
+
anchorKey: typeof frame.anchorKey === "string" ? frame.anchorKey : "",
|
|
8891
|
+
messages
|
|
8892
|
+
};
|
|
8893
|
+
}
|
|
8894
|
+
function upsertTaskMessages(existing, incoming, max = TASK_MESSAGE_QUEUE_MAX) {
|
|
8895
|
+
const byId = /* @__PURE__ */ new Map();
|
|
8896
|
+
for (const message of existing)
|
|
8897
|
+
byId.set(message.id, message);
|
|
8898
|
+
for (const message of incoming)
|
|
8899
|
+
byId.set(message.id, message);
|
|
8900
|
+
const merged = [...byId.values()].sort((a, b) => a.seq - b.seq || a.id.localeCompare(b.id));
|
|
8901
|
+
const dropped = Math.max(0, merged.length - max);
|
|
8902
|
+
return { messages: dropped > 0 ? merged.slice(dropped) : merged, dropped };
|
|
8903
|
+
}
|
|
8904
|
+
function selectConsumedMessages(messages, taskId) {
|
|
8905
|
+
return messages.filter((message) => message.targetTaskId === taskId);
|
|
8906
|
+
}
|
|
8907
|
+
function selectSteerMessages(messages, taskId) {
|
|
8908
|
+
return messages.filter((message) => message.mode === "steer").filter((message) => message.deliveryState !== "consumed" && message.deliveryState !== "withdrawn").filter((message) => message.targetTaskId === taskId).sort((a, b) => a.seq - b.seq);
|
|
8909
|
+
}
|
|
8910
|
+
function renderMessageList(messages) {
|
|
8911
|
+
return messages.map((message) => `${message.seq}. \u3010${message.authorType === "user" ? "\u4EBA" : message.authorType === "system" ? "\u5E73\u53F0" : "\u8FD0\u884C\u65F6"}\u3011${message.content}`).join("\n\n");
|
|
8912
|
+
}
|
|
8913
|
+
function buildSteerPrompt(input) {
|
|
8914
|
+
const opinions = input.messages.length > 0 ? renderMessageList(input.messages) : "\uFF08\u6253\u65AD\u8BF7\u6C42\u672A\u88AB\u53D6\u5230\uFF0C\u8BF7\u6309\u539F\u59CB\u9700\u6C42\u7EE7\u7EED\uFF09";
|
|
8915
|
+
const resetNote = input.sessionReset ? "\n- \u26A0\uFE0F \u4F1A\u8BDD\u5DF2\u91CD\u5EFA\uFF1A\u4E0A\u4E00\u56DE\u5408\u7684\u5BF9\u8BDD\u4E0A\u4E0B\u6587**\u672A\u4FDD\u7559**\uFF0C\u8BF7\u4EE5\u672C\u56DE\u5408\u7ED9\u51FA\u7684\u4FE1\u606F\u4E3A\u51C6\uFF08\u5DE5\u4F5C\u533A\u6587\u4EF6\u4E0E git \u72B6\u6001\u4ECD\u5728\uFF09\u3002" : "";
|
|
8916
|
+
const originalPrompt = input.taskPrompt?.trim() ? `
|
|
8917
|
+
|
|
8918
|
+
\u3010\u539F\u59CB\u4EFB\u52A1\u9700\u6C42\uFF08\u672C\u8F6E\u662F\u540C\u4E00\u6B21\u5DE5\u4F5C\u7684\u5EF6\u7EED\uFF09\u3011
|
|
8919
|
+
${input.taskPrompt.trim()}` : "";
|
|
8920
|
+
return `\u3010\u8FD0\u884C\u4E2D\u6253\u65AD \xB7 \u8BF7\u5148\u6309\u8FD9\u4E9B\u610F\u89C1\u8C03\u6574\uFF0C\u518D\u7EE7\u7EED\u3011
|
|
8921
|
+
\u4F60\u6B63\u5728\u6267\u884C\u7B2C ${input.turnNo - 1} \u56DE\u5408\uFF0C\u4EBA\u5728\u8FD0\u884C\u8FC7\u7A0B\u4E2D\u63D0\u51FA\u4E86\u4EE5\u4E0B\u610F\u89C1\uFF08\u6309\u63D0\u51FA\u987A\u5E8F\uFF09\uFF1A
|
|
8922
|
+
|
|
8923
|
+
${opinions}
|
|
8924
|
+
|
|
8925
|
+
\u8981\u6C42\uFF1A
|
|
8926
|
+
1. \u5148\u9010\u6761\u5904\u7406\u4E0A\u8FF0\u610F\u89C1\uFF0C\u518D\u7EE7\u7EED\u5B8C\u6210\u672C\u4EFB\u52A1\uFF1B\u610F\u89C1\u4E0E\u539F\u59CB\u9700\u6C42\u51B2\u7A81\u65F6**\u4EE5\u610F\u89C1\u4E3A\u51C6**\uFF0C\u5E76\u5728\u6700\u7EC8\u603B\u7ED3\u91CC\u8BF4\u660E\u5DEE\u5F02\u3002
|
|
8927
|
+
2. \u4E0D\u8981\u63A8\u7FFB\u4E0A\u8FF0\u610F\u89C1\u672A\u6D89\u53CA\u7684\u90E8\u5206\uFF1B\u5DF2\u7ECF\u5B8C\u6210\u4E14\u4E0D\u5728\u610F\u89C1\u8303\u56F4\u5185\u7684\u6539\u52A8\u4FDD\u7559\u3002
|
|
8928
|
+
3. \u4E0A\u4E00\u56DE\u5408\u5728\u5B89\u5168\u70B9\u88AB\u4E2D\u65AD\uFF0C\u5176\u4E2D\u7684\u5DE5\u5177\u8FDB\u5EA6\u5DF2\u4E22\u5F03\u3001\u88AB\u4E2D\u65AD\u7684\u8C03\u7528**\u6CA1\u6709\u7ED3\u679C**\uFF08\u65F6\u95F4\u7EBF\u4E0A\u6807\u4E3A\u5DF2\u4E2D\u65AD\uFF09\u2014\u2014\u9700\u8981\u65F6\u91CD\u65B0\u6267\u884C\uFF0C\u4E0D\u8981\u5047\u5B9A\u5B83\u5DF2\u6210\u529F\u3002${resetNote}${originalPrompt}`;
|
|
8929
|
+
}
|
|
8930
|
+
|
|
7307
8931
|
// src/daemon/ws-client.ts
|
|
7308
8932
|
import { createHash as createHash2 } from "node:crypto";
|
|
7309
8933
|
import { io } from "socket.io-client";
|
|
@@ -7419,7 +9043,7 @@ var DaemonWsClient = class {
|
|
|
7419
9043
|
handlers.onTaskResubscribed?.(taskId);
|
|
7420
9044
|
});
|
|
7421
9045
|
socket.on("agent-task:dispatched", (payload) => {
|
|
7422
|
-
handlers.onTaskDispatched(payload.taskId, payload.runtimeId);
|
|
9046
|
+
handlers.onTaskDispatched(payload.taskId, payload.runtimeId, payload.daemonId);
|
|
7423
9047
|
});
|
|
7424
9048
|
socket.on("agent-task:status-changed", (payload) => {
|
|
7425
9049
|
if (payload?.status === "cancelled" && payload.taskId) {
|
|
@@ -7440,6 +9064,12 @@ var DaemonWsClient = class {
|
|
|
7440
9064
|
if (event)
|
|
7441
9065
|
handlers.onTaskMessage(event);
|
|
7442
9066
|
});
|
|
9067
|
+
for (const kind of ["open", "prompt", "cancel", "config", "close"]) {
|
|
9068
|
+
socket.on(`agent-session:${kind}`, (payload) => {
|
|
9069
|
+
if (payload && typeof payload === "object")
|
|
9070
|
+
handlers.onSessionCommand?.(kind, payload);
|
|
9071
|
+
});
|
|
9072
|
+
}
|
|
7443
9073
|
socket.on("connect_error", (error) => {
|
|
7444
9074
|
this.onConnectError(socket, error);
|
|
7445
9075
|
});
|
|
@@ -7547,6 +9177,7 @@ var OUTBOX_MAX_ATTEMPTS_PER_ROUND = 2;
|
|
|
7547
9177
|
var PROGRESS_BATCH_MAX = 500;
|
|
7548
9178
|
var OBSERVATION_WARN_INTERVAL_MS = 3e4;
|
|
7549
9179
|
var TERMINAL_OBSERVATION_DRAIN_BUDGET_MS = Number(process.env.MORULA_TERMINAL_DRAIN_BUDGET_MS ?? 2e3);
|
|
9180
|
+
var CANCEL_TRACE_TEXT_MAX = 4e3;
|
|
7550
9181
|
async function drainObservationTail(args) {
|
|
7551
9182
|
const startedAt = Date.now();
|
|
7552
9183
|
const progressDrained = await waitWithin(args.progress, args.budgetMs);
|
|
@@ -7568,7 +9199,7 @@ async function waitWithin(promise, ms) {
|
|
|
7568
9199
|
clearTimeout(timer);
|
|
7569
9200
|
return done;
|
|
7570
9201
|
}
|
|
7571
|
-
var OUTBOX_PERMANENT_CODES = ["NOT_FOUND", "CONFLICT"];
|
|
9202
|
+
var OUTBOX_PERMANENT_CODES = ["NOT_FOUND", "CONFLICT", "RUNTIME_MISMATCH"];
|
|
7572
9203
|
var DEFAULT_MODEL = "deepseek-v4-flash";
|
|
7573
9204
|
var DaemonRunner = class {
|
|
7574
9205
|
constructor(client, token, store, tenantId, options = {}) {
|
|
@@ -7590,6 +9221,12 @@ var DaemonRunner = class {
|
|
|
7590
9221
|
ws;
|
|
7591
9222
|
log;
|
|
7592
9223
|
runtimeId = null;
|
|
9224
|
+
/**
|
|
9225
|
+
* 本进程的**实例标识**(`daemonId`,config dir 内持久化):同机多实例注册时会复用同一行
|
|
9226
|
+
* `AgentRuntime`(同一 runtimeId),下行命令靠 `runtimeId` + `daemonId` 双重过滤才能定向到实例
|
|
9227
|
+
* (issues/duplicate-daemon-same-runtime-double-execution.md)。
|
|
9228
|
+
*/
|
|
9229
|
+
daemonId = null;
|
|
7593
9230
|
running = false;
|
|
7594
9231
|
/** D1:心跳定时器(独立于 tick)与单飞标志——上一拍未返回则跳过本拍,避免慢请求堆积 */
|
|
7595
9232
|
heartbeatTimer = null;
|
|
@@ -7622,6 +9259,12 @@ var DaemonRunner = class {
|
|
|
7622
9259
|
isolatedDimEnv = {};
|
|
7623
9260
|
/** ACP 连接池(ACP-only:daemon 启动即建,懒 spawn,首个任务才起 dim acp 进程) */
|
|
7624
9261
|
acpPool = null;
|
|
9262
|
+
/**
|
|
9263
|
+
* 会话宿主(steward-core-acp-migration §4.4,P0 骨架):长驻会话(管家对话)的执行载体,
|
|
9264
|
+
* 每会话独立 dim acp 进程(不走连接池)。P0 只接骨架(WS 命令转发 + 机器协议上报),
|
|
9265
|
+
* 服务端发射端(agent-session:*)在 P1-5 接入。
|
|
9266
|
+
*/
|
|
9267
|
+
sessionHost = null;
|
|
7625
9268
|
/** LOCAL-AGENT-REPO-READ-001:本地 repo checkout 端点(agent 本轮内读仓库) */
|
|
7626
9269
|
repoCheckout = null;
|
|
7627
9270
|
async start() {
|
|
@@ -7657,6 +9300,15 @@ var DaemonRunner = class {
|
|
|
7657
9300
|
env: this.isolatedDimEnv
|
|
7658
9301
|
});
|
|
7659
9302
|
this.log("[daemon] dim executor: acp-v1\uFF08ACP-only\uFF0C\u957F\u9A7B dim acp \u8FDB\u7A0B\uFF09");
|
|
9303
|
+
this.sessionHost = new SessionHost({
|
|
9304
|
+
api: this.api,
|
|
9305
|
+
workdir: cfg2?.workdir ?? process.cwd(),
|
|
9306
|
+
dimPath: this.dimPath,
|
|
9307
|
+
isolatedDimEnv: this.isolatedDimEnv,
|
|
9308
|
+
baseUrl: this.client.baseUrl,
|
|
9309
|
+
log: this.log
|
|
9310
|
+
});
|
|
9311
|
+
this.log(`[daemon] session host: ready\uFF08\u7A7A\u95F2\u56DE\u6536 ${Math.round(resolveSessionIdleMs() / 6e4)}min\uFF0CMORULA_SESSION_IDLE_MS \u8986\u76D6\uFF09`);
|
|
7660
9312
|
this.repoCheckout = new LocalRepoCheckoutService({
|
|
7661
9313
|
api: this.api,
|
|
7662
9314
|
cfg: cfg2 ?? loadDaemonConfig(this.store),
|
|
@@ -7675,7 +9327,7 @@ var DaemonRunner = class {
|
|
|
7675
9327
|
this.log(`[daemon] dim CLI: ${this.dimPath}`);
|
|
7676
9328
|
let binaryMtime = "unknown";
|
|
7677
9329
|
try {
|
|
7678
|
-
binaryMtime =
|
|
9330
|
+
binaryMtime = statSync4(process.execPath).mtime.toISOString();
|
|
7679
9331
|
} catch (_) {
|
|
7680
9332
|
}
|
|
7681
9333
|
this.log(`[daemon] binary=${process.execPath} mtime=${binaryMtime} v=${BUILD_VERSION2}`);
|
|
@@ -7712,6 +9364,7 @@ var DaemonRunner = class {
|
|
|
7712
9364
|
providers: inventory
|
|
7713
9365
|
});
|
|
7714
9366
|
this.runtimeId = registered.id;
|
|
9367
|
+
this.daemonId = cfg.daemonId;
|
|
7715
9368
|
if (cfg.runtimeId !== registered.id) {
|
|
7716
9369
|
saveDaemonConfig(this.store, { runtimeId: registered.id });
|
|
7717
9370
|
}
|
|
@@ -7741,6 +9394,11 @@ var DaemonRunner = class {
|
|
|
7741
9394
|
});
|
|
7742
9395
|
this.acpPool = null;
|
|
7743
9396
|
}
|
|
9397
|
+
if (this.sessionHost) {
|
|
9398
|
+
void this.sessionHost.closeAll().catch(() => {
|
|
9399
|
+
});
|
|
9400
|
+
this.sessionHost = null;
|
|
9401
|
+
}
|
|
7744
9402
|
this.repoCheckout?.close();
|
|
7745
9403
|
this.repoCheckout = null;
|
|
7746
9404
|
}
|
|
@@ -7857,6 +9515,41 @@ var DaemonRunner = class {
|
|
|
7857
9515
|
this.log(`[daemon] cleanup poll \u5F02\u5E38: ${error instanceof Error ? error.message : String(error)}`);
|
|
7858
9516
|
}
|
|
7859
9517
|
}
|
|
9518
|
+
/**
|
|
9519
|
+
* 取消留痕(issues/daemon-stream-events-silently-stop-mid-run.md 未实施项 5;用户 2026-09-21 定案「取消也留痕」)。
|
|
9520
|
+
*
|
|
9521
|
+
* 取消路径原本直接 `return`:这一轮**已产出**的内容(dim 已写完的回答、中断前的部分回答)在平台侧
|
|
9522
|
+
* 零留存(唯一痕迹是 `agent_failed{status:cancelled}`)→「取消 = 这轮白跑且无法追溯」。
|
|
9523
|
+
*
|
|
9524
|
+
* 契约(定案):**不得**把任务状态改回 completed —— 服务端只把 `result.text` 合并进
|
|
9525
|
+
* `agent_tasks.result.cancelled.lastOutput`(既有列,与取消时的 who/when/step 同一条记录)。
|
|
9526
|
+
*
|
|
9527
|
+
* best-effort 边界:**不走 outbox、不重试、失败只 log** —— 留痕不得让取消流程变慢或失败;
|
|
9528
|
+
* 无已产出内容时不发请求(不制造空留痕噪声)。旧服务端不认识 `cancelled` 字段(whitelist 剥离)
|
|
9529
|
+
* → 行为退化为原样,不会报错。
|
|
9530
|
+
*/
|
|
9531
|
+
async reportCancelTrace(taskId, text, reason) {
|
|
9532
|
+
const produced = (text ?? "").trim();
|
|
9533
|
+
if (!produced) {
|
|
9534
|
+
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u88AB\u53D6\u6D88\uFF0C\u672C\u8F6E\u65E0\u5DF2\u4EA7\u51FA\u5185\u5BB9\uFF0C\u4E0D\u5199\u53D6\u6D88\u7559\u75D5\uFF08${reason}\uFF09`);
|
|
9535
|
+
return;
|
|
9536
|
+
}
|
|
9537
|
+
try {
|
|
9538
|
+
const res = await this.api.submitResult(taskId, {
|
|
9539
|
+
runtimeId: this.runtimeId ?? "",
|
|
9540
|
+
ok: true,
|
|
9541
|
+
cancelled: true,
|
|
9542
|
+
// 与服务端 `result.text` 的 4000 上限同口径:够追溯「这轮产出了什么」
|
|
9543
|
+
result: { text: produced.slice(0, CANCEL_TRACE_TEXT_MAX) }
|
|
9544
|
+
});
|
|
9545
|
+
if (res?.ok)
|
|
9546
|
+
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u88AB\u53D6\u6D88\uFF1A\u5DF2\u8865\u8BB0\u672C\u8F6E\u4EA7\u51FA ${produced.length} \u5B57\uFF08\u670D\u52A1\u7AEF\u53EA\u7559\u75D5\uFF0C\u4E0D\u6539\u4EFB\u52A1\u72B6\u6001\uFF09`);
|
|
9547
|
+
else
|
|
9548
|
+
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u53D6\u6D88\u7559\u75D5\u672A\u843D\u5E93\uFF08${res?.code ?? "unknown"}\uFF09\u2014\u2014 \u4E0D\u5F71\u54CD\u53D6\u6D88\u672C\u8EAB`);
|
|
9549
|
+
} catch (error) {
|
|
9550
|
+
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u53D6\u6D88\u7559\u75D5\u4E0A\u62A5\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`);
|
|
9551
|
+
}
|
|
9552
|
+
}
|
|
7860
9553
|
/**
|
|
7861
9554
|
* M1-A2:终态结果可靠回传——先写盘 → 单次尝试 → 成功删盘(顺序语义不变)。
|
|
7862
9555
|
*
|
|
@@ -7895,7 +9588,7 @@ var DaemonRunner = class {
|
|
|
7895
9588
|
let res = null;
|
|
7896
9589
|
let transportError = null;
|
|
7897
9590
|
try {
|
|
7898
|
-
res = await this.api.submitResult(taskId, body);
|
|
9591
|
+
res = await this.api.submitResult(taskId, { ...body, runtimeId: this.runtimeId ?? "" });
|
|
7899
9592
|
} catch (err) {
|
|
7900
9593
|
transportError = err?.message ?? String(err);
|
|
7901
9594
|
}
|
|
@@ -8046,8 +9739,8 @@ var DaemonRunner = class {
|
|
|
8046
9739
|
*/
|
|
8047
9740
|
buildWsHandlers() {
|
|
8048
9741
|
return {
|
|
8049
|
-
onTaskDispatched: (taskId, runtimeId) => {
|
|
8050
|
-
this.onTaskDispatched(taskId, runtimeId);
|
|
9742
|
+
onTaskDispatched: (taskId, runtimeId, daemonId) => {
|
|
9743
|
+
this.onTaskDispatched(taskId, runtimeId, daemonId);
|
|
8051
9744
|
},
|
|
8052
9745
|
// M1-A1:取消信号 → 中断执行中的 dim 进程
|
|
8053
9746
|
onTaskCancelled: (taskId) => {
|
|
@@ -8077,9 +9770,74 @@ var DaemonRunner = class {
|
|
|
8077
9770
|
// P1:重连后房间已重放 → 立即按 afterSeq 重新补齐(断连期间的帧不会补发,只能拉)
|
|
8078
9771
|
onTaskResubscribed: (taskId) => {
|
|
8079
9772
|
void this.catchUpTaskMessages(taskId);
|
|
9773
|
+
},
|
|
9774
|
+
// 会话宿主命令(steward-core-acp-migration §4.4,P1-5):转发给 SessionHost。
|
|
9775
|
+
// 服务端发到 **tenant 房间**(同租户每台 daemon 都收到,WS 无 per-runtime 房间),
|
|
9776
|
+
// 故与 `agent-task:dispatched` 同口径按 `runtimeId` + `daemonId` 自过滤。
|
|
9777
|
+
onSessionCommand: (kind, payload) => {
|
|
9778
|
+
void this.handleSessionCommand(kind, payload);
|
|
8080
9779
|
}
|
|
8081
9780
|
};
|
|
8082
9781
|
}
|
|
9782
|
+
/**
|
|
9783
|
+
* 会话宿主命令分发。
|
|
9784
|
+
*
|
|
9785
|
+
* 两道闸:
|
|
9786
|
+
* ① **runtimeId + daemonId 自过滤**(tenant 广播 → 只有归属设备处理;字段缺失时不过滤,兼容旧服务端)。
|
|
9787
|
+
* daemonId 是**实例**维度(issues/duplicate-daemon-same-runtime-double-execution.md):同机多实例
|
|
9788
|
+
* 注册时会复用同一行 AgentRuntime(同一 runtimeId),缺了它两个实例会各自 `open` 同一 sessionId
|
|
9789
|
+
* 并各跑一轮 prompt(实测:同一轮两份正文事件、两个 daemon 都「会话已打开」);
|
|
9790
|
+
* ② **按会话串行**:服务端每轮 prompt 前会补发 `open` 做兜底,而 socket.io 只保证「送达有序」,
|
|
9791
|
+
* 不保证「异步处理有序」——`void handleSessionCommand()` 会让 open/prompt 交错执行,
|
|
9792
|
+
* prompt 可能先于 open 完成而拿到 SESSION_NOT_OPEN。故按 sessionId 串成一条 promise 链。
|
|
9793
|
+
*/
|
|
9794
|
+
sessionCommandChains = /* @__PURE__ */ new Map();
|
|
9795
|
+
handleSessionCommand(kind, payload) {
|
|
9796
|
+
const runtimeId = typeof payload.runtimeId === "string" ? payload.runtimeId : void 0;
|
|
9797
|
+
if (runtimeId && this.runtimeId && runtimeId !== this.runtimeId)
|
|
9798
|
+
return Promise.resolve();
|
|
9799
|
+
const daemonId = typeof payload.daemonId === "string" ? payload.daemonId : void 0;
|
|
9800
|
+
if (daemonId && this.daemonId && daemonId !== this.daemonId)
|
|
9801
|
+
return Promise.resolve();
|
|
9802
|
+
const sessionId = typeof payload.sessionId === "string" ? payload.sessionId : "";
|
|
9803
|
+
if (!sessionId) {
|
|
9804
|
+
this.log(`[daemon] \u4F1A\u8BDD\u547D\u4EE4 ${kind} \u7F3A\u5C11 sessionId\uFF08\u5FFD\u7565\uFF09`);
|
|
9805
|
+
return Promise.resolve();
|
|
9806
|
+
}
|
|
9807
|
+
const prev = this.sessionCommandChains.get(sessionId) ?? Promise.resolve();
|
|
9808
|
+
const next = prev.catch(() => {
|
|
9809
|
+
}).then(() => this.runSessionCommand(kind, payload)).catch((err) => {
|
|
9810
|
+
this.log(`[daemon] \u4F1A\u8BDD\u547D\u4EE4 ${kind} \u6267\u884C\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
9811
|
+
});
|
|
9812
|
+
this.sessionCommandChains.set(sessionId, next);
|
|
9813
|
+
void next.finally(() => {
|
|
9814
|
+
if (this.sessionCommandChains.get(sessionId) === next)
|
|
9815
|
+
this.sessionCommandChains.delete(sessionId);
|
|
9816
|
+
});
|
|
9817
|
+
return next;
|
|
9818
|
+
}
|
|
9819
|
+
/** 单条会话命令执行(畸形命令记日志不抛——单条坏命令不该打断 daemon 主循环) */
|
|
9820
|
+
async runSessionCommand(kind, payload) {
|
|
9821
|
+
const host = this.sessionHost;
|
|
9822
|
+
if (!host) {
|
|
9823
|
+
this.log(`[daemon] \u6536\u5230\u4F1A\u8BDD\u547D\u4EE4 ${kind}\uFF0C\u4F46\u4F1A\u8BDD\u5BBF\u4E3B\u672A\u5C31\u7EEA\uFF08\u5FFD\u7565\uFF09`);
|
|
9824
|
+
return;
|
|
9825
|
+
}
|
|
9826
|
+
try {
|
|
9827
|
+
if (kind === "open")
|
|
9828
|
+
await host.open(payload);
|
|
9829
|
+
else if (kind === "prompt")
|
|
9830
|
+
await host.prompt(payload);
|
|
9831
|
+
else if (kind === "cancel")
|
|
9832
|
+
host.cancel(payload);
|
|
9833
|
+
else if (kind === "config")
|
|
9834
|
+
await host.config(payload);
|
|
9835
|
+
else if (kind === "close")
|
|
9836
|
+
await host.close(payload);
|
|
9837
|
+
} catch (err) {
|
|
9838
|
+
this.log(`[daemon] \u4F1A\u8BDD\u547D\u4EE4 ${kind} \u6267\u884C\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
9839
|
+
}
|
|
9840
|
+
}
|
|
8083
9841
|
/**
|
|
8084
9842
|
* 派发事件入口(P0-b,issues/agent-task-false-failure-on-daemon-fetch-error.md):
|
|
8085
9843
|
* 服务端把派发事件发到 **tenant 房间**(daemon-executor.service.ts `sendToTenant`,WS 无 per-runtime
|
|
@@ -8088,10 +9846,17 @@ var DaemonRunner = class {
|
|
|
8088
9846
|
* 不校验任务归属,见同 issue 根因 ③ / ④)。
|
|
8089
9847
|
*
|
|
8090
9848
|
* `runtimeId` 缺失(旧服务端)→ 保持旧行为不过滤;poll 兜底本就按 runtimeId 查询,不受影响。
|
|
9849
|
+
*
|
|
9850
|
+
* `daemonId`(issues/duplicate-daemon-same-runtime-double-execution.md):同机多实例注册时会复用
|
|
9851
|
+
* 同一行 `AgentRuntime`(同一 runtimeId,服务端按设备收敛重复行)→ 单靠 runtimeId 两个实例都会处理。
|
|
9852
|
+
* 故服务端下行带**实例标识**,两个字段都匹配才处理;`daemonId` 缺失(旧服务端)→ 退回只按 runtimeId
|
|
9853
|
+
* 过滤,否则新 daemon 接旧服务端会全丢事件。
|
|
8091
9854
|
*/
|
|
8092
|
-
onTaskDispatched(taskId, runtimeId) {
|
|
9855
|
+
onTaskDispatched(taskId, runtimeId, daemonId) {
|
|
8093
9856
|
if (runtimeId && this.runtimeId && runtimeId !== this.runtimeId)
|
|
8094
9857
|
return;
|
|
9858
|
+
if (daemonId && this.daemonId && daemonId !== this.daemonId)
|
|
9859
|
+
return;
|
|
8095
9860
|
void this.handleTask(taskId);
|
|
8096
9861
|
}
|
|
8097
9862
|
/** 任务处理(幂等:已在处理中则跳过) */
|
|
@@ -8306,7 +10071,14 @@ var DaemonRunner = class {
|
|
|
8306
10071
|
// 本任务的 AgentTask ID:`morula repo mr create`(服务端建 MR 通道)等机器协议据此定位任务;
|
|
8307
10072
|
// agent 不需要也不应自己拼(issues/mr-created-outside-bound-project.md)
|
|
8308
10073
|
...taskId ? { MORULA_TASK_ID: taskId } : {},
|
|
8309
|
-
...this.client?.baseUrl ? { MORULA_API_URL: this.client.baseUrl } : {}
|
|
10074
|
+
...this.client?.baseUrl ? { MORULA_API_URL: this.client.baseUrl } : {},
|
|
10075
|
+
// 插件 bin 前置 + 显式 CLI 路径(与会话链路同源):任务里的 agent 也必须用本包物化的 `morula`,
|
|
10076
|
+
// 而不是宿主/桌面应用自带的旧版(旧版不认任务/会话凭证,且缺部分命令旗标 → 契约指令直接失败)。
|
|
10077
|
+
// PATH 只是第一道保险(dim 会重写 agent shell 的 PATH),可靠的是 `MORULA_CLI` 绝对路径。
|
|
10078
|
+
// MORULA_CLI 优先本包自带入口(与当前 daemon 同源),产物缺失才回退插件启动器。
|
|
10079
|
+
// 见 issues/steward-cli-resolves-stale-bundled-morula.md、issues/agent-cli-stale-build-missing-steward-commands.md。
|
|
10080
|
+
PATH: prependPluginBinToPath(process.env.PATH),
|
|
10081
|
+
MORULA_CLI: resolveAgentCliPath()
|
|
8310
10082
|
};
|
|
8311
10083
|
let repoDir = cfg.workdir;
|
|
8312
10084
|
let taskRoot = cfg.workdir;
|
|
@@ -8360,12 +10132,12 @@ var DaemonRunner = class {
|
|
|
8360
10132
|
});
|
|
8361
10133
|
if (spaceToken?.token)
|
|
8362
10134
|
env = { ...env, ...buildRepoEnv(spaceToken.token, spaceToken.baseUrl) };
|
|
8363
|
-
repoDir =
|
|
10135
|
+
repoDir = join20(cfg.workdir, "scratch", taskId);
|
|
8364
10136
|
taskRoot = repoDir;
|
|
8365
|
-
|
|
8366
|
-
const readme =
|
|
10137
|
+
mkdirSync13(repoDir, { recursive: true });
|
|
10138
|
+
const readme = join20(repoDir, "README.md");
|
|
8367
10139
|
if (!existsSync15(readme)) {
|
|
8368
|
-
|
|
10140
|
+
writeFileSync11(
|
|
8369
10141
|
readme,
|
|
8370
10142
|
`# \u9694\u79BB\u5DE5\u4F5C\u533A\uFF08\u4EFB\u52A1 ${taskId}\uFF09
|
|
8371
10143
|
|
|
@@ -8373,7 +10145,8 @@ var DaemonRunner = class {
|
|
|
8373
10145
|
\u7981\u6B62\u63A2\u7D22/\u4FEE\u6539\u672C\u76EE\u5F55\u4E4B\u5916\u7684\u4EFB\u4F55\u8DEF\u5F84\u2014\u2014\u5C24\u5176\u662F daemon \u6839\u76EE\u5F55\u4E0B\u7684 repos/\u3001worktrees/\u3001scratch/ \u7B49\u3002
|
|
8374
10146
|
|
|
8375
10147
|
## \u8BFB\u53D6\u4ED3\u5E93\u5185\u5BB9\uFF08\u91CD\u8981\uFF09
|
|
8376
|
-
\u82E5\
|
|
10148
|
+
\u82E5\u4EFB\u52A1\u9700\u8981\u8BFB/\u6539\u67D0\u4E2A\u4ED3\u5E93\uFF08\u4EFB\u52A1\u63CF\u8FF0\u91CC\u63D0\u5230\u3001\u6216\u4F60\u88AB @ \u4E86\u67D0\u4E2A\u4ED3\u5E93\u5982 \`human-resources/xxx\`\uFF09\uFF0C\u672C\u76EE\u5F55\u91CC\u6CA1\u6709\u8BE5\u4ED3\u5E93\u662F\u6B63\u5E38\u7684\u2014\u2014\u7A7A\u95F4\u7ED1\u5B9A\u4E86\u54EA\u4E9B\u4ED3\u5E93\u7528 \`morula repo list --space <spaceId>\` \u67E5\uFF08spaceId \u89C1\u7CFB\u7EDF\u63D0\u793A\u300C\u57FA\u672C\u4FE1\u606F\u300D\u6BB5\uFF09\u3002
|
|
10149
|
+
\u4ED3\u5E93\u7531 \`morula repo checkout <\u672C\u4EFB\u52A1ID> <repoFullName>\` \u7269\u5316\uFF08\u672C\u5730 daemon \u7AEF\u70B9\uFF0C\u672C\u8F6E\u7ACB\u5373\u7269\u5316\u5E76\u8FD4\u56DE\u8DEF\u5F84\uFF09\uFF0C\u7269\u5316\u540E\u53EA\u8BFB\u67E5\u8BC1\uFF08git log / \u8BFB\u6587\u4EF6\uFF09\uFF0C\u4E0D\u5EFA\u5206\u652F\u3001\u4E0D\u6539\u4EE3\u7801\u3002
|
|
8377
10150
|
\u82E5\u4EFB\u52A1\u9700\u8981**\u6539\u52A8**\u4ED3\u5E93\uFF08\u591A\u4ED3\u5E93\u4EFB\u52A1\u7531\u4F60\u5224\u65AD\u6D89\u53CA\u54EA\u4E9B\u4ED3\u5E93\uFF09\uFF0C\u7528 \`morula repo prepare <\u672C\u4EFB\u52A1ID> <repoFullName>\` \u7269\u5316\u5230\u672C\u76EE\u5F55\uFF08\u8FD4\u56DE\u8DEF\u5F84\uFF09\uFF0C\u4ED3\u5E93\u4F1A\u4F5C\u4E3A\u672C\u76EE\u5F55\u4E0B\u7684\u5B50\u6587\u4EF6\u5939\u51FA\u73B0\uFF08\u5982 \`<owner>-<repo>/\`\uFF09\uFF0Cgit \u64CD\u4F5C\u5728\u8BE5\u5B50\u6587\u4EF6\u5939\u5185\u8FDB\u884C\u3002
|
|
8378
10151
|
\u4E0D\u8981\u5728\u672C\u5730\u641C\u7D22\u4ED3\u5E93\u662F\u5426\u5B58\u5728\u2014\u2014\u672C\u5730\u6CA1\u6709 \u2260 \u4ED3\u5E93\u4E0D\u5B58\u5728\uFF0C\u5148\u6267\u884C checkout/prepare\u3002\u8BE6\u89C1\u534F\u4F5C\u5951\u7EA6 0.5 \u8282\u3002
|
|
8379
10152
|
`,
|
|
@@ -8466,6 +10239,8 @@ var DaemonRunner = class {
|
|
|
8466
10239
|
dimWorkDir: taskRoot
|
|
8467
10240
|
});
|
|
8468
10241
|
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u5B8C\u6210`);
|
|
10242
|
+
if (signal?.aborted)
|
|
10243
|
+
await this.reportCancelTrace(taskId, result.finalText, "\u6536\u5C3E\u9636\u6BB5\u88AB\u53D6\u6D88");
|
|
8469
10244
|
const isReviewTask = detail.kind === "review";
|
|
8470
10245
|
const isAnalysisTask = detail.stageCode === "todo";
|
|
8471
10246
|
if (!isReviewTask && !isAnalysisTask && isFlagEnabled("MORULA_LOCAL_AGENT_DELIVERY_V1", true))
|
|
@@ -8474,6 +10249,7 @@ var DaemonRunner = class {
|
|
|
8474
10249
|
const error = result.error?.message ?? "dim \u6267\u884C\u5931\u8D25";
|
|
8475
10250
|
if (signal?.aborted) {
|
|
8476
10251
|
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u5DF2\u53D6\u6D88\uFF0C\u8DF3\u8FC7\u7ED3\u679C\u56DE\u4F20: ${error}`);
|
|
10252
|
+
await this.reportCancelTrace(taskId, result.finalText, error);
|
|
8477
10253
|
return;
|
|
8478
10254
|
}
|
|
8479
10255
|
await this.submitResultReliable(taskId, { ok: false, error });
|
|
@@ -8549,8 +10325,6 @@ var DaemonRunner = class {
|
|
|
8549
10325
|
*/
|
|
8550
10326
|
async runOnce(taskId, detail, taskRoot, provider, providerKey, requestedModelId, env, repoBaseUrl, signal) {
|
|
8551
10327
|
const streamChunks = [];
|
|
8552
|
-
let textBuf = "";
|
|
8553
|
-
let textTimer = null;
|
|
8554
10328
|
let thoughtBuf = "";
|
|
8555
10329
|
let thoughtTimer = null;
|
|
8556
10330
|
let streamFlushing = false;
|
|
@@ -8649,6 +10423,16 @@ var DaemonRunner = class {
|
|
|
8649
10423
|
void flushStreamChunks(false);
|
|
8650
10424
|
}
|
|
8651
10425
|
};
|
|
10426
|
+
const textStream = new AssistantTextStream({
|
|
10427
|
+
onDelta: (seg, text) => {
|
|
10428
|
+
streamChunks.push({ chunkType: "delta", data: { kind: "text", seg, text } });
|
|
10429
|
+
void flushStreamChunks(true);
|
|
10430
|
+
},
|
|
10431
|
+
onParagraph: (seg, text) => {
|
|
10432
|
+
streamChunks.push({ chunkType: "text", seqHint: nextChunkSeq(), data: { text, seg } });
|
|
10433
|
+
void flushStreamChunks(true);
|
|
10434
|
+
}
|
|
10435
|
+
}, { log: (line) => this.log(`[daemon] ${line}`) });
|
|
8652
10436
|
const resumeSessionId = providerKey === "dim" ? detail.dimSessionId ?? detail.lastRun?.dimSessionId ?? null : null;
|
|
8653
10437
|
const resumeWorkDir = detail.dimWorkDir ?? detail.lastRun?.dimWorkDir ?? null;
|
|
8654
10438
|
const canResume = !!resumeSessionId && !!resumeWorkDir && existsSync15(resumeWorkDir);
|
|
@@ -8695,16 +10479,19 @@ var DaemonRunner = class {
|
|
|
8695
10479
|
}
|
|
8696
10480
|
}
|
|
8697
10481
|
if (event.type === "tool_call_started") {
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
10482
|
+
void textStream.flushParagraph().then(() => {
|
|
10483
|
+
streamChunks.push({
|
|
10484
|
+
chunkType: "tool",
|
|
10485
|
+
seqHint: nextChunkSeq(),
|
|
10486
|
+
data: {
|
|
10487
|
+
toolName: event.toolName,
|
|
10488
|
+
callId: event.callId,
|
|
10489
|
+
input: event.input !== void 0 ? redactInputSummary(event.input) : void 0
|
|
10490
|
+
}
|
|
10491
|
+
});
|
|
10492
|
+
void flushStreamChunks(true);
|
|
10493
|
+
}).catch(() => {
|
|
8706
10494
|
});
|
|
8707
|
-
void flushStreamChunks(true);
|
|
8708
10495
|
}
|
|
8709
10496
|
if (event.type === "tool_call_completed") {
|
|
8710
10497
|
streamChunks.push({
|
|
@@ -8720,17 +10507,7 @@ var DaemonRunner = class {
|
|
|
8720
10507
|
void flushStreamChunks(true);
|
|
8721
10508
|
}
|
|
8722
10509
|
if (event.type === "assistant_text_delta") {
|
|
8723
|
-
|
|
8724
|
-
if (!textTimer) {
|
|
8725
|
-
textTimer = setTimeout(() => {
|
|
8726
|
-
textTimer = null;
|
|
8727
|
-
if (textBuf) {
|
|
8728
|
-
streamChunks.push({ chunkType: "text", seqHint: nextChunkSeq(), data: { text: textBuf } });
|
|
8729
|
-
textBuf = "";
|
|
8730
|
-
void flushStreamChunks(false);
|
|
8731
|
-
}
|
|
8732
|
-
}, 200);
|
|
8733
|
-
}
|
|
10510
|
+
textStream.push(event.text);
|
|
8734
10511
|
}
|
|
8735
10512
|
if (event.type === "usage") {
|
|
8736
10513
|
streamChunks.push({ chunkType: "usage", seqHint: nextChunkSeq(), data: event.usage });
|
|
@@ -8748,14 +10525,8 @@ var DaemonRunner = class {
|
|
|
8748
10525
|
}
|
|
8749
10526
|
};
|
|
8750
10527
|
const result = await provider.run(input);
|
|
8751
|
-
|
|
8752
|
-
|
|
8753
|
-
textTimer = null;
|
|
8754
|
-
}
|
|
8755
|
-
if (textBuf) {
|
|
8756
|
-
streamChunks.push({ chunkType: "text", seqHint: nextChunkSeq(), data: { text: textBuf } });
|
|
8757
|
-
textBuf = "";
|
|
8758
|
-
}
|
|
10528
|
+
await textStream.flushParagraph();
|
|
10529
|
+
textStream.dispose();
|
|
8759
10530
|
if (thoughtTimer) {
|
|
8760
10531
|
clearTimeout(thoughtTimer);
|
|
8761
10532
|
thoughtTimer = null;
|
|
@@ -8925,7 +10696,7 @@ var DaemonRunner = class {
|
|
|
8925
10696
|
await this.api.submitCleanupResult(item.id, this.runtimeId, { ok: true });
|
|
8926
10697
|
return;
|
|
8927
10698
|
}
|
|
8928
|
-
const managedRoot =
|
|
10699
|
+
const managedRoot = join20(cfg.workdir, "worktrees", this.tenantId);
|
|
8929
10700
|
if (!wc.worktreePath.startsWith(`${managedRoot}/`)) {
|
|
8930
10701
|
await this.api.submitCleanupResult(item.id, this.runtimeId, {
|
|
8931
10702
|
ok: false,
|
|
@@ -8944,7 +10715,7 @@ var DaemonRunner = class {
|
|
|
8944
10715
|
repoFullName: wc.repoFullName,
|
|
8945
10716
|
branchName: wc.branchName
|
|
8946
10717
|
};
|
|
8947
|
-
const repoCachePath = buildRepoCachePath(cfg.workdir, this.tenantId, wc.repoFullName);
|
|
10718
|
+
const repoCachePath = buildRepoCachePath(cfg.workdir, this.tenantId, this.runtimeId, wc.repoFullName);
|
|
8948
10719
|
const errorCode = validateRemovable(wc.worktreePath, expected, repoCachePath);
|
|
8949
10720
|
if (errorCode) {
|
|
8950
10721
|
this.log(`[daemon] cleanup ${item.id} \u62D2\u7EDD\u5220\u9664: ${errorCode}`);
|
|
@@ -8973,38 +10744,38 @@ function isDebugLogEnabled() {
|
|
|
8973
10744
|
var spaceTokenFailureLogged = false;
|
|
8974
10745
|
function writeCustomSkills(targetDir, skills, log) {
|
|
8975
10746
|
const NAME_RE = /^[\w-]{1,64}$/;
|
|
8976
|
-
const base =
|
|
10747
|
+
const base = join20(targetDir, ".agents", "skills");
|
|
8977
10748
|
for (const skill of skills) {
|
|
8978
10749
|
const name = skill.name.trim();
|
|
8979
10750
|
if (!NAME_RE.test(name)) {
|
|
8980
10751
|
log?.(`[daemon] \u8DF3\u8FC7\u975E\u6CD5 skill \u540D\u79F0: ${JSON.stringify(name)}`);
|
|
8981
10752
|
continue;
|
|
8982
10753
|
}
|
|
8983
|
-
const dir =
|
|
8984
|
-
if (
|
|
10754
|
+
const dir = join20(base, name);
|
|
10755
|
+
if (dirname8(dir) !== base) {
|
|
8985
10756
|
log?.(`[daemon] \u8DF3\u8FC7\u8D8A\u754C skill \u8DEF\u5F84: ${JSON.stringify(name)}`);
|
|
8986
10757
|
continue;
|
|
8987
10758
|
}
|
|
8988
|
-
|
|
10759
|
+
mkdirSync13(dir, { recursive: true });
|
|
8989
10760
|
const meta = ["---", `name: ${name}`];
|
|
8990
10761
|
if (skill.description?.trim()) {
|
|
8991
10762
|
meta.push(`description: ${skill.description.trim().replace(/\n/g, " ")}`);
|
|
8992
10763
|
}
|
|
8993
10764
|
meta.push("---", "");
|
|
8994
|
-
|
|
10765
|
+
writeFileSync11(join20(dir, "SKILL.md"), `${meta.join("\n")}
|
|
8995
10766
|
${skill.content ?? ""}`, "utf8");
|
|
8996
10767
|
}
|
|
8997
10768
|
}
|
|
8998
10769
|
function writeCollaborationSkill(targetDir, repo, repoBaseUrl, log, baseBranch, provider = repoBaseUrl ? "gitlab" : "github") {
|
|
8999
|
-
const dir =
|
|
9000
|
-
const refsDir =
|
|
9001
|
-
|
|
10770
|
+
const dir = join20(targetDir, ".agents", "skills", COLLABORATION_SKILL_NAME);
|
|
10771
|
+
const refsDir = join20(dir, "references");
|
|
10772
|
+
mkdirSync13(refsDir, { recursive: true });
|
|
9002
10773
|
const isGitlab = provider === "gitlab" && !!repoBaseUrl;
|
|
9003
10774
|
const front = isGitlab ? buildGitlabSkillFrontmatter() : COLLABORATION_SKILL_FRONTMATTER;
|
|
9004
10775
|
const body = isGitlab ? buildGitlabSkillBody(repo ?? "", repoBaseUrl, baseBranch) : buildGithubSkillBody(baseBranch);
|
|
9005
|
-
|
|
10776
|
+
writeFileSync11(join20(dir, "SKILL.md"), `${front}
|
|
9006
10777
|
${body}`, "utf8");
|
|
9007
|
-
|
|
10778
|
+
writeFileSync11(join20(refsDir, "working-on-issues-source-map.md"), COLLABORATION_SKILL_SOURCE_MAP, "utf8");
|
|
9008
10779
|
log?.(`[daemon] \u5DF2\u6CE8\u5165\u534F\u4F5C\u5951\u7EA6 skill ${COLLABORATION_SKILL_NAME}\uFF08${isGitlab ? "GitLab" : "GitHub"} \u7248\uFF0C\u4ED3\u5E93=${repo ?? "\u65E0"}\uFF09\u5230 .agents/skills/`);
|
|
9009
10780
|
}
|
|
9010
10781
|
function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundBaseBranch) {
|
|
@@ -9035,17 +10806,18 @@ function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundB
|
|
|
9035
10806
|
};
|
|
9036
10807
|
}
|
|
9037
10808
|
function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
|
|
9038
|
-
const briefDir =
|
|
9039
|
-
|
|
10809
|
+
const briefDir = join20(targetDir, ".agent_context");
|
|
10810
|
+
mkdirSync13(briefDir, { recursive: true });
|
|
9040
10811
|
const brief = `# \u4EFB\u52A1\u4E0A\u4E0B\u6587\uFF08Task Context\uFF09
|
|
9041
10812
|
|
|
9042
10813
|
- \u672C AgentTask \u4EFB\u52A1 ID\uFF1A\`${taskId}\`
|
|
9043
10814
|
- \u5DE5\u4F5C\u76EE\u5F55\uFF1A\u672C\u6587\u4EF6\u6240\u5728\u76EE\u5F55\uFF08\u4EFB\u52A1\u5DE5\u4F5C\u533A\u6839\uFF1B\u4ED3\u5E93\u662F\u5B83\u4E0B\u9762\u7684\u5B50\u76EE\u5F55\uFF0Cgit \u64CD\u4F5C\u5728\u4ED3\u5E93\u5B50\u76EE\u5F55\u5185\uFF09
|
|
10815
|
+
- \u534F\u4F5C\u5951\u7EA6\uFF08\u5FC5\u8BFB\uFF09\uFF1A\`.agents/skills/morula-working-on-issues/SKILL.md\`\uFF08\u76F8\u5BF9\u672C\u76EE\u5F55\uFF09\u3002\u82E5 \`skill\` \u5DE5\u5177\u62A5 \`Unknown skill\`\uFF0C**\u76F4\u63A5\u8BFB\u8BE5\u6587\u4EF6**\uFF08read/cat\uFF09\uFF0C\u4E0D\u8981\u56E0\u4E3A\u52A0\u8F7D\u5931\u8D25\u5C31\u8DF3\u8FC7\u5951\u7EA6\u3002
|
|
9044
10816
|
|
|
9045
10817
|
---
|
|
9046
10818
|
|
|
9047
10819
|
${buildTaskPromptWithContract(detail, void 0)}`;
|
|
9048
|
-
|
|
10820
|
+
writeFileSync11(join20(briefDir, "task-context.md"), brief, "utf8");
|
|
9049
10821
|
log?.(`[daemon] \u5DF2\u6CE8\u5165\u4EFB\u52A1\u4E0A\u4E0B\u6587 brief \u5230 .agent_context/task-context.md`);
|
|
9050
10822
|
}
|
|
9051
10823
|
var PROJECT_AGENTS_MD_MAX_CHARS = 1e5;
|
|
@@ -9059,8 +10831,8 @@ function writeProjectAgentsMd(targetDir, agentsMd, log) {
|
|
|
9059
10831
|
log?.(`[daemon] \u7A7A\u95F4 AGENTS.md \u5185\u5BB9\u8D85\u957F\uFF08${content.length} \u5B57\u7B26 > ${PROJECT_AGENTS_MD_MAX_CHARS}\uFF09\uFF0C\u5DF2\u622A\u65AD\u5199\u5165`);
|
|
9060
10832
|
content = content.slice(0, PROJECT_AGENTS_MD_MAX_CHARS);
|
|
9061
10833
|
}
|
|
9062
|
-
|
|
9063
|
-
|
|
10834
|
+
mkdirSync13(targetDir, { recursive: true });
|
|
10835
|
+
writeFileSync11(join20(targetDir, "AGENTS.md"), `${content}
|
|
9064
10836
|
`, "utf8");
|
|
9065
10837
|
log?.(`[daemon] \u5DF2\u6CE8\u5165\u7A7A\u95F4\u9879\u76EE\u8BF4\u660E\u5230 AGENTS.md\uFF08${content.length} \u5B57\u7B26\uFF0C\u4F5C\u4E3A project.instructions \u81EA\u52A8\u52A0\u8F7D\uFF09`);
|
|
9066
10838
|
return true;
|
|
@@ -9080,52 +10852,20 @@ function resolveRepo(detail) {
|
|
|
9080
10852
|
}
|
|
9081
10853
|
function buildTaskPromptWithContract(detail, repoBaseUrl) {
|
|
9082
10854
|
const prompt = detail.taskPrompt ?? "";
|
|
9083
|
-
|
|
9084
|
-
const
|
|
9085
|
-
const
|
|
9086
|
-
const
|
|
9087
|
-
const multiRepoList = availableRepos.length > 1 ? availableRepos.map((r) => `- ${r.repoFullName}\uFF08\u9ED8\u8BA4\u5206\u652F\uFF1A${r.defaultBranch || "main"}\uFF09`).join("\n") : "";
|
|
9088
|
-
const multiRepoGuide = availableRepos.length > 1 ? `\u3010\u591A\u4ED3\u5E93\u4EFB\u52A1\u3011\u672C\u4EFB\u52A1\u7A7A\u95F4\u7ED1\u5B9A\u4E86\u591A\u4E2A\u4ED3\u5E93\uFF08\u6E05\u5355\u89C1\u4E0B\uFF09\uFF0C\u6D89\u53CA\u54EA\u4E9B\u4ED3\u5E93\u7531\u4F60\u7ED3\u5408\u4EFB\u52A1\u63CF\u8FF0\u5224\u65AD\u6267\u884C\uFF08\u4EFB\u52A1\u63CF\u8FF0\u300C\u6D89\u53CA\u4ED3\u5E93\u300D\u6BB5\u4E3A\u521D\u59CB\u5224\u65AD\uFF09\uFF1B\u6539\u52A8\u53D1\u751F\u5728\u54EA\u4E2A\u4ED3\u5E93\uFF0C\u5C31\u5728\u54EA\u4E2A\u4ED3\u5E93\u5185\u5EFA\u5206\u652F\u5E76\u63D0 PR/MR\uFF0C\u6BCF\u4E2A\u4ED3\u5E93\u7684 PR \u90FD\u4F1A\u88AB\u670D\u52A1\u7AEF\u81EA\u52A8\u5173\u8054\u5230\u672C\u4EFB\u52A1\u3002
|
|
9089
|
-
\u53EF\u7528\u4ED3\u5E93\u6E05\u5355\uFF1A
|
|
9090
|
-
${multiRepoList}
|
|
9091
|
-
|
|
9092
|
-
` : "";
|
|
9093
|
-
let repoHint = "";
|
|
9094
|
-
const repoRefs = Array.isArray(detail.contextSnapshot?.repoRefs) ? detail.contextSnapshot.repoRefs : [];
|
|
9095
|
-
if (repoRefs.length > 0) {
|
|
9096
|
-
const list = repoRefs.map((r) => r.repoFullName).filter(Boolean).join("\u3001");
|
|
9097
|
-
repoHint = `\u7528\u6237 @ \u6307\u5B9A\u8BFB\u53D6\u4ED3\u5E93\uFF1A${list}\uFF08\u53EA\u8BFB\u67E5\u8BC1\uFF0C\u4E0D\u5EFA\u5206\u652F\u3001\u4E0D\u6539\u4EE3\u7801\uFF1B\u7269\u5316\u65B9\u5F0F\u89C1 \`morula-working-on-issues\` skill \xA70.5\uFF09\u3002
|
|
9098
|
-
|
|
9099
|
-
`;
|
|
9100
|
-
} else if (repo) {
|
|
9101
|
-
repoHint = `${multiRepoGuide}\u53EF\u7528\u4ED3\u5E93\uFF1A${repo}\uFF08\u7A7A\u95F4\u7ED1\u5B9A\u4ED3\u5E93\uFF1B\u7269\u5316/\u5206\u652F/MR \u64CD\u4F5C\u65B9\u5F0F\u89C1 \`morula-working-on-issues\` skill \xA70.5\uFF09\u3002
|
|
9102
|
-
|
|
9103
|
-
`;
|
|
9104
|
-
} else if (repoResolution === "MULTI_REPO_AGENT_DECIDES") {
|
|
9105
|
-
repoHint = `${multiRepoGuide}\u53EF\u7528\u4ED3\u5E93\uFF1A${availableRepos.map((r) => r.repoFullName).join("\u3001")}\uFF08\u4EFB\u52A1\u7A7A\u95F4\u7ED1\u5B9A\uFF1B\u7269\u5316/\u5206\u652F/MR \u64CD\u4F5C\u65B9\u5F0F\u89C1 \`morula-working-on-issues\` skill \xA70.5\uFF09\u3002
|
|
9106
|
-
|
|
9107
|
-
`;
|
|
9108
|
-
} else if (repoResolution === "REPO_SELECTION_REQUIRED") {
|
|
9109
|
-
const snapCandidates = Array.isArray(detail.contextSnapshot?.repoCandidates) ? detail.contextSnapshot.repoCandidates : [];
|
|
9110
|
-
const candidates = snapCandidates.length > 0 ? snapCandidates : availableRepos.map((r) => r.repoFullName).filter((n) => Boolean(n));
|
|
9111
|
-
repoHint = candidates.length > 0 ? `\u7A7A\u95F4\u5173\u8054\u591A\u4E2A\u4ED3\u5E93\uFF0C\u9700\u8981\u5411\u7528\u6237\u786E\u8BA4\u8981\u64CD\u4F5C\u7684\u4ED3\u5E93\uFF08\u5019\u9009\uFF1A${candidates.join("\u3001")}\uFF09\u3002
|
|
9112
|
-
|
|
9113
|
-
` : `\u7A7A\u95F4\u5173\u8054\u591A\u4E2A\u4ED3\u5E93\uFF0C\u9700\u8981\u5411\u7528\u6237\u786E\u8BA4\u8981\u64CD\u4F5C\u7684\u4ED3\u5E93\u3002
|
|
9114
|
-
|
|
9115
|
-
`;
|
|
9116
|
-
} else if (!repo && (repoResolution === "NO_CONNECTION" || !repoResolution)) {
|
|
9117
|
-
if (availableRepos.length > 0) {
|
|
9118
|
-
const list = availableRepos.map((r) => r.repoFullName).filter(Boolean).join("\u3001");
|
|
9119
|
-
repoHint = `\u53EF\u7528\u4ED3\u5E93\uFF1A${list}\uFF08\u4EFB\u52A1\u7A7A\u95F4\u7ED1\u5B9A\uFF1B\u53EA\u8BFB\u67E5\u8BC1\u65B9\u5F0F\u89C1 \`morula-working-on-issues\` skill \xA70.5\uFF09\u3002
|
|
10855
|
+
void repoBaseUrl;
|
|
10856
|
+
const snap = detail.contextSnapshot;
|
|
10857
|
+
const spaceId = typeof snap?.spaceId === "string" && snap.spaceId.trim() ? snap.spaceId.trim() : "";
|
|
10858
|
+
const keysHint = `## \u57FA\u672C\u4FE1\u606F
|
|
9120
10859
|
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
10860
|
+
- \u4EFB\u52A1 ID\uFF08AgentTask\uFF0C\`morula task branch\` \u7B49\u547D\u4EE4\u7684 taskId \u7528\u5B83\uFF09\uFF1A\`${detail.id}\`
|
|
10861
|
+
${spaceId ? `- \u7A7A\u95F4 ID\uFF08\u6240\u6709 \`--space\` \u7528\u5B83\uFF09\uFF1A\`${spaceId}\`
|
|
10862
|
+
` : ""}`;
|
|
9124
10863
|
const toolsHint = `## \u5DE5\u4F5C\u533A\u4E0E\u5DE5\u5177
|
|
9125
10864
|
|
|
9126
|
-
- \u5DE5\u4F5C\u76EE\u5F55 = \u4EFB\u52A1\u5DE5\u4F5C\u533A\u6839\
|
|
10865
|
+
- \u5DE5\u4F5C\u76EE\u5F55 = \u4EFB\u52A1\u5DE5\u4F5C\u533A\u6839\uFF08cwd\uFF09\uFF1B\u4ED3\u5E93\u662F\u5B83\u4E0B\u9762\u7684\u5B50\u76EE\u5F55\uFF08\u5E03\u5C40 / \u7269\u5316 / \u5206\u652F / MR \u7528\u6CD5\u89C1 \`morula-working-on-issues\` skill \xA70.5\uFF09\uFF1B
|
|
9127
10866
|
- \u5E73\u53F0\u4EA4\u4E92\u5951\u7EA6\u4E0E\u547D\u4EE4\u7528\u6CD5\uFF1A\`morula-working-on-issues\` skill\uFF08\u5DF2\u6CE8\u5165 \`.agents/skills/\`\uFF09\uFF0C\u6309\u9700\u8BFB\u53D6\uFF1B
|
|
9128
|
-
- \u4EFB\u52A1\u8BE6\u60C5\uFF08\u4EFB\u52A1\u7F16\u53F7 / \u5173\u8054\u5BF9\u8C61 / \u4E1A\u52A1\u76EE\u6807 / \u8BC4\u8BBA / PR\uFF09\uFF1A\`morula task get\`\uFF08\u65E0\u53C2\uFF0CtaskId \u7531\u6267\u884C\u73AF\u5883\u63D0\u4F9B\uFF09\
|
|
10867
|
+
- \u4EFB\u52A1\u8BE6\u60C5\uFF08\u4EFB\u52A1\u7F16\u53F7 / \u5173\u8054\u5BF9\u8C61 / \u4E1A\u52A1\u76EE\u6807 / \u8BC4\u8BBA / PR\uFF09\uFF1A\`morula task get\`\uFF08\u65E0\u53C2\uFF0CtaskId \u7531\u6267\u884C\u73AF\u5883\u63D0\u4F9B\uFF09\uFF1B
|
|
10868
|
+
- \u7A7A\u95F4\u4ED3\u5E93\u6E05\u5355\uFF08\u591A\u4ED3\u5E93\u4EFB\u52A1\uFF09\uFF1A\`morula repo list --space <spaceId>\`\uFF08spaceId \u89C1\u4E0A\u300C\u57FA\u672C\u4FE1\u606F\u300D\u6BB5\uFF09\u3002
|
|
9129
10869
|
|
|
9130
10870
|
`;
|
|
9131
10871
|
const taskSource = detail.contextSnapshot?.source;
|
|
@@ -9133,19 +10873,12 @@ ${multiRepoList}
|
|
|
9133
10873
|
const stageContractBlock = stageContract ? `${stageContract}
|
|
9134
10874
|
|
|
9135
10875
|
` : "";
|
|
9136
|
-
const
|
|
9137
|
-
|
|
9138
|
-
if (snapCtx?.triggerSource === "comment_mention" && snapCtx.commentId) {
|
|
9139
|
-
extraHint += `\u672C\u4EFB\u52A1\u7531\u8BC4\u8BBA\u89E6\u53D1\uFF08\u6700\u7EC8\u603B\u7ED3\u7531\u670D\u52A1\u7AEF\u81EA\u52A8\u4F5C\u4E3A\u8BE5\u89E6\u53D1\u8BC4\u8BBA\u7684\u56DE\u590D\u53D1\u51FA\uFF0C\u65E0\u9700\u81EA\u884C\u8BC4\u8BBA\uFF09\u3002
|
|
9140
|
-
|
|
9141
|
-
`;
|
|
9142
|
-
}
|
|
9143
|
-
if (snapCtx?.handoffNote && typeof snapCtx.handoffNote === "string" && snapCtx.handoffNote.trim()) {
|
|
9144
|
-
extraHint += `\u6307\u6D3E\u4EA4\u4ED8\u8981\u6C42\uFF08\u7528\u6237\u663E\u5F0F\u6307\u5B9A\uFF0C\u5FC5\u987B\u6EE1\u8DB3\uFF09\uFF1A**${snapCtx.handoffNote.trim()}**
|
|
10876
|
+
const handoff = typeof snap?.handoffNote === "string" ? snap.handoffNote.trim() : "";
|
|
10877
|
+
const extraHint = handoff ? `\u6307\u6D3E\u4EA4\u4ED8\u8981\u6C42\uFF08\u7528\u6237\u663E\u5F0F\u6307\u5B9A\uFF0C\u5FC5\u987B\u6EE1\u8DB3\uFF09\uFF1A**${handoff}**
|
|
9145
10878
|
|
|
9146
|
-
|
|
9147
|
-
}
|
|
9148
|
-
|
|
10879
|
+
` : "";
|
|
10880
|
+
const head = `${stageContractBlock}${keysHint}
|
|
10881
|
+
${toolsHint}${extraHint}`;
|
|
9149
10882
|
return head ? `${head}========== \u4F60\u7684\u76EE\u6807 ==========
|
|
9150
10883
|
${prompt}` : `${prompt}`;
|
|
9151
10884
|
}
|
|
@@ -9158,7 +10891,7 @@ async function reportRepoTargets(taskId, targets, env, ops) {
|
|
|
9158
10891
|
}
|
|
9159
10892
|
async function reportOneRepoTarget(taskId, target, env, ops) {
|
|
9160
10893
|
const execEnv = mergeExecEnv(env);
|
|
9161
|
-
if (!existsSync15(target.dir) || !existsSync15(
|
|
10894
|
+
if (!existsSync15(target.dir) || !existsSync15(join20(target.dir, ".git"))) {
|
|
9162
10895
|
ops.log(`[daemon] \u4EFB\u52A1 ${taskId} \u4ED3\u5E93 ${target.repoFullName} \u4EA4\u4ED8\u8DF3\u8FC7\uFF1A\u5DE5\u4F5C\u76EE\u5F55\u5DF2\u4E0D\u5B58\u5728\uFF08cleanup/\u5206\u652F\u6539\u540D\u65F6\u5E8F\uFF09`);
|
|
9163
10896
|
return;
|
|
9164
10897
|
}
|
|
@@ -9327,8 +11060,8 @@ Task ref: ${taskCode}\uFF08\u7531 morula \u81EA\u52A8\u8865\u5145\uFF0C\u7528\u4
|
|
|
9327
11060
|
// src/commands/doctor.ts
|
|
9328
11061
|
init_dist2();
|
|
9329
11062
|
import { existsSync as existsSync16, readFileSync as readFileSync10, realpathSync } from "node:fs";
|
|
9330
|
-
import { homedir as
|
|
9331
|
-
import { dirname as
|
|
11063
|
+
import { homedir as homedir12 } from "node:os";
|
|
11064
|
+
import { dirname as dirname9, join as join21 } from "node:path";
|
|
9332
11065
|
init_daemon_config();
|
|
9333
11066
|
init_daemon_lock();
|
|
9334
11067
|
init_dim_locate();
|
|
@@ -9348,10 +11081,10 @@ async function doctorCommand(context) {
|
|
|
9348
11081
|
async function collectDoctor(context, probe) {
|
|
9349
11082
|
const checks = [];
|
|
9350
11083
|
const env = context.env;
|
|
9351
|
-
const configDir = env.MORULA_CONFIG_DIR ??
|
|
9352
|
-
const configPath = env.MORULA_CONFIG ??
|
|
9353
|
-
const daemonPath =
|
|
9354
|
-
const logFile =
|
|
11084
|
+
const configDir = env.MORULA_CONFIG_DIR ?? join21(homedir12(), ".morula-cli");
|
|
11085
|
+
const configPath = env.MORULA_CONFIG ?? join21(configDir, "config.yaml");
|
|
11086
|
+
const daemonPath = join21(dirname9(configPath), "daemon");
|
|
11087
|
+
const logFile = join21(daemonPath, "daemon.log");
|
|
9355
11088
|
const contextName = context.runtime.context ?? null;
|
|
9356
11089
|
const baseUrl = context.runtime.baseUrl ?? null;
|
|
9357
11090
|
const tokenPresent = Boolean(context.runtime.token);
|
|
@@ -9365,12 +11098,11 @@ async function collectDoctor(context, probe) {
|
|
|
9365
11098
|
configReadable = false;
|
|
9366
11099
|
}
|
|
9367
11100
|
}
|
|
9368
|
-
const lockPath =
|
|
11101
|
+
const lockPath = join21(daemonPath, DAEMON_LOCK_FILE);
|
|
9369
11102
|
const lock = probeDaemonLock(lockPath);
|
|
9370
|
-
const pidFile =
|
|
9371
|
-
const filePid =
|
|
9372
|
-
const
|
|
9373
|
-
const daemonPid = lockPid || (isProcessAlive(filePid) ? filePid : 0) || null;
|
|
11103
|
+
const pidFile = join21(daemonPath, DAEMON_PID_FILE);
|
|
11104
|
+
const filePid = readPidFile(pidFile);
|
|
11105
|
+
const daemonPid = probeDaemonInstance(configDir).pid;
|
|
9374
11106
|
const cfg = loadDaemonConfig(context.store);
|
|
9375
11107
|
const dim = safeResolveDim();
|
|
9376
11108
|
const dimPath = dim.path;
|
|
@@ -9538,13 +11270,14 @@ function detectInstallMode() {
|
|
|
9538
11270
|
|
|
9539
11271
|
// src/commands/daemon.ts
|
|
9540
11272
|
function configFilePath(env) {
|
|
9541
|
-
return env.MORULA_CONFIG ??
|
|
11273
|
+
return env.MORULA_CONFIG ?? join22(env.MORULA_CONFIG_DIR ?? join22(homedir13(), ".morula-cli"), "config.yaml");
|
|
9542
11274
|
}
|
|
9543
11275
|
function daemonDir(configPath) {
|
|
9544
|
-
return
|
|
11276
|
+
return join22(dirname10(configPath), "daemon");
|
|
9545
11277
|
}
|
|
9546
|
-
var PID_FILE =
|
|
11278
|
+
var PID_FILE = DAEMON_PID_FILE;
|
|
9547
11279
|
var LOG_FILE = "daemon.log";
|
|
11280
|
+
var DAEMON_EXIT_FALLBACK_MS = 3e3;
|
|
9548
11281
|
async function daemonCommand(context) {
|
|
9549
11282
|
const { action } = context.parsed;
|
|
9550
11283
|
if (action === "run")
|
|
@@ -9727,6 +11460,9 @@ async function models(context) {
|
|
|
9727
11460
|
}
|
|
9728
11461
|
async function run2(context) {
|
|
9729
11462
|
rejectUnknownFlags(context.parsed.flags, ["name", "workdir", "max-concurrent"]);
|
|
11463
|
+
const armBoundedExit = () => {
|
|
11464
|
+
setTimeout(() => process.exit(0), DAEMON_EXIT_FALLBACK_MS).unref();
|
|
11465
|
+
};
|
|
9730
11466
|
const name = flagString(context.parsed.flags, "name") ?? void 0;
|
|
9731
11467
|
const workdir = flagString(context.parsed.flags, "workdir") ?? void 0;
|
|
9732
11468
|
const maxConcurrent = parseMaxConcurrent(context);
|
|
@@ -9735,13 +11471,14 @@ async function run2(context) {
|
|
|
9735
11471
|
saveDaemonConfig(context.store, { name, workdir, maxConcurrent });
|
|
9736
11472
|
}
|
|
9737
11473
|
const resolvedWorkdir = workdir ?? loadDaemonConfig(context.store)?.workdir ?? DEFAULT_WORKDIR;
|
|
9738
|
-
|
|
9739
|
-
const lockPath =
|
|
11474
|
+
mkdirSync14(resolvedWorkdir, { recursive: true });
|
|
11475
|
+
const lockPath = join22(daemonDir(configFilePath(context.env)), DAEMON_LOCK_FILE);
|
|
9740
11476
|
const acquired = acquireDaemonLock(lockPath, { workdir: resolvedWorkdir });
|
|
9741
11477
|
if (!acquired.ok) {
|
|
9742
11478
|
const pid = acquired.existing?.pid;
|
|
9743
11479
|
if (context.env.MORULA_DAEMON_SUPERVISED === "1") {
|
|
9744
11480
|
context.err(`[daemon] \u5DF2\u6709\u5B9E\u4F8B\u5728\u8FD0\u884C\uFF08pid=${pid ?? "\u672A\u77E5"}\uFF09\uFF0C\u81EA\u542F\u5B9E\u4F8B\u793C\u8BA9\u9000\u51FA`);
|
|
11481
|
+
armBoundedExit();
|
|
9745
11482
|
return { running: false, yieldedTo: pid ?? null };
|
|
9746
11483
|
}
|
|
9747
11484
|
throw new CliError(
|
|
@@ -9782,12 +11519,14 @@ async function run2(context) {
|
|
|
9782
11519
|
if (isAuthRejected(error)) {
|
|
9783
11520
|
context.err(`[daemon] \u6CE8\u518C\u88AB\u62D2\uFF08\u767B\u5F55\u6001\u5931\u6548\uFF09\uFF1A${error instanceof Error ? error.message : String(error)}`);
|
|
9784
11521
|
context.err("[daemon] \u8BF7\u91CD\u65B0\u767B\u5F55\u540E\u518D\u6B21\u542F\u52A8\uFF1Amorula-runtime auth login && morula-runtime daemon start");
|
|
11522
|
+
armBoundedExit();
|
|
9785
11523
|
return { running: false, stoppedReason: "auth" };
|
|
9786
11524
|
}
|
|
9787
11525
|
throw error;
|
|
9788
11526
|
} finally {
|
|
9789
11527
|
releaseLock();
|
|
9790
11528
|
}
|
|
11529
|
+
armBoundedExit();
|
|
9791
11530
|
return { running: false };
|
|
9792
11531
|
}
|
|
9793
11532
|
function isAuthRejected(error) {
|
|
@@ -9810,10 +11549,10 @@ async function start(context) {
|
|
|
9810
11549
|
}
|
|
9811
11550
|
const dir = daemonDir(configFilePath(context.env));
|
|
9812
11551
|
if (!dryRun)
|
|
9813
|
-
|
|
9814
|
-
const pidFile =
|
|
9815
|
-
const logFile =
|
|
9816
|
-
const lockPath =
|
|
11552
|
+
mkdirSync14(dir, { recursive: true, mode: 448 });
|
|
11553
|
+
const pidFile = join22(dir, PID_FILE);
|
|
11554
|
+
const logFile = join22(dir, LOG_FILE);
|
|
11555
|
+
const lockPath = join22(dir, DAEMON_LOCK_FILE);
|
|
9817
11556
|
const lock = probeDaemonLock(lockPath);
|
|
9818
11557
|
if (lock.state === "active") {
|
|
9819
11558
|
throw new CliError(
|
|
@@ -9832,7 +11571,7 @@ async function start(context) {
|
|
|
9832
11571
|
});
|
|
9833
11572
|
}
|
|
9834
11573
|
if (!dryRun)
|
|
9835
|
-
|
|
11574
|
+
rmSync10(pidFile, { force: true });
|
|
9836
11575
|
}
|
|
9837
11576
|
const env = {
|
|
9838
11577
|
...process.env,
|
|
@@ -9870,12 +11609,12 @@ async function start(context) {
|
|
|
9870
11609
|
const { spawnDaemonProcess: spawnDaemonProcess2 } = await Promise.resolve().then(() => (init_spawn(), spawn_exports));
|
|
9871
11610
|
const result = spawnDaemonProcess2({ logFile, env });
|
|
9872
11611
|
if (result.error || result.pid <= 0) {
|
|
9873
|
-
|
|
11612
|
+
rmSync10(pidFile, { force: true });
|
|
9874
11613
|
throw new CliError("DAEMON_START_FAILED", EXIT.OTHER, `[daemon] \u542F\u52A8\u5931\u8D25: ${result.error ?? `pid=${result.pid}`}`, false, {
|
|
9875
11614
|
hint: `tail -n 50 ${logFile}`
|
|
9876
11615
|
});
|
|
9877
11616
|
}
|
|
9878
|
-
|
|
11617
|
+
writeFileSync12(pidFile, String(result.pid), { mode: 384 });
|
|
9879
11618
|
context.err(`[daemon] \u5DF2\u540E\u53F0\u542F\u52A8\uFF08pid=${result.pid}\uFF09\uFF0C\u65E5\u5FD7: ${logFile}`);
|
|
9880
11619
|
return { running: true, pid: result.pid, logFile, lockFile: lockPath };
|
|
9881
11620
|
}
|
|
@@ -9883,8 +11622,8 @@ async function stop(context) {
|
|
|
9883
11622
|
rejectUnknownFlags(context.parsed.flags, ["dry-run"]);
|
|
9884
11623
|
const dryRun = isDryRun(context);
|
|
9885
11624
|
const dir = daemonDir(configFilePath(context.env));
|
|
9886
|
-
const pidFile =
|
|
9887
|
-
const lockPath =
|
|
11625
|
+
const pidFile = join22(dir, PID_FILE);
|
|
11626
|
+
const lockPath = join22(dir, DAEMON_LOCK_FILE);
|
|
9888
11627
|
const lock = probeDaemonLock(lockPath);
|
|
9889
11628
|
const lockPid = lock.state === "absent" ? null : lock.record?.pid ?? null;
|
|
9890
11629
|
const filePid = existsSync17(pidFile) ? Number(readFileSync11(pidFile, "utf8").trim()) : null;
|
|
@@ -9897,7 +11636,7 @@ async function stop(context) {
|
|
|
9897
11636
|
};
|
|
9898
11637
|
}
|
|
9899
11638
|
if (!pid) {
|
|
9900
|
-
|
|
11639
|
+
rmSync10(pidFile, { force: true });
|
|
9901
11640
|
releaseDaemonLock(lockPath);
|
|
9902
11641
|
return { running: false, message: "daemon \u672A\u5728\u8FD0\u884C\uFF08\u65E0\u5B58\u6D3B pid\uFF09" };
|
|
9903
11642
|
}
|
|
@@ -9906,7 +11645,7 @@ async function stop(context) {
|
|
|
9906
11645
|
releaseDaemonLock(lockPath);
|
|
9907
11646
|
else
|
|
9908
11647
|
releaseDaemonLock(lockPath, pid);
|
|
9909
|
-
|
|
11648
|
+
rmSync10(pidFile, { force: true });
|
|
9910
11649
|
if (result.outcome === "killed")
|
|
9911
11650
|
context.err(`[daemon] pid=${pid} \u672A\u54CD\u5E94 SIGTERM\uFF0C\u5DF2 SIGKILL\uFF08\u9501\u5DF2\u91CA\u653E\uFF09`);
|
|
9912
11651
|
return { running: false, stoppedPid: pid, outcome: result.outcome };
|
|
@@ -9915,21 +11654,17 @@ async function status2(context) {
|
|
|
9915
11654
|
rejectUnknownFlags(context.parsed.flags, []);
|
|
9916
11655
|
const cfg = loadDaemonConfig(context.store);
|
|
9917
11656
|
const dir = daemonDir(configFilePath(context.env));
|
|
9918
|
-
const
|
|
9919
|
-
const
|
|
9920
|
-
const lock = probeDaemonLock(lockPath);
|
|
9921
|
-
const lockPid = lock.state === "active" ? lock.record.pid : null;
|
|
9922
|
-
const filePid = existsSync17(pidFile) ? Number(readFileSync11(pidFile, "utf8").trim() || 0) : 0;
|
|
9923
|
-
const pid = lockPid ?? (isProcessAlive(filePid) ? filePid : null);
|
|
11657
|
+
const lockPath = join22(dir, DAEMON_LOCK_FILE);
|
|
11658
|
+
const probe = probeDaemonInstance(dirname10(configFilePath(context.env)));
|
|
9924
11659
|
return {
|
|
9925
|
-
running:
|
|
9926
|
-
pid,
|
|
11660
|
+
running: probe.running,
|
|
11661
|
+
pid: probe.pid,
|
|
9927
11662
|
daemonId: cfg?.daemonId ?? null,
|
|
9928
11663
|
runtimeId: cfg?.runtimeId ?? null,
|
|
9929
11664
|
name: cfg?.name ?? null,
|
|
9930
11665
|
workdir: cfg?.workdir ?? DEFAULT_WORKDIR,
|
|
9931
11666
|
maxConcurrent: cfg?.maxConcurrent ?? DEFAULT_MAX_CONCURRENT,
|
|
9932
|
-
logFile:
|
|
11667
|
+
logFile: join22(dir, LOG_FILE),
|
|
9933
11668
|
lockFile: lockPath
|
|
9934
11669
|
};
|
|
9935
11670
|
}
|
|
@@ -9957,95 +11692,6 @@ function parseMaxConcurrent(context) {
|
|
|
9957
11692
|
|
|
9958
11693
|
// src/commands/setup.ts
|
|
9959
11694
|
init_dist2();
|
|
9960
|
-
|
|
9961
|
-
// src/daemon/plugin-register.ts
|
|
9962
|
-
import { chmodSync as chmodSync3, cpSync, existsSync as existsSync18, mkdirSync as mkdirSync13, readdirSync as readdirSync5, readFileSync as readFileSync12, rmSync as rmSync9, statSync as statSync4, writeFileSync as writeFileSync11 } from "node:fs";
|
|
9963
|
-
import { homedir as homedir13 } from "node:os";
|
|
9964
|
-
import { dirname as dirname10, join as join21 } from "node:path";
|
|
9965
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
9966
|
-
var PLUGIN_NAME = "morula-cli";
|
|
9967
|
-
var MANIFEST_DIR_NAME = ".codex-plugin";
|
|
9968
|
-
function legacyAgentsPluginDir(home = homedir13()) {
|
|
9969
|
-
return join21(home, ".agents", "plugins", PLUGIN_NAME);
|
|
9970
|
-
}
|
|
9971
|
-
function resolveDimcodeHome(env = process.env, home = homedir13()) {
|
|
9972
|
-
const fromEnv = env.DIMCODE_HOME?.trim();
|
|
9973
|
-
return fromEnv || join21(home, ".dimcode", "v2");
|
|
9974
|
-
}
|
|
9975
|
-
function defaultPluginsRoot(env = process.env, home = homedir13()) {
|
|
9976
|
-
return join21(resolveDimcodeHome(env, home), "plugins");
|
|
9977
|
-
}
|
|
9978
|
-
function resolvePackagePaths(entryUrl) {
|
|
9979
|
-
const distDir = dirname10(fileURLToPath2(entryUrl));
|
|
9980
|
-
return {
|
|
9981
|
-
packageRoot: dirname10(distDir),
|
|
9982
|
-
distDir,
|
|
9983
|
-
assetsDir: join21(distDir, "plugin")
|
|
9984
|
-
};
|
|
9985
|
-
}
|
|
9986
|
-
function buildLauncherScript(opts) {
|
|
9987
|
-
const entry = join21(opts.packageRoot, "dist", "morula.js");
|
|
9988
|
-
return `#!/bin/sh
|
|
9989
|
-
# Morula \u4E1A\u52A1 CLI \u542F\u52A8\u5668\uFF08\u7531 \`morula-runtime setup\` \u751F\u6210\uFF1BNode \u5347\u7EA7\u540E\u91CD\u8DD1 setup \u5237\u65B0\uFF09\u3002
|
|
9990
|
-
# \u4E0D\u4F9D\u8D56\u8C03\u7528\u65B9 PATH \u91CC\u7684 node\uFF08agent \u6267\u884C\u73AF\u5883 PATH \u5E38\u88AB\u88C1\u526A\uFF09\uFF0C\u4EC5\u4F5C\u515C\u5E95\u3002
|
|
9991
|
-
NODE=${JSON.stringify(opts.nodePath)}
|
|
9992
|
-
[ -x "$NODE" ] || NODE="$(command -v node 2>/dev/null || true)"
|
|
9993
|
-
if [ -z "$NODE" ]; then
|
|
9994
|
-
echo '{"code":"ERROR","message":"node not found; reinstall or re-run: morula-runtime setup"}' >&2
|
|
9995
|
-
exit 127
|
|
9996
|
-
fi
|
|
9997
|
-
exec "$NODE" ${JSON.stringify(entry)} "$@"
|
|
9998
|
-
`;
|
|
9999
|
-
}
|
|
10000
|
-
function assetsReady(paths) {
|
|
10001
|
-
return existsSync18(join21(paths.assetsDir, "plugin.json")) && existsSync18(join21(paths.assetsDir, "skills"));
|
|
10002
|
-
}
|
|
10003
|
-
function listSkillNames(assetsDir) {
|
|
10004
|
-
const dir = join21(assetsDir, "skills");
|
|
10005
|
-
if (!existsSync18(dir))
|
|
10006
|
-
return [];
|
|
10007
|
-
return readdirSync5(dir).filter((name) => {
|
|
10008
|
-
try {
|
|
10009
|
-
return statSync4(join21(dir, name)).isDirectory();
|
|
10010
|
-
} catch {
|
|
10011
|
-
return false;
|
|
10012
|
-
}
|
|
10013
|
-
}).sort();
|
|
10014
|
-
}
|
|
10015
|
-
function registerPlugin(opts) {
|
|
10016
|
-
const { pluginsRoot, paths, version } = opts;
|
|
10017
|
-
const nodePath = opts.nodePath ?? process.execPath;
|
|
10018
|
-
const pluginDir = join21(pluginsRoot, PLUGIN_NAME);
|
|
10019
|
-
if (!assetsReady(paths))
|
|
10020
|
-
throw new Error(`plugin assets not found at ${paths.assetsDir}; run \`pnpm run build:dist\` (dev) or reinstall the package`);
|
|
10021
|
-
const skillsSrc = join21(paths.assetsDir, "skills");
|
|
10022
|
-
const skills = listSkillNames(paths.assetsDir);
|
|
10023
|
-
const manifestDir = join21(pluginDir, MANIFEST_DIR_NAME);
|
|
10024
|
-
mkdirSync13(manifestDir, { recursive: true });
|
|
10025
|
-
const manifest = JSON.parse(readFileSync12(join21(paths.assetsDir, "plugin.json"), "utf8"));
|
|
10026
|
-
writeFileSync11(join21(manifestDir, "plugin.json"), `${JSON.stringify({ ...manifest, version }, null, 2)}
|
|
10027
|
-
`);
|
|
10028
|
-
const skillsDest = join21(pluginDir, "skills");
|
|
10029
|
-
rmSync9(skillsDest, { recursive: true, force: true });
|
|
10030
|
-
mkdirSync13(skillsDest, { recursive: true });
|
|
10031
|
-
cpSync(skillsSrc, skillsDest, { recursive: true });
|
|
10032
|
-
const binDir = join21(pluginDir, "bin");
|
|
10033
|
-
mkdirSync13(binDir, { recursive: true });
|
|
10034
|
-
const binPath = join21(binDir, "morula");
|
|
10035
|
-
writeFileSync11(binPath, buildLauncherScript({ nodePath, packageRoot: paths.packageRoot }));
|
|
10036
|
-
chmodSync3(binPath, 493);
|
|
10037
|
-
const legacy = legacyAgentsPluginDir(opts.home);
|
|
10038
|
-
return {
|
|
10039
|
-
pluginDir,
|
|
10040
|
-
binPath,
|
|
10041
|
-
skills,
|
|
10042
|
-
version,
|
|
10043
|
-
node: nodePath,
|
|
10044
|
-
legacyDir: existsSync18(join21(legacy, MANIFEST_DIR_NAME)) ? legacy : null
|
|
10045
|
-
};
|
|
10046
|
-
}
|
|
10047
|
-
|
|
10048
|
-
// src/commands/setup.ts
|
|
10049
11695
|
var KNOWN_FLAGS = ["root", "help"];
|
|
10050
11696
|
async function setupCommand(context) {
|
|
10051
11697
|
rejectUnknownFlags(context.parsed.flags, KNOWN_FLAGS);
|