@arcships/morula-runtime 0.1.0-alpha.6 → 0.1.0-alpha.8
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 +2236 -608
- package/dist/morula.js +1584 -109
- 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 +11 -9
- package/dist/plugin/skills/morula-working-on-issues/references/working-on-issues-source-map.md +1 -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 = "20260921065825.nogit";
|
|
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,6 +886,93 @@ 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
|
|
830
977
|
function sanitizeHeaderValue(raw, maxLength) {
|
|
831
978
|
if (!raw)
|
|
@@ -870,28 +1017,35 @@ function isEnvelope(value) {
|
|
|
870
1017
|
function errorFromResponse(status3, parsed, raw) {
|
|
871
1018
|
const message = extractMessage(parsed, raw, status3);
|
|
872
1019
|
const details = isEnvelope(parsed) ? parsed.data : parsed;
|
|
1020
|
+
const errorCode = extractErrorCode(parsed);
|
|
873
1021
|
if (status3 === 400 || status3 === 422) {
|
|
874
|
-
return new MorulaApiError("BAD_REQUEST", message, EXIT_CODE.USAGE, status3, false, details);
|
|
1022
|
+
return new MorulaApiError("BAD_REQUEST", message, EXIT_CODE.USAGE, status3, false, details, errorCode);
|
|
875
1023
|
}
|
|
876
1024
|
if (status3 === 401) {
|
|
877
|
-
return new MorulaApiError("UNAUTHENTICATED", message, EXIT_CODE.UNAUTHENTICATED, status3, false, details);
|
|
1025
|
+
return new MorulaApiError("UNAUTHENTICATED", message, EXIT_CODE.UNAUTHENTICATED, status3, false, details, errorCode);
|
|
878
1026
|
}
|
|
879
1027
|
if (status3 === 403) {
|
|
880
|
-
return new MorulaApiError("FORBIDDEN", message, EXIT_CODE.FORBIDDEN, status3, false, details);
|
|
1028
|
+
return new MorulaApiError("FORBIDDEN", message, EXIT_CODE.FORBIDDEN, status3, false, details, errorCode);
|
|
881
1029
|
}
|
|
882
1030
|
if (status3 === 404) {
|
|
883
|
-
return new MorulaApiError("NOT_FOUND", message, EXIT_CODE.NOT_FOUND, status3, false, details);
|
|
1031
|
+
return new MorulaApiError("NOT_FOUND", message, EXIT_CODE.NOT_FOUND, status3, false, details, errorCode);
|
|
884
1032
|
}
|
|
885
1033
|
if (status3 === 409) {
|
|
886
|
-
return new MorulaApiError("CONFLICT", message, EXIT_CODE.CONFLICT, status3, false, details);
|
|
1034
|
+
return new MorulaApiError("CONFLICT", message, EXIT_CODE.CONFLICT, status3, false, details, errorCode);
|
|
887
1035
|
}
|
|
888
1036
|
if (status3 === 429) {
|
|
889
|
-
return new MorulaApiError("RATE_LIMITED", message, EXIT_CODE.UPSTREAM, status3, true, details);
|
|
1037
|
+
return new MorulaApiError("RATE_LIMITED", message, EXIT_CODE.UPSTREAM, status3, true, details, errorCode);
|
|
890
1038
|
}
|
|
891
1039
|
if (status3 >= 500) {
|
|
892
|
-
return new MorulaApiError("UPSTREAM", message, EXIT_CODE.UPSTREAM, status3, true, details);
|
|
1040
|
+
return new MorulaApiError("UPSTREAM", message, EXIT_CODE.UPSTREAM, status3, true, details, errorCode);
|
|
893
1041
|
}
|
|
894
|
-
return new MorulaApiError("HTTP_ERROR", message, EXIT_CODE.OTHER, status3, false, details);
|
|
1042
|
+
return new MorulaApiError("HTTP_ERROR", message, EXIT_CODE.OTHER, status3, false, details, errorCode);
|
|
1043
|
+
}
|
|
1044
|
+
function extractErrorCode(parsed) {
|
|
1045
|
+
if (!parsed || typeof parsed !== "object")
|
|
1046
|
+
return void 0;
|
|
1047
|
+
const value = parsed.errorCode;
|
|
1048
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
895
1049
|
}
|
|
896
1050
|
function extractMessage(parsed, raw, status3) {
|
|
897
1051
|
if (parsed && typeof parsed === "object" && "message" in parsed) {
|
|
@@ -927,14 +1081,16 @@ var init_client = __esm({
|
|
|
927
1081
|
status;
|
|
928
1082
|
retryable;
|
|
929
1083
|
details;
|
|
1084
|
+
errorCode;
|
|
930
1085
|
name = "MorulaApiError";
|
|
931
|
-
constructor(code, message, exitCode, status3, retryable = false, details) {
|
|
1086
|
+
constructor(code, message, exitCode, status3, retryable = false, details, errorCode) {
|
|
932
1087
|
super(message);
|
|
933
1088
|
this.code = code;
|
|
934
1089
|
this.exitCode = exitCode;
|
|
935
1090
|
this.status = status3;
|
|
936
1091
|
this.retryable = retryable;
|
|
937
1092
|
this.details = details;
|
|
1093
|
+
this.errorCode = errorCode;
|
|
938
1094
|
}
|
|
939
1095
|
};
|
|
940
1096
|
CLI_BUILD_HEADER = "X-Morula-Cli-Build";
|
|
@@ -1061,7 +1217,7 @@ var init_client = __esm({
|
|
|
1061
1217
|
});
|
|
1062
1218
|
|
|
1063
1219
|
// ../morula-api-client/dist/comment.js
|
|
1064
|
-
function
|
|
1220
|
+
function requiredId3(id) {
|
|
1065
1221
|
const normalized = id.trim();
|
|
1066
1222
|
if (!normalized)
|
|
1067
1223
|
throw new TypeError("comment id is required");
|
|
@@ -1083,23 +1239,142 @@ var init_comment = __esm({
|
|
|
1083
1239
|
return this.client.get("/comments", params);
|
|
1084
1240
|
}
|
|
1085
1241
|
get(id) {
|
|
1086
|
-
return this.client.get(`/comments/${encodeURIComponent(
|
|
1242
|
+
return this.client.get(`/comments/${encodeURIComponent(requiredId3(id))}`);
|
|
1087
1243
|
}
|
|
1088
1244
|
create(input) {
|
|
1089
1245
|
return this.client.post("/comments", input);
|
|
1090
1246
|
}
|
|
1091
1247
|
update(id, input) {
|
|
1092
|
-
return this.client.patch(`/comments/${encodeURIComponent(
|
|
1248
|
+
return this.client.patch(`/comments/${encodeURIComponent(requiredId3(id))}`, input);
|
|
1093
1249
|
}
|
|
1094
1250
|
delete(id) {
|
|
1095
|
-
return this.client.delete(`/comments/${encodeURIComponent(
|
|
1251
|
+
return this.client.delete(`/comments/${encodeURIComponent(requiredId3(id))}`);
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
|
|
1257
|
+
// ../morula-api-client/dist/dashboard.js
|
|
1258
|
+
function requiredId4(id) {
|
|
1259
|
+
const normalized = id.trim();
|
|
1260
|
+
if (!normalized)
|
|
1261
|
+
throw new TypeError("space id is required");
|
|
1262
|
+
return normalized;
|
|
1263
|
+
}
|
|
1264
|
+
var DashboardApi;
|
|
1265
|
+
var init_dashboard = __esm({
|
|
1266
|
+
"../morula-api-client/dist/dashboard.js"() {
|
|
1267
|
+
"use strict";
|
|
1268
|
+
DashboardApi = class {
|
|
1269
|
+
client;
|
|
1270
|
+
constructor(client) {
|
|
1271
|
+
this.client = client;
|
|
1272
|
+
}
|
|
1273
|
+
get(spaceId) {
|
|
1274
|
+
return this.client.get(`/spaces/${encodeURIComponent(requiredId4(spaceId))}/dashboard`);
|
|
1275
|
+
}
|
|
1276
|
+
statistics(spaceId) {
|
|
1277
|
+
return this.client.get(`/spaces/${encodeURIComponent(requiredId4(spaceId))}/statistics`);
|
|
1278
|
+
}
|
|
1279
|
+
workload(spaceId) {
|
|
1280
|
+
return this.client.get(`/spaces/${encodeURIComponent(requiredId4(spaceId))}/workload`);
|
|
1281
|
+
}
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
// ../morula-api-client/dist/document.js
|
|
1287
|
+
function requiredId5(id) {
|
|
1288
|
+
const normalized = String(id).trim();
|
|
1289
|
+
if (!normalized)
|
|
1290
|
+
throw new TypeError("document id is required");
|
|
1291
|
+
return normalized;
|
|
1292
|
+
}
|
|
1293
|
+
var DocumentApi;
|
|
1294
|
+
var init_document = __esm({
|
|
1295
|
+
"../morula-api-client/dist/document.js"() {
|
|
1296
|
+
"use strict";
|
|
1297
|
+
DocumentApi = class {
|
|
1298
|
+
client;
|
|
1299
|
+
constructor(client) {
|
|
1300
|
+
this.client = client;
|
|
1301
|
+
}
|
|
1302
|
+
list(query = {}) {
|
|
1303
|
+
return this.client.get("/documents", query);
|
|
1304
|
+
}
|
|
1305
|
+
get(id) {
|
|
1306
|
+
return this.client.get(`/documents/${encodeURIComponent(requiredId5(id))}`);
|
|
1307
|
+
}
|
|
1308
|
+
/** 已解析归档的文档内容(Markdown 正文等) */
|
|
1309
|
+
artifacts(id) {
|
|
1310
|
+
return this.client.get(`/documents/${encodeURIComponent(requiredId5(id))}/artifacts`);
|
|
1311
|
+
}
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1316
|
+
// ../morula-api-client/dist/object-meta.js
|
|
1317
|
+
function requiredId6(id) {
|
|
1318
|
+
const normalized = id.trim();
|
|
1319
|
+
if (!normalized)
|
|
1320
|
+
throw new TypeError("object id is required");
|
|
1321
|
+
return normalized;
|
|
1322
|
+
}
|
|
1323
|
+
function requiredKey(key) {
|
|
1324
|
+
const normalized = key.trim();
|
|
1325
|
+
if (!normalized)
|
|
1326
|
+
throw new TypeError("meta key is required");
|
|
1327
|
+
return normalized;
|
|
1328
|
+
}
|
|
1329
|
+
var ObjectMetaApi;
|
|
1330
|
+
var init_object_meta = __esm({
|
|
1331
|
+
"../morula-api-client/dist/object-meta.js"() {
|
|
1332
|
+
"use strict";
|
|
1333
|
+
ObjectMetaApi = class {
|
|
1334
|
+
client;
|
|
1335
|
+
constructor(client) {
|
|
1336
|
+
this.client = client;
|
|
1337
|
+
}
|
|
1338
|
+
list(objectId) {
|
|
1339
|
+
return this.client.get(`/objects/${encodeURIComponent(requiredId6(objectId))}/agent-meta`);
|
|
1340
|
+
}
|
|
1341
|
+
set(objectId, key, value) {
|
|
1342
|
+
return this.client.request(`/objects/${encodeURIComponent(requiredId6(objectId))}/agent-meta/${encodeURIComponent(requiredKey(key))}`, { method: "PUT", body: { value } });
|
|
1343
|
+
}
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
1347
|
+
|
|
1348
|
+
// ../morula-api-client/dist/repo.js
|
|
1349
|
+
function requiredId7(id) {
|
|
1350
|
+
const normalized = id.trim();
|
|
1351
|
+
if (!normalized)
|
|
1352
|
+
throw new TypeError("space id is required");
|
|
1353
|
+
return normalized;
|
|
1354
|
+
}
|
|
1355
|
+
var RepoApi;
|
|
1356
|
+
var init_repo = __esm({
|
|
1357
|
+
"../morula-api-client/dist/repo.js"() {
|
|
1358
|
+
"use strict";
|
|
1359
|
+
RepoApi = class {
|
|
1360
|
+
client;
|
|
1361
|
+
constructor(client) {
|
|
1362
|
+
this.client = client;
|
|
1363
|
+
}
|
|
1364
|
+
/** 目录清单(path 缺省 = 仓库根) */
|
|
1365
|
+
files(spaceId, query = {}) {
|
|
1366
|
+
return this.client.get(`/spaces/${encodeURIComponent(requiredId7(spaceId))}/repo/files`, query);
|
|
1367
|
+
}
|
|
1368
|
+
/** 单文件内容(path 必填) */
|
|
1369
|
+
read(spaceId, query) {
|
|
1370
|
+
return this.client.get(`/spaces/${encodeURIComponent(requiredId7(spaceId))}/repo/file`, query);
|
|
1096
1371
|
}
|
|
1097
1372
|
};
|
|
1098
1373
|
}
|
|
1099
1374
|
});
|
|
1100
1375
|
|
|
1101
1376
|
// ../morula-api-client/dist/report.js
|
|
1102
|
-
function
|
|
1377
|
+
function requiredId8(id, label = "report") {
|
|
1103
1378
|
const normalized = id.trim();
|
|
1104
1379
|
if (!normalized)
|
|
1105
1380
|
throw new TypeError(`${label} id is required`);
|
|
@@ -1118,17 +1393,17 @@ var init_report = __esm({
|
|
|
1118
1393
|
return this.client.post("/agent-reports", input);
|
|
1119
1394
|
}
|
|
1120
1395
|
get(id) {
|
|
1121
|
-
return this.client.get(`/agent-reports/${encodeURIComponent(
|
|
1396
|
+
return this.client.get(`/agent-reports/${encodeURIComponent(requiredId8(id))}`);
|
|
1122
1397
|
}
|
|
1123
1398
|
listByAutopilot(autopilotId, query = {}) {
|
|
1124
|
-
return this.client.get(`/agent-autopilots/${encodeURIComponent(
|
|
1399
|
+
return this.client.get(`/agent-autopilots/${encodeURIComponent(requiredId8(autopilotId, "autopilot"))}/reports`, { ...query });
|
|
1125
1400
|
}
|
|
1126
1401
|
};
|
|
1127
1402
|
}
|
|
1128
1403
|
});
|
|
1129
1404
|
|
|
1130
1405
|
// ../morula-api-client/dist/space-member.js
|
|
1131
|
-
function
|
|
1406
|
+
function requiredId9(id) {
|
|
1132
1407
|
const normalized = id.trim();
|
|
1133
1408
|
if (!normalized)
|
|
1134
1409
|
throw new TypeError("space member id is required");
|
|
@@ -1147,23 +1422,23 @@ var init_space_member = __esm({
|
|
|
1147
1422
|
return this.client.get("/space-members", { spaceId, ...query });
|
|
1148
1423
|
}
|
|
1149
1424
|
get(id) {
|
|
1150
|
-
return this.client.get(`/space-members/${encodeURIComponent(
|
|
1425
|
+
return this.client.get(`/space-members/${encodeURIComponent(requiredId9(id))}`);
|
|
1151
1426
|
}
|
|
1152
1427
|
create(input) {
|
|
1153
1428
|
return this.client.post("/space-members", input);
|
|
1154
1429
|
}
|
|
1155
1430
|
update(id, input) {
|
|
1156
|
-
return this.client.patch(`/space-members/${encodeURIComponent(
|
|
1431
|
+
return this.client.patch(`/space-members/${encodeURIComponent(requiredId9(id))}`, input);
|
|
1157
1432
|
}
|
|
1158
1433
|
delete(id) {
|
|
1159
|
-
return this.client.delete(`/space-members/${encodeURIComponent(
|
|
1434
|
+
return this.client.delete(`/space-members/${encodeURIComponent(requiredId9(id))}`);
|
|
1160
1435
|
}
|
|
1161
1436
|
};
|
|
1162
1437
|
}
|
|
1163
1438
|
});
|
|
1164
1439
|
|
|
1165
1440
|
// ../morula-api-client/dist/space.js
|
|
1166
|
-
function
|
|
1441
|
+
function requiredId10(id) {
|
|
1167
1442
|
const normalized = id.trim();
|
|
1168
1443
|
if (!normalized)
|
|
1169
1444
|
throw new TypeError("space id is required");
|
|
@@ -1182,23 +1457,51 @@ var init_space = __esm({
|
|
|
1182
1457
|
return this.client.get("/spaces", query);
|
|
1183
1458
|
}
|
|
1184
1459
|
get(id) {
|
|
1185
|
-
return this.client.get(`/spaces/${encodeURIComponent(
|
|
1460
|
+
return this.client.get(`/spaces/${encodeURIComponent(requiredId10(id))}`);
|
|
1186
1461
|
}
|
|
1187
1462
|
create(input) {
|
|
1188
1463
|
return this.client.post("/spaces", input);
|
|
1189
1464
|
}
|
|
1190
1465
|
update(id, input) {
|
|
1191
|
-
return this.client.patch(`/spaces/${encodeURIComponent(
|
|
1466
|
+
return this.client.patch(`/spaces/${encodeURIComponent(requiredId10(id))}`, input);
|
|
1192
1467
|
}
|
|
1193
1468
|
delete(id) {
|
|
1194
|
-
return this.client.delete(`/spaces/${encodeURIComponent(
|
|
1469
|
+
return this.client.delete(`/spaces/${encodeURIComponent(requiredId10(id))}`);
|
|
1470
|
+
}
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
});
|
|
1474
|
+
|
|
1475
|
+
// ../morula-api-client/dist/steward.js
|
|
1476
|
+
function requiredId11(id) {
|
|
1477
|
+
const normalized = id.trim();
|
|
1478
|
+
if (!normalized)
|
|
1479
|
+
throw new TypeError("project id is required");
|
|
1480
|
+
return normalized;
|
|
1481
|
+
}
|
|
1482
|
+
var StewardApi;
|
|
1483
|
+
var init_steward = __esm({
|
|
1484
|
+
"../morula-api-client/dist/steward.js"() {
|
|
1485
|
+
"use strict";
|
|
1486
|
+
StewardApi = class {
|
|
1487
|
+
client;
|
|
1488
|
+
constructor(client) {
|
|
1489
|
+
this.client = client;
|
|
1490
|
+
}
|
|
1491
|
+
/** 管家信息(未接管的空间返回 null) */
|
|
1492
|
+
get(projectId) {
|
|
1493
|
+
return this.client.get(`/stewards/projects/${encodeURIComponent(requiredId11(projectId))}`);
|
|
1494
|
+
}
|
|
1495
|
+
/** 派单:建任务并派发(目标状态由 state 决定,backlog 不派发执行) */
|
|
1496
|
+
dispatch(projectId, input) {
|
|
1497
|
+
return this.client.post(`/stewards/projects/${encodeURIComponent(requiredId11(projectId))}/dispatch`, input);
|
|
1195
1498
|
}
|
|
1196
1499
|
};
|
|
1197
1500
|
}
|
|
1198
1501
|
});
|
|
1199
1502
|
|
|
1200
1503
|
// ../morula-api-client/dist/task.js
|
|
1201
|
-
function
|
|
1504
|
+
function requiredId12(id) {
|
|
1202
1505
|
const normalized = id.trim();
|
|
1203
1506
|
if (!normalized)
|
|
1204
1507
|
throw new TypeError("task id is required");
|
|
@@ -1217,16 +1520,16 @@ var init_task = __esm({
|
|
|
1217
1520
|
return this.client.get("/objects", query);
|
|
1218
1521
|
}
|
|
1219
1522
|
get(id) {
|
|
1220
|
-
return this.client.get(`/objects/${encodeURIComponent(
|
|
1523
|
+
return this.client.get(`/objects/${encodeURIComponent(requiredId12(id))}`);
|
|
1221
1524
|
}
|
|
1222
1525
|
create(input) {
|
|
1223
1526
|
return this.client.post("/objects", input);
|
|
1224
1527
|
}
|
|
1225
1528
|
update(id, input) {
|
|
1226
|
-
return this.client.patch(`/objects/${encodeURIComponent(
|
|
1529
|
+
return this.client.patch(`/objects/${encodeURIComponent(requiredId12(id))}`, input);
|
|
1227
1530
|
}
|
|
1228
1531
|
delete(id) {
|
|
1229
|
-
return this.client.delete(`/objects/${encodeURIComponent(
|
|
1532
|
+
return this.client.delete(`/objects/${encodeURIComponent(requiredId12(id))}`);
|
|
1230
1533
|
}
|
|
1231
1534
|
/** 查询空间字段模板(字段 key → id 映射、options) */
|
|
1232
1535
|
getSchemaFields(spaceId) {
|
|
@@ -1293,44 +1596,76 @@ var MorulaClient;
|
|
|
1293
1596
|
var init_dist = __esm({
|
|
1294
1597
|
"../morula-api-client/dist/index.js"() {
|
|
1295
1598
|
"use strict";
|
|
1599
|
+
init_agent_task();
|
|
1296
1600
|
init_attachment();
|
|
1297
1601
|
init_auth();
|
|
1602
|
+
init_autopilot();
|
|
1603
|
+
init_ci();
|
|
1298
1604
|
init_client();
|
|
1299
1605
|
init_comment();
|
|
1606
|
+
init_dashboard();
|
|
1607
|
+
init_document();
|
|
1608
|
+
init_object_meta();
|
|
1609
|
+
init_repo();
|
|
1300
1610
|
init_report();
|
|
1301
1611
|
init_space_member();
|
|
1302
1612
|
init_space();
|
|
1613
|
+
init_steward();
|
|
1303
1614
|
init_task();
|
|
1304
1615
|
init_webhook();
|
|
1616
|
+
init_agent_task();
|
|
1305
1617
|
init_attachment();
|
|
1306
1618
|
init_auth();
|
|
1619
|
+
init_autopilot();
|
|
1620
|
+
init_ci();
|
|
1307
1621
|
init_client();
|
|
1308
1622
|
init_comment();
|
|
1623
|
+
init_dashboard();
|
|
1624
|
+
init_document();
|
|
1625
|
+
init_object_meta();
|
|
1626
|
+
init_repo();
|
|
1309
1627
|
init_report();
|
|
1310
1628
|
init_space_member();
|
|
1311
1629
|
init_space();
|
|
1630
|
+
init_steward();
|
|
1312
1631
|
init_task();
|
|
1313
1632
|
init_types();
|
|
1314
1633
|
init_webhook();
|
|
1315
1634
|
MorulaClient = class {
|
|
1316
1635
|
http;
|
|
1636
|
+
agentTasks;
|
|
1317
1637
|
attachments;
|
|
1318
1638
|
auth;
|
|
1639
|
+
autopilots;
|
|
1640
|
+
ci;
|
|
1319
1641
|
comments;
|
|
1642
|
+
dashboard;
|
|
1643
|
+
documents;
|
|
1644
|
+
objectMeta;
|
|
1320
1645
|
reports;
|
|
1646
|
+
repos;
|
|
1321
1647
|
tasks;
|
|
1322
1648
|
spaces;
|
|
1323
1649
|
spaceMembers;
|
|
1650
|
+
stewards;
|
|
1324
1651
|
webhooks;
|
|
1325
1652
|
constructor(options) {
|
|
1326
1653
|
this.http = new MorulaApiClient(options);
|
|
1654
|
+
this.agentTasks = new AgentTaskApi(this.http);
|
|
1327
1655
|
this.attachments = new AttachmentApi(this.http);
|
|
1328
1656
|
this.auth = new AuthApi(this.http);
|
|
1657
|
+
this.autopilots = new AutopilotApi(this.http);
|
|
1658
|
+
this.ci = new CiApi(this.http);
|
|
1329
1659
|
this.comments = new CommentApi(this.http);
|
|
1660
|
+
this.dashboard = new DashboardApi(this.http);
|
|
1661
|
+
this.documents = new DocumentApi(this.http);
|
|
1662
|
+
this.objectMeta = new ObjectMetaApi(this.http);
|
|
1330
1663
|
this.reports = new ReportApi(this.http);
|
|
1664
|
+
this.repos = new RepoApi(this.http);
|
|
1331
1665
|
this.tasks = new TaskApi(this.http);
|
|
1332
1666
|
this.spaces = new SpaceApi(this.http);
|
|
1333
1667
|
this.spaceMembers = new SpaceMemberApi(this.http);
|
|
1668
|
+
this.stewards = new StewardApi(this.http);
|
|
1334
1669
|
this.webhooks = new WebhookApi(this.http);
|
|
1335
1670
|
}
|
|
1336
1671
|
};
|
|
@@ -1384,7 +1719,8 @@ function renderActionHelp(base, resource, action) {
|
|
|
1384
1719
|
...f.valueHint ? { valueHint: f.valueHint } : {},
|
|
1385
1720
|
required: f.required ?? false,
|
|
1386
1721
|
...f.enum ? { enum: f.enum } : {},
|
|
1387
|
-
...f.default !== void 0 ? { default: f.default } : {}
|
|
1722
|
+
...f.default !== void 0 ? { default: f.default } : {},
|
|
1723
|
+
...f.repeatable ? { repeatable: true } : {}
|
|
1388
1724
|
})),
|
|
1389
1725
|
examples: action.examples ?? [],
|
|
1390
1726
|
writes: action.writes ?? "none",
|
|
@@ -1522,6 +1858,16 @@ function validateArgs(resource, action, parsed, options = {}) {
|
|
|
1522
1858
|
hint: `${usageHint} (e.g. --${flag} ${valuePart})`
|
|
1523
1859
|
});
|
|
1524
1860
|
}
|
|
1861
|
+
for (const [name, values] of Object.entries(parsed.repeatedFlags)) {
|
|
1862
|
+
if (values.length < 2)
|
|
1863
|
+
continue;
|
|
1864
|
+
const spec = declaredFlags.find((f) => f.name === name);
|
|
1865
|
+
if (spec?.repeatable)
|
|
1866
|
+
continue;
|
|
1867
|
+
throw usage(`duplicate flag --${name} (given ${values.length} times; only the last value would be used)`, {
|
|
1868
|
+
hint: usageHint
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1525
1871
|
for (const spec of declaredFlags) {
|
|
1526
1872
|
if (!spec.required)
|
|
1527
1873
|
continue;
|
|
@@ -1631,6 +1977,8 @@ function resolveToken(flags, env, context, baseUrl, saved, options) {
|
|
|
1631
1977
|
return { token: explicitToken, tokenSource: "flag" };
|
|
1632
1978
|
if (!options.excludeTaskToken && env.MORULA_TASK_TOKEN)
|
|
1633
1979
|
return { token: env.MORULA_TASK_TOKEN, tokenSource: "env:MORULA_TASK_TOKEN" };
|
|
1980
|
+
if (!options.excludeTaskToken && env.MORULA_SESSION_TOKEN)
|
|
1981
|
+
return { token: env.MORULA_SESSION_TOKEN, tokenSource: "env:MORULA_SESSION_TOKEN" };
|
|
1634
1982
|
if (env.MORULA_TOKEN)
|
|
1635
1983
|
return { token: env.MORULA_TOKEN, tokenSource: "env:MORULA_TOKEN" };
|
|
1636
1984
|
const savedToken = saved?.url && normalizeBaseUrl(saved.url) === baseUrl ? saved.token : void 0;
|
|
@@ -1775,6 +2123,10 @@ async function run(argv, deps = {}) {
|
|
|
1775
2123
|
code: error.code,
|
|
1776
2124
|
message: error.message,
|
|
1777
2125
|
...error.retryable ? { retryable: true } : {},
|
|
2126
|
+
// 服务端业务错误码(如 SESSION_SCOPE_DENIED):`code` 是按 HTTP 状态派生的粗档位,
|
|
2127
|
+
// 行为契约要判断的是业务码——不打印出来,契约就永远不可执行
|
|
2128
|
+
// (issues/api-client-drops-business-error-code.md)。退出码仍由 `error.exitCode` 决定。
|
|
2129
|
+
...error instanceof MorulaApiError && error.errorCode ? { errorCode: error.errorCode } : {},
|
|
1778
2130
|
// 新增字段(可选):可执行的下一步 + typo 建议
|
|
1779
2131
|
...error instanceof CliError && error.hint ? { hint: error.hint } : {},
|
|
1780
2132
|
...error instanceof CliError && error.didYouMean ? { didYouMean: error.didYouMean } : {}
|
|
@@ -2293,8 +2645,27 @@ var init_daemon_config = __esm({
|
|
|
2293
2645
|
|
|
2294
2646
|
// src/daemon/daemon-lock.ts
|
|
2295
2647
|
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";
|
|
2648
|
+
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
2649
|
import { dirname as dirname4, join as join6 } from "node:path";
|
|
2650
|
+
function daemonPidPath(configDir) {
|
|
2651
|
+
return join6(configDir, "daemon", DAEMON_PID_FILE);
|
|
2652
|
+
}
|
|
2653
|
+
function readPidFile(file) {
|
|
2654
|
+
if (!existsSync5(file))
|
|
2655
|
+
return 0;
|
|
2656
|
+
try {
|
|
2657
|
+
return Number(readFileSync2(file, "utf8").trim() || 0);
|
|
2658
|
+
} catch {
|
|
2659
|
+
return 0;
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
function probeDaemonInstance(configDir) {
|
|
2663
|
+
const lock = probeDaemonLock(join6(configDir, "daemon", DAEMON_LOCK_FILE));
|
|
2664
|
+
const lockPid = lock.state === "active" ? lock.record.pid : 0;
|
|
2665
|
+
const filePid = readPidFile(daemonPidPath(configDir));
|
|
2666
|
+
const pid = lockPid || (isProcessAlive(filePid) ? filePid : 0);
|
|
2667
|
+
return { running: pid > 0, pid: pid > 0 ? pid : null, lockState: lock.state };
|
|
2668
|
+
}
|
|
2298
2669
|
function isProcessAlive(pid) {
|
|
2299
2670
|
if (!pid || pid <= 0)
|
|
2300
2671
|
return false;
|
|
@@ -2444,11 +2815,12 @@ async function stopDaemonByPid(pid, options = {}) {
|
|
|
2444
2815
|
await waitForExit(pid, 2e3, pollMs);
|
|
2445
2816
|
return { pid, outcome: "killed" };
|
|
2446
2817
|
}
|
|
2447
|
-
var DAEMON_LOCK_FILE;
|
|
2818
|
+
var DAEMON_LOCK_FILE, DAEMON_PID_FILE;
|
|
2448
2819
|
var init_daemon_lock = __esm({
|
|
2449
2820
|
"src/daemon/daemon-lock.ts"() {
|
|
2450
2821
|
"use strict";
|
|
2451
2822
|
DAEMON_LOCK_FILE = "daemon.lock";
|
|
2823
|
+
DAEMON_PID_FILE = "daemon.pid";
|
|
2452
2824
|
}
|
|
2453
2825
|
});
|
|
2454
2826
|
|
|
@@ -2458,10 +2830,9 @@ __export(portal_instances_exports, {
|
|
|
2458
2830
|
candidateConfigFiles: () => candidateConfigFiles,
|
|
2459
2831
|
createPortalEnvironmentResolver: () => createPortalEnvironmentResolver,
|
|
2460
2832
|
normalizeBaseUrl: () => normalizeBaseUrl2,
|
|
2461
|
-
parseBaseUrl: () => parseBaseUrl
|
|
2462
|
-
readPidFile: () => readPidFile
|
|
2833
|
+
parseBaseUrl: () => parseBaseUrl
|
|
2463
2834
|
});
|
|
2464
|
-
import {
|
|
2835
|
+
import { readdirSync, statSync } from "node:fs";
|
|
2465
2836
|
import { homedir as homedir5 } from "node:os";
|
|
2466
2837
|
import { dirname as dirname5, join as join7 } from "node:path";
|
|
2467
2838
|
function parseBaseUrl(raw) {
|
|
@@ -2527,16 +2898,6 @@ function readContexts(file) {
|
|
|
2527
2898
|
return null;
|
|
2528
2899
|
}
|
|
2529
2900
|
}
|
|
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
2901
|
function createPortalEnvironmentResolver(options = {}) {
|
|
2541
2902
|
const env = options.env ?? process.env;
|
|
2542
2903
|
const log = options.log ?? (() => {
|
|
@@ -2565,7 +2926,7 @@ function createPortalEnvironmentResolver(options = {}) {
|
|
|
2565
2926
|
baseUrl: entry.url,
|
|
2566
2927
|
normalized,
|
|
2567
2928
|
token: entry.token,
|
|
2568
|
-
hasLiveDaemon:
|
|
2929
|
+
hasLiveDaemon: probeDaemonInstance(configDir).running,
|
|
2569
2930
|
isDefaultDir: configDir === defaultConfigDir,
|
|
2570
2931
|
configMtimeMs: configMtimeMs(file)
|
|
2571
2932
|
});
|
|
@@ -2611,6 +2972,25 @@ function createPortalEnvironmentResolver(options = {}) {
|
|
|
2611
2972
|
return void 0;
|
|
2612
2973
|
}
|
|
2613
2974
|
},
|
|
2975
|
+
daemonId: () => {
|
|
2976
|
+
try {
|
|
2977
|
+
return loadDaemonConfig(new FileConfigStore(join7(instance.configDir, "config.yaml")))?.daemonId;
|
|
2978
|
+
} catch {
|
|
2979
|
+
return void 0;
|
|
2980
|
+
}
|
|
2981
|
+
},
|
|
2982
|
+
isDeviceOnline: async (daemonId) => {
|
|
2983
|
+
if (!instance.token)
|
|
2984
|
+
return null;
|
|
2985
|
+
try {
|
|
2986
|
+
const client = createMorulaClient({ baseUrl: instance.baseUrl, token: instance.token, fetch: options.fetch });
|
|
2987
|
+
const rows = await client.http.get("/agent-runtimes");
|
|
2988
|
+
return (Array.isArray(rows) ? rows : []).some((row) => row.daemonId === daemonId && row.status === "online");
|
|
2989
|
+
} catch (error) {
|
|
2990
|
+
log(`[portal] \u67E5\u8BE2 ${instance.context} \u7684\u8BBE\u5907\u5728\u7EBF\u72B6\u6001\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`);
|
|
2991
|
+
return null;
|
|
2992
|
+
}
|
|
2993
|
+
},
|
|
2614
2994
|
validateToken: async (token) => {
|
|
2615
2995
|
if (!instance.token)
|
|
2616
2996
|
return false;
|
|
@@ -2669,9 +3049,13 @@ var init_portal_instances = __esm({
|
|
|
2669
3049
|
var portal_server_exports = {};
|
|
2670
3050
|
__export(portal_server_exports, {
|
|
2671
3051
|
DEFAULT_PORTAL_PORT: () => DEFAULT_PORTAL_PORT,
|
|
2672
|
-
PortalServer: () => PortalServer
|
|
3052
|
+
PortalServer: () => PortalServer,
|
|
3053
|
+
REGISTRATION_CONFIRM_TIMEOUT_MS: () => REGISTRATION_CONFIRM_TIMEOUT_MS,
|
|
3054
|
+
START_CONFIRM_TIMEOUT_MS: () => START_CONFIRM_TIMEOUT_MS,
|
|
3055
|
+
waitForDaemonRegistration: () => waitForDaemonRegistration,
|
|
3056
|
+
waitForDaemonStart: () => waitForDaemonStart
|
|
2673
3057
|
});
|
|
2674
|
-
import {
|
|
3058
|
+
import { readFileSync as readFileSync3, rmSync as rmSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2675
3059
|
import { createServer as createServer2 } from "node:http";
|
|
2676
3060
|
import { homedir as homedir6 } from "node:os";
|
|
2677
3061
|
import { join as join8 } from "node:path";
|
|
@@ -2679,35 +3063,61 @@ function stateDir() {
|
|
|
2679
3063
|
return process.env.MORULA_CONFIG_DIR ?? join8(homedir6(), ".morula-cli");
|
|
2680
3064
|
}
|
|
2681
3065
|
function pidFileIn(configDir) {
|
|
2682
|
-
return join8(configDir, "daemon",
|
|
3066
|
+
return join8(configDir, "daemon", DAEMON_PID_FILE);
|
|
2683
3067
|
}
|
|
2684
3068
|
function daemonLogIn(configDir) {
|
|
2685
3069
|
return join8(configDir, "daemon", "daemon.log");
|
|
2686
3070
|
}
|
|
2687
|
-
function
|
|
2688
|
-
|
|
2689
|
-
|
|
3071
|
+
async function waitForDaemonStart(configDir, childPid, timeoutMs = START_CONFIRM_TIMEOUT_MS) {
|
|
3072
|
+
const deadline = Date.now() + timeoutMs;
|
|
3073
|
+
for (; ; ) {
|
|
3074
|
+
const probe = probeDaemonInstance(configDir);
|
|
3075
|
+
if (probe.running)
|
|
3076
|
+
return { running: true, pid: probe.pid };
|
|
3077
|
+
if (!isProcessAlive(childPid))
|
|
3078
|
+
return { running: false, reason: "exited" };
|
|
3079
|
+
if (Date.now() >= deadline)
|
|
3080
|
+
return { running: false, reason: "timeout" };
|
|
3081
|
+
await new Promise((resolve2) => setTimeout(resolve2, START_CONFIRM_POLL_MS));
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
async function waitForDaemonRegistration(isDeviceOnline, daemonId, childPid, timeoutMs = REGISTRATION_CONFIRM_TIMEOUT_MS) {
|
|
3085
|
+
const deadline = Date.now() + timeoutMs;
|
|
3086
|
+
let queryFailed = false;
|
|
3087
|
+
for (; ; ) {
|
|
3088
|
+
const online = await isDeviceOnline(daemonId);
|
|
3089
|
+
if (online === true)
|
|
3090
|
+
return "registered";
|
|
3091
|
+
if (online === null)
|
|
3092
|
+
queryFailed = true;
|
|
3093
|
+
if (!isProcessAlive(childPid))
|
|
3094
|
+
return "exited";
|
|
3095
|
+
if (Date.now() >= deadline)
|
|
3096
|
+
return queryFailed ? "unknown" : "not-registered";
|
|
3097
|
+
await new Promise((resolve2) => setTimeout(resolve2, START_CONFIRM_POLL_MS));
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
function tailDaemonLog(configDir, maxLines = 5) {
|
|
2690
3101
|
try {
|
|
2691
|
-
|
|
2692
|
-
return
|
|
3102
|
+
const lines = readFileSync3(daemonLogIn(configDir), "utf8").trimEnd().split("\n");
|
|
3103
|
+
return lines.slice(-maxLines).join(" / ") || null;
|
|
2693
3104
|
} catch {
|
|
2694
|
-
return
|
|
3105
|
+
return null;
|
|
2695
3106
|
}
|
|
2696
3107
|
}
|
|
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;
|
|
3108
|
+
var DEFAULT_PORTAL_PORT, DEFAULT_CORS_ORIGINS, START_CONFIRM_TIMEOUT_MS, REGISTRATION_CONFIRM_TIMEOUT_MS, START_CONFIRM_POLL_MS, PortalServer;
|
|
2703
3109
|
var init_portal_server = __esm({
|
|
2704
3110
|
"src/daemon/portal-server.ts"() {
|
|
2705
3111
|
"use strict";
|
|
2706
3112
|
init_daemon_config();
|
|
3113
|
+
init_daemon_lock();
|
|
2707
3114
|
init_portal_instances();
|
|
2708
3115
|
init_spawn();
|
|
2709
3116
|
DEFAULT_PORTAL_PORT = 23491;
|
|
2710
3117
|
DEFAULT_CORS_ORIGINS = ["*"];
|
|
3118
|
+
START_CONFIRM_TIMEOUT_MS = 5e3;
|
|
3119
|
+
REGISTRATION_CONFIRM_TIMEOUT_MS = 5e3;
|
|
3120
|
+
START_CONFIRM_POLL_MS = 200;
|
|
2711
3121
|
PortalServer = class {
|
|
2712
3122
|
port;
|
|
2713
3123
|
server = null;
|
|
@@ -2848,14 +3258,13 @@ var init_portal_server = __esm({
|
|
|
2848
3258
|
if (failed)
|
|
2849
3259
|
return;
|
|
2850
3260
|
const configDir = target?.configDir ?? stateDir();
|
|
2851
|
-
const
|
|
2852
|
-
const running = isPidAlive(pid);
|
|
3261
|
+
const probe = probeDaemonInstance(configDir);
|
|
2853
3262
|
const runtimeId = target ? target.runtimeId() : this.store ? loadDaemonConfig(this.store)?.runtimeId : void 0;
|
|
2854
3263
|
const self = target ? { context: target.context, baseUrl: target.baseUrl } : this.selfEnvironment();
|
|
2855
3264
|
this.json(res, 200, {
|
|
2856
3265
|
ok: true,
|
|
2857
|
-
daemonRunning: running,
|
|
2858
|
-
pid:
|
|
3266
|
+
daemonRunning: probe.running,
|
|
3267
|
+
pid: probe.pid,
|
|
2859
3268
|
runtimeId: runtimeId ?? null,
|
|
2860
3269
|
// 支持按请求声明目标环境(前端据此决定是否走多环境路径;缺省 = 旧版 portal)
|
|
2861
3270
|
envRouting: true,
|
|
@@ -2870,10 +3279,10 @@ var init_portal_server = __esm({
|
|
|
2870
3279
|
if (!await this.authorize(req, res, target))
|
|
2871
3280
|
return;
|
|
2872
3281
|
const configDir = target?.configDir ?? stateDir();
|
|
2873
|
-
const
|
|
2874
|
-
const existing =
|
|
2875
|
-
if (
|
|
2876
|
-
this.json(res, 200, { ok: true, pid: existing, alreadyRunning: true });
|
|
3282
|
+
const where = target ? `\uFF08\u73AF\u5883 ${target.context}\uFF0C\u76EE\u5F55 ${configDir}\uFF09` : "";
|
|
3283
|
+
const existing = probeDaemonInstance(configDir);
|
|
3284
|
+
if (existing.running) {
|
|
3285
|
+
this.json(res, 200, { ok: true, pid: existing.pid, alreadyRunning: true });
|
|
2877
3286
|
return;
|
|
2878
3287
|
}
|
|
2879
3288
|
const result = spawnDaemonProcess({
|
|
@@ -2884,18 +3293,54 @@ var init_portal_server = __esm({
|
|
|
2884
3293
|
this.json(res, 500, { ok: false, error: result.error ?? `spawn \u5931\u8D25\uFF08pid=${result.pid}\uFF09` });
|
|
2885
3294
|
return;
|
|
2886
3295
|
}
|
|
3296
|
+
const confirmed = await waitForDaemonStart(configDir, result.pid);
|
|
3297
|
+
if (!confirmed.running) {
|
|
3298
|
+
if (!isProcessAlive(readPidFile(pidFileIn(configDir))))
|
|
3299
|
+
rmSync5(pidFileIn(configDir), { force: true });
|
|
3300
|
+
this.log(`[portal] daemon \u672A\u80FD\u542F\u52A8 pid=${result.pid}${where}\uFF1A${confirmed.reason}`);
|
|
3301
|
+
this.json(res, 500, {
|
|
3302
|
+
ok: false,
|
|
3303
|
+
error: confirmed.reason === "exited" ? "DAEMON_EXITED" : "DAEMON_START_TIMEOUT",
|
|
3304
|
+
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"}`,
|
|
3305
|
+
logFile: daemonLogIn(configDir)
|
|
3306
|
+
});
|
|
3307
|
+
return;
|
|
3308
|
+
}
|
|
3309
|
+
if (confirmed.pid !== result.pid) {
|
|
3310
|
+
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`);
|
|
3311
|
+
this.json(res, 200, { ok: true, pid: confirmed.pid, alreadyRunning: true });
|
|
3312
|
+
return;
|
|
3313
|
+
}
|
|
3314
|
+
const daemonId = target?.daemonId();
|
|
3315
|
+
if (target && daemonId) {
|
|
3316
|
+
const reg = await waitForDaemonRegistration(target.isDeviceOnline, daemonId, confirmed.pid);
|
|
3317
|
+
if (reg === "exited" || reg === "not-registered") {
|
|
3318
|
+
if (!isProcessAlive(readPidFile(pidFileIn(configDir))))
|
|
3319
|
+
rmSync5(pidFileIn(configDir), { force: true });
|
|
3320
|
+
this.log(`[portal] daemon \u8FDB\u7A0B\u5DF2\u8D77\u6765\u4F46\u672A\u6CE8\u518C\u4E0A\u7EBF pid=${confirmed.pid}${where}\uFF1A${reg}`);
|
|
3321
|
+
this.json(res, 500, {
|
|
3322
|
+
ok: false,
|
|
3323
|
+
error: "DAEMON_NOT_REGISTERED",
|
|
3324
|
+
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"}`,
|
|
3325
|
+
logFile: daemonLogIn(configDir)
|
|
3326
|
+
});
|
|
3327
|
+
return;
|
|
3328
|
+
}
|
|
3329
|
+
if (reg === "unknown")
|
|
3330
|
+
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`);
|
|
3331
|
+
}
|
|
2887
3332
|
try {
|
|
2888
|
-
writeFileSync4(
|
|
3333
|
+
writeFileSync4(pidFileIn(configDir), String(confirmed.pid), { mode: 384 });
|
|
2889
3334
|
} catch {
|
|
2890
3335
|
try {
|
|
2891
|
-
process.kill(
|
|
3336
|
+
process.kill(confirmed.pid, "SIGTERM");
|
|
2892
3337
|
} catch {
|
|
2893
3338
|
}
|
|
2894
3339
|
this.json(res, 500, { ok: false, error: "pid \u6587\u4EF6\u5199\u5165\u5931\u8D25" });
|
|
2895
3340
|
return;
|
|
2896
3341
|
}
|
|
2897
|
-
this.log(`[portal] daemon \u5DF2\u62C9\u8D77 pid=${
|
|
2898
|
-
this.json(res, 200, { ok: true, pid:
|
|
3342
|
+
this.log(`[portal] daemon \u5DF2\u62C9\u8D77 pid=${confirmed.pid}${where}`);
|
|
3343
|
+
this.json(res, 200, { ok: true, pid: confirmed.pid, alreadyRunning: false });
|
|
2899
3344
|
}
|
|
2900
3345
|
async handleStop(req, res, url) {
|
|
2901
3346
|
const { target, failed } = this.resolveTarget(res, url);
|
|
@@ -2904,15 +3349,15 @@ var init_portal_server = __esm({
|
|
|
2904
3349
|
if (!await this.authorize(req, res, target))
|
|
2905
3350
|
return;
|
|
2906
3351
|
const configDir = target?.configDir ?? stateDir();
|
|
2907
|
-
const existing =
|
|
2908
|
-
if (
|
|
3352
|
+
const existing = probeDaemonInstance(configDir);
|
|
3353
|
+
if (existing.running) {
|
|
2909
3354
|
try {
|
|
2910
|
-
process.kill(existing, "SIGTERM");
|
|
3355
|
+
process.kill(existing.pid, "SIGTERM");
|
|
2911
3356
|
} catch {
|
|
2912
3357
|
}
|
|
2913
|
-
this.log(`[portal] daemon \u505C\u6B62\u4FE1\u53F7\u5DF2\u53D1\u9001 pid=${existing}${target ? `\uFF08\u73AF\u5883 ${target.context}\uFF09` : ""}`);
|
|
3358
|
+
this.log(`[portal] daemon \u505C\u6B62\u4FE1\u53F7\u5DF2\u53D1\u9001 pid=${existing.pid}${target ? `\uFF08\u73AF\u5883 ${target.context}\uFF09` : ""}`);
|
|
2914
3359
|
}
|
|
2915
|
-
this.json(res, 200, { ok: true, pid: existing
|
|
3360
|
+
this.json(res, 200, { ok: true, pid: existing.pid });
|
|
2916
3361
|
}
|
|
2917
3362
|
/**
|
|
2918
3363
|
* start/stop 鉴权:目标环境用**它自己的**服务端校验 token(这正是跨环境 401 的修复点);
|
|
@@ -3020,6 +3465,7 @@ __export(acp_connection_exports, {
|
|
|
3020
3465
|
AcpConnection: () => AcpConnection,
|
|
3021
3466
|
DEFAULT_ACP_INIT_TIMEOUT_MS: () => DEFAULT_ACP_INIT_TIMEOUT_MS,
|
|
3022
3467
|
countDimAcpProcesses: () => countDimAcpProcesses,
|
|
3468
|
+
normalizeAcpConfigOptions: () => normalizeAcpConfigOptions,
|
|
3023
3469
|
resolveAcpInitTimeoutMs: () => resolveAcpInitTimeoutMs
|
|
3024
3470
|
});
|
|
3025
3471
|
import { execFileSync as execFileSync4, spawn as spawn3 } from "node:child_process";
|
|
@@ -3048,6 +3494,16 @@ function countDimAcpProcesses() {
|
|
|
3048
3494
|
return null;
|
|
3049
3495
|
}
|
|
3050
3496
|
}
|
|
3497
|
+
function normalizeAcpConfigOptions(raw) {
|
|
3498
|
+
if (Array.isArray(raw))
|
|
3499
|
+
return raw;
|
|
3500
|
+
if (raw && typeof raw === "object") {
|
|
3501
|
+
const nested = raw.configOptions;
|
|
3502
|
+
if (Array.isArray(nested))
|
|
3503
|
+
return nested;
|
|
3504
|
+
}
|
|
3505
|
+
return null;
|
|
3506
|
+
}
|
|
3051
3507
|
var DEFAULT_ACP_INIT_TIMEOUT_MS, cachedDimVersion, requestSeq, AcpConnection;
|
|
3052
3508
|
var init_acp_connection = __esm({
|
|
3053
3509
|
"src/daemon/acp/acp-connection.ts"() {
|
|
@@ -3063,6 +3519,7 @@ var init_acp_connection = __esm({
|
|
|
3063
3519
|
outcome: { outcome: "selected", optionId: "allow" }
|
|
3064
3520
|
}));
|
|
3065
3521
|
this.onExit = options.onExit;
|
|
3522
|
+
this.elicitationHandler = options.onElicitation;
|
|
3066
3523
|
}
|
|
3067
3524
|
options;
|
|
3068
3525
|
child = null;
|
|
@@ -3070,6 +3527,7 @@ var init_acp_connection = __esm({
|
|
|
3070
3527
|
pending = /* @__PURE__ */ new Map();
|
|
3071
3528
|
exited = false;
|
|
3072
3529
|
permissionHandler;
|
|
3530
|
+
elicitationHandler;
|
|
3073
3531
|
onExit;
|
|
3074
3532
|
/** sessionId → 当前 update 处理器(同一 session 单活跃 prompt) */
|
|
3075
3533
|
updateHandlers = /* @__PURE__ */ new Map();
|
|
@@ -3103,9 +3561,9 @@ var init_acp_connection = __esm({
|
|
|
3103
3561
|
process.stderr.write(`[daemon] dim spawn \u5931\u8D25 ${dimPath}\uFF08\u5C1D\u8BD5\u4E0B\u4E00\u4E2A\u5019\u9009\uFF09: ${message}
|
|
3104
3562
|
`);
|
|
3105
3563
|
try {
|
|
3106
|
-
const { existsSync:
|
|
3564
|
+
const { existsSync: existsSync18 } = await import("node:fs");
|
|
3107
3565
|
process.stderr.write(
|
|
3108
|
-
`[daemon] diag: fileExists=${
|
|
3566
|
+
`[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
3567
|
`
|
|
3110
3568
|
);
|
|
3111
3569
|
} catch {
|
|
@@ -3155,7 +3613,12 @@ var init_acp_connection = __esm({
|
|
|
3155
3613
|
child.stdout.on("data", (d) => this.onStdout(d.toString()));
|
|
3156
3614
|
const init = {
|
|
3157
3615
|
protocolVersion: 1,
|
|
3158
|
-
|
|
3616
|
+
// elicitation.form 只在**有处理器时**声明:声明了却不应答会让 dim 挂起等用户输入。
|
|
3617
|
+
// 形状是对象而非 boolean(V2 实测:boolean 被 dim 以 -32602 拒绝)。
|
|
3618
|
+
clientCapabilities: {
|
|
3619
|
+
auth: { terminal: true },
|
|
3620
|
+
...this.elicitationHandler ? { elicitation: { form: {} } } : {}
|
|
3621
|
+
},
|
|
3159
3622
|
clientInfo: { name: "morula-runtime", version: "1.0.0" }
|
|
3160
3623
|
};
|
|
3161
3624
|
const initTimeoutMs = resolveAcpInitTimeoutMs();
|
|
@@ -3200,26 +3663,38 @@ var init_acp_connection = __esm({
|
|
|
3200
3663
|
return this.request("session/set_config_option", { sessionId, configId, value }).then(() => {
|
|
3201
3664
|
});
|
|
3202
3665
|
}
|
|
3666
|
+
/**
|
|
3667
|
+
* 设置会话配置项并**全量返回** configOptions(ACP 规范:set_config_option 响应即全量配置)。
|
|
3668
|
+
* 会话宿主用(§4.4 配置变更回写);任务链路只需 fire-and-forget,继续用 setConfigOption。
|
|
3669
|
+
*
|
|
3670
|
+
* **返回已归一化为数组**:ACP 的响应体是 `{ configOptions: ConfigOption[] }`(对象),
|
|
3671
|
+
* 而 `session/new` / `resume` 的 `configOptions` 字段本身就是数组——两处形状不同,
|
|
3672
|
+
* 调用方(会话宿主 → 服务端落库 → 前端输入区值域)必须拿到同一种形状,否则前端拿到对象会崩。
|
|
3673
|
+
*/
|
|
3674
|
+
setConfigOptionFull(sessionId, configId, value) {
|
|
3675
|
+
return this.request("session/set_config_option", { sessionId, configId, value }).then((res) => normalizeAcpConfigOptions(res) ?? []);
|
|
3676
|
+
}
|
|
3203
3677
|
/**
|
|
3204
3678
|
* 发起 prompt:立即返回;事件流经 updateHandler 推送;返回的 Promise 在
|
|
3205
3679
|
* turn 结束(prompt 响应)时 resolve。
|
|
3206
3680
|
*
|
|
3207
3681
|
* @param sessionId ACP 会话 ID
|
|
3208
|
-
* @param
|
|
3682
|
+
* @param prompt 用户输入。字符串 = 纯文本;数组 = 多模态 content block
|
|
3683
|
+
* (`image` block 用于对话附件,见 steward-core-acp-migration §五 P2-3)
|
|
3209
3684
|
* @param updateHandler 会话事件回调
|
|
3210
|
-
* @param timeoutMs prompt 整轮 RPC
|
|
3211
|
-
*
|
|
3212
|
-
*
|
|
3213
|
-
*
|
|
3685
|
+
* @param timeoutMs prompt 整轮 RPC 超时。**未设 / <= 0 = 不设超时(默认)**:dim 直到整轮
|
|
3686
|
+
* agent 循环结束(stopReason)才回复 prompt,多步工具执行/长模型生成都会占满窗口,
|
|
3687
|
+
* 给 RPC 层设超时等于给「合法的长回合」埋误杀点。限时与否由调用方(executor 的
|
|
3688
|
+
* 单回合预算)决定,executor 默认即传 0(不设超时)。
|
|
3214
3689
|
*/
|
|
3215
|
-
prompt(sessionId,
|
|
3690
|
+
prompt(sessionId, prompt, updateHandler, timeoutMs = 0) {
|
|
3216
3691
|
if (this.updateHandlers.has(sessionId)) {
|
|
3217
3692
|
return Promise.reject(new Error(`session ${sessionId} \u5DF2\u6709\u6D3B\u8DC3 prompt\uFF08ACP \u5355\u6D3B\u8DC3\u7EA6\u675F\uFF09`));
|
|
3218
3693
|
}
|
|
3219
3694
|
this.updateHandlers.set(sessionId, updateHandler);
|
|
3220
3695
|
const params = {
|
|
3221
3696
|
sessionId,
|
|
3222
|
-
prompt: [{ type: "text", text }],
|
|
3697
|
+
prompt: typeof prompt === "string" ? [{ type: "text", text: prompt }] : prompt,
|
|
3223
3698
|
messageId: `m-${randomUUID2().slice(0, 8)}`
|
|
3224
3699
|
};
|
|
3225
3700
|
return this.request("session/prompt", params, timeoutMs).then(
|
|
@@ -3278,17 +3753,20 @@ var init_acp_connection = __esm({
|
|
|
3278
3753
|
return Promise.reject(new Error(`dim acp \u8FDB\u7A0B\u4E0D\u53EF\u7528\uFF08${method}\uFF09`));
|
|
3279
3754
|
const id = ++requestSeq;
|
|
3280
3755
|
return new Promise((resolve2, reject) => {
|
|
3281
|
-
const
|
|
3756
|
+
const onTimeout = () => {
|
|
3282
3757
|
this.pending.delete(id);
|
|
3283
3758
|
reject(new Error(`dim acp ${method} \u8D85\u65F6\uFF08${Math.round(timeoutMs / 1e3)}s\uFF09`));
|
|
3284
|
-
}
|
|
3759
|
+
};
|
|
3760
|
+
const timer = timeoutMs > 0 ? setTimeout(onTimeout, timeoutMs) : null;
|
|
3285
3761
|
this.pending.set(id, {
|
|
3286
3762
|
resolve: (v) => {
|
|
3287
|
-
|
|
3763
|
+
if (timer)
|
|
3764
|
+
clearTimeout(timer);
|
|
3288
3765
|
resolve2(v);
|
|
3289
3766
|
},
|
|
3290
3767
|
reject: (e) => {
|
|
3291
|
-
|
|
3768
|
+
if (timer)
|
|
3769
|
+
clearTimeout(timer);
|
|
3292
3770
|
reject(e);
|
|
3293
3771
|
}
|
|
3294
3772
|
});
|
|
@@ -3359,6 +3837,21 @@ var init_acp_connection = __esm({
|
|
|
3359
3837
|
);
|
|
3360
3838
|
return;
|
|
3361
3839
|
}
|
|
3840
|
+
if (method === "elicitation/create") {
|
|
3841
|
+
const id = msg.id;
|
|
3842
|
+
const params = msg.params;
|
|
3843
|
+
const handler = this.elicitationHandler;
|
|
3844
|
+
if (!handler) {
|
|
3845
|
+
this.respond(id, { action: "decline" });
|
|
3846
|
+
return;
|
|
3847
|
+
}
|
|
3848
|
+
void handler(params).then(
|
|
3849
|
+
(response) => this.respond(id, response),
|
|
3850
|
+
// 处理器异常(含超时):回 cancel,让 agent 走「用户没答」的分支,而不是挂死
|
|
3851
|
+
() => this.respond(id, { action: "cancel" })
|
|
3852
|
+
);
|
|
3853
|
+
return;
|
|
3854
|
+
}
|
|
3362
3855
|
if (method === "$/cancel_request") {
|
|
3363
3856
|
return;
|
|
3364
3857
|
}
|
|
@@ -3378,7 +3871,7 @@ __export(dim_home_exports, {
|
|
|
3378
3871
|
resolveIsolatedDimEnv: () => resolveIsolatedDimEnv
|
|
3379
3872
|
});
|
|
3380
3873
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
3381
|
-
import { chmodSync as chmodSync2, copyFileSync, existsSync as
|
|
3874
|
+
import { chmodSync as chmodSync2, copyFileSync, existsSync as existsSync6, readFileSync as readFileSync4, rmSync as rmSync6 } from "node:fs";
|
|
3382
3875
|
import { homedir as homedir8 } from "node:os";
|
|
3383
3876
|
import { join as join10 } from "node:path";
|
|
3384
3877
|
function isolatedDimHome() {
|
|
@@ -3405,11 +3898,11 @@ __export(dim_model_exports, {
|
|
|
3405
3898
|
import { createRequire } from "node:module";
|
|
3406
3899
|
import { spawnSync } from "node:child_process";
|
|
3407
3900
|
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
|
|
3901
|
+
import { homedir as homedir11, tmpdir } from "node:os";
|
|
3902
|
+
import { join as join14 } from "node:path";
|
|
3410
3903
|
function dimSqlitePath(env) {
|
|
3411
3904
|
const home = env?.DIMCODE_HOME?.trim();
|
|
3412
|
-
const candidates = home && home !== "" ? [
|
|
3905
|
+
const candidates = home && home !== "" ? [join14(home, "v2", "dimcode.sqlite"), join14(home, "dimcode.sqlite")] : [join14(homedir11(), ".dimcode", "v2", "dimcode.sqlite")];
|
|
3413
3906
|
return candidates.find((p) => existsSync10(p)) ?? null;
|
|
3414
3907
|
}
|
|
3415
3908
|
function listDimModels(dimPath = "dim", env) {
|
|
@@ -3444,12 +3937,12 @@ function listDimModels(dimPath = "dim", env) {
|
|
|
3444
3937
|
function hostDimEnv(env) {
|
|
3445
3938
|
const home = env?.DIMCODE_HOME?.trim();
|
|
3446
3939
|
if (home && home !== "" && home.includes(".morula") && home.includes("dimcode")) {
|
|
3447
|
-
return { ...process.env, DIMCODE_HOME:
|
|
3940
|
+
return { ...process.env, DIMCODE_HOME: join14(homedir11(), ".dimcode", "v2") };
|
|
3448
3941
|
}
|
|
3449
3942
|
return env ? { ...env } : null;
|
|
3450
3943
|
}
|
|
3451
3944
|
function listDimModelsFromCli(dimPath, env) {
|
|
3452
|
-
const tmpFile =
|
|
3945
|
+
const tmpFile = join14(tmpdir(), `${DIM_MODELS_TMP_PREFIX}${process.pid}-${Date.now()}.json`);
|
|
3453
3946
|
const fd = openSync3(tmpFile, "w");
|
|
3454
3947
|
let collected = "";
|
|
3455
3948
|
try {
|
|
@@ -3486,8 +3979,8 @@ function listDimModelsFromCli(dimPath, env) {
|
|
|
3486
3979
|
}
|
|
3487
3980
|
function dimOAuthTokenPath(env) {
|
|
3488
3981
|
const home = env?.DIMCODE_HOME?.trim();
|
|
3489
|
-
const dir = home && home !== "" ? home :
|
|
3490
|
-
for (const p of [
|
|
3982
|
+
const dir = home && home !== "" ? home : join14(homedir11(), ".dimcode", "v2");
|
|
3983
|
+
for (const p of [join14(dir, "auth.json"), join14(dir, "v2", "auth.json")]) {
|
|
3491
3984
|
if (existsSync10(p))
|
|
3492
3985
|
return p;
|
|
3493
3986
|
}
|
|
@@ -3632,14 +4125,14 @@ init_dist2();
|
|
|
3632
4125
|
init_dist2();
|
|
3633
4126
|
|
|
3634
4127
|
// src/build-env.ts
|
|
3635
|
-
var BUILD_VERSION2 = "0.1.0-alpha.
|
|
4128
|
+
var BUILD_VERSION2 = "0.1.0-alpha.8";
|
|
3636
4129
|
|
|
3637
4130
|
// src/commands/daemon.ts
|
|
3638
4131
|
init_dist();
|
|
3639
4132
|
init_dist2();
|
|
3640
|
-
import { existsSync as existsSync17, mkdirSync as
|
|
3641
|
-
import { homedir as
|
|
3642
|
-
import { dirname as
|
|
4133
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync14, readFileSync as readFileSync11, rmSync as rmSync10, writeFileSync as writeFileSync12 } from "node:fs";
|
|
4134
|
+
import { homedir as homedir13 } from "node:os";
|
|
4135
|
+
import { dirname as dirname10, join as join22 } from "node:path";
|
|
3643
4136
|
|
|
3644
4137
|
// src/daemon/daemon-autostart.ts
|
|
3645
4138
|
init_portal_autostart();
|
|
@@ -3708,9 +4201,9 @@ init_portal_server();
|
|
|
3708
4201
|
// src/daemon/runner.ts
|
|
3709
4202
|
init_dist2();
|
|
3710
4203
|
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
3711
|
-
import { existsSync as existsSync15, mkdirSync as
|
|
4204
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync13, statSync as statSync4, writeFileSync as writeFileSync11 } from "node:fs";
|
|
3712
4205
|
import { arch, hostname } from "node:os";
|
|
3713
|
-
import { dirname as
|
|
4206
|
+
import { dirname as dirname8, join as join20 } from "node:path";
|
|
3714
4207
|
|
|
3715
4208
|
// src/daemon/acp/acp-connection-pool.ts
|
|
3716
4209
|
var DEFAULT_IDLE_MS = 30 * 60 * 1e3;
|
|
@@ -3954,7 +4447,7 @@ var DaemonApi = class {
|
|
|
3954
4447
|
recordEvent(taskId, body) {
|
|
3955
4448
|
return this.taskRequest(taskId, `/agent-tasks/${taskId}/events`, { body });
|
|
3956
4449
|
}
|
|
3957
|
-
/** ACP 流式事件(Phase 1):daemon → Server → WS
|
|
4450
|
+
/** ACP 流式事件(Phase 1 → P2-2):daemon → Server → WS 前端(`delta` 为 transient,不落库) */
|
|
3958
4451
|
recordStreamChunk(taskId, body) {
|
|
3959
4452
|
return this.taskRequest(taskId, `/agent-tasks/${taskId}/stream-chunk`, { body });
|
|
3960
4453
|
}
|
|
@@ -4015,19 +4508,200 @@ var DaemonApi = class {
|
|
|
4015
4508
|
getGitHubToken(taskId, repo) {
|
|
4016
4509
|
return this.client.get("/vcs/daemon-token", { taskId, repo });
|
|
4017
4510
|
}
|
|
4511
|
+
// ── 会话宿主机器协议(steward-core-acp-migration §4.3/§4.4,P0 骨架) ──
|
|
4512
|
+
/** 会话级 token 缓存(每会话独立凭证,注入工作区 env MORULA_SESSION_TOKEN) */
|
|
4513
|
+
sessionTokens = /* @__PURE__ */ new Map();
|
|
4514
|
+
setSessionToken(sessionId, token) {
|
|
4515
|
+
this.sessionTokens.set(sessionId, token);
|
|
4516
|
+
}
|
|
4517
|
+
clearSessionToken(sessionId) {
|
|
4518
|
+
this.sessionTokens.delete(sessionId);
|
|
4519
|
+
}
|
|
4520
|
+
/** 会话级机器协议请求(session token 通道;无 token 时回退设备级 client 凭证) */
|
|
4521
|
+
sessionRequest(sessionId, path, options = {}) {
|
|
4522
|
+
const token = this.sessionTokens.get(sessionId);
|
|
4523
|
+
return this.client.request(path, {
|
|
4524
|
+
method: "POST",
|
|
4525
|
+
...options,
|
|
4526
|
+
...token ? { token } : {}
|
|
4527
|
+
});
|
|
4528
|
+
}
|
|
4529
|
+
/** 会话级机器协议请求(可指定 method;elicitation 查询用 GET + 长轮询) */
|
|
4530
|
+
sessionRequestWithMethod(sessionId, method, path, options = {}) {
|
|
4531
|
+
const token = this.sessionTokens.get(sessionId);
|
|
4532
|
+
return this.client.request(path, {
|
|
4533
|
+
method,
|
|
4534
|
+
...options,
|
|
4535
|
+
...token ? { token } : {}
|
|
4536
|
+
});
|
|
4537
|
+
}
|
|
4538
|
+
/** 会话宿主事件上报(→ agent_events owner=session + WS exec:session 房间) */
|
|
4539
|
+
recordSessionEvent(sessionId, body) {
|
|
4540
|
+
return this.sessionRequest(sessionId, `/agent-sessions/${sessionId}/events`, { body });
|
|
4541
|
+
}
|
|
4018
4542
|
/**
|
|
4019
|
-
*
|
|
4020
|
-
*
|
|
4021
|
-
*
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4543
|
+
* 会话流式增量上报(P2-2:transient,**不落库**;服务端转推 `agent-task:delta` 到 exec:session 房间)。
|
|
4544
|
+
* 与 `recordSessionEvent` 的语义差别:这里是「可丢的过程信号」,失败不重试、不占 seq。
|
|
4545
|
+
* `kind`:`text` = assistant 正文,`thinking` = 推理原文(durable 对应 `agent_thought_delta`)。
|
|
4546
|
+
*/
|
|
4547
|
+
recordSessionStreamChunk(sessionId, body) {
|
|
4548
|
+
return this.sessionRequest(sessionId, `/agent-sessions/${sessionId}/stream-chunk`, { body });
|
|
4549
|
+
}
|
|
4550
|
+
/** 会话绑定与指针回传(dimSessionId / workspaceDir / runtimeId;只写绑定列不改状态) */
|
|
4551
|
+
reportSessionBinding(sessionId, body) {
|
|
4552
|
+
return this.sessionRequest(sessionId, `/agent-sessions/${sessionId}/session`, { body });
|
|
4553
|
+
}
|
|
4554
|
+
/**
|
|
4555
|
+
* 会话关闭上报。
|
|
4556
|
+
* `reason='idle'`(空闲回收)→ 服务端只回到 `idle`(保留 dimSessionId,下次可 resume);
|
|
4557
|
+
* `reason='command'`(用户/平台显式关闭)→ 终态 `closed`(§4.4/§4.1 的衔接)。
|
|
4558
|
+
*/
|
|
4559
|
+
closeAgentSession(sessionId, reason = "command") {
|
|
4560
|
+
return this.sessionRequest(sessionId, `/agent-sessions/${sessionId}/close`, { body: { reason } });
|
|
4561
|
+
}
|
|
4562
|
+
/**
|
|
4563
|
+
* 结构化提问登记(§4.6 ②):服务端落库 + 推前端确认卡,立即返回 elicitationId。
|
|
4564
|
+
* 用户应答由 `getElicitation` 长轮询取回(见 session-host.handleElicitation)。
|
|
4565
|
+
*/
|
|
4566
|
+
createElicitation(sessionId, body) {
|
|
4567
|
+
return this.sessionRequest(
|
|
4568
|
+
sessionId,
|
|
4569
|
+
`/agent-sessions/${sessionId}/elicitation`,
|
|
4570
|
+
{ body }
|
|
4571
|
+
);
|
|
4572
|
+
}
|
|
4573
|
+
/** 结构化提问结果查询(waitMs 内被回答则提前返回;服务端最多持 waitMs) */
|
|
4574
|
+
getElicitation(sessionId, elicitationId, waitMs) {
|
|
4575
|
+
return this.sessionRequestWithMethod(sessionId, "GET", `/agent-sessions/${sessionId}/elicitations/${elicitationId}`, {
|
|
4576
|
+
query: { waitMs },
|
|
4577
|
+
// 服务端最多持 waitMs,客户端留 10s 余量(避免「服务端刚返回、客户端已超时」的假失败)
|
|
4578
|
+
timeoutMs: waitMs + 1e4
|
|
4579
|
+
});
|
|
4580
|
+
}
|
|
4581
|
+
/**
|
|
4582
|
+
* A2(issues/multi-repo-task-missing-workcontext-link.md):**不传 repo** 的平台凭证
|
|
4583
|
+
* ——服务端按「任务空间内 active 的连接」择一签发(凭证是平台凭据,不是某仓库的凭据)。
|
|
4584
|
+
*
|
|
4585
|
+
* 用于无主仓库 / 多仓库任务(daemon 走 scratch 隔离目录、`resolveRepo(detail) = null`):
|
|
4586
|
+
* 这些任务的 agent 同样需要 MORULA_GITLAB_TOKEN / MORULA_GITLAB_URL 才能自己走 GitLab API
|
|
4587
|
+
* 兜底建 MR(否则链路两端都没有自愈路径)。旧版服务端强制校验 repo(400)——调用方需容忍失败。
|
|
4025
4588
|
*/
|
|
4026
4589
|
getSpaceDaemonToken(taskId) {
|
|
4027
4590
|
return this.client.get("/vcs/daemon-token", { taskId });
|
|
4028
4591
|
}
|
|
4029
4592
|
};
|
|
4030
4593
|
|
|
4594
|
+
// src/daemon/assistant-text-stream.ts
|
|
4595
|
+
var TEXT_DELTA_WINDOW_MS = 200;
|
|
4596
|
+
var TEXT_SEGMENT_MAX_CHARS = 2e3;
|
|
4597
|
+
var PARAGRAPH_BREAK = "\n\n";
|
|
4598
|
+
var AssistantTextStream = class {
|
|
4599
|
+
constructor(sink, options = {}) {
|
|
4600
|
+
this.sink = sink;
|
|
4601
|
+
this.windowMs = options.windowMs ?? TEXT_DELTA_WINDOW_MS;
|
|
4602
|
+
this.segmentMaxChars = Math.max(1, options.segmentMaxChars ?? TEXT_SEGMENT_MAX_CHARS);
|
|
4603
|
+
this.log = options.log ?? (() => {
|
|
4604
|
+
});
|
|
4605
|
+
}
|
|
4606
|
+
sink;
|
|
4607
|
+
windowMs;
|
|
4608
|
+
segmentMaxChars;
|
|
4609
|
+
log;
|
|
4610
|
+
/** 当前段的完整文本(未落段部分) */
|
|
4611
|
+
buffer = "";
|
|
4612
|
+
/** 当前段已通过 onDelta 发出的字符数 */
|
|
4613
|
+
sent = 0;
|
|
4614
|
+
/** 当前段序号(落段后 +1) */
|
|
4615
|
+
seg = 0;
|
|
4616
|
+
timer = null;
|
|
4617
|
+
disposed = false;
|
|
4618
|
+
/** 段落落库串行链:段落事件必须按 seg 顺序落库(上行 HTTP 并发会乱序 → 回放正文错序) */
|
|
4619
|
+
paragraphChain = Promise.resolve();
|
|
4620
|
+
/** 当前段落序号(诊断/测试用) */
|
|
4621
|
+
get currentSeg() {
|
|
4622
|
+
return this.seg;
|
|
4623
|
+
}
|
|
4624
|
+
/** 喂入一个文本增量 */
|
|
4625
|
+
push(text) {
|
|
4626
|
+
if (this.disposed || !text)
|
|
4627
|
+
return;
|
|
4628
|
+
this.buffer += text;
|
|
4629
|
+
let idx = this.buffer.lastIndexOf(PARAGRAPH_BREAK);
|
|
4630
|
+
while (idx >= 0) {
|
|
4631
|
+
this.cutParagraph(idx + PARAGRAPH_BREAK.length);
|
|
4632
|
+
idx = this.buffer.lastIndexOf(PARAGRAPH_BREAK);
|
|
4633
|
+
}
|
|
4634
|
+
while (this.buffer.length >= this.segmentMaxChars)
|
|
4635
|
+
this.cutParagraph(this.segmentMaxChars);
|
|
4636
|
+
if (this.buffer.length > 0)
|
|
4637
|
+
this.scheduleDelta();
|
|
4638
|
+
}
|
|
4639
|
+
/** 把当前 buffer 的前 `length` 个字符作为一段落切出去(同步改状态,落库走串行链) */
|
|
4640
|
+
cutParagraph(length) {
|
|
4641
|
+
const head = this.buffer.slice(0, length);
|
|
4642
|
+
const rest = this.buffer.slice(length);
|
|
4643
|
+
this.buffer = head;
|
|
4644
|
+
this.sent = Math.min(this.sent, head.length);
|
|
4645
|
+
void this.flushParagraph();
|
|
4646
|
+
this.buffer = rest;
|
|
4647
|
+
this.sent = 0;
|
|
4648
|
+
}
|
|
4649
|
+
/** 强制结束当前段落(工具调用前 / 回合结束前调用并 await) */
|
|
4650
|
+
async flushParagraph() {
|
|
4651
|
+
if (this.timer) {
|
|
4652
|
+
clearTimeout(this.timer);
|
|
4653
|
+
this.timer = null;
|
|
4654
|
+
}
|
|
4655
|
+
this.flushDelta();
|
|
4656
|
+
if (!this.buffer)
|
|
4657
|
+
return this.paragraphChain;
|
|
4658
|
+
const text = this.buffer;
|
|
4659
|
+
const seg = this.seg;
|
|
4660
|
+
this.buffer = "";
|
|
4661
|
+
this.sent = 0;
|
|
4662
|
+
this.seg += 1;
|
|
4663
|
+
this.paragraphChain = this.paragraphChain.then(async () => {
|
|
4664
|
+
try {
|
|
4665
|
+
await this.sink.onParagraph(seg, text);
|
|
4666
|
+
} catch (err) {
|
|
4667
|
+
this.log(`[text-stream] \u6BB5\u843D ${seg} \u843D\u5E93\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
4668
|
+
}
|
|
4669
|
+
});
|
|
4670
|
+
return this.paragraphChain;
|
|
4671
|
+
}
|
|
4672
|
+
/** 释放定时器(会话关闭 / 任务收尾) */
|
|
4673
|
+
dispose() {
|
|
4674
|
+
this.disposed = true;
|
|
4675
|
+
if (this.timer) {
|
|
4676
|
+
clearTimeout(this.timer);
|
|
4677
|
+
this.timer = null;
|
|
4678
|
+
}
|
|
4679
|
+
this.buffer = "";
|
|
4680
|
+
this.sent = 0;
|
|
4681
|
+
}
|
|
4682
|
+
scheduleDelta() {
|
|
4683
|
+
if (this.timer || this.disposed)
|
|
4684
|
+
return;
|
|
4685
|
+
this.timer = setTimeout(() => {
|
|
4686
|
+
this.timer = null;
|
|
4687
|
+
this.flushDelta();
|
|
4688
|
+
}, this.windowMs);
|
|
4689
|
+
this.timer.unref?.();
|
|
4690
|
+
}
|
|
4691
|
+
/** 把「未发出的新增文本」作为一次 transient 增量直通 */
|
|
4692
|
+
flushDelta() {
|
|
4693
|
+
if (this.buffer.length <= this.sent)
|
|
4694
|
+
return;
|
|
4695
|
+
const text = this.buffer.slice(this.sent);
|
|
4696
|
+
this.sent = this.buffer.length;
|
|
4697
|
+
try {
|
|
4698
|
+
this.sink.onDelta(this.seg, text);
|
|
4699
|
+
} catch (err) {
|
|
4700
|
+
this.log(`[text-stream] \u589E\u91CF\u76F4\u901A\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
4701
|
+
}
|
|
4702
|
+
}
|
|
4703
|
+
};
|
|
4704
|
+
|
|
4031
4705
|
// src/daemon/contract-skill.ts
|
|
4032
4706
|
var COLLABORATION_SKILL_NAME = "morula-working-on-issues";
|
|
4033
4707
|
var COLLABORATION_SKILL_FRONTMATTER = `---
|
|
@@ -4099,11 +4773,13 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
|
|
|
4099
4773
|
|
|
4100
4774
|
## 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
4775
|
|
|
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
|
-
**\
|
|
4776
|
+
\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
|
|
4777
|
+
**\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
|
|
4778
|
+
\`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
4779
|
|
|
4105
4780
|
- **\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
4781
|
- **\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
|
|
4782
|
+
- **\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
4783
|
- **\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
4784
|
|
|
4109
4785
|
> **\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
|
|
@@ -4137,7 +4813,7 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
|
|
|
4137
4813
|
|
|
4138
4814
|
## 0.3 \u5206\u652F\u547D\u540D\uFF1A\u7531\u4F60\uFF08Agent\uFF09\u53D6\u540D\u5E76\u4E0A\u62A5
|
|
4139
4815
|
|
|
4140
|
-
- **\u4EFB\u52A1 ID \u83B7\u53D6**\uFF1A\u672C\u4EFB\u52A1\u7684 AgentTask ID\uFF08UUID\uFF09\
|
|
4816
|
+
- **\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
4817
|
- **\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
4818
|
- **\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
4819
|
- **\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 +4866,13 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
|
|
|
4190
4866
|
\u251C\u2500\u2500 .agents/skills/ \u2190 \u534F\u4F5C\u5951\u7EA6 + \u672C\u4EFB\u52A1\u81EA\u5B9A\u4E49 skill
|
|
4191
4867
|
\u251C\u2500\u2500 .agent_context/task-context.md \u2190 \u4EFB\u52A1\u4E0A\u4E0B\u6587\uFF08\u89C1 0 \u8282\uFF09
|
|
4192
4868
|
\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\
|
|
4869
|
+
\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
4870
|
\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
4871
|
\`\`\`
|
|
4196
4872
|
|
|
4197
4873
|
- **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
4874
|
\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\
|
|
4875
|
+
- **\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
4876
|
- **\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
4877
|
- \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
4878
|
- \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 +4880,7 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
|
|
|
4204
4880
|
- **\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
4881
|
\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
4882
|
- **\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\
|
|
4883
|
+
- **\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
4884
|
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
4885
|
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
4886
|
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 +4902,8 @@ var COLLABORATION_SKILL_BODY = `# Working on Morula issues\uFF08\u534F\u4F5C\u59
|
|
|
4226
4902
|
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
4903
|
# \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
4904
|
\`\`\`
|
|
4229
|
-
- **\u53EF\u7528\u4ED3\u5E93\u6E05\u5355**\
|
|
4230
|
-
\`morula repo list --space <spaceId>\`\uFF08\`<spaceId>\`
|
|
4905
|
+
- **\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
|
|
4906
|
+
\`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
4907
|
- **\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
4908
|
- **\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
4909
|
- **\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 +5090,7 @@ morula task inbox --peek # \u53EA\u770B\u4E0D\u53D6\uFF08\u786E\u8BA4\u6709\
|
|
|
4414
5090
|
|
|
4415
5091
|
- **\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
5092
|
- **\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\
|
|
5093
|
+
- **\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
5094
|
- **\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
5095
|
- **\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
5096
|
- \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 +5145,7 @@ var COLLABORATION_SKILL_SOURCE_MAP = `# morula-working-on-issues \u2014 \u5951\u
|
|
|
4469
5145
|
| 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
5146
|
| 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
5147
|
| 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
|
-
| \u7CFB\u7EDF\u63D0\u793A\u6CE8\u5165\uFF08\u9636\u6BB5\u804C\u8D23 / \u76EE\u6807 / \
|
|
5148
|
+
| \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
5149
|
| \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
5150
|
| \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
5151
|
|
|
@@ -4545,7 +5221,7 @@ function isFlagEnabled(name, defaultValue = true) {
|
|
|
4545
5221
|
|
|
4546
5222
|
// src/daemon/git-env.ts
|
|
4547
5223
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
4548
|
-
import { existsSync as
|
|
5224
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6 } from "node:fs";
|
|
4549
5225
|
import { join as join11 } from "node:path";
|
|
4550
5226
|
var GIT_CANDIDATE_DIRS = ["/usr/bin", "/usr/local/bin", "/opt/homebrew/bin", "/opt/local/bin"];
|
|
4551
5227
|
function prependToPath(dirs) {
|
|
@@ -4562,14 +5238,14 @@ function ensureGitOnPath() {
|
|
|
4562
5238
|
return;
|
|
4563
5239
|
} catch {
|
|
4564
5240
|
}
|
|
4565
|
-
const missing = prependToPath(GIT_CANDIDATE_DIRS.filter((d) =>
|
|
5241
|
+
const missing = prependToPath(GIT_CANDIDATE_DIRS.filter((d) => existsSync7(join11(d, "git"))));
|
|
4566
5242
|
if (missing.length) {
|
|
4567
5243
|
console.warn(`[daemon] PATH \u7F3A\u5C11 git\uFF0C\u5DF2\u8865\u5145: ${missing.join(", ")}`);
|
|
4568
5244
|
}
|
|
4569
5245
|
}
|
|
4570
5246
|
function ensureNodeOnPath() {
|
|
4571
5247
|
const nodeDir = process.execPath ? join11(process.execPath, "..") : "";
|
|
4572
|
-
const candidates = [nodeDir, "/usr/local/bin", "/opt/homebrew/bin"].filter((d) => d &&
|
|
5248
|
+
const candidates = [nodeDir, "/usr/local/bin", "/opt/homebrew/bin"].filter((d) => d && existsSync7(join11(d, "node")));
|
|
4573
5249
|
const missing = prependToPath(candidates);
|
|
4574
5250
|
if (missing.length) {
|
|
4575
5251
|
console.warn(`[daemon] PATH \u7F3A\u5C11 node\uFF0C\u5DF2\u8865\u5145: ${missing.join(", ")}`);
|
|
@@ -4605,24 +5281,49 @@ function buildGitlabSshRemote(repoFullName, baseUrl) {
|
|
|
4605
5281
|
return `ssh://git@${host}:${url.port}/${repoFullName}.git`;
|
|
4606
5282
|
return `git@${host}:${repoFullName}.git`;
|
|
4607
5283
|
}
|
|
4608
|
-
function
|
|
5284
|
+
function buildDaemonRepoUrl(repoFullName, baseUrl) {
|
|
5285
|
+
if (!baseUrl)
|
|
5286
|
+
return `https://github.com/${repoFullName}.git`;
|
|
5287
|
+
const url = new URL(baseUrl);
|
|
5288
|
+
const path = repoFullName.replace(/^\/+/, "");
|
|
5289
|
+
return `${url.protocol}//${url.host}/${path}.git`;
|
|
5290
|
+
}
|
|
5291
|
+
function ensureRepo(workdir, tenantId, repo, env, remoteUrl, daemonRemoteUrl) {
|
|
4609
5292
|
const repoDir = join11(workdir, tenantId, repo);
|
|
4610
|
-
|
|
5293
|
+
const fetchUrl = daemonRemoteUrl ?? remoteUrl ?? buildDaemonRepoUrl(repo);
|
|
5294
|
+
if (existsSync7(join11(repoDir, ".git"))) {
|
|
4611
5295
|
if (remoteUrl)
|
|
4612
5296
|
syncRemoteUrl(repoDir, remoteUrl, env);
|
|
4613
5297
|
try {
|
|
4614
|
-
|
|
4615
|
-
|
|
5298
|
+
if (daemonRemoteUrl) {
|
|
5299
|
+
execFileSync6(
|
|
5300
|
+
"git",
|
|
5301
|
+
["-C", repoDir, "fetch", "--prune", fetchUrl, "+refs/heads/*:refs/remotes/origin/*"],
|
|
5302
|
+
{ env, stdio: "pipe" }
|
|
5303
|
+
);
|
|
5304
|
+
} else {
|
|
5305
|
+
execFileSync6("git", ["-C", repoDir, "fetch", "origin", "--prune"], { env, stdio: "pipe" });
|
|
5306
|
+
}
|
|
5307
|
+
} catch (err) {
|
|
5308
|
+
throw new Error(`REPO_SYNC_FAILED: \u62C9\u53D6\u4ED3\u5E93\u5931\u8D25 ${repo}: ${gitErrorText(err)}`);
|
|
4616
5309
|
}
|
|
4617
5310
|
return repoDir;
|
|
4618
5311
|
}
|
|
4619
5312
|
mkdirSync6(repoDir, { recursive: true });
|
|
4620
|
-
|
|
4621
|
-
env,
|
|
4622
|
-
|
|
4623
|
-
|
|
5313
|
+
try {
|
|
5314
|
+
execFileSync6("git", ["clone", "--filter=blob:none", fetchUrl, repoDir], { env, stdio: "pipe" });
|
|
5315
|
+
} catch (err) {
|
|
5316
|
+
throw new Error(`REPO_SYNC_FAILED: \u514B\u9686\u4ED3\u5E93\u5931\u8D25 ${repo}: ${gitErrorText(err)}`);
|
|
5317
|
+
}
|
|
5318
|
+
if (remoteUrl && remoteUrl !== fetchUrl)
|
|
5319
|
+
syncRemoteUrl(repoDir, remoteUrl, env);
|
|
4624
5320
|
return repoDir;
|
|
4625
5321
|
}
|
|
5322
|
+
function gitErrorText(err) {
|
|
5323
|
+
const e = err;
|
|
5324
|
+
const stderr = e?.stderr ? e.stderr.toString().trim() : "";
|
|
5325
|
+
return stderr || e?.message || String(err);
|
|
5326
|
+
}
|
|
4626
5327
|
function syncRemoteUrl(repoDir, remoteUrl, env) {
|
|
4627
5328
|
try {
|
|
4628
5329
|
const cur = execFileSync6("git", ["-C", repoDir, "remote", "get-url", "origin"], { env, stdio: "pipe" }).toString().trim();
|
|
@@ -4635,7 +5336,7 @@ function syncRemoteUrl(repoDir, remoteUrl, env) {
|
|
|
4635
5336
|
function isLinkedWorktree(repoDir) {
|
|
4636
5337
|
try {
|
|
4637
5338
|
const gitDir = execFileSync6("git", ["-C", repoDir, "rev-parse", "--absolute-git-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
4638
|
-
return !!gitDir &&
|
|
5339
|
+
return !!gitDir && existsSync7(join11(gitDir, "commondir"));
|
|
4639
5340
|
} catch {
|
|
4640
5341
|
return false;
|
|
4641
5342
|
}
|
|
@@ -4673,7 +5374,7 @@ function ensureGitIdentity(repoDir, personaName) {
|
|
|
4673
5374
|
}
|
|
4674
5375
|
|
|
4675
5376
|
// src/daemon/outbox.ts
|
|
4676
|
-
import { existsSync as
|
|
5377
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync7, readdirSync as readdirSync2, readFileSync as readFileSync5, rmSync as rmSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4677
5378
|
import { homedir as homedir9 } from "node:os";
|
|
4678
5379
|
import { join as join12 } from "node:path";
|
|
4679
5380
|
var OUTBOX_RETRY_SCHEDULE = [1e3, 4e3, 16e3, 6e4, 3e5];
|
|
@@ -4689,14 +5390,14 @@ function writeOutbox(entry, configDir) {
|
|
|
4689
5390
|
}
|
|
4690
5391
|
function readOutboxEntries(configDir) {
|
|
4691
5392
|
const dir = outboxDir(configDir);
|
|
4692
|
-
if (!
|
|
5393
|
+
if (!existsSync8(dir))
|
|
4693
5394
|
return [];
|
|
4694
5395
|
const entries = [];
|
|
4695
5396
|
for (const file of readdirSync2(dir)) {
|
|
4696
5397
|
if (!file.endsWith(".json"))
|
|
4697
5398
|
continue;
|
|
4698
5399
|
try {
|
|
4699
|
-
const raw =
|
|
5400
|
+
const raw = readFileSync5(join12(dir, file), "utf8");
|
|
4700
5401
|
const parsed = JSON.parse(raw);
|
|
4701
5402
|
if (parsed && typeof parsed.taskId === "string")
|
|
4702
5403
|
entries.push(parsed);
|
|
@@ -4706,7 +5407,7 @@ function readOutboxEntries(configDir) {
|
|
|
4706
5407
|
return entries;
|
|
4707
5408
|
}
|
|
4708
5409
|
function removeOutbox(taskId, configDir) {
|
|
4709
|
-
|
|
5410
|
+
rmSync7(join12(outboxDir(configDir), `${taskId}.json`), { force: true });
|
|
4710
5411
|
}
|
|
4711
5412
|
function staleDir(configDir) {
|
|
4712
5413
|
return join12(outboxDir(configDir), "stale");
|
|
@@ -4729,13 +5430,113 @@ function outboxAge(entry) {
|
|
|
4729
5430
|
return Number.isFinite(created) ? Date.now() - created : Number.POSITIVE_INFINITY;
|
|
4730
5431
|
}
|
|
4731
5432
|
|
|
5433
|
+
// src/daemon/plugin-register.ts
|
|
5434
|
+
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";
|
|
5435
|
+
import { homedir as homedir10 } from "node:os";
|
|
5436
|
+
import { dirname as dirname6, join as join13 } from "node:path";
|
|
5437
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5438
|
+
var PLUGIN_NAME = "morula-cli";
|
|
5439
|
+
var MANIFEST_DIR_NAME = ".codex-plugin";
|
|
5440
|
+
function legacyAgentsPluginDir(home = homedir10()) {
|
|
5441
|
+
return join13(home, ".agents", "plugins", PLUGIN_NAME);
|
|
5442
|
+
}
|
|
5443
|
+
function resolveDimcodeHome(env = process.env, home = homedir10()) {
|
|
5444
|
+
const fromEnv = env.DIMCODE_HOME?.trim();
|
|
5445
|
+
return fromEnv || join13(home, ".dimcode", "v2");
|
|
5446
|
+
}
|
|
5447
|
+
function defaultPluginsRoot(env = process.env, home = homedir10()) {
|
|
5448
|
+
return join13(resolveDimcodeHome(env, home), "plugins");
|
|
5449
|
+
}
|
|
5450
|
+
function resolvePackagePaths(entryUrl) {
|
|
5451
|
+
const distDir = dirname6(fileURLToPath2(entryUrl));
|
|
5452
|
+
return {
|
|
5453
|
+
packageRoot: dirname6(distDir),
|
|
5454
|
+
distDir,
|
|
5455
|
+
assetsDir: join13(distDir, "plugin")
|
|
5456
|
+
};
|
|
5457
|
+
}
|
|
5458
|
+
function buildLauncherScript(opts) {
|
|
5459
|
+
const entry = join13(opts.packageRoot, "dist", "morula.js");
|
|
5460
|
+
return `#!/bin/sh
|
|
5461
|
+
# 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
|
|
5462
|
+
# \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
|
|
5463
|
+
NODE=${JSON.stringify(opts.nodePath)}
|
|
5464
|
+
[ -x "$NODE" ] || NODE="$(command -v node 2>/dev/null || true)"
|
|
5465
|
+
if [ -z "$NODE" ]; then
|
|
5466
|
+
echo '{"code":"ERROR","message":"node not found; reinstall or re-run: morula-runtime setup"}' >&2
|
|
5467
|
+
exit 127
|
|
5468
|
+
fi
|
|
5469
|
+
exec "$NODE" ${JSON.stringify(entry)} "$@"
|
|
5470
|
+
`;
|
|
5471
|
+
}
|
|
5472
|
+
function assetsReady(paths) {
|
|
5473
|
+
return existsSync9(join13(paths.assetsDir, "plugin.json")) && existsSync9(join13(paths.assetsDir, "skills"));
|
|
5474
|
+
}
|
|
5475
|
+
function listSkillNames(assetsDir) {
|
|
5476
|
+
const dir = join13(assetsDir, "skills");
|
|
5477
|
+
if (!existsSync9(dir))
|
|
5478
|
+
return [];
|
|
5479
|
+
return readdirSync3(dir).filter((name) => {
|
|
5480
|
+
try {
|
|
5481
|
+
return statSync2(join13(dir, name)).isDirectory();
|
|
5482
|
+
} catch {
|
|
5483
|
+
return false;
|
|
5484
|
+
}
|
|
5485
|
+
}).sort();
|
|
5486
|
+
}
|
|
5487
|
+
function pluginBinDir(env = process.env, home = homedir10()) {
|
|
5488
|
+
return join13(defaultPluginsRoot(env, home), PLUGIN_NAME, "bin");
|
|
5489
|
+
}
|
|
5490
|
+
function pluginBinPath(env = process.env, home = homedir10()) {
|
|
5491
|
+
return join13(pluginBinDir(env, home), "morula");
|
|
5492
|
+
}
|
|
5493
|
+
function prependPluginBinToPath(currentPath, env = process.env, home = homedir10()) {
|
|
5494
|
+
const binDir = pluginBinDir(env, home);
|
|
5495
|
+
const segments = (currentPath ?? "").split(":").filter(Boolean);
|
|
5496
|
+
if (segments[0] === binDir)
|
|
5497
|
+
return currentPath ?? binDir;
|
|
5498
|
+
return [binDir, ...segments.filter((s) => s !== binDir)].join(":");
|
|
5499
|
+
}
|
|
5500
|
+
function registerPlugin(opts) {
|
|
5501
|
+
const { pluginsRoot, paths, version } = opts;
|
|
5502
|
+
const nodePath = opts.nodePath ?? process.execPath;
|
|
5503
|
+
const pluginDir = join13(pluginsRoot, PLUGIN_NAME);
|
|
5504
|
+
if (!assetsReady(paths))
|
|
5505
|
+
throw new Error(`plugin assets not found at ${paths.assetsDir}; run \`pnpm run build:dist\` (dev) or reinstall the package`);
|
|
5506
|
+
const skillsSrc = join13(paths.assetsDir, "skills");
|
|
5507
|
+
const skills = listSkillNames(paths.assetsDir);
|
|
5508
|
+
const manifestDir = join13(pluginDir, MANIFEST_DIR_NAME);
|
|
5509
|
+
mkdirSync8(manifestDir, { recursive: true });
|
|
5510
|
+
const manifest = JSON.parse(readFileSync6(join13(paths.assetsDir, "plugin.json"), "utf8"));
|
|
5511
|
+
writeFileSync6(join13(manifestDir, "plugin.json"), `${JSON.stringify({ ...manifest, version }, null, 2)}
|
|
5512
|
+
`);
|
|
5513
|
+
const skillsDest = join13(pluginDir, "skills");
|
|
5514
|
+
rmSync8(skillsDest, { recursive: true, force: true });
|
|
5515
|
+
mkdirSync8(skillsDest, { recursive: true });
|
|
5516
|
+
cpSync(skillsSrc, skillsDest, { recursive: true });
|
|
5517
|
+
const binDir = join13(pluginDir, "bin");
|
|
5518
|
+
mkdirSync8(binDir, { recursive: true });
|
|
5519
|
+
const binPath = join13(binDir, "morula");
|
|
5520
|
+
writeFileSync6(binPath, buildLauncherScript({ nodePath, packageRoot: paths.packageRoot }));
|
|
5521
|
+
chmodSync3(binPath, 493);
|
|
5522
|
+
const legacy = legacyAgentsPluginDir(opts.home);
|
|
5523
|
+
return {
|
|
5524
|
+
pluginDir,
|
|
5525
|
+
binPath,
|
|
5526
|
+
skills,
|
|
5527
|
+
version,
|
|
5528
|
+
node: nodePath,
|
|
5529
|
+
legacyDir: existsSync9(join13(legacy, MANIFEST_DIR_NAME)) ? legacy : null
|
|
5530
|
+
};
|
|
5531
|
+
}
|
|
5532
|
+
|
|
4732
5533
|
// src/daemon/acp/acp-event-mapper.ts
|
|
4733
5534
|
var AcpEventMapper = class {
|
|
4734
5535
|
lastUsage = void 0;
|
|
4735
5536
|
reset() {
|
|
4736
5537
|
this.lastUsage = void 0;
|
|
4737
5538
|
}
|
|
4738
|
-
/** 取当前 usage
|
|
5539
|
+
/** 取当前 usage(回放/观测用;**不再**作为 tokenUsage 附着——token 总量以 prompt 响应的 usage 为准) */
|
|
4739
5540
|
get latestUsage() {
|
|
4740
5541
|
return this.lastUsage;
|
|
4741
5542
|
}
|
|
@@ -4761,6 +5562,13 @@ var AcpEventMapper = class {
|
|
|
4761
5562
|
}
|
|
4762
5563
|
break;
|
|
4763
5564
|
}
|
|
5565
|
+
case "user_message_chunk": {
|
|
5566
|
+
const text = update.content?.type === "text" ? update.content.text : "";
|
|
5567
|
+
if (text) {
|
|
5568
|
+
messages.push({ eventType: "user_message:delta", payload: { delta: text } });
|
|
5569
|
+
}
|
|
5570
|
+
break;
|
|
5571
|
+
}
|
|
4764
5572
|
case "tool_call": {
|
|
4765
5573
|
messages.push({
|
|
4766
5574
|
eventType: "tool:started",
|
|
@@ -4788,11 +5596,13 @@ var AcpEventMapper = class {
|
|
|
4788
5596
|
break;
|
|
4789
5597
|
}
|
|
4790
5598
|
case "usage_update": {
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
5599
|
+
const usage2 = {
|
|
5600
|
+
contextUsed: typeof update.used === "number" ? update.used : 0,
|
|
5601
|
+
contextSize: typeof update.size === "number" ? update.size : 0,
|
|
5602
|
+
cost: update.cost ?? null
|
|
4795
5603
|
};
|
|
5604
|
+
this.lastUsage = usage2;
|
|
5605
|
+
messages.push({ eventType: "usage:context", payload: { usage: usage2 } });
|
|
4796
5606
|
break;
|
|
4797
5607
|
}
|
|
4798
5608
|
default:
|
|
@@ -4888,14 +5698,9 @@ function normalizeOptionId(decision) {
|
|
|
4888
5698
|
}
|
|
4889
5699
|
|
|
4890
5700
|
// src/daemon/acp/dim-acp-executor.ts
|
|
4891
|
-
var DEFAULT_TIMEOUT_MS2 = 30 * 60 * 1e3;
|
|
4892
|
-
var DEFAULT_IDLE_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
4893
|
-
var DEFAULT_TOOL_IDLE_TIMEOUT_MS = 20 * 60 * 1e3;
|
|
4894
|
-
var DEFAULT_MAX_TURNS = 8;
|
|
4895
|
-
var DEFAULT_TASK_WALL_CLOCK_MS = 2 * 60 * 60 * 1e3;
|
|
4896
5701
|
var DEFAULT_STEER_SAFETY_MS = 30 * 1e3;
|
|
4897
5702
|
var PROMPT_RPC_TIMEOUT_GRACE_MS = 1e4;
|
|
4898
|
-
var
|
|
5703
|
+
var STEER_POLL_TICK_MS = 15e3;
|
|
4899
5704
|
var KILL_GRACE_MS = 3e3;
|
|
4900
5705
|
function resolveSteerSafetyMs(raw = process.env.MORULA_STEER_SAFETY_MS) {
|
|
4901
5706
|
if (raw === void 0 || raw === "")
|
|
@@ -5038,7 +5843,7 @@ var DimAcpExecutor = class {
|
|
|
5038
5843
|
/** 每次 run 前由调用方注入(daemon 单例池) */
|
|
5039
5844
|
pool = null;
|
|
5040
5845
|
async run(opts) {
|
|
5041
|
-
const timeout = opts.timeoutMs
|
|
5846
|
+
const timeout = opts.timeoutMs && opts.timeoutMs > 0 ? opts.timeoutMs : 0;
|
|
5042
5847
|
const mapper = new AcpEventMapper();
|
|
5043
5848
|
const pool = opts.pool ?? this.pool;
|
|
5044
5849
|
return await new Promise((resolve2) => {
|
|
@@ -5051,9 +5856,7 @@ var DimAcpExecutor = class {
|
|
|
5051
5856
|
let turnNo = 1;
|
|
5052
5857
|
const inFlightToolCalls = /* @__PURE__ */ new Map();
|
|
5053
5858
|
const inFlightTools = () => inFlightToolCalls.size;
|
|
5054
|
-
let
|
|
5055
|
-
let watchdogTimer = null;
|
|
5056
|
-
let watchdogFired = false;
|
|
5859
|
+
let steerPollTimer = null;
|
|
5057
5860
|
let resumeFailed;
|
|
5058
5861
|
const killTree = (c) => {
|
|
5059
5862
|
if (!c)
|
|
@@ -5064,10 +5867,10 @@ var DimAcpExecutor = class {
|
|
|
5064
5867
|
c.kill();
|
|
5065
5868
|
}
|
|
5066
5869
|
};
|
|
5067
|
-
const
|
|
5068
|
-
if (
|
|
5069
|
-
clearInterval(
|
|
5070
|
-
|
|
5870
|
+
const stopSteerPoll = () => {
|
|
5871
|
+
if (steerPollTimer) {
|
|
5872
|
+
clearInterval(steerPollTimer);
|
|
5873
|
+
steerPollTimer = null;
|
|
5071
5874
|
}
|
|
5072
5875
|
};
|
|
5073
5876
|
let abortHandler = () => {
|
|
@@ -5078,7 +5881,7 @@ var DimAcpExecutor = class {
|
|
|
5078
5881
|
settled = true;
|
|
5079
5882
|
if (timer)
|
|
5080
5883
|
clearTimeout(timer);
|
|
5081
|
-
|
|
5884
|
+
stopSteerPoll();
|
|
5082
5885
|
if (ownsConnection && conn) {
|
|
5083
5886
|
void conn.close().catch(() => {
|
|
5084
5887
|
});
|
|
@@ -5103,45 +5906,17 @@ var DimAcpExecutor = class {
|
|
|
5103
5906
|
finish({ ok: false, error: "\u4EFB\u52A1\u88AB\u53D6\u6D88", sessionId: sessionId || void 0 });
|
|
5104
5907
|
}
|
|
5105
5908
|
};
|
|
5106
|
-
const
|
|
5107
|
-
|
|
5108
|
-
if (idle <= 0)
|
|
5109
|
-
return;
|
|
5110
|
-
lastActivityAt = Date.now();
|
|
5111
|
-
inFlightToolCalls.clear();
|
|
5112
|
-
watchdogTimer = setInterval(() => {
|
|
5909
|
+
const startSteerPoll = () => {
|
|
5910
|
+
steerPollTimer = setInterval(() => {
|
|
5113
5911
|
maybeInterruptAtSafePoint();
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
const budget = inFlightTools() > 0 ? toolBudget : idle;
|
|
5117
|
-
if (Date.now() - lastActivityAt > budget) {
|
|
5118
|
-
watchdogFired = true;
|
|
5119
|
-
if (sessionId && conn)
|
|
5120
|
-
conn.cancel(sessionId);
|
|
5121
|
-
killTree(conn);
|
|
5122
|
-
finish({
|
|
5123
|
-
ok: false,
|
|
5124
|
-
error: `daemon: idle watchdog fired\uFF08${Math.round(budget / 1e3)}s \u65E0\u6D3B\u52A8\uFF09`,
|
|
5125
|
-
sessionId: sessionId || void 0,
|
|
5126
|
-
turns: turnNo
|
|
5127
|
-
});
|
|
5128
|
-
}
|
|
5129
|
-
}, WATCHDOG_TICK_MS);
|
|
5130
|
-
watchdogTimer.unref?.();
|
|
5131
|
-
};
|
|
5132
|
-
const markActivity = () => {
|
|
5133
|
-
lastActivityAt = Date.now();
|
|
5912
|
+
}, STEER_POLL_TICK_MS);
|
|
5913
|
+
steerPollTimer.unref?.();
|
|
5134
5914
|
};
|
|
5135
5915
|
let activeSteer = null;
|
|
5136
5916
|
let steerWaitingSince = 0;
|
|
5137
|
-
let wallClockFired = false;
|
|
5138
5917
|
let interruptedTurns = 0;
|
|
5139
5918
|
let turnKind = "initial";
|
|
5140
5919
|
const steerSafetyMs = resolveSteerSafetyMs();
|
|
5141
|
-
const taskStartedAt = Date.now();
|
|
5142
|
-
const maxTurns = Math.max(1, Math.trunc(opts.steer?.maxTurns ?? DEFAULT_MAX_TURNS));
|
|
5143
|
-
const taskWallClockMs = opts.steer?.wallClockMs ?? 0;
|
|
5144
|
-
const wallClockExceeded = () => taskWallClockMs > 0 && Date.now() - taskStartedAt > taskWallClockMs;
|
|
5145
5920
|
function maybeInterruptAtSafePoint() {
|
|
5146
5921
|
if (!opts.steer || activeSteer || !conn || !sessionId)
|
|
5147
5922
|
return;
|
|
@@ -5165,15 +5940,6 @@ var DimAcpExecutor = class {
|
|
|
5165
5940
|
`);
|
|
5166
5941
|
conn.cancel(sessionId);
|
|
5167
5942
|
}
|
|
5168
|
-
function enforceTaskWallClock() {
|
|
5169
|
-
if (wallClockFired || !wallClockExceeded())
|
|
5170
|
-
return;
|
|
5171
|
-
wallClockFired = true;
|
|
5172
|
-
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
|
|
5173
|
-
`);
|
|
5174
|
-
if (sessionId && conn)
|
|
5175
|
-
conn.cancel(sessionId);
|
|
5176
|
-
}
|
|
5177
5943
|
function emitAbortedTools() {
|
|
5178
5944
|
const aborted = [];
|
|
5179
5945
|
for (const [callId, toolName] of inFlightToolCalls) {
|
|
@@ -5212,8 +5978,12 @@ var DimAcpExecutor = class {
|
|
|
5212
5978
|
});
|
|
5213
5979
|
setup().then(async () => {
|
|
5214
5980
|
const armTurnTimer = () => {
|
|
5215
|
-
if (timer)
|
|
5981
|
+
if (timer) {
|
|
5216
5982
|
clearTimeout(timer);
|
|
5983
|
+
timer = void 0;
|
|
5984
|
+
}
|
|
5985
|
+
if (timeout <= 0)
|
|
5986
|
+
return;
|
|
5217
5987
|
timer = setTimeout(() => {
|
|
5218
5988
|
if (sessionId && conn)
|
|
5219
5989
|
conn.cancel(sessionId);
|
|
@@ -5229,7 +5999,7 @@ var DimAcpExecutor = class {
|
|
|
5229
5999
|
};
|
|
5230
6000
|
armTurnTimer();
|
|
5231
6001
|
opts.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
5232
|
-
|
|
6002
|
+
startSteerPoll();
|
|
5233
6003
|
const bridge = new AcpPermissionBridge();
|
|
5234
6004
|
conn.setPermissionHandler(bridge.createHandler(opts.approval, opts.approvalMode === "manual"));
|
|
5235
6005
|
const sessionParams = { cwd: opts.cwd, mcpServers: [] };
|
|
@@ -5279,7 +6049,7 @@ var DimAcpExecutor = class {
|
|
|
5279
6049
|
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
|
|
5280
6050
|
`);
|
|
5281
6051
|
} else {
|
|
5282
|
-
await conn.
|
|
6052
|
+
await conn.setConfigOption(sess.sessionId, "model", modelId).catch((err) => {
|
|
5283
6053
|
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}
|
|
5284
6054
|
`);
|
|
5285
6055
|
});
|
|
@@ -5289,7 +6059,6 @@ var DimAcpExecutor = class {
|
|
|
5289
6059
|
});
|
|
5290
6060
|
sessionId = sess.sessionId;
|
|
5291
6061
|
pool?.touch();
|
|
5292
|
-
markActivity();
|
|
5293
6062
|
opts.onEvent?.(mapper.readyMessage(sessionId), "");
|
|
5294
6063
|
const reopenSessionAfterInterrupt = async () => {
|
|
5295
6064
|
try {
|
|
@@ -5320,7 +6089,6 @@ var DimAcpExecutor = class {
|
|
|
5320
6089
|
activeSteer = null;
|
|
5321
6090
|
opts.steer?.onTurnStarted?.({ turnNo, kind: turnKind });
|
|
5322
6091
|
const result = await conn.prompt(sessionId, promptText, (update) => {
|
|
5323
|
-
markActivity();
|
|
5324
6092
|
pool?.touch();
|
|
5325
6093
|
const { messages } = mapper.map(sessionId, update);
|
|
5326
6094
|
for (const msg of messages) {
|
|
@@ -5353,37 +6121,18 @@ var DimAcpExecutor = class {
|
|
|
5353
6121
|
}
|
|
5354
6122
|
}
|
|
5355
6123
|
maybeInterruptAtSafePoint();
|
|
5356
|
-
}, timeout + PROMPT_RPC_TIMEOUT_GRACE_MS);
|
|
6124
|
+
}, timeout > 0 ? timeout + PROMPT_RPC_TIMEOUT_GRACE_MS : 0);
|
|
5357
6125
|
if (settled)
|
|
5358
6126
|
return;
|
|
5359
6127
|
pool?.touch();
|
|
5360
|
-
|
|
5361
|
-
const usage2 = result.usage ?? mapper.latestUsage;
|
|
6128
|
+
const usage2 = result.usage ?? void 0;
|
|
5362
6129
|
const cancelled = result.stopReason === "cancelled";
|
|
5363
6130
|
const base = { sessionId, turns: turnNo, interrupted: interruptedTurns > 0 };
|
|
5364
|
-
if (wallClockFired || wallClockExceeded()) {
|
|
5365
|
-
const abortedTools = emitAbortedTools();
|
|
5366
|
-
opts.steer?.onTurnInterrupted?.({ turnNo, reason: "wall-clock", abortedTools });
|
|
5367
|
-
killTree(conn);
|
|
5368
|
-
finish({
|
|
5369
|
-
...base,
|
|
5370
|
-
ok: false,
|
|
5371
|
-
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`
|
|
5372
|
-
});
|
|
5373
|
-
return;
|
|
5374
|
-
}
|
|
5375
6131
|
if (cancelled && activeSteer) {
|
|
5376
6132
|
interruptedTurns += 1;
|
|
5377
6133
|
const abortedTools = emitAbortedTools();
|
|
5378
6134
|
const nextTurn = turnNo + 1;
|
|
5379
|
-
|
|
5380
|
-
opts.steer?.onTurnInterrupted?.({ turnNo, reason: withinBudget ? "steer" : "max-turns", abortedTools });
|
|
5381
|
-
if (!withinBudget) {
|
|
5382
|
-
process.stderr.write(`[daemon] \u5DF2\u8FBE\u6700\u5927\u56DE\u5408\u6570 ${maxTurns}\uFF0C\u5269\u4F59\u6253\u65AD\u8BF7\u6C42\u4EA4\u7531\u4E0B\u4E00\u8F6E
|
|
5383
|
-
`);
|
|
5384
|
-
finish({ ...base, ok: true, text: finalText, tokenUsage: usage2 });
|
|
5385
|
-
return;
|
|
5386
|
-
}
|
|
6135
|
+
opts.steer?.onTurnInterrupted?.({ turnNo, reason: "steer", abortedTools });
|
|
5387
6136
|
turnKind = "after-interrupt";
|
|
5388
6137
|
promptText = opts.steer.consume({ turnNo: nextTurn, sessionReset });
|
|
5389
6138
|
turnNo = nextTurn;
|
|
@@ -5406,7 +6155,7 @@ var DimAcpExecutor = class {
|
|
|
5406
6155
|
return;
|
|
5407
6156
|
}
|
|
5408
6157
|
const leftover = activeSteer ?? opts.steer?.peek() ?? null;
|
|
5409
|
-
if (leftover
|
|
6158
|
+
if (leftover) {
|
|
5410
6159
|
const nextTurn = turnNo + 1;
|
|
5411
6160
|
turnKind = "appended";
|
|
5412
6161
|
promptText = opts.steer.consume({ turnNo: nextTurn, sessionReset });
|
|
@@ -5507,7 +6256,15 @@ function createDimEventNormalizer(opts) {
|
|
|
5507
6256
|
let currentMessageId = null;
|
|
5508
6257
|
let currentText = "";
|
|
5509
6258
|
let hasBoundarySignal = false;
|
|
6259
|
+
let pendingUserText = "";
|
|
6260
|
+
const flushUserMessage = () => {
|
|
6261
|
+
if (pendingUserText.trim().length > 0) {
|
|
6262
|
+
opts.onEvent({ type: "user_message", text: pendingUserText });
|
|
6263
|
+
}
|
|
6264
|
+
pendingUserText = "";
|
|
6265
|
+
};
|
|
5510
6266
|
const startMessage = (messageId) => {
|
|
6267
|
+
flushUserMessage();
|
|
5511
6268
|
if (currentMessageId !== null && currentText.trim().length > 0) {
|
|
5512
6269
|
opts.onEvent({ type: "assistant_message_completed", messageId: currentMessageId, text: currentText });
|
|
5513
6270
|
}
|
|
@@ -5520,7 +6277,22 @@ function createDimEventNormalizer(opts) {
|
|
|
5520
6277
|
const eventType = msg.eventType;
|
|
5521
6278
|
if (!eventType)
|
|
5522
6279
|
return;
|
|
6280
|
+
if (eventType !== "user_message:delta")
|
|
6281
|
+
flushUserMessage();
|
|
5523
6282
|
switch (eventType) {
|
|
6283
|
+
case "user_message:delta": {
|
|
6284
|
+
const delta = msg.payload?.delta ?? "";
|
|
6285
|
+
if (delta)
|
|
6286
|
+
pendingUserText += delta;
|
|
6287
|
+
break;
|
|
6288
|
+
}
|
|
6289
|
+
case "usage:context": {
|
|
6290
|
+
const usage2 = msg.payload?.usage;
|
|
6291
|
+
if (usage2 && typeof usage2 === "object") {
|
|
6292
|
+
opts.onEvent({ type: "usage", usage: normalizeUsage(usage2) });
|
|
6293
|
+
}
|
|
6294
|
+
break;
|
|
6295
|
+
}
|
|
5524
6296
|
case "message:started": {
|
|
5525
6297
|
const messageId = extractMessageId(msg.payload) ?? `msg-${msg.payload?.messageId ?? "run"}`;
|
|
5526
6298
|
startMessage(messageId);
|
|
@@ -5535,9 +6307,12 @@ function createDimEventNormalizer(opts) {
|
|
|
5535
6307
|
currentText = "";
|
|
5536
6308
|
break;
|
|
5537
6309
|
}
|
|
5538
|
-
case "thinking:delta":
|
|
6310
|
+
case "thinking:delta": {
|
|
6311
|
+
const delta = msg.payload?.delta ?? "";
|
|
6312
|
+
if (delta)
|
|
6313
|
+
opts.onEvent({ type: "assistant_thinking_delta", text: delta });
|
|
5539
6314
|
break;
|
|
5540
|
-
|
|
6315
|
+
}
|
|
5541
6316
|
case "text:delta": {
|
|
5542
6317
|
const delta = msg.payload?.delta ?? "";
|
|
5543
6318
|
if (!delta)
|
|
@@ -5611,6 +6386,7 @@ function createDimEventNormalizer(opts) {
|
|
|
5611
6386
|
break;
|
|
5612
6387
|
}
|
|
5613
6388
|
case "run:ended": {
|
|
6389
|
+
flushUserMessage();
|
|
5614
6390
|
if (currentMessageId !== null && currentText.trim().length > 0) {
|
|
5615
6391
|
opts.onEvent({ type: "assistant_message_completed", messageId: currentMessageId, text: currentText });
|
|
5616
6392
|
} else if (!hasBoundarySignal && finalText.trim()) {
|
|
@@ -5648,7 +6424,11 @@ function normalizeUsage(raw) {
|
|
|
5648
6424
|
return {
|
|
5649
6425
|
inputTokens: typeof u.inputTokens === "number" ? u.inputTokens : void 0,
|
|
5650
6426
|
outputTokens: typeof u.outputTokens === "number" ? u.outputTokens : void 0,
|
|
5651
|
-
totalTokens: typeof u.totalTokens === "number" ? u.totalTokens : void 0
|
|
6427
|
+
totalTokens: typeof u.totalTokens === "number" ? u.totalTokens : void 0,
|
|
6428
|
+
// ACP usage_update(上下文占用语义,P0-8):透传给前端状态行
|
|
6429
|
+
contextUsed: typeof u.contextUsed === "number" ? u.contextUsed : void 0,
|
|
6430
|
+
contextSize: typeof u.contextSize === "number" ? u.contextSize : void 0,
|
|
6431
|
+
cost: u.cost && typeof u.cost === "object" && typeof u.cost.amount === "number" ? u.cost : null
|
|
5652
6432
|
};
|
|
5653
6433
|
}
|
|
5654
6434
|
var DIM_CAPABILITIES = {
|
|
@@ -5703,8 +6483,8 @@ var DimAgentAdapter = class {
|
|
|
5703
6483
|
}
|
|
5704
6484
|
}
|
|
5705
6485
|
async probe() {
|
|
5706
|
-
const { existsSync:
|
|
5707
|
-
const installed =
|
|
6486
|
+
const { existsSync: existsSync18 } = await import("node:fs");
|
|
6487
|
+
const installed = existsSync18(this.dimPath) || this.dimPath === "dim";
|
|
5708
6488
|
let authStatus = installed ? "unknown" : "error";
|
|
5709
6489
|
let lastProbeErrorCode = installed ? void 0 : "DIM_CLI_NOT_FOUND";
|
|
5710
6490
|
let lastProbeError = installed ? void 0 : `\u672A\u627E\u5230 dim CLI\uFF08${this.dimPath}\uFF09`;
|
|
@@ -5996,62 +6776,89 @@ function pickLegacyDimModels(inventory) {
|
|
|
5996
6776
|
|
|
5997
6777
|
// src/daemon/repo-cache.ts
|
|
5998
6778
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
5999
|
-
import { existsSync as existsSync11, mkdirSync as
|
|
6000
|
-
import { dirname as
|
|
6779
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync9 } from "node:fs";
|
|
6780
|
+
import { dirname as dirname7, join as join15 } from "node:path";
|
|
6001
6781
|
function ownerOf(repoFullName) {
|
|
6002
6782
|
const idx = repoFullName.indexOf("/");
|
|
6003
6783
|
return idx > 0 ? repoFullName.slice(0, idx) : "_default";
|
|
6004
6784
|
}
|
|
6005
6785
|
function buildRepoCachePath(workdir, tenantId, repoFullName) {
|
|
6006
|
-
return
|
|
6786
|
+
return join15(workdir, "repos", tenantId, ownerOf(repoFullName), repoFullName);
|
|
6007
6787
|
}
|
|
6008
|
-
function openRepoCache(workdir, tenantId, repoFullName, env,
|
|
6788
|
+
function openRepoCache(workdir, tenantId, repoFullName, env, agentRemoteUrl, daemonRemoteUrl) {
|
|
6009
6789
|
const repoCachePath = buildRepoCachePath(workdir, tenantId, repoFullName);
|
|
6010
|
-
const
|
|
6790
|
+
const currentOrigin = () => {
|
|
6791
|
+
if (!existsSync11(join15(repoCachePath, ".git")))
|
|
6792
|
+
return null;
|
|
6793
|
+
try {
|
|
6794
|
+
return execFileSync7("git", ["-C", repoCachePath, "remote", "get-url", "origin"], { encoding: "utf8", env, stdio: "pipe" }).trim() || null;
|
|
6795
|
+
} catch {
|
|
6796
|
+
return null;
|
|
6797
|
+
}
|
|
6798
|
+
};
|
|
6799
|
+
const fetchUrl = () => daemonRemoteUrl ?? currentOrigin() ?? agentRemoteUrl ?? `https://github.com/${repoFullName}.git`;
|
|
6800
|
+
const originUrl = () => agentRemoteUrl ?? fetchUrl();
|
|
6011
6801
|
return {
|
|
6012
6802
|
repoCachePath,
|
|
6013
6803
|
ensure() {
|
|
6014
|
-
if (existsSync11(
|
|
6015
|
-
|
|
6016
|
-
syncRemoteUrl(repoCachePath, url, env);
|
|
6804
|
+
if (existsSync11(join15(repoCachePath, ".git"))) {
|
|
6805
|
+
syncRemoteUrl(repoCachePath, originUrl(), env);
|
|
6017
6806
|
return;
|
|
6018
6807
|
}
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
stdio: "pipe"
|
|
6023
|
-
})
|
|
6808
|
+
mkdirSync9(dirname7(repoCachePath), { recursive: true });
|
|
6809
|
+
const cloneUrl = fetchUrl();
|
|
6810
|
+
try {
|
|
6811
|
+
execFileSync7("git", ["clone", "--filter=blob:none", cloneUrl, repoCachePath], { env, stdio: "pipe" });
|
|
6812
|
+
} catch (err) {
|
|
6813
|
+
throw new Error(`REPO_SYNC_FAILED: \u514B\u9686\u4ED3\u5E93\u5931\u8D25 ${repoFullName}: ${gitErrorText2(err)}`);
|
|
6814
|
+
}
|
|
6815
|
+
if (originUrl() !== cloneUrl)
|
|
6816
|
+
syncRemoteUrl(repoCachePath, originUrl(), env);
|
|
6024
6817
|
},
|
|
6025
6818
|
fetch() {
|
|
6026
6819
|
try {
|
|
6027
|
-
|
|
6028
|
-
|
|
6820
|
+
if (daemonRemoteUrl) {
|
|
6821
|
+
execFileSync7(
|
|
6822
|
+
"git",
|
|
6823
|
+
["-C", repoCachePath, "fetch", "--prune", daemonRemoteUrl, "+refs/heads/*:refs/remotes/origin/*"],
|
|
6824
|
+
{ env, stdio: "pipe" }
|
|
6825
|
+
);
|
|
6826
|
+
} else {
|
|
6827
|
+
execFileSync7("git", ["-C", repoCachePath, "fetch", "origin", "--prune"], { env, stdio: "pipe" });
|
|
6828
|
+
}
|
|
6829
|
+
} catch (err) {
|
|
6830
|
+
throw new Error(`REPO_SYNC_FAILED: \u62C9\u53D6\u4ED3\u5E93\u5931\u8D25 ${repoFullName}: ${gitErrorText2(err)}`);
|
|
6029
6831
|
}
|
|
6030
6832
|
}
|
|
6031
6833
|
};
|
|
6032
6834
|
}
|
|
6835
|
+
function gitErrorText2(err) {
|
|
6836
|
+
const e = err;
|
|
6837
|
+
const stderr = e?.stderr ? e.stderr.toString().trim() : "";
|
|
6838
|
+
return stderr || e?.message || String(err);
|
|
6839
|
+
}
|
|
6033
6840
|
|
|
6034
6841
|
// src/daemon/repo-checkout-service.ts
|
|
6035
6842
|
import { createServer as createServer3 } from "node:http";
|
|
6036
6843
|
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
6037
|
-
import { existsSync as existsSync14, mkdirSync as
|
|
6038
|
-
import { basename as basename2, join as
|
|
6844
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync11, readdirSync as readdirSync5, writeFileSync as writeFileSync9 } from "node:fs";
|
|
6845
|
+
import { basename as basename2, join as join18 } from "node:path";
|
|
6039
6846
|
|
|
6040
6847
|
// src/daemon/worktree-manager.ts
|
|
6041
6848
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
6042
|
-
import { existsSync as existsSync13, mkdirSync as
|
|
6043
|
-
import { basename, join as
|
|
6849
|
+
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";
|
|
6850
|
+
import { basename, join as join17, sep as sep2 } from "node:path";
|
|
6044
6851
|
|
|
6045
6852
|
// src/daemon/workspace-marker.ts
|
|
6046
|
-
import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as
|
|
6047
|
-
import { join as
|
|
6853
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "node:fs";
|
|
6854
|
+
import { join as join16 } from "node:path";
|
|
6048
6855
|
var WORKTREE_MARKER_FILE = ".morula-worktree.json";
|
|
6049
6856
|
var WORKTREE_MARKER_SCHEMA = 1;
|
|
6050
6857
|
function markerPath(worktreePath) {
|
|
6051
|
-
return
|
|
6858
|
+
return join16(worktreePath, WORKTREE_MARKER_FILE);
|
|
6052
6859
|
}
|
|
6053
6860
|
function writeWorktreeMarker(worktreePath, marker) {
|
|
6054
|
-
|
|
6861
|
+
writeFileSync7(markerPath(worktreePath), `${JSON.stringify(marker, null, 2)}
|
|
6055
6862
|
`, "utf8");
|
|
6056
6863
|
}
|
|
6057
6864
|
function readWorktreeMarker(worktreePath) {
|
|
@@ -6083,14 +6890,14 @@ function buildTaskRootPath(workdir, tenantId, runtimeId, workContextId, code) {
|
|
|
6083
6890
|
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");
|
|
6084
6891
|
}
|
|
6085
6892
|
const dirName = code ? `${safeDirName(code)}-${workContextId.slice(0, 8)}` : workContextId;
|
|
6086
|
-
return
|
|
6893
|
+
return join17(workdir, "worktrees", tenantId, runtimeSegment, dirName);
|
|
6087
6894
|
}
|
|
6088
6895
|
function buildWorktreePath(workdir, tenantId, runtimeId, workContextId, code, repoFullName) {
|
|
6089
|
-
return
|
|
6896
|
+
return join17(buildTaskRootPath(workdir, tenantId, runtimeId, workContextId, code), safeDirName(repoFullName));
|
|
6090
6897
|
}
|
|
6091
6898
|
function buildLegacyWorktreePath(workdir, tenantId, workContextId, code) {
|
|
6092
6899
|
const dirName = code ? `${safeDirName(code)}-${workContextId.slice(0, 8)}` : workContextId;
|
|
6093
|
-
return
|
|
6900
|
+
return join17(workdir, "worktrees", tenantId, dirName);
|
|
6094
6901
|
}
|
|
6095
6902
|
var legacyPathWarned = /* @__PURE__ */ new Set();
|
|
6096
6903
|
function warnLegacyWorktreeResidue(legacyPath) {
|
|
@@ -6101,13 +6908,13 @@ function warnLegacyWorktreeResidue(legacyPath) {
|
|
|
6101
6908
|
}
|
|
6102
6909
|
var legacyLayoutWarned = /* @__PURE__ */ new Set();
|
|
6103
6910
|
function warnLegacyLayoutWorktreeResidue(taskRoot, worktreePath) {
|
|
6104
|
-
if (legacyLayoutWarned.has(taskRoot) || existsSync13(worktreePath) || !existsSync13(
|
|
6911
|
+
if (legacyLayoutWarned.has(taskRoot) || existsSync13(worktreePath) || !existsSync13(join17(taskRoot, WORKTREE_MARKER_FILE)))
|
|
6105
6912
|
return;
|
|
6106
6913
|
legacyLayoutWarned.add(taskRoot);
|
|
6107
6914
|
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`);
|
|
6108
6915
|
}
|
|
6109
6916
|
function ensureMarkerIgnoredAndCommitted(worktreePath, env) {
|
|
6110
|
-
const gitignorePath =
|
|
6917
|
+
const gitignorePath = join17(worktreePath, ".gitignore");
|
|
6111
6918
|
const existing = existsSync13(gitignorePath) ? readFileSync9(gitignorePath, "utf8") : "";
|
|
6112
6919
|
const requiredLines = [WORKTREE_MARKER_FILE];
|
|
6113
6920
|
const missing = requiredLines.filter((l) => !existing.split("\n").some((line) => line.trim() === l));
|
|
@@ -6115,7 +6922,7 @@ function ensureMarkerIgnoredAndCommitted(worktreePath, env) {
|
|
|
6115
6922
|
const next = `${existing.trimEnd() ? `${existing.trimEnd()}
|
|
6116
6923
|
` : ""}${missing.join("\n")}
|
|
6117
6924
|
`;
|
|
6118
|
-
|
|
6925
|
+
writeFileSync8(gitignorePath, next, "utf8");
|
|
6119
6926
|
try {
|
|
6120
6927
|
execFileSync8("git", ["-C", worktreePath, "add", ".gitignore"], { env, stdio: "pipe" });
|
|
6121
6928
|
execFileSync8(
|
|
@@ -6195,17 +7002,17 @@ function sleepSync2(ms) {
|
|
|
6195
7002
|
function backoffMs(baseMs, random = Math.random) {
|
|
6196
7003
|
return Math.round(baseMs * (0.5 + random() * 0.5));
|
|
6197
7004
|
}
|
|
6198
|
-
function
|
|
7005
|
+
function gitErrorText3(err) {
|
|
6199
7006
|
const e = err;
|
|
6200
7007
|
const stderr = e?.stderr ? e.stderr.toString() : "";
|
|
6201
7008
|
return `${stderr}${stderr ? "\n" : ""}${e?.message ?? String(err)}`;
|
|
6202
7009
|
}
|
|
6203
7010
|
function isIndexLockConflict(err) {
|
|
6204
|
-
const text =
|
|
7011
|
+
const text = gitErrorText3(err);
|
|
6205
7012
|
return /index\.lock/i.test(text) || /另一个 git 进程|another git process/i.test(text);
|
|
6206
7013
|
}
|
|
6207
7014
|
function worktreeIndexLockPath(repoCachePath, worktreePath) {
|
|
6208
|
-
return
|
|
7015
|
+
return join17(repoCachePath, ".git", "worktrees", basename(worktreePath), "index.lock");
|
|
6209
7016
|
}
|
|
6210
7017
|
function indexLockPathFromError(text) {
|
|
6211
7018
|
const m = text.match(/[^\s'‘’"]*index\.lock/);
|
|
@@ -6242,7 +7049,7 @@ function addWorktreeWithRetry(repoCachePath, worktreePath, ref, deps = {}) {
|
|
|
6242
7049
|
throw err;
|
|
6243
7050
|
if (attempt === WORKTREE_ADD_MAX_ATTEMPTS)
|
|
6244
7051
|
break;
|
|
6245
|
-
const lockPath = indexLockPathFromError(
|
|
7052
|
+
const lockPath = indexLockPathFromError(gitErrorText3(err)) ?? worktreeIndexLockPath(repoCachePath, worktreePath);
|
|
6246
7053
|
const released = waitForIndexLockRelease(lockPath, waitMs);
|
|
6247
7054
|
const backoff = backoffMs(WORKTREE_ADD_BACKOFF_MS[attempt - 1]);
|
|
6248
7055
|
console.warn(
|
|
@@ -6251,7 +7058,7 @@ function addWorktreeWithRetry(repoCachePath, worktreePath, ref, deps = {}) {
|
|
|
6251
7058
|
sleepSync2(backoff);
|
|
6252
7059
|
}
|
|
6253
7060
|
}
|
|
6254
|
-
throw new Error(`WORKTREE_ADD_FAILED: worktree add \u8FDE\u7EED ${WORKTREE_ADD_MAX_ATTEMPTS} \u6B21\u5931\u8D25\uFF08${
|
|
7061
|
+
throw new Error(`WORKTREE_ADD_FAILED: worktree add \u8FDE\u7EED ${WORKTREE_ADD_MAX_ATTEMPTS} \u6B21\u5931\u8D25\uFF08${gitErrorText3(lastErr).trim()}\uFF09`);
|
|
6255
7062
|
}
|
|
6256
7063
|
var FOREIGN_RESIDUE_MAX_ENTRIES = 64;
|
|
6257
7064
|
var FOREIGN_RESIDUE_SCAN_LIMIT = 2e3;
|
|
@@ -6259,7 +7066,7 @@ var foreignResidueHits = /* @__PURE__ */ new Map();
|
|
|
6259
7066
|
function inspectForeignDirectory(dir) {
|
|
6260
7067
|
let entries = [];
|
|
6261
7068
|
try {
|
|
6262
|
-
entries =
|
|
7069
|
+
entries = readdirSync4(dir, { withFileTypes: true });
|
|
6263
7070
|
} catch {
|
|
6264
7071
|
}
|
|
6265
7072
|
const topNames = entries.slice(0, 20).map((e) => `${e.name}${e.isDirectory() ? "/" : ""}`);
|
|
@@ -6273,7 +7080,7 @@ function inspectForeignDirectory(dir) {
|
|
|
6273
7080
|
return;
|
|
6274
7081
|
let children = [];
|
|
6275
7082
|
try {
|
|
6276
|
-
children =
|
|
7083
|
+
children = readdirSync4(p, { withFileTypes: true });
|
|
6277
7084
|
} catch {
|
|
6278
7085
|
return;
|
|
6279
7086
|
}
|
|
@@ -6281,9 +7088,9 @@ function inspectForeignDirectory(dir) {
|
|
|
6281
7088
|
if (count >= FOREIGN_RESIDUE_SCAN_LIMIT)
|
|
6282
7089
|
return;
|
|
6283
7090
|
count++;
|
|
6284
|
-
const full =
|
|
7091
|
+
const full = join17(p, e.name);
|
|
6285
7092
|
try {
|
|
6286
|
-
const st =
|
|
7093
|
+
const st = statSync3(full);
|
|
6287
7094
|
bytes += st.size;
|
|
6288
7095
|
minMs = Math.min(minMs, st.mtimeMs);
|
|
6289
7096
|
maxMs = Math.max(maxMs, st.mtimeMs);
|
|
@@ -6313,7 +7120,7 @@ function reportForeignResidue(worktreePath, forensics, cleaned) {
|
|
|
6313
7120
|
console.warn(`[daemon] ${detail}`);
|
|
6314
7121
|
}
|
|
6315
7122
|
function prepareWorktree(input) {
|
|
6316
|
-
const cache = openRepoCache(input.workdir, input.tenantId, input.repoFullName, input.env, input.remoteUrl);
|
|
7123
|
+
const cache = openRepoCache(input.workdir, input.tenantId, input.repoFullName, input.env, input.remoteUrl, input.daemonRemoteUrl);
|
|
6317
7124
|
cache.ensure();
|
|
6318
7125
|
cache.fetch();
|
|
6319
7126
|
const requestedBase = input.baseBranch?.trim();
|
|
@@ -6369,7 +7176,7 @@ function prepareWorktree(input) {
|
|
|
6369
7176
|
if (!isGitRepo) {
|
|
6370
7177
|
const forensics = inspectForeignDirectory(worktreePath);
|
|
6371
7178
|
try {
|
|
6372
|
-
|
|
7179
|
+
rmSync9(worktreePath, { recursive: true, force: true });
|
|
6373
7180
|
} catch {
|
|
6374
7181
|
reportForeignResidue(worktreePath, forensics, false);
|
|
6375
7182
|
throw new Error(`WORKTREE_RESIDUE: \u76EE\u5F55 ${worktreePath} \u6B8B\u7559\u4E14\u6E05\u7406\u5931\u8D25\uFF0C\u8BF7\u4EBA\u5DE5\u5904\u7406`);
|
|
@@ -6425,7 +7232,7 @@ function prepareWorktree(input) {
|
|
|
6425
7232
|
}
|
|
6426
7233
|
}
|
|
6427
7234
|
if (!existsSync13(worktreePath)) {
|
|
6428
|
-
|
|
7235
|
+
mkdirSync10(join17(worktreePath, ".."), { recursive: true });
|
|
6429
7236
|
addWorktreeWithRetry(cache.repoCachePath, worktreePath, `origin/${input.baseBranch}`);
|
|
6430
7237
|
writeWorktreeMarker(worktreePath, {
|
|
6431
7238
|
schemaVersion: 1,
|
|
@@ -6442,7 +7249,7 @@ function prepareWorktree(input) {
|
|
|
6442
7249
|
{
|
|
6443
7250
|
const dotMorulaRescue = `${worktreePath}.morula-rescue`;
|
|
6444
7251
|
if (existsSync13(dotMorulaRescue)) {
|
|
6445
|
-
const dotMorulaDir =
|
|
7252
|
+
const dotMorulaDir = join17(worktreePath, ".morula");
|
|
6446
7253
|
if (!existsSync13(dotMorulaDir)) {
|
|
6447
7254
|
try {
|
|
6448
7255
|
renameSync2(dotMorulaRescue, dotMorulaDir);
|
|
@@ -6451,7 +7258,7 @@ function prepareWorktree(input) {
|
|
|
6451
7258
|
console.warn(`[daemon] worktree \u6F02\u79FB\u81EA\u52A8\u6062\u590D: .morula/ \u5F52\u8FD8\u5931\u8D25\uFF0C\u5907\u4EFD\u4FDD\u7559\u5728 ${dotMorulaRescue}`);
|
|
6452
7259
|
}
|
|
6453
7260
|
} else {
|
|
6454
|
-
|
|
7261
|
+
rmSync9(dotMorulaRescue, { recursive: true, force: true });
|
|
6455
7262
|
}
|
|
6456
7263
|
}
|
|
6457
7264
|
}
|
|
@@ -6471,10 +7278,10 @@ function prepareWorktree(input) {
|
|
|
6471
7278
|
};
|
|
6472
7279
|
}
|
|
6473
7280
|
function resolveWorkspaceLayout(root, expected) {
|
|
6474
|
-
const primaryDir =
|
|
6475
|
-
if (existsSync13(
|
|
7281
|
+
const primaryDir = join17(root, safeDirName(expected.repoFullName));
|
|
7282
|
+
if (existsSync13(join17(primaryDir, WORKTREE_MARKER_FILE)))
|
|
6476
7283
|
return { primaryDir, legacy: false };
|
|
6477
|
-
if (existsSync13(
|
|
7284
|
+
if (existsSync13(join17(root, WORKTREE_MARKER_FILE)))
|
|
6478
7285
|
return { primaryDir: root, legacy: true };
|
|
6479
7286
|
return null;
|
|
6480
7287
|
}
|
|
@@ -6498,7 +7305,7 @@ function validateRemovable(taskRoot, expected, repoCachePath) {
|
|
|
6498
7305
|
).join("\n");
|
|
6499
7306
|
if (statusRelevant.length > 0)
|
|
6500
7307
|
return "WORKTREE_DIRTY";
|
|
6501
|
-
const additionalActivity = legacy ? findActivityInReposDir(
|
|
7308
|
+
const additionalActivity = legacy ? findActivityInReposDir(join17(primaryDir, ".morula", "repos")) : findAdditionalRepoActivity(taskRoot, { excludeDirs: [primaryDir] });
|
|
6502
7309
|
if (additionalActivity)
|
|
6503
7310
|
return "WORKTREE_DIRTY";
|
|
6504
7311
|
try {
|
|
@@ -6541,13 +7348,13 @@ function findActivityInReposDir(reposDir) {
|
|
|
6541
7348
|
return null;
|
|
6542
7349
|
let names = [];
|
|
6543
7350
|
try {
|
|
6544
|
-
names =
|
|
7351
|
+
names = readdirSync4(reposDir);
|
|
6545
7352
|
} catch {
|
|
6546
7353
|
return null;
|
|
6547
7354
|
}
|
|
6548
7355
|
for (const name of names) {
|
|
6549
|
-
const repoPath =
|
|
6550
|
-
if (!existsSync13(
|
|
7356
|
+
const repoPath = join17(reposDir, name);
|
|
7357
|
+
if (!existsSync13(join17(repoPath, ".git")))
|
|
6551
7358
|
continue;
|
|
6552
7359
|
if (repoHasActivity(repoPath))
|
|
6553
7360
|
return repoPath;
|
|
@@ -6558,20 +7365,20 @@ function findAdditionalRepoActivity(taskRoot, options = {}) {
|
|
|
6558
7365
|
const excluded = new Set(options.excludeDirs ?? []);
|
|
6559
7366
|
let entries = [];
|
|
6560
7367
|
try {
|
|
6561
|
-
entries =
|
|
7368
|
+
entries = readdirSync4(taskRoot, { withFileTypes: true });
|
|
6562
7369
|
} catch {
|
|
6563
7370
|
return null;
|
|
6564
7371
|
}
|
|
6565
|
-
const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) =>
|
|
6566
|
-
const legacyReposDir =
|
|
7372
|
+
const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => join17(taskRoot, e.name)).filter((dir) => !excluded.has(dir));
|
|
7373
|
+
const legacyReposDir = join17(taskRoot, ".morula", "repos");
|
|
6567
7374
|
if (existsSync13(legacyReposDir)) {
|
|
6568
7375
|
try {
|
|
6569
|
-
candidates.push(...
|
|
7376
|
+
candidates.push(...readdirSync4(legacyReposDir).map((n) => join17(legacyReposDir, n)));
|
|
6570
7377
|
} catch {
|
|
6571
7378
|
}
|
|
6572
7379
|
}
|
|
6573
7380
|
for (const repoPath of candidates) {
|
|
6574
|
-
if (!existsSync13(
|
|
7381
|
+
if (!existsSync13(join17(repoPath, ".git")))
|
|
6575
7382
|
continue;
|
|
6576
7383
|
if (repoHasActivity(repoPath))
|
|
6577
7384
|
return repoPath;
|
|
@@ -6579,18 +7386,18 @@ function findAdditionalRepoActivity(taskRoot, options = {}) {
|
|
|
6579
7386
|
return null;
|
|
6580
7387
|
}
|
|
6581
7388
|
function cleanupInjectedSkills(dir) {
|
|
6582
|
-
const injectedSkillsRoot =
|
|
7389
|
+
const injectedSkillsRoot = join17(dir, ".agents", "skills");
|
|
6583
7390
|
if (!existsSync13(injectedSkillsRoot))
|
|
6584
7391
|
return;
|
|
6585
7392
|
try {
|
|
6586
|
-
for (const name of
|
|
7393
|
+
for (const name of readdirSync4(injectedSkillsRoot)) {
|
|
6587
7394
|
if (name === COLLABORATION_SKILL_NAME || /^[\w-]{1,64}$/.test(name)) {
|
|
6588
|
-
|
|
7395
|
+
rmSync9(join17(injectedSkillsRoot, name), { recursive: true, force: true });
|
|
6589
7396
|
}
|
|
6590
7397
|
}
|
|
6591
|
-
if (
|
|
6592
|
-
|
|
6593
|
-
|
|
7398
|
+
if (readdirSync4(injectedSkillsRoot).length === 0) {
|
|
7399
|
+
rmSync9(join17(dir, ".agents", "skills"), { recursive: true, force: true });
|
|
7400
|
+
rmSync9(join17(dir, ".agents"), { recursive: true, force: true });
|
|
6594
7401
|
}
|
|
6595
7402
|
} catch {
|
|
6596
7403
|
}
|
|
@@ -6599,15 +7406,15 @@ function collectSiblingGitCommonDirs(taskRoot, excludeDir) {
|
|
|
6599
7406
|
const dirs = /* @__PURE__ */ new Set();
|
|
6600
7407
|
let entries = [];
|
|
6601
7408
|
try {
|
|
6602
|
-
entries =
|
|
7409
|
+
entries = readdirSync4(taskRoot, { withFileTypes: true });
|
|
6603
7410
|
} catch {
|
|
6604
7411
|
return [];
|
|
6605
7412
|
}
|
|
6606
7413
|
for (const e of entries) {
|
|
6607
7414
|
if (!e.isDirectory() || e.name.startsWith("."))
|
|
6608
7415
|
continue;
|
|
6609
|
-
const repoPath =
|
|
6610
|
-
if (repoPath === excludeDir || !existsSync13(
|
|
7416
|
+
const repoPath = join17(taskRoot, e.name);
|
|
7417
|
+
if (repoPath === excludeDir || !existsSync13(join17(repoPath, ".git")))
|
|
6611
7418
|
continue;
|
|
6612
7419
|
try {
|
|
6613
7420
|
const commonDir = execFileSync8("git", ["-C", repoPath, "rev-parse", "--path-format=absolute", "--git-common-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
@@ -6624,13 +7431,13 @@ function removeTaskWorkspace(repoCachePath, taskRoot, expected) {
|
|
|
6624
7431
|
throw new Error("MARKER_MISMATCH");
|
|
6625
7432
|
const { primaryDir, legacy } = layout;
|
|
6626
7433
|
if (!legacy) {
|
|
6627
|
-
if (primaryDir !==
|
|
7434
|
+
if (primaryDir !== join17(taskRoot, safeDirName(expected.repoFullName)) || !isManagedWorkspaceRoot(taskRoot))
|
|
6628
7435
|
throw new Error("GIT_REMOVE_FAILED");
|
|
6629
7436
|
}
|
|
6630
7437
|
cleanupInjectedSkills(legacy ? primaryDir : taskRoot);
|
|
6631
7438
|
const siblingCommonDirs = legacy ? [] : collectSiblingGitCommonDirs(taskRoot, primaryDir);
|
|
6632
7439
|
try {
|
|
6633
|
-
|
|
7440
|
+
rmSync9(taskRoot, { recursive: true, force: true });
|
|
6634
7441
|
} catch {
|
|
6635
7442
|
throw new Error("GIT_REMOVE_FAILED");
|
|
6636
7443
|
}
|
|
@@ -6654,7 +7461,7 @@ function removeWorktree(repoCachePath, worktreePath) {
|
|
|
6654
7461
|
}
|
|
6655
7462
|
|
|
6656
7463
|
// src/daemon/repo-checkout-service.ts
|
|
6657
|
-
var CHECKOUT_JSON_FILE =
|
|
7464
|
+
var CHECKOUT_JSON_FILE = join18(".morula", "checkout.json");
|
|
6658
7465
|
var LocalRepoCheckoutService = class {
|
|
6659
7466
|
constructor(opts) {
|
|
6660
7467
|
this.opts = opts;
|
|
@@ -6742,9 +7549,9 @@ var LocalRepoCheckoutService = class {
|
|
|
6742
7549
|
return;
|
|
6743
7550
|
this.tasks.set(taskId, { workdir, checkToken, primaryRepoDir });
|
|
6744
7551
|
try {
|
|
6745
|
-
const dir =
|
|
6746
|
-
|
|
6747
|
-
|
|
7552
|
+
const dir = join18(workdir, ".morula");
|
|
7553
|
+
mkdirSync11(dir, { recursive: true, mode: 448 });
|
|
7554
|
+
writeFileSync9(join18(dir, "checkout.json"), JSON.stringify({
|
|
6748
7555
|
taskId,
|
|
6749
7556
|
port: this.boundPort,
|
|
6750
7557
|
checkToken
|
|
@@ -6798,7 +7605,8 @@ var LocalRepoCheckoutService = class {
|
|
|
6798
7605
|
this.opts.tenantId,
|
|
6799
7606
|
repoFullName,
|
|
6800
7607
|
env,
|
|
6801
|
-
baseUrl ? buildGitlabSshRemote(repoFullName, baseUrl) : void 0
|
|
7608
|
+
baseUrl ? buildGitlabSshRemote(repoFullName, baseUrl) : void 0,
|
|
7609
|
+
baseUrl ? buildDaemonRepoUrl(repoFullName, baseUrl) : void 0
|
|
6802
7610
|
);
|
|
6803
7611
|
cache.ensure();
|
|
6804
7612
|
cache.fetch();
|
|
@@ -6806,6 +7614,8 @@ var LocalRepoCheckoutService = class {
|
|
|
6806
7614
|
repoCachePath: cache.repoCachePath,
|
|
6807
7615
|
taskRoot: task.workdir,
|
|
6808
7616
|
repoFullName,
|
|
7617
|
+
baseBranch: serverRes.baseBranch,
|
|
7618
|
+
ref: req.ref,
|
|
6809
7619
|
env
|
|
6810
7620
|
});
|
|
6811
7621
|
const headSha = execFileSync9("git", ["-C", worktreePath, "rev-parse", "HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
|
|
@@ -6859,7 +7669,8 @@ var LocalRepoCheckoutService = class {
|
|
|
6859
7669
|
this.opts.tenantId,
|
|
6860
7670
|
repoFullName,
|
|
6861
7671
|
env,
|
|
6862
|
-
baseUrl ? buildGitlabSshRemote(repoFullName, baseUrl) : void 0
|
|
7672
|
+
baseUrl ? buildGitlabSshRemote(repoFullName, baseUrl) : void 0,
|
|
7673
|
+
baseUrl ? buildDaemonRepoUrl(repoFullName, baseUrl) : void 0
|
|
6863
7674
|
);
|
|
6864
7675
|
cache.ensure();
|
|
6865
7676
|
cache.fetch();
|
|
@@ -6904,11 +7715,11 @@ function isReadOnlyRepoDir(dirName) {
|
|
|
6904
7715
|
return READ_ONLY_DIR_RE.test(dirName) || dirName.startsWith(LEGACY_READ_ONLY_DIR_PREFIX);
|
|
6905
7716
|
}
|
|
6906
7717
|
function resolveRepoTargetDir(taskRoot, base) {
|
|
6907
|
-
const direct =
|
|
6908
|
-
if (!existsSync14(direct) || existsSync14(
|
|
7718
|
+
const direct = join18(taskRoot, base);
|
|
7719
|
+
if (!existsSync14(direct) || existsSync14(join18(direct, ".git")))
|
|
6909
7720
|
return direct;
|
|
6910
7721
|
for (let i = 2; i < 100; i++) {
|
|
6911
|
-
const candidate =
|
|
7722
|
+
const candidate = join18(taskRoot, `${base}-${i}`);
|
|
6912
7723
|
if (!existsSync14(candidate))
|
|
6913
7724
|
return candidate;
|
|
6914
7725
|
}
|
|
@@ -6916,36 +7727,88 @@ function resolveRepoTargetDir(taskRoot, base) {
|
|
|
6916
7727
|
}
|
|
6917
7728
|
function materializeReadWorktree(input) {
|
|
6918
7729
|
const worktreePath = resolveRepoTargetDir(input.taskRoot, `${safeRepoDirName(input.repoFullName)}${READ_ONLY_REPO_DIR_SUFFIX}`);
|
|
6919
|
-
|
|
6920
|
-
|
|
7730
|
+
const targetRef = resolveReadRef(input.repoCachePath, input.env, input.ref, input.baseBranch);
|
|
7731
|
+
if (existsSync14(join18(worktreePath, ".git"))) {
|
|
7732
|
+
const current = currentBranchName(worktreePath);
|
|
7733
|
+
if (current)
|
|
7734
|
+
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`);
|
|
7735
|
+
if (hasLocalChanges2(worktreePath, input.env))
|
|
7736
|
+
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");
|
|
7737
|
+
execFileSync9("git", ["-C", worktreePath, "checkout", "--detach", targetRef], { env: input.env, stdio: "pipe" });
|
|
6921
7738
|
} else {
|
|
6922
|
-
|
|
6923
|
-
execFileSync9("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", worktreePath,
|
|
7739
|
+
mkdirSync11(join18(worktreePath, ".."), { recursive: true });
|
|
7740
|
+
execFileSync9("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", worktreePath, targetRef], {
|
|
6924
7741
|
env: input.env,
|
|
6925
7742
|
stdio: "pipe"
|
|
6926
7743
|
});
|
|
6927
7744
|
}
|
|
6928
7745
|
return worktreePath;
|
|
6929
7746
|
}
|
|
6930
|
-
function
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
7747
|
+
function resolveReadRef(repoCachePath, env, requestedRef, baseBranch) {
|
|
7748
|
+
const requested = requestedRef?.trim();
|
|
7749
|
+
if (requested) {
|
|
7750
|
+
const candidates = requested.startsWith("refs/") ? [requested] : [`origin/${requested}`, requested];
|
|
7751
|
+
for (const ref of candidates) {
|
|
7752
|
+
if (refExists(repoCachePath, ref, env))
|
|
7753
|
+
return ref;
|
|
7754
|
+
}
|
|
7755
|
+
throw new Error(`REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u5F15\u7528 ${requested}`);
|
|
7756
|
+
}
|
|
7757
|
+
const explicitBase = baseBranch?.trim();
|
|
7758
|
+
if (explicitBase) {
|
|
7759
|
+
const ref = `origin/${explicitBase}`;
|
|
7760
|
+
if (refExists(repoCachePath, ref, env))
|
|
6934
7761
|
return ref;
|
|
7762
|
+
throw new Error(`BASE_REF_NOT_FOUND: repo cache \u65E0\u9ED8\u8BA4\u5206\u652F ${explicitBase}`);
|
|
7763
|
+
}
|
|
7764
|
+
const remoteHead = (() => {
|
|
7765
|
+
try {
|
|
7766
|
+
const out = execFileSync9("git", ["-C", repoCachePath, "symbolic-ref", "refs/remotes/origin/HEAD"], { encoding: "utf8", env, stdio: "pipe" }).trim();
|
|
7767
|
+
return out.replace(/^refs\/remotes\//, "");
|
|
6935
7768
|
} catch {
|
|
7769
|
+
return null;
|
|
6936
7770
|
}
|
|
7771
|
+
})();
|
|
7772
|
+
for (const ref of [remoteHead, "origin/main", "origin/master"]) {
|
|
7773
|
+
if (!ref || !refExists(repoCachePath, ref, env))
|
|
7774
|
+
continue;
|
|
7775
|
+
return ref;
|
|
7776
|
+
}
|
|
7777
|
+
throw new Error("BASE_REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u8FDC\u7AEF\u5F15\u7528\uFF08origin/HEAD / origin/main / origin/master\uFF09");
|
|
7778
|
+
}
|
|
7779
|
+
function refExists(repoPath, ref, env) {
|
|
7780
|
+
try {
|
|
7781
|
+
execFileSync9("git", ["-C", repoPath, "rev-parse", "--verify", ref], { env, stdio: "pipe" });
|
|
7782
|
+
return true;
|
|
7783
|
+
} catch {
|
|
7784
|
+
return false;
|
|
7785
|
+
}
|
|
7786
|
+
}
|
|
7787
|
+
function currentBranchName(repoPath) {
|
|
7788
|
+
try {
|
|
7789
|
+
const out = execFileSync9("git", ["-C", repoPath, "branch", "--show-current"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
7790
|
+
return out || null;
|
|
7791
|
+
} catch {
|
|
7792
|
+
return null;
|
|
7793
|
+
}
|
|
7794
|
+
}
|
|
7795
|
+
function hasLocalChanges2(repoPath, env) {
|
|
7796
|
+
try {
|
|
7797
|
+
const out = execFileSync9("git", ["-C", repoPath, "status", "--porcelain"], { encoding: "utf8", env, stdio: "pipe" }).trim();
|
|
7798
|
+
return out.length > 0;
|
|
7799
|
+
} catch {
|
|
7800
|
+
return true;
|
|
6937
7801
|
}
|
|
6938
|
-
throw new Error("BASE_REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u5F15\u7528\uFF08origin/main / origin/master / HEAD\uFF09");
|
|
6939
7802
|
}
|
|
6940
7803
|
function listMaterializedRepoDirs(taskRoot, options = {}) {
|
|
6941
7804
|
const excluded = new Set(options.excludeDirs ?? []);
|
|
6942
7805
|
let entries;
|
|
6943
7806
|
try {
|
|
6944
|
-
entries =
|
|
7807
|
+
entries = readdirSync5(taskRoot, { withFileTypes: true });
|
|
6945
7808
|
} catch {
|
|
6946
7809
|
return [];
|
|
6947
7810
|
}
|
|
6948
|
-
return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) =>
|
|
7811
|
+
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();
|
|
6949
7812
|
}
|
|
6950
7813
|
function parseRepoFullNameFromRemoteUrl(url) {
|
|
6951
7814
|
const raw = url?.trim();
|
|
@@ -6967,7 +7830,7 @@ function parseRepoFullNameFromRemoteUrl(url) {
|
|
|
6967
7830
|
}
|
|
6968
7831
|
function collectMaterializedRepoDeliveryTargets(input) {
|
|
6969
7832
|
const excludedNames = new Set(input.excludeRepoFullNames?.filter(Boolean) ?? []);
|
|
6970
|
-
const excludedDirs = [...excludedNames].map((name) =>
|
|
7833
|
+
const excludedDirs = [...excludedNames].map((name) => join18(input.taskRoot, safeRepoDirName(name)));
|
|
6971
7834
|
const recordsByDir = /* @__PURE__ */ new Map();
|
|
6972
7835
|
const recordsByDirName = /* @__PURE__ */ new Map();
|
|
6973
7836
|
for (const [repoFullName, record] of input.prepared ?? []) {
|
|
@@ -7030,11 +7893,15 @@ function syncGitIdentity(primaryDir, targetDir, env) {
|
|
|
7030
7893
|
}
|
|
7031
7894
|
}
|
|
7032
7895
|
function materializeAdditionalRepo(input) {
|
|
7033
|
-
if (existsSync14(
|
|
7034
|
-
|
|
7896
|
+
if (existsSync14(join18(input.targetPath, ".git"))) {
|
|
7897
|
+
const current = currentBranchName(input.targetPath);
|
|
7898
|
+
if (!current && !hasLocalChanges2(input.targetPath, input.env)) {
|
|
7899
|
+
const targetRef = resolveReadRef(input.repoCachePath, input.env, void 0, input.baseBranch);
|
|
7900
|
+
execFileSync9("git", ["-C", input.targetPath, "checkout", "--detach", targetRef], { env: input.env, stdio: "pipe" });
|
|
7901
|
+
}
|
|
7035
7902
|
return input.targetPath;
|
|
7036
7903
|
}
|
|
7037
|
-
const candidates = input.baseBranch ? [`origin/${input.baseBranch}`, "origin/main", "origin/master"
|
|
7904
|
+
const candidates = input.baseBranch ? [`origin/${input.baseBranch}`, "origin/main", "origin/master"] : ["origin/main", "origin/master"];
|
|
7038
7905
|
let baseRef = null;
|
|
7039
7906
|
for (const ref of candidates) {
|
|
7040
7907
|
try {
|
|
@@ -7045,8 +7912,8 @@ function materializeAdditionalRepo(input) {
|
|
|
7045
7912
|
}
|
|
7046
7913
|
}
|
|
7047
7914
|
if (!baseRef)
|
|
7048
|
-
throw new Error("BASE_REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u5F15\u7528\uFF08origin/<base> / origin/main / origin/master
|
|
7049
|
-
|
|
7915
|
+
throw new Error("BASE_REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u8FDC\u7AEF\u5F15\u7528\uFF08origin/<base> / origin/main / origin/master\uFF09");
|
|
7916
|
+
mkdirSync11(join18(input.targetPath, ".."), { recursive: true });
|
|
7050
7917
|
execFileSync9("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", input.targetPath, baseRef], {
|
|
7051
7918
|
env: input.env,
|
|
7052
7919
|
stdio: "pipe"
|
|
@@ -7192,52 +8059,857 @@ function buildStageContract(stageCode, source) {
|
|
|
7192
8059
|
return STAGE_CONTRACTS[stageCode] ?? "";
|
|
7193
8060
|
}
|
|
7194
8061
|
|
|
7195
|
-
// src/daemon/
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
}
|
|
7235
|
-
/** 当前已订阅的任务房间(诊断/测试用只读快照) */
|
|
7236
|
-
get subscribedTaskIds() {
|
|
7237
|
-
return [...this.taskRooms.keys()];
|
|
8062
|
+
// src/daemon/session-host.ts
|
|
8063
|
+
init_dist2();
|
|
8064
|
+
init_acp_connection();
|
|
8065
|
+
import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync10 } from "node:fs";
|
|
8066
|
+
import { join as join19 } from "node:path";
|
|
8067
|
+
|
|
8068
|
+
// src/daemon/steward-instructions.ts
|
|
8069
|
+
var STEWARD_SYSTEM_INSTRUCTIONS = `\u4F60\u662F\u300C\u7A7A\u95F4\u7BA1\u5BB6\u667A\u80FD\u4F53\u300D\uFF08Project Steward\uFF09\u3002
|
|
8070
|
+
|
|
8071
|
+
\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
|
|
8072
|
+
\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
|
|
8073
|
+
|
|
8074
|
+
## \u4F60\u7684\u5DE5\u4F5C\u533A\u4E0E\u5DE5\u5177
|
|
8075
|
+
|
|
8076
|
+
- \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
|
|
8077
|
+
- \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
|
|
8078
|
+
- \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
|
|
8079
|
+
\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`;
|
|
8080
|
+
function buildStewardContextMd(facts) {
|
|
8081
|
+
const lines = [
|
|
8082
|
+
"# \u7BA1\u5BB6\u4F1A\u8BDD\u4E0A\u4E0B\u6587",
|
|
8083
|
+
"",
|
|
8084
|
+
"| \u4E8B\u5B9E | \u503C |",
|
|
8085
|
+
"|------|-----|",
|
|
8086
|
+
`| \u7A7A\u95F4 ID\uFF08\`--space\` \u7528\u5B83\uFF09 | \`${facts.projectId}\` |`,
|
|
8087
|
+
`| \u7A7A\u95F4\u540D\u79F0 | ${facts.projectName?.trim() || "\uFF08\u672A\u547D\u540D\uFF09"} |`,
|
|
8088
|
+
`| \u79DF\u6237 | \`${facts.tenantId}\` |`,
|
|
8089
|
+
`| \u5F53\u524D\u7528\u6237 | ${facts.userName?.trim() || "\uFF08\u672A\u77E5\uFF09"} |`
|
|
8090
|
+
];
|
|
8091
|
+
if (facts.stewardPrincipalId)
|
|
8092
|
+
lines.push(`| \u7BA1\u5BB6\u7F72\u540D\u8EAB\u4EFD | \`${facts.stewardPrincipalId}\` |`);
|
|
8093
|
+
lines.push("");
|
|
8094
|
+
if (facts.repos?.length) {
|
|
8095
|
+
lines.push("## \u672C\u7A7A\u95F4\u7ED1\u5B9A\u4ED3\u5E93", "");
|
|
8096
|
+
for (const repo of facts.repos)
|
|
8097
|
+
lines.push(`- \`${repo}\``);
|
|
8098
|
+
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");
|
|
8099
|
+
} else {
|
|
8100
|
+
lines.push("## \u672C\u7A7A\u95F4\u7ED1\u5B9A\u4ED3\u5E93", "", "\uFF08\u672A\u7ED1\u5B9A\u4ED3\u5E93\uFF09");
|
|
7238
8101
|
}
|
|
7239
|
-
|
|
7240
|
-
|
|
8102
|
+
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");
|
|
8103
|
+
return `${lines.join("\n")}
|
|
8104
|
+
`;
|
|
8105
|
+
}
|
|
8106
|
+
|
|
8107
|
+
// src/daemon/steward-skill.ts
|
|
8108
|
+
var STEWARD_SKILL_NAME = "steward-management";
|
|
8109
|
+
var STEWARD_SKILL_FRONTMATTER = `---
|
|
8110
|
+
name: steward-management
|
|
8111
|
+
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."
|
|
8112
|
+
user-invocable: false
|
|
8113
|
+
allowed-tools: Bash(morula *)
|
|
8114
|
+
---
|
|
8115
|
+
`;
|
|
8116
|
+
var STEWARD_SKILL_BODY = `# \u7A7A\u95F4\u7BA1\u5BB6\u5951\u7EA6\uFF08Morula\uFF09
|
|
8117
|
+
|
|
8118
|
+
\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**
|
|
8119
|
+
\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
|
|
8120
|
+
|
|
8121
|
+
## 0. \u804C\u8D23\u8FB9\u754C\u4E0E\u786C\u7EA6\u675F
|
|
8122
|
+
|
|
8123
|
+
### 0.1 \u804C\u8D23\u8FB9\u754C
|
|
8124
|
+
|
|
8125
|
+
- **\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
|
|
8126
|
+
- **\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
|
|
8127
|
+
- **\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
|
|
8128
|
+
- **\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
|
|
8129
|
+
- \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
|
|
8130
|
+
|
|
8131
|
+
### 0.2 \u786C\u7EA6\u675F
|
|
8132
|
+
|
|
8133
|
+
- **\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
|
|
8134
|
+
- **\u4E0D\u8981\u7528\u4F9D\u8D56\u4EFB\u52A1\u4E0A\u4E0B\u6587\u7684\u547D\u4EE4**\uFF1A\u672C\u4F1A\u8BDD**\u6CA1\u6709 \`MORULA_TASK_ID\`**\u3002
|
|
8135
|
+
\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
|
|
8136
|
+
\`morula task get\`\uFF08\u65E0\u53C2\u5F62\u5F0F\uFF09\u3001\`morula task branch\`\u3001\`morula task state\`\u3001
|
|
8137
|
+
\`morula task analysis-verdict\`\u3001\`morula task inbox\`\u3001\`morula review submit\`\u3001
|
|
8138
|
+
\`morula repo checkout\`\u3001\`morula repo prepare\`\u3001\`morula repo mr *\`\u3001\`morula attachment upload\`\u3002
|
|
8139
|
+
\u540C\u7406\uFF0C**CI \u547D\u4EE4\u4E00\u5F8B\u7528 \`--space\` \u5F62\u5F0F**\uFF08\`morula ci status --space <id>\`\uFF09\uFF0C
|
|
8140
|
+
\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
|
|
8141
|
+
- **\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
|
|
8142
|
+
- **\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
|
|
8143
|
+
- \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
|
|
8144
|
+
- **\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
|
|
8145
|
+
|
|
8146
|
+
## 1. \u5E38\u7528\u547D\u4EE4\uFF08\u5168\u90E8\u663E\u5F0F\u53C2\u6570\uFF09
|
|
8147
|
+
|
|
8148
|
+
### \u770B\u7A7A\u95F4\u4E0E\u4EFB\u52A1
|
|
8149
|
+
\`\`\`bash
|
|
8150
|
+
morula steward get --space <spaceId> # \u7BA1\u5BB6\u72B6\u6001 / \u7A7A\u95F4\u8BF4\u660E\uFF08AGENTS.md\uFF09/ \u9636\u6BB5\u6267\u884C\u7ED1\u5B9A
|
|
8151
|
+
morula task list --space <spaceId> [--state <code>] [--assignee <principalId>] [--search <text>]
|
|
8152
|
+
morula task get <objectId> # \u5355\u4E2A\u4E1A\u52A1\u4EFB\u52A1\u8BE6\u60C5\uFF08\u5FC5\u987B\u5E26 id\uFF09
|
|
8153
|
+
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
|
|
8154
|
+
morula comment list --object <objectId> # \u4EFB\u52A1\u8BC4\u8BBA\uFF08\u5206\u6790\u7ED3\u8BBA / \u8BC4\u5BA1\u610F\u89C1\u90FD\u5728\u8FD9\u91CC\uFF09
|
|
8155
|
+
morula space list # \u53EF\u89C1\u7A7A\u95F4
|
|
8156
|
+
morula space-member list --space <spaceId> # \u7A7A\u95F4\u6210\u5458\uFF08\u627E\u8D1F\u8D23\u4EBA principalId\uFF09
|
|
8157
|
+
morula dashboard get --space <spaceId> # \u6982\u89C8\uFF1A\u4EFB\u52A1\u603B\u6570/\u5B8C\u6210\u7387/\u72B6\u6001\u5206\u5E03/\u6210\u5458\u8D1F\u8377
|
|
8158
|
+
morula dashboard statistics --space <spaceId> # \u6309\u4F18\u5148\u7EA7\u7EDF\u8BA1
|
|
8159
|
+
morula dashboard workload --space <spaceId> # \u6210\u5458\u8D1F\u8377\u660E\u7EC6
|
|
8160
|
+
\`\`\`
|
|
8161
|
+
|
|
8162
|
+
### \u770B\u6D41\u6C34\u7EBF\uFF08CI/CD\uFF09
|
|
8163
|
+
\`\`\`bash
|
|
8164
|
+
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
|
|
8165
|
+
morula ci deployments --space <spaceId> # \u90E8\u7F72\u8BB0\u5F55\uFF08\u53EA\u8BFB\u5C55\u793A\uFF09
|
|
8166
|
+
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
|
|
8167
|
+
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
|
|
8168
|
+
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**
|
|
8169
|
+
\`\`\`
|
|
8170
|
+
|
|
8171
|
+
- **\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
|
|
8172
|
+
**\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
|
|
8173
|
+
- **\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
|
|
8174
|
+
\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
|
|
8175
|
+
- \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
|
|
8176
|
+
- \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
|
|
8177
|
+
- \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
|
|
8178
|
+
- \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
|
|
8179
|
+
- \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
|
|
8180
|
+
|
|
8181
|
+
### \u6D3E\u5355
|
|
8182
|
+
\`\`\`bash
|
|
8183
|
+
morula steward dispatch --space <spaceId> --title "<\u4E00\u53E5\u8BDD\u76EE\u6807>" \\
|
|
8184
|
+
[--description "<\u80CC\u666F/\u52A8\u673A/\u76EE\u6807/\u7EA6\u675F>"] \\
|
|
8185
|
+
[--state backlog|todo|in_progress] \\
|
|
8186
|
+
[--assignee <principalId>] \\
|
|
8187
|
+
[--repo <owner/name>]
|
|
8188
|
+
\`\`\`
|
|
8189
|
+
|
|
8190
|
+
- \`--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
|
|
8191
|
+
- \`--state backlog\`\uFF1A\u53EA\u843D\u300C\u5F85\u89C4\u5212\u300D\uFF0C**\u4E0D\u6D3E\u53D1\u6267\u884C**\uFF1B\u7B49\u4EBA\u6216\u4F60\u540E\u7EED\u7528
|
|
8192
|
+
\`morula task update <objectId> --state todo\` \u63A8\u8FDB\u3002
|
|
8193
|
+
- \`--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
|
|
8194
|
+
- \`--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
|
|
8195
|
+
- \`--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
|
|
8196
|
+
\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
|
|
8197
|
+
|
|
8198
|
+
### \u6D3E\u5355\u6D41\u7A0B\uFF08\u5206\u6790\u81EA\u52A8\u8FDB\u5F00\u53D1\uFF09
|
|
8199
|
+
|
|
8200
|
+
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
|
|
8201
|
+
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
|
|
8202
|
+
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
|
|
8203
|
+
|
|
8204
|
+
### \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
|
|
8205
|
+
|
|
8206
|
+
- **\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
|
|
8207
|
+
- **\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
|
|
8208
|
+
- \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
|
|
8209
|
+
- **\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
|
|
8210
|
+
|
|
8211
|
+
### \u591A\u4ED3\u5E93\u7A7A\u95F4\uFF08\u7A7A\u95F4\u7ED1\u5B9A\u4E86\u591A\u4E2A\u4ED3\u5E93\u65F6\uFF09
|
|
8212
|
+
|
|
8213
|
+
- \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
|
|
8214
|
+
- **\u6D3E\u5355\u65F6\u5FC5\u987B\u628A\u6D89\u53CA\u4ED3\u5E93\u4F20\u7ED9 \`--repo\`**\uFF08\u5199\u4ED3\u5E93\u540D\uFF0C\u5982 \`nextim/morula-web\`\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
|
|
8215
|
+
|
|
8216
|
+
### \u7BA1\u7406\u4EFB\u52A1\uFF08\u6539\u5C5E\u6027 / \u63A8\u8FDB\u72B6\u6001 / \u5EFA\u5355 / \u5220\u5355\uFF09
|
|
8217
|
+
\`\`\`bash
|
|
8218
|
+
morula task create --space <spaceId> --title "<\u6807\u9898>" [--description "<\u63CF\u8FF0>"] \\
|
|
8219
|
+
[--priority <low|medium|high|urgent>] [--state backlog|todo]
|
|
8220
|
+
morula task update <objectId> [--title <t>] [--description <d>] [--priority <p>] [--assignee <principalId>] [--state <code>]
|
|
8221
|
+
morula task delete <objectId> # \u5220\u9664\u4EFB\u52A1\uFF08\u7834\u574F\u6027\uFF1A\u5148\u8981\u786E\u8BA4\uFF09
|
|
8222
|
+
\`\`\`
|
|
8223
|
+
|
|
8224
|
+
- \`--state\` \u7528\u72B6\u6001 code\uFF08\`backlog\` / \`todo\` / \`in_progress\` / \`review\` / \`merged\` / \`released\` / \`closed\`\uFF09\uFF0C\u4E0D\u8981\u4F20 UUID\u3002
|
|
8225
|
+
- **\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
|
|
8226
|
+
- **\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
|
|
8227
|
+
- \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
|
|
8228
|
+
- \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
|
|
8229
|
+
|
|
8230
|
+
### \u8BC4\u8BBA\u4E0E @ \u4EBA\uFF08\u50AC\u529E\uFF09
|
|
8231
|
+
\`\`\`bash
|
|
8232
|
+
morula comment add --object <objectId> --content "<\u5185\u5BB9>" [--format markdown] [--parent <commentId>] [--mention <principalId>]...
|
|
8233
|
+
morula comment update <commentId> --content "<\u65B0\u5185\u5BB9>"
|
|
8234
|
+
morula comment delete <commentId>
|
|
8235
|
+
\`\`\`
|
|
8236
|
+
|
|
8237
|
+
- \`--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
|
|
8238
|
+
- **\u4E0D\u8981 @ \u8FD0\u884C\u65F6**\uFF08\u89C1 \xA70.2\uFF09\u3002
|
|
8239
|
+
- \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
|
|
8240
|
+
|
|
8241
|
+
### \u8FD0\u884C\u5E72\u9884\uFF08\u505C\u6389\u5361\u4F4F\u7684\u6267\u884C / \u91CD\u8DD1\u5931\u8D25\u7684\uFF09
|
|
8242
|
+
\`\`\`bash
|
|
8243
|
+
morula task agent-tasks --object <objectId> --status running # \u5148\u627E\u5230\u5728\u8DD1\u7684 AgentTask id
|
|
8244
|
+
morula task cancel <agentTaskId> # \u53D6\u6D88\uFF08\u7834\u574F\u6027\uFF1A\u5148\u8981\u786E\u8BA4\uFF09
|
|
8245
|
+
morula task rerun <agentTaskId> # \u91CD\u8DD1\uFF08\u4EC5 failed / cancelled \u53EF\u91CD\u8DD1\uFF1B\u5148\u8981\u786E\u8BA4\uFF09
|
|
8246
|
+
\`\`\`
|
|
8247
|
+
|
|
8248
|
+
- \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
|
|
8249
|
+
- **\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
|
|
8250
|
+
- \u505C/\u91CD\u8DD1\u540E\u56DE\u8BFB\u4E00\u6B21\u72B6\u6001\u786E\u8BA4\u7ED3\u679C\uFF0C\u518D\u5411\u7528\u6237\u6C47\u62A5\u3002
|
|
8251
|
+
|
|
8252
|
+
## 2. \u88C1\u5B9A\u8F93\u51FA\u683C\u5F0F\uFF08\u524D\u7AEF\u6309\u6B64\u6E32\u67D3\uFF0C\u5FC5\u987B\u9075\u5B88\uFF09
|
|
8253
|
+
|
|
8254
|
+
\u56DE\u590D\u7528\u6237\u65F6\uFF0C\u7ED3\u8BBA\u884C\u5FC5\u987B\u662F\u4E0B\u5217\u56DB\u8005\u4E4B\u4E00\uFF0C\u5355\u72EC\u6210\u884C\uFF1A
|
|
8255
|
+
|
|
8256
|
+
- \`\u7ED3\u8BBA\uFF1A\u2705 \u5EFA\u8BAE\u505A\`
|
|
8257
|
+
- \`\u7ED3\u8BBA\uFF1A\u274C \u4E0D\u5EFA\u8BAE\u505A\`
|
|
8258
|
+
- \`\u7ED3\u8BBA\uFF1A\u23F8 \u6682\u7F13\`
|
|
8259
|
+
- \`\u7ED3\u8BBA\uFF1A\u2753 \u9700\u6F84\u6E05\`
|
|
8260
|
+
|
|
8261
|
+
\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
|
|
8262
|
+
\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
|
|
8263
|
+
|
|
8264
|
+
## 3. \u4E8B\u5B9E\u7EAA\u5F8B\u4E0E\u8FDB\u5C55\u540C\u6B65
|
|
8265
|
+
|
|
8266
|
+
- **\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
|
|
8267
|
+
\u8FD4\u56DE\u503C\u91CC\u6CA1\u6709\u7F16\u53F7\u5C31\u4E0D\u8981\u5199\u7F16\u53F7\uFF0C**\u4E0D\u5F97\u81EA\u884C\u7F16\u9020**\u3002
|
|
8268
|
+
- \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
|
|
8269
|
+
- **\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
|
|
8270
|
+
- \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
|
|
8271
|
+
\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
|
|
8272
|
+
- \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
|
|
8273
|
+
\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
|
|
8274
|
+
`;
|
|
8275
|
+
function buildStewardSkillFile() {
|
|
8276
|
+
return `${STEWARD_SKILL_FRONTMATTER}
|
|
8277
|
+
${STEWARD_SKILL_BODY}`;
|
|
8278
|
+
}
|
|
8279
|
+
|
|
8280
|
+
// src/daemon/session-host.ts
|
|
8281
|
+
var SESSION_IDLE_DEFAULT_MS = 30 * 60 * 1e3;
|
|
8282
|
+
var ELICITATION_DEFAULT_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
8283
|
+
var ATTACHMENT_FETCH_TIMEOUT_MS = 3e4;
|
|
8284
|
+
function safeAttachmentName(fileName) {
|
|
8285
|
+
const base = (fileName || "attachment").split(/[\\/]/).pop() ?? "attachment";
|
|
8286
|
+
const cleaned = base.replace(/[\u0000-\u001F<>:"|?*]/g, "_").replace(/^\.+/, "_").trim();
|
|
8287
|
+
return cleaned.slice(0, 120) || "attachment";
|
|
8288
|
+
}
|
|
8289
|
+
function resolveSessionIdleMs(env = process.env) {
|
|
8290
|
+
const raw = Number(env.MORULA_SESSION_IDLE_MS);
|
|
8291
|
+
return Number.isFinite(raw) && raw > 0 ? raw : SESSION_IDLE_DEFAULT_MS;
|
|
8292
|
+
}
|
|
8293
|
+
function resolveElicitationTimeoutMs(env = process.env) {
|
|
8294
|
+
const raw = Number(env.MORULA_DIM_APPROVAL_TIMEOUT_MS);
|
|
8295
|
+
return Number.isFinite(raw) && raw > 0 ? raw : ELICITATION_DEFAULT_TIMEOUT_MS;
|
|
8296
|
+
}
|
|
8297
|
+
var ELICITATION_POLL_WAIT_MS = 15e3;
|
|
8298
|
+
var SessionHost = class {
|
|
8299
|
+
constructor(options) {
|
|
8300
|
+
this.options = options;
|
|
8301
|
+
this.log = options.log ?? ((line) => console.log(line));
|
|
8302
|
+
this.idleMs = resolveSessionIdleMs();
|
|
8303
|
+
}
|
|
8304
|
+
options;
|
|
8305
|
+
sessions = /* @__PURE__ */ new Map();
|
|
8306
|
+
log;
|
|
8307
|
+
idleMs;
|
|
8308
|
+
/** 当前打开的会话(morula AgentSession.id 列表;诊断/测试用) */
|
|
8309
|
+
get openSessionIds() {
|
|
8310
|
+
return [...this.sessions.keys()];
|
|
8311
|
+
}
|
|
8312
|
+
/**
|
|
8313
|
+
* 打开会话:建/复用工作区 → 起**独立** dim acp 进程 → session/new(或 resume 续跑)
|
|
8314
|
+
* → 应用配置 → 指针回传。工作区保留(close 不清理)。
|
|
8315
|
+
*
|
|
8316
|
+
* **幂等**:已打开且进程存活 → 只刷新凭证/注入物与变化过的配置,**不重建进程**
|
|
8317
|
+
* (服务端每轮 prompt 前都会补发 open 做兜底:daemon 可能重启或空闲回收过,
|
|
8318
|
+
* 用同一条命令覆盖「首次打开」与「兜底确认」两种语义,避免两端各维护一套「是否已打开」状态)。
|
|
8319
|
+
*/
|
|
8320
|
+
async open(cmd) {
|
|
8321
|
+
if (!cmd?.sessionId || !cmd?.tenantId || !cmd?.projectId)
|
|
8322
|
+
return { ok: false, error: "SESSION_OPEN_INVALID_ARGS" };
|
|
8323
|
+
const workspaceDir = join19(this.options.workdir, "steward", cmd.tenantId, cmd.projectId);
|
|
8324
|
+
mkdirSync12(workspaceDir, { recursive: true });
|
|
8325
|
+
this.writeInjections(workspaceDir, cmd);
|
|
8326
|
+
const existing = this.sessions.get(cmd.sessionId);
|
|
8327
|
+
if (existing?.conn.isAlive) {
|
|
8328
|
+
if (cmd.sessionToken) {
|
|
8329
|
+
this.options.api.setSessionToken(cmd.sessionId, cmd.sessionToken);
|
|
8330
|
+
existing.env.MORULA_SESSION_TOKEN = cmd.sessionToken;
|
|
8331
|
+
}
|
|
8332
|
+
this.touch(existing);
|
|
8333
|
+
const changed = this.diffConfig(existing.appliedConfig, this.configFrom(cmd));
|
|
8334
|
+
for (const [configId, value] of Object.entries(changed)) {
|
|
8335
|
+
await existing.conn.setConfigOptionFull(existing.dimSessionId, configId, value).then((raw) => {
|
|
8336
|
+
const full = normalizeAcpConfigOptions(raw) ?? [];
|
|
8337
|
+
if (full.length > 0) {
|
|
8338
|
+
void this.options.api.reportSessionBinding(cmd.sessionId, { configOptions: full }).catch(() => {
|
|
8339
|
+
});
|
|
8340
|
+
}
|
|
8341
|
+
}).catch((err) => this.log(`[session-host] \u4F1A\u8BDD ${cmd.sessionId} \u914D\u7F6E ${configId} \u589E\u91CF\u4E0B\u53D1\u5931\u8D25: ${err.message}`));
|
|
8342
|
+
}
|
|
8343
|
+
existing.appliedConfig = { ...existing.appliedConfig, ...changed };
|
|
8344
|
+
return { ok: true, dimSessionId: existing.dimSessionId, workspaceDir: existing.workspaceDir, alreadyOpen: true };
|
|
8345
|
+
}
|
|
8346
|
+
if (existing)
|
|
8347
|
+
await this.close({ sessionId: cmd.sessionId }, "reopen");
|
|
8348
|
+
if (cmd.sessionToken)
|
|
8349
|
+
this.options.api.setSessionToken(cmd.sessionId, cmd.sessionToken);
|
|
8350
|
+
const env = {
|
|
8351
|
+
...cmd.sessionToken ? { MORULA_SESSION_TOKEN: cmd.sessionToken } : {},
|
|
8352
|
+
...this.options.baseUrl ? { MORULA_API_URL: this.options.baseUrl } : {},
|
|
8353
|
+
// 会话上下文(对齐任务链路的 MORULA_TASK_ID):让 `morula` CLI 知道「我在哪个会话里」,
|
|
8354
|
+
// 派单幂等与 elicitation 回传需要它;管家会话**没有** MORULA_TASK_ID(任务专属命令会因此拒绝执行)
|
|
8355
|
+
MORULA_SESSION_ID: cmd.sessionId,
|
|
8356
|
+
MORULA_SPACE_ID: cmd.projectId,
|
|
8357
|
+
// 插件 bin 前置 + 显式 CLI 路径:否则 agent 会解析到宿主/桌面应用自带的旧 `morula`——
|
|
8358
|
+
// 新命令不存在,且旧 CLI 不认 MORULA_SESSION_TOKEN(回落用户凭证)→ 写操作署名错、
|
|
8359
|
+
// 写面作用域校验被绕过。PATH 只是第一道保险(dim 会重写 agent shell 的 PATH),
|
|
8360
|
+
// 可靠的是 `MORULA_CLI` 绝对路径(契约要求 agent 用它)。
|
|
8361
|
+
// 见 issues/steward-cli-resolves-stale-bundled-morula.md。
|
|
8362
|
+
PATH: prependPluginBinToPath(process.env.PATH),
|
|
8363
|
+
MORULA_CLI: pluginBinPath(),
|
|
8364
|
+
...this.options.isolatedDimEnv ?? {}
|
|
8365
|
+
};
|
|
8366
|
+
const onExit = (code) => {
|
|
8367
|
+
this.log(`[session-host] \u4F1A\u8BDD ${cmd.sessionId} \u7684 dim acp \u8FDB\u7A0B\u9000\u51FA\uFF08code=${code}\uFF09`);
|
|
8368
|
+
const managed2 = this.sessions.get(cmd.sessionId);
|
|
8369
|
+
if (managed2 && managed2.conn === conn) {
|
|
8370
|
+
this.sessions.delete(cmd.sessionId);
|
|
8371
|
+
this.options.api.clearSessionToken(cmd.sessionId);
|
|
8372
|
+
}
|
|
8373
|
+
};
|
|
8374
|
+
const connOptions = {
|
|
8375
|
+
dimPath: this.options.dimPath,
|
|
8376
|
+
cwd: workspaceDir,
|
|
8377
|
+
// process.env 值域含 undefined(ProcessEnv),显式收敛为 string 索引(spawn env 语义)
|
|
8378
|
+
env: { ...process.env, ...env },
|
|
8379
|
+
// P1-7:审批默认全自动放行(管家人在环收敛到 elicitation 一处,见 §4.7 取舍)。
|
|
8380
|
+
// 注意:dim 的权限引擎决定 allow/deny/ask,client 无法强制——`permission` 只选 preset。
|
|
8381
|
+
onPermissionRequest: async () => ({ outcome: { outcome: "selected", optionId: "allow" } }),
|
|
8382
|
+
onElicitation: async (request) => this.handleElicitation(cmd.sessionId, request),
|
|
8383
|
+
onExit
|
|
8384
|
+
};
|
|
8385
|
+
const conn = this.options.acpConnectionFactory ? this.options.acpConnectionFactory(connOptions) : new AcpConnection(connOptions);
|
|
8386
|
+
try {
|
|
8387
|
+
await conn.start();
|
|
8388
|
+
} catch (err) {
|
|
8389
|
+
return { ok: false, error: `dim acp \u542F\u52A8\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}` };
|
|
8390
|
+
}
|
|
8391
|
+
const sessionParams = { cwd: workspaceDir, mcpServers: [] };
|
|
8392
|
+
let dimSessionId;
|
|
8393
|
+
let resumed = false;
|
|
8394
|
+
let resumeFailed = null;
|
|
8395
|
+
let configOptions;
|
|
8396
|
+
if (cmd.dimSessionId) {
|
|
8397
|
+
try {
|
|
8398
|
+
const r = await conn.resumeSession({ ...sessionParams, sessionId: cmd.dimSessionId });
|
|
8399
|
+
configOptions = normalizeAcpConfigOptions(r.configOptions) ?? void 0;
|
|
8400
|
+
dimSessionId = cmd.dimSessionId;
|
|
8401
|
+
resumed = true;
|
|
8402
|
+
} catch {
|
|
8403
|
+
try {
|
|
8404
|
+
const r = await conn.loadSession({ ...sessionParams, sessionId: cmd.dimSessionId });
|
|
8405
|
+
configOptions = normalizeAcpConfigOptions(r.configOptions) ?? void 0;
|
|
8406
|
+
dimSessionId = cmd.dimSessionId;
|
|
8407
|
+
resumed = true;
|
|
8408
|
+
} catch (err) {
|
|
8409
|
+
resumeFailed = err instanceof Error ? err.message : String(err);
|
|
8410
|
+
const created = await conn.newSession(sessionParams);
|
|
8411
|
+
configOptions = normalizeAcpConfigOptions(created.configOptions) ?? void 0;
|
|
8412
|
+
dimSessionId = created.sessionId;
|
|
8413
|
+
}
|
|
8414
|
+
}
|
|
8415
|
+
} else {
|
|
8416
|
+
const created = await conn.newSession(sessionParams);
|
|
8417
|
+
configOptions = normalizeAcpConfigOptions(created.configOptions) ?? void 0;
|
|
8418
|
+
dimSessionId = created.sessionId;
|
|
8419
|
+
}
|
|
8420
|
+
const appliedConfig = this.configFrom(cmd);
|
|
8421
|
+
const permission = appliedConfig.permission || "workspace-write";
|
|
8422
|
+
const applyConfig = async (configId, value) => {
|
|
8423
|
+
try {
|
|
8424
|
+
const full = normalizeAcpConfigOptions(await conn.setConfigOptionFull(dimSessionId, configId, value)) ?? [];
|
|
8425
|
+
if (full.length > 0)
|
|
8426
|
+
configOptions = full;
|
|
8427
|
+
} catch (err) {
|
|
8428
|
+
this.log(`[session-host] \u4F1A\u8BDD ${cmd.sessionId} \u914D\u7F6E ${configId}=${value} \u4E0B\u53D1\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
8429
|
+
}
|
|
8430
|
+
};
|
|
8431
|
+
await applyConfig("permission", permission);
|
|
8432
|
+
if (appliedConfig.modelId)
|
|
8433
|
+
await applyConfig("model", appliedConfig.modelId);
|
|
8434
|
+
if (appliedConfig.thoughtLevel)
|
|
8435
|
+
await applyConfig("thought_level", appliedConfig.thoughtLevel);
|
|
8436
|
+
if (appliedConfig.agentMode)
|
|
8437
|
+
await applyConfig("mode", appliedConfig.agentMode);
|
|
8438
|
+
void this.options.api.reportSessionBinding(cmd.sessionId, {
|
|
8439
|
+
dimSessionId,
|
|
8440
|
+
workspaceDir,
|
|
8441
|
+
...cmd.runtimeId ? { runtimeId: cmd.runtimeId } : {},
|
|
8442
|
+
...cmd.providerKey ? { providerKey: cmd.providerKey } : {},
|
|
8443
|
+
...configOptions !== void 0 ? { configOptions } : {}
|
|
8444
|
+
}).catch((err) => {
|
|
8445
|
+
this.log(`[session-host] \u4F1A\u8BDD ${cmd.sessionId} \u6307\u9488\u56DE\u4F20\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
8446
|
+
});
|
|
8447
|
+
const managed = {
|
|
8448
|
+
sessionId: cmd.sessionId,
|
|
8449
|
+
conn,
|
|
8450
|
+
dimSessionId,
|
|
8451
|
+
workspaceDir,
|
|
8452
|
+
env,
|
|
8453
|
+
turnNo: 0,
|
|
8454
|
+
promptInFlight: null,
|
|
8455
|
+
cancelRequested: false,
|
|
8456
|
+
// resume 成功 → dim 会话里已有上一进程注入的管家指令,不重复前置
|
|
8457
|
+
instructed: resumed,
|
|
8458
|
+
appliedConfig: { ...appliedConfig, permission },
|
|
8459
|
+
idleTimer: setTimeout(() => {
|
|
8460
|
+
void this.close({ sessionId: cmd.sessionId }, "idle");
|
|
8461
|
+
}, this.idleMs),
|
|
8462
|
+
lastActiveAt: Date.now()
|
|
8463
|
+
};
|
|
8464
|
+
managed.idleTimer.unref?.();
|
|
8465
|
+
this.sessions.set(cmd.sessionId, managed);
|
|
8466
|
+
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`);
|
|
8467
|
+
return { ok: true, dimSessionId, workspaceDir };
|
|
8468
|
+
}
|
|
8469
|
+
/**
|
|
8470
|
+
* 送一轮 prompt(同一会话单活跃——ACP 硬约束;忙时返回 SESSION_BUSY 不排队,
|
|
8471
|
+
* 排队语义由服务端消息投递状态机承载,见 §4.2)。
|
|
8472
|
+
* 事件映射复用任务链路同款 mapper + normalizer(含 P0-8 修正:user_message / usage 上下文语义)。
|
|
8473
|
+
*
|
|
8474
|
+
* **管家指令前置**(§4.4):新建 dim 会话的**首条** prompt 前置 `STEWARD_SYSTEM_INSTRUCTIONS`
|
|
8475
|
+
* (ACP 无 system prompt 通道)。resume 的会话里已有,不重复注入。
|
|
8476
|
+
* 该常量只含身份/边界/目标/工作区与工具入口(提示词极简,见 steward-instructions.ts 文件头);
|
|
8477
|
+
* 行为规范与命令用法在 `.agents/skills/steward-management/SKILL.md`,由 agent 按需读取。
|
|
8478
|
+
*/
|
|
8479
|
+
async prompt(cmd) {
|
|
8480
|
+
const managed = this.sessions.get(cmd.sessionId);
|
|
8481
|
+
if (!managed)
|
|
8482
|
+
return { ok: false, error: "SESSION_NOT_OPEN" };
|
|
8483
|
+
if (managed.promptInFlight)
|
|
8484
|
+
return { ok: false, error: "SESSION_BUSY" };
|
|
8485
|
+
if (!cmd.prompt?.trim())
|
|
8486
|
+
return { ok: false, error: "SESSION_PROMPT_EMPTY" };
|
|
8487
|
+
managed.turnNo += 1;
|
|
8488
|
+
managed.cancelRequested = false;
|
|
8489
|
+
this.touch(managed);
|
|
8490
|
+
const turnNo = managed.turnNo;
|
|
8491
|
+
const mapper = new AcpEventMapper();
|
|
8492
|
+
let seqHint = 0;
|
|
8493
|
+
const nextSeq = () => ++seqHint;
|
|
8494
|
+
const textStream = new AssistantTextStream({
|
|
8495
|
+
onDelta: (seg, delta) => {
|
|
8496
|
+
void this.options.api.recordSessionStreamChunk(managed.sessionId, {
|
|
8497
|
+
chunkType: "delta",
|
|
8498
|
+
data: { kind: "text", seg, text: delta }
|
|
8499
|
+
}).catch(() => {
|
|
8500
|
+
});
|
|
8501
|
+
},
|
|
8502
|
+
onParagraph: (seg, text2) => this.options.api.recordSessionEvent(managed.sessionId, {
|
|
8503
|
+
type: "agent_message_delta",
|
|
8504
|
+
output: { text: redactValue(text2), seg },
|
|
8505
|
+
turnNo,
|
|
8506
|
+
seqHint: nextSeq()
|
|
8507
|
+
}).then(() => void 0).catch((err) => {
|
|
8508
|
+
this.log(`[session-host] \u4F1A\u8BDD ${managed.sessionId} \u6BB5\u843D\u4E8B\u4EF6\u4E0A\u62A5\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
8509
|
+
})
|
|
8510
|
+
}, { log: this.log });
|
|
8511
|
+
const thinkingStream = new AssistantTextStream({
|
|
8512
|
+
onDelta: (seg, delta) => {
|
|
8513
|
+
void this.options.api.recordSessionStreamChunk(managed.sessionId, {
|
|
8514
|
+
chunkType: "delta",
|
|
8515
|
+
data: { kind: "thinking", seg, text: delta }
|
|
8516
|
+
}).catch(() => {
|
|
8517
|
+
});
|
|
8518
|
+
},
|
|
8519
|
+
onParagraph: (seg, text2) => this.options.api.recordSessionEvent(managed.sessionId, {
|
|
8520
|
+
type: "agent_thought_delta",
|
|
8521
|
+
output: { text: redactValue(text2), seg },
|
|
8522
|
+
turnNo,
|
|
8523
|
+
seqHint: nextSeq()
|
|
8524
|
+
}).then(() => void 0).catch((err) => {
|
|
8525
|
+
this.log(`[session-host] \u4F1A\u8BDD ${managed.sessionId} \u601D\u8003\u6BB5\u843D\u4E0A\u62A5\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
8526
|
+
})
|
|
8527
|
+
}, { log: this.log });
|
|
8528
|
+
let textChain = Promise.resolve();
|
|
8529
|
+
let thinkingPending = false;
|
|
8530
|
+
const gateThinking = () => {
|
|
8531
|
+
if (thinkingPending) {
|
|
8532
|
+
thinkingPending = false;
|
|
8533
|
+
textChain = textChain.then(() => thinkingStream.flushParagraph());
|
|
8534
|
+
}
|
|
8535
|
+
return textChain;
|
|
8536
|
+
};
|
|
8537
|
+
const onEvent = (event) => {
|
|
8538
|
+
this.touch(managed);
|
|
8539
|
+
if (event.type === "assistant_thinking_delta") {
|
|
8540
|
+
thinkingPending = true;
|
|
8541
|
+
thinkingStream.push(event.text);
|
|
8542
|
+
} else if (event.type === "assistant_text_delta") {
|
|
8543
|
+
void gateThinking().then(() => textStream.push(event.text));
|
|
8544
|
+
} else if (event.type === "tool_call_started") {
|
|
8545
|
+
void gateThinking().then(() => textStream.flushParagraph()).then(() => this.options.api.recordSessionEvent(managed.sessionId, {
|
|
8546
|
+
type: "tool_call_started",
|
|
8547
|
+
toolName: event.toolName,
|
|
8548
|
+
callId: event.callId,
|
|
8549
|
+
input: event.input !== void 0 ? redactInputSummary(event.input) : void 0,
|
|
8550
|
+
output: void 0,
|
|
8551
|
+
turnNo,
|
|
8552
|
+
seqHint: nextSeq()
|
|
8553
|
+
})).catch(() => {
|
|
8554
|
+
});
|
|
8555
|
+
} else if (event.type === "tool_call_completed") {
|
|
8556
|
+
void this.options.api.recordSessionEvent(managed.sessionId, {
|
|
8557
|
+
type: "tool_call_completed",
|
|
8558
|
+
callId: event.callId,
|
|
8559
|
+
toolName: event.toolName,
|
|
8560
|
+
output: {
|
|
8561
|
+
callId: event.callId,
|
|
8562
|
+
toolName: event.toolName,
|
|
8563
|
+
status: event.aborted ? "aborted" : "completed",
|
|
8564
|
+
...event.outputSummary !== void 0 ? { summary: String(redactValue(String(event.outputSummary).slice(0, 200))) } : {}
|
|
8565
|
+
},
|
|
8566
|
+
turnNo,
|
|
8567
|
+
seqHint: nextSeq()
|
|
8568
|
+
}).catch(() => {
|
|
8569
|
+
});
|
|
8570
|
+
} else if (event.type === "user_message") {
|
|
8571
|
+
void this.options.api.recordSessionEvent(managed.sessionId, {
|
|
8572
|
+
type: "user_message",
|
|
8573
|
+
output: { text: event.text },
|
|
8574
|
+
turnNo
|
|
8575
|
+
}).catch(() => {
|
|
8576
|
+
});
|
|
8577
|
+
} else if (event.type === "usage") {
|
|
8578
|
+
void this.options.api.recordSessionEvent(managed.sessionId, {
|
|
8579
|
+
type: "usage",
|
|
8580
|
+
output: event.usage,
|
|
8581
|
+
turnNo
|
|
8582
|
+
}).catch(() => {
|
|
8583
|
+
});
|
|
8584
|
+
} else if (event.type === "session" && event.providerSessionId) {
|
|
8585
|
+
void this.options.api.reportSessionBinding(managed.sessionId, { dimSessionId: event.providerSessionId }).catch(() => {
|
|
8586
|
+
});
|
|
8587
|
+
}
|
|
8588
|
+
};
|
|
8589
|
+
const normalizer = createDimEventNormalizer({ onEvent });
|
|
8590
|
+
const updateHandler = (update) => {
|
|
8591
|
+
const { messages } = mapper.map(managed.dimSessionId, update);
|
|
8592
|
+
for (const message of messages)
|
|
8593
|
+
normalizer.handle(message, "");
|
|
8594
|
+
};
|
|
8595
|
+
const text = managed.instructed ? cmd.prompt : `${STEWARD_SYSTEM_INSTRUCTIONS}
|
|
8596
|
+
|
|
8597
|
+
---
|
|
8598
|
+
|
|
8599
|
+
${cmd.prompt}`;
|
|
8600
|
+
const run3 = (async () => {
|
|
8601
|
+
try {
|
|
8602
|
+
const { blocks, fileRefs } = await this.materializeAttachments(managed.workspaceDir, cmd.attachments ?? []);
|
|
8603
|
+
const promptBlocks = [{ type: "text", text: fileRefs.length > 0 ? `${text}
|
|
8604
|
+
|
|
8605
|
+
---
|
|
8606
|
+
|
|
8607
|
+
\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
|
|
8608
|
+
${fileRefs.join("\n")}` : text }];
|
|
8609
|
+
promptBlocks.push(...blocks);
|
|
8610
|
+
const pending = managed.conn.prompt(managed.dimSessionId, promptBlocks, updateHandler);
|
|
8611
|
+
managed.instructed = true;
|
|
8612
|
+
void this.options.api.recordSessionEvent(managed.sessionId, {
|
|
8613
|
+
type: "turn_started",
|
|
8614
|
+
output: { ...cmd.messageId ? { messageId: cmd.messageId } : {} },
|
|
8615
|
+
turnNo
|
|
8616
|
+
}).catch(() => {
|
|
8617
|
+
});
|
|
8618
|
+
const result = await pending;
|
|
8619
|
+
await gateThinking();
|
|
8620
|
+
await textStream.flushParagraph();
|
|
8621
|
+
normalizer.finish({ eventType: "run:ended", payload: { stopReason: result.stopReason } }, "");
|
|
8622
|
+
await this.options.api.recordSessionEvent(managed.sessionId, {
|
|
8623
|
+
type: "turn_completed",
|
|
8624
|
+
output: { stopReason: result.stopReason, ...cmd.messageId ? { messageId: cmd.messageId } : {} },
|
|
8625
|
+
turnNo
|
|
8626
|
+
}).catch(() => {
|
|
8627
|
+
});
|
|
8628
|
+
return { ok: true, stopReason: result.stopReason };
|
|
8629
|
+
} catch (err) {
|
|
8630
|
+
await gateThinking();
|
|
8631
|
+
await textStream.flushParagraph();
|
|
8632
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8633
|
+
await this.options.api.recordSessionEvent(managed.sessionId, {
|
|
8634
|
+
type: "turn_failed",
|
|
8635
|
+
output: { error: message, ...cmd.messageId ? { messageId: cmd.messageId } : {} },
|
|
8636
|
+
turnNo
|
|
8637
|
+
}).catch(() => {
|
|
8638
|
+
});
|
|
8639
|
+
return { ok: false, error: message };
|
|
8640
|
+
} finally {
|
|
8641
|
+
textStream.dispose();
|
|
8642
|
+
thinkingStream.dispose();
|
|
8643
|
+
managed.promptInFlight = null;
|
|
8644
|
+
this.touch(managed);
|
|
8645
|
+
}
|
|
8646
|
+
})();
|
|
8647
|
+
managed.promptInFlight = run3;
|
|
8648
|
+
return run3;
|
|
8649
|
+
}
|
|
8650
|
+
/**
|
|
8651
|
+
* 附件物化(§五 P2-3):
|
|
8652
|
+
* - **图片** → ACP `image` content block(base64 + mimeType),直接进模型上下文;
|
|
8653
|
+
* - **其他文件** → 下载到 `<workspace>/attachments/<安全文件名>`,返回路径引用文本
|
|
8654
|
+
* (doc 明确「文件走工作区文件 + 路径引用」:不进上下文,由 agent 按需读取)。
|
|
8655
|
+
*
|
|
8656
|
+
* 下载失败不抛:记日志并跳过该附件(本轮照常发出)。
|
|
8657
|
+
*/
|
|
8658
|
+
async materializeAttachments(workspaceDir, attachments) {
|
|
8659
|
+
const blocks = [];
|
|
8660
|
+
const fileRefs = [];
|
|
8661
|
+
if (attachments.length === 0)
|
|
8662
|
+
return { blocks, fileRefs };
|
|
8663
|
+
const dir = join19(workspaceDir, "attachments");
|
|
8664
|
+
for (const attachment of attachments) {
|
|
8665
|
+
try {
|
|
8666
|
+
const res = await fetch(attachment.downloadUrl, { signal: AbortSignal.timeout(ATTACHMENT_FETCH_TIMEOUT_MS) });
|
|
8667
|
+
if (!res.ok)
|
|
8668
|
+
throw new Error(`HTTP ${res.status}`);
|
|
8669
|
+
const bytes = Buffer.from(await res.arrayBuffer());
|
|
8670
|
+
if (attachment.isImage) {
|
|
8671
|
+
blocks.push({ type: "image", data: bytes.toString("base64"), mimeType: attachment.fileType || "image/png" });
|
|
8672
|
+
} else {
|
|
8673
|
+
mkdirSync12(dir, { recursive: true });
|
|
8674
|
+
const target = join19(dir, safeAttachmentName(attachment.fileName));
|
|
8675
|
+
writeFileSync10(target, bytes);
|
|
8676
|
+
fileRefs.push(`- ${attachment.fileName} \u2192 ${target}`);
|
|
8677
|
+
}
|
|
8678
|
+
} catch (err) {
|
|
8679
|
+
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)}`);
|
|
8680
|
+
}
|
|
8681
|
+
}
|
|
8682
|
+
return { blocks, fileRefs };
|
|
8683
|
+
}
|
|
8684
|
+
/** 取消当前回合(ACP session/cancel;会话保持打开——长驻语义) */
|
|
8685
|
+
cancel(cmd) {
|
|
8686
|
+
const managed = this.sessions.get(cmd.sessionId);
|
|
8687
|
+
if (!managed)
|
|
8688
|
+
return { ok: false, error: "SESSION_NOT_OPEN" };
|
|
8689
|
+
managed.cancelRequested = true;
|
|
8690
|
+
managed.conn.cancel(managed.dimSessionId);
|
|
8691
|
+
this.touch(managed);
|
|
8692
|
+
return { ok: true };
|
|
8693
|
+
}
|
|
8694
|
+
/**
|
|
8695
|
+
* 配置变更(configId ∈ model / thought_level / mode / permission)。
|
|
8696
|
+
* ACP `set_config_option` 全量返回 configOptions——回写由服务端(配置端点)消费,
|
|
8697
|
+
* 这里原样返回给调用方。
|
|
8698
|
+
*/
|
|
8699
|
+
async config(cmd) {
|
|
8700
|
+
const managed = this.sessions.get(cmd.sessionId);
|
|
8701
|
+
if (!managed)
|
|
8702
|
+
return { ok: false, error: "SESSION_NOT_OPEN" };
|
|
8703
|
+
this.touch(managed);
|
|
8704
|
+
try {
|
|
8705
|
+
const result = normalizeAcpConfigOptions(await managed.conn.setConfigOptionFull(managed.dimSessionId, cmd.configId, cmd.value)) ?? [];
|
|
8706
|
+
managed.appliedConfig = { ...managed.appliedConfig, [this.configKey(cmd.configId)]: cmd.value };
|
|
8707
|
+
if (result.length > 0) {
|
|
8708
|
+
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)}`));
|
|
8709
|
+
}
|
|
8710
|
+
return { ok: true, configOptions: result };
|
|
8711
|
+
} catch (err) {
|
|
8712
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
8713
|
+
}
|
|
8714
|
+
}
|
|
8715
|
+
/**
|
|
8716
|
+
* 关闭会话:ACP session/close → 进程回收 → 服务端上报。
|
|
8717
|
+
* **工作区保留**(steward/<tenant>/<project> 可复用;清理属服务端清理命令域)。
|
|
8718
|
+
*
|
|
8719
|
+
* @param reason `idle` = 空闲回收(服务端回到 idle,保留 dimSessionId 以便 resume);
|
|
8720
|
+
* `command` = 用户/平台显式关闭(服务端终态 closed);
|
|
8721
|
+
* `reopen` = daemon 内部重建(**不上报**,紧接着的 open 会重新绑定)。
|
|
8722
|
+
*/
|
|
8723
|
+
async close(cmd, reason = "command") {
|
|
8724
|
+
const managed = this.sessions.get(cmd.sessionId);
|
|
8725
|
+
if (!managed)
|
|
8726
|
+
return { ok: true };
|
|
8727
|
+
this.sessions.delete(cmd.sessionId);
|
|
8728
|
+
clearTimeout(managed.idleTimer);
|
|
8729
|
+
try {
|
|
8730
|
+
await managed.conn.close();
|
|
8731
|
+
} catch {
|
|
8732
|
+
}
|
|
8733
|
+
if (reason === "reopen") {
|
|
8734
|
+
this.log(`[session-host] \u4F1A\u8BDD ${cmd.sessionId} \u8FDE\u63A5\u91CD\u5EFA\uFF08\u5DE5\u4F5C\u533A\u4FDD\u7559 ${managed.workspaceDir}\uFF09`);
|
|
8735
|
+
return { ok: true };
|
|
8736
|
+
}
|
|
8737
|
+
this.options.api.clearSessionToken(cmd.sessionId);
|
|
8738
|
+
void this.options.api.closeAgentSession(cmd.sessionId, reason).catch((err) => {
|
|
8739
|
+
this.log(`[session-host] \u4F1A\u8BDD ${cmd.sessionId} \u5173\u95ED\u4E0A\u62A5\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
8740
|
+
});
|
|
8741
|
+
this.log(`[session-host] \u4F1A\u8BDD\u5DF2\u5173\u95ED ${cmd.sessionId}\uFF08\u539F\u56E0=${reason}\uFF1B\u5DE5\u4F5C\u533A\u4FDD\u7559 ${managed.workspaceDir}\uFF09`);
|
|
8742
|
+
return { ok: true };
|
|
8743
|
+
}
|
|
8744
|
+
/**
|
|
8745
|
+
* 关闭全部会话(daemon 停机时收尾)。
|
|
8746
|
+
* 用 `idle` 而非 `command`:daemon 停机不是用户关闭会话,dim 会话应当可 resume
|
|
8747
|
+
* (否则服务端会把会话打成终态 `closed`,下次上线再也接不回来)。
|
|
8748
|
+
*/
|
|
8749
|
+
async closeAll() {
|
|
8750
|
+
for (const sessionId of [...this.sessions.keys()])
|
|
8751
|
+
await this.close({ sessionId }, "idle").catch(() => {
|
|
8752
|
+
});
|
|
8753
|
+
}
|
|
8754
|
+
// ────────────────────────── 内部 ──────────────────────────
|
|
8755
|
+
/** 活跃信号:刷新空闲回收计时(事件/prompt/取消都算活跃)。定时器 unref——回收器不应单独钉住进程(daemon 有 WS/心跳常驻句柄) */
|
|
8756
|
+
touch(managed) {
|
|
8757
|
+
managed.lastActiveAt = Date.now();
|
|
8758
|
+
clearTimeout(managed.idleTimer);
|
|
8759
|
+
managed.idleTimer = setTimeout(() => {
|
|
8760
|
+
void this.close({ sessionId: managed.sessionId }, "idle");
|
|
8761
|
+
}, this.idleMs);
|
|
8762
|
+
managed.idleTimer.unref?.();
|
|
8763
|
+
}
|
|
8764
|
+
/** 工作区注入物(幂等覆盖;daemon 侧组装,P1-6) */
|
|
8765
|
+
writeInjections(workspaceDir, cmd) {
|
|
8766
|
+
writeProjectAgentsMd(workspaceDir, cmd.agentsMd, this.log);
|
|
8767
|
+
const skillDir = join19(workspaceDir, ".agents", "skills", STEWARD_SKILL_NAME);
|
|
8768
|
+
mkdirSync12(skillDir, { recursive: true });
|
|
8769
|
+
writeFileSync10(join19(skillDir, "SKILL.md"), buildStewardSkillFile(), "utf8");
|
|
8770
|
+
const contextDir = join19(workspaceDir, ".agent_context");
|
|
8771
|
+
mkdirSync12(contextDir, { recursive: true });
|
|
8772
|
+
writeFileSync10(
|
|
8773
|
+
join19(contextDir, "steward-context.md"),
|
|
8774
|
+
buildStewardContextMd(cmd.facts ?? { projectId: cmd.projectId, tenantId: cmd.tenantId }),
|
|
8775
|
+
"utf8"
|
|
8776
|
+
);
|
|
8777
|
+
}
|
|
8778
|
+
configFrom(cmd) {
|
|
8779
|
+
return {
|
|
8780
|
+
...cmd.modelId?.trim() ? { modelId: cmd.modelId.trim() } : {},
|
|
8781
|
+
...cmd.thoughtLevel?.trim() ? { thoughtLevel: cmd.thoughtLevel.trim() } : {},
|
|
8782
|
+
...cmd.agentMode?.trim() ? { agentMode: cmd.agentMode.trim() } : {},
|
|
8783
|
+
...cmd.permission?.trim() ? { permission: cmd.permission.trim() } : {}
|
|
8784
|
+
};
|
|
8785
|
+
}
|
|
8786
|
+
/** 配置差异 → ACP configId 映射(只返回变化项;open 幂等路径用) */
|
|
8787
|
+
diffConfig(applied, next) {
|
|
8788
|
+
const out = {};
|
|
8789
|
+
for (const key of ["modelId", "thoughtLevel", "agentMode", "permission"]) {
|
|
8790
|
+
const value = next[key];
|
|
8791
|
+
if (value && value !== applied[key])
|
|
8792
|
+
out[this.configIdOf(key)] = value;
|
|
8793
|
+
}
|
|
8794
|
+
return out;
|
|
8795
|
+
}
|
|
8796
|
+
/** 本地字段名 → ACP configId(§4.4:model / thought_level / mode / permission) */
|
|
8797
|
+
configIdOf(key) {
|
|
8798
|
+
if (key === "modelId")
|
|
8799
|
+
return "model";
|
|
8800
|
+
if (key === "thoughtLevel")
|
|
8801
|
+
return "thought_level";
|
|
8802
|
+
if (key === "agentMode")
|
|
8803
|
+
return "mode";
|
|
8804
|
+
return key;
|
|
8805
|
+
}
|
|
8806
|
+
/** ACP configId → 本地字段名(config 变更回写用) */
|
|
8807
|
+
configKey(configId) {
|
|
8808
|
+
if (configId === "model")
|
|
8809
|
+
return "modelId";
|
|
8810
|
+
if (configId === "thought_level")
|
|
8811
|
+
return "thoughtLevel";
|
|
8812
|
+
if (configId === "mode")
|
|
8813
|
+
return "agentMode";
|
|
8814
|
+
return configId;
|
|
8815
|
+
}
|
|
8816
|
+
/**
|
|
8817
|
+
* 结构化提问上行(§4.6 ②):登记 → 长轮询切片等待用户应答 → 超时回 cancel。
|
|
8818
|
+
*
|
|
8819
|
+
* 为什么两段式(登记 + 轮询)而不是一个长连接:用户可能几分钟才填完,单个悬挂 HTTP 请求
|
|
8820
|
+
* 会撞上服务端 requestTimeout 与中间层空闲超时;切片等待把「无悬挂长连接」与
|
|
8821
|
+
* 「应答即达」两个目标同时拿到。
|
|
8822
|
+
*/
|
|
8823
|
+
async handleElicitation(sessionId, request) {
|
|
8824
|
+
const timeoutMs = resolveElicitationTimeoutMs();
|
|
8825
|
+
let elicitationId;
|
|
8826
|
+
try {
|
|
8827
|
+
const created = await this.options.api.createElicitation(sessionId, {
|
|
8828
|
+
toolCallId: request.toolCallId,
|
|
8829
|
+
message: request.message,
|
|
8830
|
+
requestedSchema: request.requestedSchema
|
|
8831
|
+
});
|
|
8832
|
+
if (!created?.ok || !created.elicitationId) {
|
|
8833
|
+
this.log(`[session-host] \u4F1A\u8BDD ${sessionId} \u7ED3\u6784\u5316\u63D0\u95EE\u767B\u8BB0\u5931\u8D25: ${created?.code ?? "unknown"}`);
|
|
8834
|
+
return { action: "decline" };
|
|
8835
|
+
}
|
|
8836
|
+
elicitationId = created.elicitationId;
|
|
8837
|
+
} catch (err) {
|
|
8838
|
+
this.log(`[session-host] \u4F1A\u8BDD ${sessionId} \u7ED3\u6784\u5316\u63D0\u95EE\u767B\u8BB0\u5F02\u5E38: ${err instanceof Error ? err.message : String(err)}`);
|
|
8839
|
+
return { action: "decline" };
|
|
8840
|
+
}
|
|
8841
|
+
const deadline = Date.now() + timeoutMs;
|
|
8842
|
+
while (Date.now() < deadline) {
|
|
8843
|
+
const remaining = deadline - Date.now();
|
|
8844
|
+
try {
|
|
8845
|
+
const result = await this.options.api.getElicitation(sessionId, elicitationId, Math.min(ELICITATION_POLL_WAIT_MS, remaining));
|
|
8846
|
+
if (result?.status === "answered")
|
|
8847
|
+
return { action: result.action ?? "accept", content: result.content ?? {} };
|
|
8848
|
+
if (result?.status === "cancelled")
|
|
8849
|
+
return { action: "cancel" };
|
|
8850
|
+
if (result?.status === "expired") {
|
|
8851
|
+
this.log(`[session-host] \u4F1A\u8BDD ${sessionId} \u7ED3\u6784\u5316\u63D0\u95EE\u5DF2\u5728\u670D\u52A1\u7AEF\u8FC7\u671F\uFF08${elicitationId}\uFF09`);
|
|
8852
|
+
return { action: "cancel" };
|
|
8853
|
+
}
|
|
8854
|
+
} catch (err) {
|
|
8855
|
+
this.log(`[session-host] \u4F1A\u8BDD ${sessionId} \u7ED3\u6784\u5316\u63D0\u95EE\u67E5\u8BE2\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
8856
|
+
return { action: "cancel" };
|
|
8857
|
+
}
|
|
8858
|
+
const managed = this.sessions.get(sessionId);
|
|
8859
|
+
if (managed)
|
|
8860
|
+
this.touch(managed);
|
|
8861
|
+
}
|
|
8862
|
+
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`);
|
|
8863
|
+
return { action: "cancel" };
|
|
8864
|
+
}
|
|
8865
|
+
};
|
|
8866
|
+
|
|
8867
|
+
// src/daemon/ws-client.ts
|
|
8868
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
8869
|
+
import { io } from "socket.io-client";
|
|
8870
|
+
var WS_AUTH_RELOAD_MAX = 3;
|
|
8871
|
+
function tokenFingerprint(token) {
|
|
8872
|
+
const digest = createHash2("sha256").update(token).digest("hex").slice(0, 8);
|
|
8873
|
+
return `len=${token.length} seg=${token.split(".").length} sha256=${digest}`;
|
|
8874
|
+
}
|
|
8875
|
+
function isWellFormedToken(token) {
|
|
8876
|
+
return token.split(".").length === 3;
|
|
8877
|
+
}
|
|
8878
|
+
function isAuthConnectError(message) {
|
|
8879
|
+
return /malformed|expired|invalid signature|invalid token|jwt|unauthorized|401/i.test(message);
|
|
8880
|
+
}
|
|
8881
|
+
var DaemonWsClient = class {
|
|
8882
|
+
constructor(baseUrl, token, resolveToken2) {
|
|
8883
|
+
this.baseUrl = baseUrl;
|
|
8884
|
+
this.resolveToken = resolveToken2;
|
|
8885
|
+
this.currentToken = token;
|
|
8886
|
+
this.currentSource = "startup";
|
|
8887
|
+
}
|
|
8888
|
+
baseUrl;
|
|
8889
|
+
resolveToken;
|
|
8890
|
+
socket = null;
|
|
8891
|
+
handlers = null;
|
|
8892
|
+
currentToken;
|
|
8893
|
+
currentSource;
|
|
8894
|
+
authReloads = 0;
|
|
8895
|
+
reconnectAttempts = 0;
|
|
8896
|
+
pollOnly = false;
|
|
8897
|
+
/**
|
|
8898
|
+
* P1:已订阅的任务房间(taskId → afterSeq)。**订阅意图独立于连接状态**——
|
|
8899
|
+
* 连接未就绪(初始连接中 / 重连中 / 凭证重载重建 socket)时先登记,连上后由 `joinTaskRooms()` 重放,
|
|
8900
|
+
* 否则「连上之前 claim 到的任务」永远收不到消息(服务端不发 tenant 广播)。
|
|
8901
|
+
*/
|
|
8902
|
+
taskRooms = /* @__PURE__ */ new Map();
|
|
8903
|
+
/** 是否已降级为 poll-only(认证长期失败;runner 的 poll 兜底继续工作) */
|
|
8904
|
+
get isPollOnly() {
|
|
8905
|
+
return this.pollOnly;
|
|
8906
|
+
}
|
|
8907
|
+
/** 当前已订阅的任务房间(诊断/测试用只读快照) */
|
|
8908
|
+
get subscribedTaskIds() {
|
|
8909
|
+
return [...this.taskRooms.keys()];
|
|
8910
|
+
}
|
|
8911
|
+
/**
|
|
8912
|
+
* P1:订阅 `task:<taskId>` 房间(幂等)——本 run 的对话消息(`agent-task:message`)只发进该房间。
|
|
7241
8913
|
*
|
|
7242
8914
|
* 连接未就绪时只登记(连上后 `joinTaskRooms()` 补发),保证「先 claim 后连上」也不漏帧。
|
|
7243
8915
|
* `afterSeq`:与前端同口径的订阅水位(补齐由 HTTP 拉取负责,见 runner 的 catch-up)。
|
|
@@ -7328,6 +9000,12 @@ var DaemonWsClient = class {
|
|
|
7328
9000
|
if (event)
|
|
7329
9001
|
handlers.onTaskMessage(event);
|
|
7330
9002
|
});
|
|
9003
|
+
for (const kind of ["open", "prompt", "cancel", "config", "close"]) {
|
|
9004
|
+
socket.on(`agent-session:${kind}`, (payload) => {
|
|
9005
|
+
if (payload && typeof payload === "object")
|
|
9006
|
+
handlers.onSessionCommand?.(kind, payload);
|
|
9007
|
+
});
|
|
9008
|
+
}
|
|
7331
9009
|
socket.on("connect_error", (error) => {
|
|
7332
9010
|
this.onConnectError(socket, error);
|
|
7333
9011
|
});
|
|
@@ -7510,6 +9188,12 @@ var DaemonRunner = class {
|
|
|
7510
9188
|
isolatedDimEnv = {};
|
|
7511
9189
|
/** ACP 连接池(ACP-only:daemon 启动即建,懒 spawn,首个任务才起 dim acp 进程) */
|
|
7512
9190
|
acpPool = null;
|
|
9191
|
+
/**
|
|
9192
|
+
* 会话宿主(steward-core-acp-migration §4.4,P0 骨架):长驻会话(管家对话)的执行载体,
|
|
9193
|
+
* 每会话独立 dim acp 进程(不走连接池)。P0 只接骨架(WS 命令转发 + 机器协议上报),
|
|
9194
|
+
* 服务端发射端(agent-session:*)在 P1-5 接入。
|
|
9195
|
+
*/
|
|
9196
|
+
sessionHost = null;
|
|
7513
9197
|
/** LOCAL-AGENT-REPO-READ-001:本地 repo checkout 端点(agent 本轮内读仓库) */
|
|
7514
9198
|
repoCheckout = null;
|
|
7515
9199
|
async start() {
|
|
@@ -7545,6 +9229,15 @@ var DaemonRunner = class {
|
|
|
7545
9229
|
env: this.isolatedDimEnv
|
|
7546
9230
|
});
|
|
7547
9231
|
this.log("[daemon] dim executor: acp-v1\uFF08ACP-only\uFF0C\u957F\u9A7B dim acp \u8FDB\u7A0B\uFF09");
|
|
9232
|
+
this.sessionHost = new SessionHost({
|
|
9233
|
+
api: this.api,
|
|
9234
|
+
workdir: cfg2?.workdir ?? process.cwd(),
|
|
9235
|
+
dimPath: this.dimPath,
|
|
9236
|
+
isolatedDimEnv: this.isolatedDimEnv,
|
|
9237
|
+
baseUrl: this.client.baseUrl,
|
|
9238
|
+
log: this.log
|
|
9239
|
+
});
|
|
9240
|
+
this.log(`[daemon] session host: ready\uFF08\u7A7A\u95F2\u56DE\u6536 ${Math.round(resolveSessionIdleMs() / 6e4)}min\uFF0CMORULA_SESSION_IDLE_MS \u8986\u76D6\uFF09`);
|
|
7548
9241
|
this.repoCheckout = new LocalRepoCheckoutService({
|
|
7549
9242
|
api: this.api,
|
|
7550
9243
|
cfg: cfg2 ?? loadDaemonConfig(this.store),
|
|
@@ -7563,7 +9256,7 @@ var DaemonRunner = class {
|
|
|
7563
9256
|
this.log(`[daemon] dim CLI: ${this.dimPath}`);
|
|
7564
9257
|
let binaryMtime = "unknown";
|
|
7565
9258
|
try {
|
|
7566
|
-
binaryMtime =
|
|
9259
|
+
binaryMtime = statSync4(process.execPath).mtime.toISOString();
|
|
7567
9260
|
} catch (_) {
|
|
7568
9261
|
}
|
|
7569
9262
|
this.log(`[daemon] binary=${process.execPath} mtime=${binaryMtime} v=${BUILD_VERSION2}`);
|
|
@@ -7629,6 +9322,11 @@ var DaemonRunner = class {
|
|
|
7629
9322
|
});
|
|
7630
9323
|
this.acpPool = null;
|
|
7631
9324
|
}
|
|
9325
|
+
if (this.sessionHost) {
|
|
9326
|
+
void this.sessionHost.closeAll().catch(() => {
|
|
9327
|
+
});
|
|
9328
|
+
this.sessionHost = null;
|
|
9329
|
+
}
|
|
7632
9330
|
this.repoCheckout?.close();
|
|
7633
9331
|
this.repoCheckout = null;
|
|
7634
9332
|
}
|
|
@@ -7965,9 +9663,68 @@ var DaemonRunner = class {
|
|
|
7965
9663
|
// P1:重连后房间已重放 → 立即按 afterSeq 重新补齐(断连期间的帧不会补发,只能拉)
|
|
7966
9664
|
onTaskResubscribed: (taskId) => {
|
|
7967
9665
|
void this.catchUpTaskMessages(taskId);
|
|
9666
|
+
},
|
|
9667
|
+
// 会话宿主命令(steward-core-acp-migration §4.4,P1-5):转发给 SessionHost。
|
|
9668
|
+
// 服务端发到 **tenant 房间**(同租户每台 daemon 都收到,WS 无 per-runtime 房间),
|
|
9669
|
+
// 故与 `agent-task:dispatched` 同口径按 `runtimeId` 自过滤。
|
|
9670
|
+
onSessionCommand: (kind, payload) => {
|
|
9671
|
+
void this.handleSessionCommand(kind, payload);
|
|
7968
9672
|
}
|
|
7969
9673
|
};
|
|
7970
9674
|
}
|
|
9675
|
+
/**
|
|
9676
|
+
* 会话宿主命令分发。
|
|
9677
|
+
*
|
|
9678
|
+
* 两道闸:
|
|
9679
|
+
* ① **runtimeId 自过滤**(tenant 广播 → 只有归属设备处理;缺失时不过滤,兼容旧服务端);
|
|
9680
|
+
* ② **按会话串行**:服务端每轮 prompt 前会补发 `open` 做兜底,而 socket.io 只保证「送达有序」,
|
|
9681
|
+
* 不保证「异步处理有序」——`void handleSessionCommand()` 会让 open/prompt 交错执行,
|
|
9682
|
+
* prompt 可能先于 open 完成而拿到 SESSION_NOT_OPEN。故按 sessionId 串成一条 promise 链。
|
|
9683
|
+
*/
|
|
9684
|
+
sessionCommandChains = /* @__PURE__ */ new Map();
|
|
9685
|
+
handleSessionCommand(kind, payload) {
|
|
9686
|
+
const runtimeId = typeof payload.runtimeId === "string" ? payload.runtimeId : void 0;
|
|
9687
|
+
if (runtimeId && this.runtimeId && runtimeId !== this.runtimeId)
|
|
9688
|
+
return Promise.resolve();
|
|
9689
|
+
const sessionId = typeof payload.sessionId === "string" ? payload.sessionId : "";
|
|
9690
|
+
if (!sessionId) {
|
|
9691
|
+
this.log(`[daemon] \u4F1A\u8BDD\u547D\u4EE4 ${kind} \u7F3A\u5C11 sessionId\uFF08\u5FFD\u7565\uFF09`);
|
|
9692
|
+
return Promise.resolve();
|
|
9693
|
+
}
|
|
9694
|
+
const prev = this.sessionCommandChains.get(sessionId) ?? Promise.resolve();
|
|
9695
|
+
const next = prev.catch(() => {
|
|
9696
|
+
}).then(() => this.runSessionCommand(kind, payload)).catch((err) => {
|
|
9697
|
+
this.log(`[daemon] \u4F1A\u8BDD\u547D\u4EE4 ${kind} \u6267\u884C\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
9698
|
+
});
|
|
9699
|
+
this.sessionCommandChains.set(sessionId, next);
|
|
9700
|
+
void next.finally(() => {
|
|
9701
|
+
if (this.sessionCommandChains.get(sessionId) === next)
|
|
9702
|
+
this.sessionCommandChains.delete(sessionId);
|
|
9703
|
+
});
|
|
9704
|
+
return next;
|
|
9705
|
+
}
|
|
9706
|
+
/** 单条会话命令执行(畸形命令记日志不抛——单条坏命令不该打断 daemon 主循环) */
|
|
9707
|
+
async runSessionCommand(kind, payload) {
|
|
9708
|
+
const host = this.sessionHost;
|
|
9709
|
+
if (!host) {
|
|
9710
|
+
this.log(`[daemon] \u6536\u5230\u4F1A\u8BDD\u547D\u4EE4 ${kind}\uFF0C\u4F46\u4F1A\u8BDD\u5BBF\u4E3B\u672A\u5C31\u7EEA\uFF08\u5FFD\u7565\uFF09`);
|
|
9711
|
+
return;
|
|
9712
|
+
}
|
|
9713
|
+
try {
|
|
9714
|
+
if (kind === "open")
|
|
9715
|
+
await host.open(payload);
|
|
9716
|
+
else if (kind === "prompt")
|
|
9717
|
+
await host.prompt(payload);
|
|
9718
|
+
else if (kind === "cancel")
|
|
9719
|
+
host.cancel(payload);
|
|
9720
|
+
else if (kind === "config")
|
|
9721
|
+
await host.config(payload);
|
|
9722
|
+
else if (kind === "close")
|
|
9723
|
+
await host.close(payload);
|
|
9724
|
+
} catch (err) {
|
|
9725
|
+
this.log(`[daemon] \u4F1A\u8BDD\u547D\u4EE4 ${kind} \u6267\u884C\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
9726
|
+
}
|
|
9727
|
+
}
|
|
7971
9728
|
/**
|
|
7972
9729
|
* 派发事件入口(P0-b,issues/agent-task-false-failure-on-daemon-fetch-error.md):
|
|
7973
9730
|
* 服务端把派发事件发到 **tenant 房间**(daemon-executor.service.ts `sendToTenant`,WS 无 per-runtime
|
|
@@ -8194,7 +9951,13 @@ var DaemonRunner = class {
|
|
|
8194
9951
|
// 本任务的 AgentTask ID:`morula repo mr create`(服务端建 MR 通道)等机器协议据此定位任务;
|
|
8195
9952
|
// agent 不需要也不应自己拼(issues/mr-created-outside-bound-project.md)
|
|
8196
9953
|
...taskId ? { MORULA_TASK_ID: taskId } : {},
|
|
8197
|
-
...this.client?.baseUrl ? { MORULA_API_URL: this.client.baseUrl } : {}
|
|
9954
|
+
...this.client?.baseUrl ? { MORULA_API_URL: this.client.baseUrl } : {},
|
|
9955
|
+
// 插件 bin 前置 + 显式 CLI 路径(与会话链路同源):任务里的 agent 也必须用本包物化的 `morula`,
|
|
9956
|
+
// 而不是宿主/桌面应用自带的旧版(旧版不认任务/会话凭证,且缺部分命令旗标 → 契约指令直接失败)。
|
|
9957
|
+
// PATH 只是第一道保险(dim 会重写 agent shell 的 PATH),可靠的是 `MORULA_CLI` 绝对路径。
|
|
9958
|
+
// 见 issues/steward-cli-resolves-stale-bundled-morula.md。
|
|
9959
|
+
PATH: prependPluginBinToPath(process.env.PATH),
|
|
9960
|
+
MORULA_CLI: pluginBinPath()
|
|
8198
9961
|
};
|
|
8199
9962
|
let repoDir = cfg.workdir;
|
|
8200
9963
|
let taskRoot = cfg.workdir;
|
|
@@ -8205,6 +9968,7 @@ var DaemonRunner = class {
|
|
|
8205
9968
|
const wc = detail.workContext;
|
|
8206
9969
|
repoBaseUrl = tokenRes.baseUrl;
|
|
8207
9970
|
const remoteUrl = repoBaseUrl ? buildGitlabSshRemote(wc?.repoFullName ?? repo, repoBaseUrl) : void 0;
|
|
9971
|
+
const daemonRemoteUrl = buildDaemonRepoUrl(wc?.repoFullName ?? repo, repoBaseUrl);
|
|
8208
9972
|
env = { ...env, ...buildRepoEnv(tokenRes.token, repoBaseUrl) };
|
|
8209
9973
|
if (wc?.baseBranch?.trim())
|
|
8210
9974
|
env = { ...env, MORULA_BASE_BRANCH: wc.baseBranch.trim() };
|
|
@@ -8220,7 +9984,8 @@ var DaemonRunner = class {
|
|
|
8220
9984
|
baseBranch: wc.baseBranch || "main",
|
|
8221
9985
|
code: detail.code ?? null,
|
|
8222
9986
|
env,
|
|
8223
|
-
remoteUrl
|
|
9987
|
+
remoteUrl,
|
|
9988
|
+
daemonRemoteUrl
|
|
8224
9989
|
});
|
|
8225
9990
|
repoDir = managed.worktreePath;
|
|
8226
9991
|
worktreePath = managed.worktreePath;
|
|
@@ -8229,7 +9994,7 @@ var DaemonRunner = class {
|
|
|
8229
9994
|
});
|
|
8230
9995
|
this.log(`[daemon] \u4EFB\u52A1 ${taskId} \u5DE5\u4F5C\u533A\u5C31\u7EEA: ${taskRoot}\uFF08\u4ED3\u5E93 ${repoDir}\uFF0Cbranch=${wc.branchName}\uFF09`);
|
|
8231
9996
|
} else {
|
|
8232
|
-
repoDir = ensureRepo(cfg.workdir, this.tenantId, repo, env, remoteUrl);
|
|
9997
|
+
repoDir = ensureRepo(cfg.workdir, this.tenantId, repo, env, remoteUrl, daemonRemoteUrl);
|
|
8233
9998
|
taskRoot = repoDir;
|
|
8234
9999
|
}
|
|
8235
10000
|
ensureGitIdentity(repoDir, detail.executor.executorName);
|
|
@@ -8246,12 +10011,12 @@ var DaemonRunner = class {
|
|
|
8246
10011
|
});
|
|
8247
10012
|
if (spaceToken?.token)
|
|
8248
10013
|
env = { ...env, ...buildRepoEnv(spaceToken.token, spaceToken.baseUrl) };
|
|
8249
|
-
repoDir =
|
|
10014
|
+
repoDir = join20(cfg.workdir, "scratch", taskId);
|
|
8250
10015
|
taskRoot = repoDir;
|
|
8251
|
-
|
|
8252
|
-
const readme =
|
|
10016
|
+
mkdirSync13(repoDir, { recursive: true });
|
|
10017
|
+
const readme = join20(repoDir, "README.md");
|
|
8253
10018
|
if (!existsSync15(readme)) {
|
|
8254
|
-
|
|
10019
|
+
writeFileSync11(
|
|
8255
10020
|
readme,
|
|
8256
10021
|
`# \u9694\u79BB\u5DE5\u4F5C\u533A\uFF08\u4EFB\u52A1 ${taskId}\uFF09
|
|
8257
10022
|
|
|
@@ -8259,7 +10024,8 @@ var DaemonRunner = class {
|
|
|
8259
10024
|
\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
|
|
8260
10025
|
|
|
8261
10026
|
## \u8BFB\u53D6\u4ED3\u5E93\u5185\u5BB9\uFF08\u91CD\u8981\uFF09
|
|
8262
|
-
\u82E5\
|
|
10027
|
+
\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
|
|
10028
|
+
\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
|
|
8263
10029
|
\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
|
|
8264
10030
|
\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
|
|
8265
10031
|
`,
|
|
@@ -8435,8 +10201,6 @@ var DaemonRunner = class {
|
|
|
8435
10201
|
*/
|
|
8436
10202
|
async runOnce(taskId, detail, taskRoot, provider, providerKey, requestedModelId, env, repoBaseUrl, signal) {
|
|
8437
10203
|
const streamChunks = [];
|
|
8438
|
-
let textBuf = "";
|
|
8439
|
-
let textTimer = null;
|
|
8440
10204
|
let thoughtBuf = "";
|
|
8441
10205
|
let thoughtTimer = null;
|
|
8442
10206
|
let streamFlushing = false;
|
|
@@ -8535,6 +10299,16 @@ var DaemonRunner = class {
|
|
|
8535
10299
|
void flushStreamChunks(false);
|
|
8536
10300
|
}
|
|
8537
10301
|
};
|
|
10302
|
+
const textStream = new AssistantTextStream({
|
|
10303
|
+
onDelta: (seg, text) => {
|
|
10304
|
+
streamChunks.push({ chunkType: "delta", data: { kind: "text", seg, text } });
|
|
10305
|
+
void flushStreamChunks(true);
|
|
10306
|
+
},
|
|
10307
|
+
onParagraph: (seg, text) => {
|
|
10308
|
+
streamChunks.push({ chunkType: "text", seqHint: nextChunkSeq(), data: { text, seg } });
|
|
10309
|
+
void flushStreamChunks(true);
|
|
10310
|
+
}
|
|
10311
|
+
}, { log: (line) => this.log(`[daemon] ${line}`) });
|
|
8538
10312
|
const resumeSessionId = providerKey === "dim" ? detail.dimSessionId ?? detail.lastRun?.dimSessionId ?? null : null;
|
|
8539
10313
|
const resumeWorkDir = detail.dimWorkDir ?? detail.lastRun?.dimWorkDir ?? null;
|
|
8540
10314
|
const canResume = !!resumeSessionId && !!resumeWorkDir && existsSync15(resumeWorkDir);
|
|
@@ -8581,16 +10355,19 @@ var DaemonRunner = class {
|
|
|
8581
10355
|
}
|
|
8582
10356
|
}
|
|
8583
10357
|
if (event.type === "tool_call_started") {
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
10358
|
+
void textStream.flushParagraph().then(() => {
|
|
10359
|
+
streamChunks.push({
|
|
10360
|
+
chunkType: "tool",
|
|
10361
|
+
seqHint: nextChunkSeq(),
|
|
10362
|
+
data: {
|
|
10363
|
+
toolName: event.toolName,
|
|
10364
|
+
callId: event.callId,
|
|
10365
|
+
input: event.input !== void 0 ? redactInputSummary(event.input) : void 0
|
|
10366
|
+
}
|
|
10367
|
+
});
|
|
10368
|
+
void flushStreamChunks(true);
|
|
10369
|
+
}).catch(() => {
|
|
8592
10370
|
});
|
|
8593
|
-
void flushStreamChunks(true);
|
|
8594
10371
|
}
|
|
8595
10372
|
if (event.type === "tool_call_completed") {
|
|
8596
10373
|
streamChunks.push({
|
|
@@ -8606,17 +10383,7 @@ var DaemonRunner = class {
|
|
|
8606
10383
|
void flushStreamChunks(true);
|
|
8607
10384
|
}
|
|
8608
10385
|
if (event.type === "assistant_text_delta") {
|
|
8609
|
-
|
|
8610
|
-
if (!textTimer) {
|
|
8611
|
-
textTimer = setTimeout(() => {
|
|
8612
|
-
textTimer = null;
|
|
8613
|
-
if (textBuf) {
|
|
8614
|
-
streamChunks.push({ chunkType: "text", seqHint: nextChunkSeq(), data: { text: textBuf } });
|
|
8615
|
-
textBuf = "";
|
|
8616
|
-
void flushStreamChunks(false);
|
|
8617
|
-
}
|
|
8618
|
-
}, 200);
|
|
8619
|
-
}
|
|
10386
|
+
textStream.push(event.text);
|
|
8620
10387
|
}
|
|
8621
10388
|
if (event.type === "usage") {
|
|
8622
10389
|
streamChunks.push({ chunkType: "usage", seqHint: nextChunkSeq(), data: event.usage });
|
|
@@ -8634,14 +10401,8 @@ var DaemonRunner = class {
|
|
|
8634
10401
|
}
|
|
8635
10402
|
};
|
|
8636
10403
|
const result = await provider.run(input);
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
textTimer = null;
|
|
8640
|
-
}
|
|
8641
|
-
if (textBuf) {
|
|
8642
|
-
streamChunks.push({ chunkType: "text", seqHint: nextChunkSeq(), data: { text: textBuf } });
|
|
8643
|
-
textBuf = "";
|
|
8644
|
-
}
|
|
10404
|
+
await textStream.flushParagraph();
|
|
10405
|
+
textStream.dispose();
|
|
8645
10406
|
if (thoughtTimer) {
|
|
8646
10407
|
clearTimeout(thoughtTimer);
|
|
8647
10408
|
thoughtTimer = null;
|
|
@@ -8811,7 +10572,7 @@ var DaemonRunner = class {
|
|
|
8811
10572
|
await this.api.submitCleanupResult(item.id, this.runtimeId, { ok: true });
|
|
8812
10573
|
return;
|
|
8813
10574
|
}
|
|
8814
|
-
const managedRoot =
|
|
10575
|
+
const managedRoot = join20(cfg.workdir, "worktrees", this.tenantId);
|
|
8815
10576
|
if (!wc.worktreePath.startsWith(`${managedRoot}/`)) {
|
|
8816
10577
|
await this.api.submitCleanupResult(item.id, this.runtimeId, {
|
|
8817
10578
|
ok: false,
|
|
@@ -8859,38 +10620,38 @@ function isDebugLogEnabled() {
|
|
|
8859
10620
|
var spaceTokenFailureLogged = false;
|
|
8860
10621
|
function writeCustomSkills(targetDir, skills, log) {
|
|
8861
10622
|
const NAME_RE = /^[\w-]{1,64}$/;
|
|
8862
|
-
const base =
|
|
10623
|
+
const base = join20(targetDir, ".agents", "skills");
|
|
8863
10624
|
for (const skill of skills) {
|
|
8864
10625
|
const name = skill.name.trim();
|
|
8865
10626
|
if (!NAME_RE.test(name)) {
|
|
8866
10627
|
log?.(`[daemon] \u8DF3\u8FC7\u975E\u6CD5 skill \u540D\u79F0: ${JSON.stringify(name)}`);
|
|
8867
10628
|
continue;
|
|
8868
10629
|
}
|
|
8869
|
-
const dir =
|
|
8870
|
-
if (
|
|
10630
|
+
const dir = join20(base, name);
|
|
10631
|
+
if (dirname8(dir) !== base) {
|
|
8871
10632
|
log?.(`[daemon] \u8DF3\u8FC7\u8D8A\u754C skill \u8DEF\u5F84: ${JSON.stringify(name)}`);
|
|
8872
10633
|
continue;
|
|
8873
10634
|
}
|
|
8874
|
-
|
|
10635
|
+
mkdirSync13(dir, { recursive: true });
|
|
8875
10636
|
const meta = ["---", `name: ${name}`];
|
|
8876
10637
|
if (skill.description?.trim()) {
|
|
8877
10638
|
meta.push(`description: ${skill.description.trim().replace(/\n/g, " ")}`);
|
|
8878
10639
|
}
|
|
8879
10640
|
meta.push("---", "");
|
|
8880
|
-
|
|
10641
|
+
writeFileSync11(join20(dir, "SKILL.md"), `${meta.join("\n")}
|
|
8881
10642
|
${skill.content ?? ""}`, "utf8");
|
|
8882
10643
|
}
|
|
8883
10644
|
}
|
|
8884
10645
|
function writeCollaborationSkill(targetDir, repo, repoBaseUrl, log, baseBranch, provider = repoBaseUrl ? "gitlab" : "github") {
|
|
8885
|
-
const dir =
|
|
8886
|
-
const refsDir =
|
|
8887
|
-
|
|
10646
|
+
const dir = join20(targetDir, ".agents", "skills", COLLABORATION_SKILL_NAME);
|
|
10647
|
+
const refsDir = join20(dir, "references");
|
|
10648
|
+
mkdirSync13(refsDir, { recursive: true });
|
|
8888
10649
|
const isGitlab = provider === "gitlab" && !!repoBaseUrl;
|
|
8889
10650
|
const front = isGitlab ? buildGitlabSkillFrontmatter() : COLLABORATION_SKILL_FRONTMATTER;
|
|
8890
10651
|
const body = isGitlab ? buildGitlabSkillBody(repo ?? "", repoBaseUrl, baseBranch) : buildGithubSkillBody(baseBranch);
|
|
8891
|
-
|
|
10652
|
+
writeFileSync11(join20(dir, "SKILL.md"), `${front}
|
|
8892
10653
|
${body}`, "utf8");
|
|
8893
|
-
|
|
10654
|
+
writeFileSync11(join20(refsDir, "working-on-issues-source-map.md"), COLLABORATION_SKILL_SOURCE_MAP, "utf8");
|
|
8894
10655
|
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/`);
|
|
8895
10656
|
}
|
|
8896
10657
|
function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundBaseBranch) {
|
|
@@ -8921,8 +10682,8 @@ function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundB
|
|
|
8921
10682
|
};
|
|
8922
10683
|
}
|
|
8923
10684
|
function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
|
|
8924
|
-
const briefDir =
|
|
8925
|
-
|
|
10685
|
+
const briefDir = join20(targetDir, ".agent_context");
|
|
10686
|
+
mkdirSync13(briefDir, { recursive: true });
|
|
8926
10687
|
const brief = `# \u4EFB\u52A1\u4E0A\u4E0B\u6587\uFF08Task Context\uFF09
|
|
8927
10688
|
|
|
8928
10689
|
- \u672C AgentTask \u4EFB\u52A1 ID\uFF1A\`${taskId}\`
|
|
@@ -8931,7 +10692,7 @@ function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
|
|
|
8931
10692
|
---
|
|
8932
10693
|
|
|
8933
10694
|
${buildTaskPromptWithContract(detail, void 0)}`;
|
|
8934
|
-
|
|
10695
|
+
writeFileSync11(join20(briefDir, "task-context.md"), brief, "utf8");
|
|
8935
10696
|
log?.(`[daemon] \u5DF2\u6CE8\u5165\u4EFB\u52A1\u4E0A\u4E0B\u6587 brief \u5230 .agent_context/task-context.md`);
|
|
8936
10697
|
}
|
|
8937
10698
|
var PROJECT_AGENTS_MD_MAX_CHARS = 1e5;
|
|
@@ -8945,8 +10706,8 @@ function writeProjectAgentsMd(targetDir, agentsMd, log) {
|
|
|
8945
10706
|
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`);
|
|
8946
10707
|
content = content.slice(0, PROJECT_AGENTS_MD_MAX_CHARS);
|
|
8947
10708
|
}
|
|
8948
|
-
|
|
8949
|
-
|
|
10709
|
+
mkdirSync13(targetDir, { recursive: true });
|
|
10710
|
+
writeFileSync11(join20(targetDir, "AGENTS.md"), `${content}
|
|
8950
10711
|
`, "utf8");
|
|
8951
10712
|
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`);
|
|
8952
10713
|
return true;
|
|
@@ -8966,52 +10727,20 @@ function resolveRepo(detail) {
|
|
|
8966
10727
|
}
|
|
8967
10728
|
function buildTaskPromptWithContract(detail, repoBaseUrl) {
|
|
8968
10729
|
const prompt = detail.taskPrompt ?? "";
|
|
8969
|
-
|
|
8970
|
-
const
|
|
8971
|
-
const
|
|
8972
|
-
const
|
|
8973
|
-
const multiRepoList = availableRepos.length > 1 ? availableRepos.map((r) => `- ${r.repoFullName}\uFF08\u9ED8\u8BA4\u5206\u652F\uFF1A${r.defaultBranch || "main"}\uFF09`).join("\n") : "";
|
|
8974
|
-
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
|
|
8975
|
-
\u53EF\u7528\u4ED3\u5E93\u6E05\u5355\uFF1A
|
|
8976
|
-
${multiRepoList}
|
|
8977
|
-
|
|
8978
|
-
` : "";
|
|
8979
|
-
let repoHint = "";
|
|
8980
|
-
const repoRefs = Array.isArray(detail.contextSnapshot?.repoRefs) ? detail.contextSnapshot.repoRefs : [];
|
|
8981
|
-
if (repoRefs.length > 0) {
|
|
8982
|
-
const list = repoRefs.map((r) => r.repoFullName).filter(Boolean).join("\u3001");
|
|
8983
|
-
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
|
|
8984
|
-
|
|
8985
|
-
`;
|
|
8986
|
-
} else if (repo) {
|
|
8987
|
-
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
|
|
8988
|
-
|
|
8989
|
-
`;
|
|
8990
|
-
} else if (repoResolution === "MULTI_REPO_AGENT_DECIDES") {
|
|
8991
|
-
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
|
|
8992
|
-
|
|
8993
|
-
`;
|
|
8994
|
-
} else if (repoResolution === "REPO_SELECTION_REQUIRED") {
|
|
8995
|
-
const snapCandidates = Array.isArray(detail.contextSnapshot?.repoCandidates) ? detail.contextSnapshot.repoCandidates : [];
|
|
8996
|
-
const candidates = snapCandidates.length > 0 ? snapCandidates : availableRepos.map((r) => r.repoFullName).filter((n) => Boolean(n));
|
|
8997
|
-
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
|
|
8998
|
-
|
|
8999
|
-
` : `\u7A7A\u95F4\u5173\u8054\u591A\u4E2A\u4ED3\u5E93\uFF0C\u9700\u8981\u5411\u7528\u6237\u786E\u8BA4\u8981\u64CD\u4F5C\u7684\u4ED3\u5E93\u3002
|
|
9000
|
-
|
|
9001
|
-
`;
|
|
9002
|
-
} else if (!repo && (repoResolution === "NO_CONNECTION" || !repoResolution)) {
|
|
9003
|
-
if (availableRepos.length > 0) {
|
|
9004
|
-
const list = availableRepos.map((r) => r.repoFullName).filter(Boolean).join("\u3001");
|
|
9005
|
-
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
|
|
10730
|
+
void repoBaseUrl;
|
|
10731
|
+
const snap = detail.contextSnapshot;
|
|
10732
|
+
const spaceId = typeof snap?.spaceId === "string" && snap.spaceId.trim() ? snap.spaceId.trim() : "";
|
|
10733
|
+
const keysHint = `## \u57FA\u672C\u4FE1\u606F
|
|
9006
10734
|
|
|
9007
|
-
|
|
9008
|
-
|
|
9009
|
-
|
|
10735
|
+
- \u4EFB\u52A1 ID\uFF08AgentTask\uFF0C\`morula task branch\` \u7B49\u547D\u4EE4\u7684 taskId \u7528\u5B83\uFF09\uFF1A\`${detail.id}\`
|
|
10736
|
+
${spaceId ? `- \u7A7A\u95F4 ID\uFF08\u6240\u6709 \`--space\` \u7528\u5B83\uFF09\uFF1A\`${spaceId}\`
|
|
10737
|
+
` : ""}`;
|
|
9010
10738
|
const toolsHint = `## \u5DE5\u4F5C\u533A\u4E0E\u5DE5\u5177
|
|
9011
10739
|
|
|
9012
|
-
- \u5DE5\u4F5C\u76EE\u5F55 = \u4EFB\u52A1\u5DE5\u4F5C\u533A\u6839\
|
|
10740
|
+
- \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
|
|
9013
10741
|
- \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
|
|
9014
|
-
- \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\
|
|
10742
|
+
- \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
|
|
10743
|
+
- \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
|
|
9015
10744
|
|
|
9016
10745
|
`;
|
|
9017
10746
|
const taskSource = detail.contextSnapshot?.source;
|
|
@@ -9019,19 +10748,12 @@ ${multiRepoList}
|
|
|
9019
10748
|
const stageContractBlock = stageContract ? `${stageContract}
|
|
9020
10749
|
|
|
9021
10750
|
` : "";
|
|
9022
|
-
const
|
|
9023
|
-
|
|
9024
|
-
if (snapCtx?.triggerSource === "comment_mention" && snapCtx.commentId) {
|
|
9025
|
-
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
|
|
9026
|
-
|
|
9027
|
-
`;
|
|
9028
|
-
}
|
|
9029
|
-
if (snapCtx?.handoffNote && typeof snapCtx.handoffNote === "string" && snapCtx.handoffNote.trim()) {
|
|
9030
|
-
extraHint += `\u6307\u6D3E\u4EA4\u4ED8\u8981\u6C42\uFF08\u7528\u6237\u663E\u5F0F\u6307\u5B9A\uFF0C\u5FC5\u987B\u6EE1\u8DB3\uFF09\uFF1A**${snapCtx.handoffNote.trim()}**
|
|
10751
|
+
const handoff = typeof snap?.handoffNote === "string" ? snap.handoffNote.trim() : "";
|
|
10752
|
+
const extraHint = handoff ? `\u6307\u6D3E\u4EA4\u4ED8\u8981\u6C42\uFF08\u7528\u6237\u663E\u5F0F\u6307\u5B9A\uFF0C\u5FC5\u987B\u6EE1\u8DB3\uFF09\uFF1A**${handoff}**
|
|
9031
10753
|
|
|
9032
|
-
|
|
9033
|
-
}
|
|
9034
|
-
|
|
10754
|
+
` : "";
|
|
10755
|
+
const head = `${stageContractBlock}${keysHint}
|
|
10756
|
+
${toolsHint}${extraHint}`;
|
|
9035
10757
|
return head ? `${head}========== \u4F60\u7684\u76EE\u6807 ==========
|
|
9036
10758
|
${prompt}` : `${prompt}`;
|
|
9037
10759
|
}
|
|
@@ -9044,7 +10766,7 @@ async function reportRepoTargets(taskId, targets, env, ops) {
|
|
|
9044
10766
|
}
|
|
9045
10767
|
async function reportOneRepoTarget(taskId, target, env, ops) {
|
|
9046
10768
|
const execEnv = mergeExecEnv(env);
|
|
9047
|
-
if (!existsSync15(target.dir) || !existsSync15(
|
|
10769
|
+
if (!existsSync15(target.dir) || !existsSync15(join20(target.dir, ".git"))) {
|
|
9048
10770
|
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`);
|
|
9049
10771
|
return;
|
|
9050
10772
|
}
|
|
@@ -9213,8 +10935,8 @@ Task ref: ${taskCode}\uFF08\u7531 morula \u81EA\u52A8\u8865\u5145\uFF0C\u7528\u4
|
|
|
9213
10935
|
// src/commands/doctor.ts
|
|
9214
10936
|
init_dist2();
|
|
9215
10937
|
import { existsSync as existsSync16, readFileSync as readFileSync10, realpathSync } from "node:fs";
|
|
9216
|
-
import { homedir as
|
|
9217
|
-
import { dirname as
|
|
10938
|
+
import { homedir as homedir12 } from "node:os";
|
|
10939
|
+
import { dirname as dirname9, join as join21 } from "node:path";
|
|
9218
10940
|
init_daemon_config();
|
|
9219
10941
|
init_daemon_lock();
|
|
9220
10942
|
init_dim_locate();
|
|
@@ -9234,10 +10956,10 @@ async function doctorCommand(context) {
|
|
|
9234
10956
|
async function collectDoctor(context, probe) {
|
|
9235
10957
|
const checks = [];
|
|
9236
10958
|
const env = context.env;
|
|
9237
|
-
const configDir = env.MORULA_CONFIG_DIR ??
|
|
9238
|
-
const configPath = env.MORULA_CONFIG ??
|
|
9239
|
-
const daemonPath =
|
|
9240
|
-
const logFile =
|
|
10959
|
+
const configDir = env.MORULA_CONFIG_DIR ?? join21(homedir12(), ".morula-cli");
|
|
10960
|
+
const configPath = env.MORULA_CONFIG ?? join21(configDir, "config.yaml");
|
|
10961
|
+
const daemonPath = join21(dirname9(configPath), "daemon");
|
|
10962
|
+
const logFile = join21(daemonPath, "daemon.log");
|
|
9241
10963
|
const contextName = context.runtime.context ?? null;
|
|
9242
10964
|
const baseUrl = context.runtime.baseUrl ?? null;
|
|
9243
10965
|
const tokenPresent = Boolean(context.runtime.token);
|
|
@@ -9251,12 +10973,11 @@ async function collectDoctor(context, probe) {
|
|
|
9251
10973
|
configReadable = false;
|
|
9252
10974
|
}
|
|
9253
10975
|
}
|
|
9254
|
-
const lockPath =
|
|
10976
|
+
const lockPath = join21(daemonPath, DAEMON_LOCK_FILE);
|
|
9255
10977
|
const lock = probeDaemonLock(lockPath);
|
|
9256
|
-
const pidFile =
|
|
9257
|
-
const filePid =
|
|
9258
|
-
const
|
|
9259
|
-
const daemonPid = lockPid || (isProcessAlive(filePid) ? filePid : 0) || null;
|
|
10978
|
+
const pidFile = join21(daemonPath, DAEMON_PID_FILE);
|
|
10979
|
+
const filePid = readPidFile(pidFile);
|
|
10980
|
+
const daemonPid = probeDaemonInstance(configDir).pid;
|
|
9260
10981
|
const cfg = loadDaemonConfig(context.store);
|
|
9261
10982
|
const dim = safeResolveDim();
|
|
9262
10983
|
const dimPath = dim.path;
|
|
@@ -9424,12 +11145,12 @@ function detectInstallMode() {
|
|
|
9424
11145
|
|
|
9425
11146
|
// src/commands/daemon.ts
|
|
9426
11147
|
function configFilePath(env) {
|
|
9427
|
-
return env.MORULA_CONFIG ??
|
|
11148
|
+
return env.MORULA_CONFIG ?? join22(env.MORULA_CONFIG_DIR ?? join22(homedir13(), ".morula-cli"), "config.yaml");
|
|
9428
11149
|
}
|
|
9429
11150
|
function daemonDir(configPath) {
|
|
9430
|
-
return
|
|
11151
|
+
return join22(dirname10(configPath), "daemon");
|
|
9431
11152
|
}
|
|
9432
|
-
var PID_FILE =
|
|
11153
|
+
var PID_FILE = DAEMON_PID_FILE;
|
|
9433
11154
|
var LOG_FILE = "daemon.log";
|
|
9434
11155
|
async function daemonCommand(context) {
|
|
9435
11156
|
const { action } = context.parsed;
|
|
@@ -9621,8 +11342,8 @@ async function run2(context) {
|
|
|
9621
11342
|
saveDaemonConfig(context.store, { name, workdir, maxConcurrent });
|
|
9622
11343
|
}
|
|
9623
11344
|
const resolvedWorkdir = workdir ?? loadDaemonConfig(context.store)?.workdir ?? DEFAULT_WORKDIR;
|
|
9624
|
-
|
|
9625
|
-
const lockPath =
|
|
11345
|
+
mkdirSync14(resolvedWorkdir, { recursive: true });
|
|
11346
|
+
const lockPath = join22(daemonDir(configFilePath(context.env)), DAEMON_LOCK_FILE);
|
|
9626
11347
|
const acquired = acquireDaemonLock(lockPath, { workdir: resolvedWorkdir });
|
|
9627
11348
|
if (!acquired.ok) {
|
|
9628
11349
|
const pid = acquired.existing?.pid;
|
|
@@ -9696,10 +11417,10 @@ async function start(context) {
|
|
|
9696
11417
|
}
|
|
9697
11418
|
const dir = daemonDir(configFilePath(context.env));
|
|
9698
11419
|
if (!dryRun)
|
|
9699
|
-
|
|
9700
|
-
const pidFile =
|
|
9701
|
-
const logFile =
|
|
9702
|
-
const lockPath =
|
|
11420
|
+
mkdirSync14(dir, { recursive: true, mode: 448 });
|
|
11421
|
+
const pidFile = join22(dir, PID_FILE);
|
|
11422
|
+
const logFile = join22(dir, LOG_FILE);
|
|
11423
|
+
const lockPath = join22(dir, DAEMON_LOCK_FILE);
|
|
9703
11424
|
const lock = probeDaemonLock(lockPath);
|
|
9704
11425
|
if (lock.state === "active") {
|
|
9705
11426
|
throw new CliError(
|
|
@@ -9718,7 +11439,7 @@ async function start(context) {
|
|
|
9718
11439
|
});
|
|
9719
11440
|
}
|
|
9720
11441
|
if (!dryRun)
|
|
9721
|
-
|
|
11442
|
+
rmSync10(pidFile, { force: true });
|
|
9722
11443
|
}
|
|
9723
11444
|
const env = {
|
|
9724
11445
|
...process.env,
|
|
@@ -9756,12 +11477,12 @@ async function start(context) {
|
|
|
9756
11477
|
const { spawnDaemonProcess: spawnDaemonProcess2 } = await Promise.resolve().then(() => (init_spawn(), spawn_exports));
|
|
9757
11478
|
const result = spawnDaemonProcess2({ logFile, env });
|
|
9758
11479
|
if (result.error || result.pid <= 0) {
|
|
9759
|
-
|
|
11480
|
+
rmSync10(pidFile, { force: true });
|
|
9760
11481
|
throw new CliError("DAEMON_START_FAILED", EXIT.OTHER, `[daemon] \u542F\u52A8\u5931\u8D25: ${result.error ?? `pid=${result.pid}`}`, false, {
|
|
9761
11482
|
hint: `tail -n 50 ${logFile}`
|
|
9762
11483
|
});
|
|
9763
11484
|
}
|
|
9764
|
-
|
|
11485
|
+
writeFileSync12(pidFile, String(result.pid), { mode: 384 });
|
|
9765
11486
|
context.err(`[daemon] \u5DF2\u540E\u53F0\u542F\u52A8\uFF08pid=${result.pid}\uFF09\uFF0C\u65E5\u5FD7: ${logFile}`);
|
|
9766
11487
|
return { running: true, pid: result.pid, logFile, lockFile: lockPath };
|
|
9767
11488
|
}
|
|
@@ -9769,8 +11490,8 @@ async function stop(context) {
|
|
|
9769
11490
|
rejectUnknownFlags(context.parsed.flags, ["dry-run"]);
|
|
9770
11491
|
const dryRun = isDryRun(context);
|
|
9771
11492
|
const dir = daemonDir(configFilePath(context.env));
|
|
9772
|
-
const pidFile =
|
|
9773
|
-
const lockPath =
|
|
11493
|
+
const pidFile = join22(dir, PID_FILE);
|
|
11494
|
+
const lockPath = join22(dir, DAEMON_LOCK_FILE);
|
|
9774
11495
|
const lock = probeDaemonLock(lockPath);
|
|
9775
11496
|
const lockPid = lock.state === "absent" ? null : lock.record?.pid ?? null;
|
|
9776
11497
|
const filePid = existsSync17(pidFile) ? Number(readFileSync11(pidFile, "utf8").trim()) : null;
|
|
@@ -9783,7 +11504,7 @@ async function stop(context) {
|
|
|
9783
11504
|
};
|
|
9784
11505
|
}
|
|
9785
11506
|
if (!pid) {
|
|
9786
|
-
|
|
11507
|
+
rmSync10(pidFile, { force: true });
|
|
9787
11508
|
releaseDaemonLock(lockPath);
|
|
9788
11509
|
return { running: false, message: "daemon \u672A\u5728\u8FD0\u884C\uFF08\u65E0\u5B58\u6D3B pid\uFF09" };
|
|
9789
11510
|
}
|
|
@@ -9792,7 +11513,7 @@ async function stop(context) {
|
|
|
9792
11513
|
releaseDaemonLock(lockPath);
|
|
9793
11514
|
else
|
|
9794
11515
|
releaseDaemonLock(lockPath, pid);
|
|
9795
|
-
|
|
11516
|
+
rmSync10(pidFile, { force: true });
|
|
9796
11517
|
if (result.outcome === "killed")
|
|
9797
11518
|
context.err(`[daemon] pid=${pid} \u672A\u54CD\u5E94 SIGTERM\uFF0C\u5DF2 SIGKILL\uFF08\u9501\u5DF2\u91CA\u653E\uFF09`);
|
|
9798
11519
|
return { running: false, stoppedPid: pid, outcome: result.outcome };
|
|
@@ -9801,21 +11522,17 @@ async function status2(context) {
|
|
|
9801
11522
|
rejectUnknownFlags(context.parsed.flags, []);
|
|
9802
11523
|
const cfg = loadDaemonConfig(context.store);
|
|
9803
11524
|
const dir = daemonDir(configFilePath(context.env));
|
|
9804
|
-
const
|
|
9805
|
-
const
|
|
9806
|
-
const lock = probeDaemonLock(lockPath);
|
|
9807
|
-
const lockPid = lock.state === "active" ? lock.record.pid : null;
|
|
9808
|
-
const filePid = existsSync17(pidFile) ? Number(readFileSync11(pidFile, "utf8").trim() || 0) : 0;
|
|
9809
|
-
const pid = lockPid ?? (isProcessAlive(filePid) ? filePid : null);
|
|
11525
|
+
const lockPath = join22(dir, DAEMON_LOCK_FILE);
|
|
11526
|
+
const probe = probeDaemonInstance(dirname10(configFilePath(context.env)));
|
|
9810
11527
|
return {
|
|
9811
|
-
running:
|
|
9812
|
-
pid,
|
|
11528
|
+
running: probe.running,
|
|
11529
|
+
pid: probe.pid,
|
|
9813
11530
|
daemonId: cfg?.daemonId ?? null,
|
|
9814
11531
|
runtimeId: cfg?.runtimeId ?? null,
|
|
9815
11532
|
name: cfg?.name ?? null,
|
|
9816
11533
|
workdir: cfg?.workdir ?? DEFAULT_WORKDIR,
|
|
9817
11534
|
maxConcurrent: cfg?.maxConcurrent ?? DEFAULT_MAX_CONCURRENT,
|
|
9818
|
-
logFile:
|
|
11535
|
+
logFile: join22(dir, LOG_FILE),
|
|
9819
11536
|
lockFile: lockPath
|
|
9820
11537
|
};
|
|
9821
11538
|
}
|
|
@@ -9843,95 +11560,6 @@ function parseMaxConcurrent(context) {
|
|
|
9843
11560
|
|
|
9844
11561
|
// src/commands/setup.ts
|
|
9845
11562
|
init_dist2();
|
|
9846
|
-
|
|
9847
|
-
// src/daemon/plugin-register.ts
|
|
9848
|
-
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";
|
|
9849
|
-
import { homedir as homedir13 } from "node:os";
|
|
9850
|
-
import { dirname as dirname10, join as join21 } from "node:path";
|
|
9851
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
9852
|
-
var PLUGIN_NAME = "morula-cli";
|
|
9853
|
-
var MANIFEST_DIR_NAME = ".codex-plugin";
|
|
9854
|
-
function legacyAgentsPluginDir(home = homedir13()) {
|
|
9855
|
-
return join21(home, ".agents", "plugins", PLUGIN_NAME);
|
|
9856
|
-
}
|
|
9857
|
-
function resolveDimcodeHome(env = process.env, home = homedir13()) {
|
|
9858
|
-
const fromEnv = env.DIMCODE_HOME?.trim();
|
|
9859
|
-
return fromEnv || join21(home, ".dimcode", "v2");
|
|
9860
|
-
}
|
|
9861
|
-
function defaultPluginsRoot(env = process.env, home = homedir13()) {
|
|
9862
|
-
return join21(resolveDimcodeHome(env, home), "plugins");
|
|
9863
|
-
}
|
|
9864
|
-
function resolvePackagePaths(entryUrl) {
|
|
9865
|
-
const distDir = dirname10(fileURLToPath2(entryUrl));
|
|
9866
|
-
return {
|
|
9867
|
-
packageRoot: dirname10(distDir),
|
|
9868
|
-
distDir,
|
|
9869
|
-
assetsDir: join21(distDir, "plugin")
|
|
9870
|
-
};
|
|
9871
|
-
}
|
|
9872
|
-
function buildLauncherScript(opts) {
|
|
9873
|
-
const entry = join21(opts.packageRoot, "dist", "morula.js");
|
|
9874
|
-
return `#!/bin/sh
|
|
9875
|
-
# 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
|
|
9876
|
-
# \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
|
|
9877
|
-
NODE=${JSON.stringify(opts.nodePath)}
|
|
9878
|
-
[ -x "$NODE" ] || NODE="$(command -v node 2>/dev/null || true)"
|
|
9879
|
-
if [ -z "$NODE" ]; then
|
|
9880
|
-
echo '{"code":"ERROR","message":"node not found; reinstall or re-run: morula-runtime setup"}' >&2
|
|
9881
|
-
exit 127
|
|
9882
|
-
fi
|
|
9883
|
-
exec "$NODE" ${JSON.stringify(entry)} "$@"
|
|
9884
|
-
`;
|
|
9885
|
-
}
|
|
9886
|
-
function assetsReady(paths) {
|
|
9887
|
-
return existsSync18(join21(paths.assetsDir, "plugin.json")) && existsSync18(join21(paths.assetsDir, "skills"));
|
|
9888
|
-
}
|
|
9889
|
-
function listSkillNames(assetsDir) {
|
|
9890
|
-
const dir = join21(assetsDir, "skills");
|
|
9891
|
-
if (!existsSync18(dir))
|
|
9892
|
-
return [];
|
|
9893
|
-
return readdirSync5(dir).filter((name) => {
|
|
9894
|
-
try {
|
|
9895
|
-
return statSync4(join21(dir, name)).isDirectory();
|
|
9896
|
-
} catch {
|
|
9897
|
-
return false;
|
|
9898
|
-
}
|
|
9899
|
-
}).sort();
|
|
9900
|
-
}
|
|
9901
|
-
function registerPlugin(opts) {
|
|
9902
|
-
const { pluginsRoot, paths, version } = opts;
|
|
9903
|
-
const nodePath = opts.nodePath ?? process.execPath;
|
|
9904
|
-
const pluginDir = join21(pluginsRoot, PLUGIN_NAME);
|
|
9905
|
-
if (!assetsReady(paths))
|
|
9906
|
-
throw new Error(`plugin assets not found at ${paths.assetsDir}; run \`pnpm run build:dist\` (dev) or reinstall the package`);
|
|
9907
|
-
const skillsSrc = join21(paths.assetsDir, "skills");
|
|
9908
|
-
const skills = listSkillNames(paths.assetsDir);
|
|
9909
|
-
const manifestDir = join21(pluginDir, MANIFEST_DIR_NAME);
|
|
9910
|
-
mkdirSync13(manifestDir, { recursive: true });
|
|
9911
|
-
const manifest = JSON.parse(readFileSync12(join21(paths.assetsDir, "plugin.json"), "utf8"));
|
|
9912
|
-
writeFileSync11(join21(manifestDir, "plugin.json"), `${JSON.stringify({ ...manifest, version }, null, 2)}
|
|
9913
|
-
`);
|
|
9914
|
-
const skillsDest = join21(pluginDir, "skills");
|
|
9915
|
-
rmSync9(skillsDest, { recursive: true, force: true });
|
|
9916
|
-
mkdirSync13(skillsDest, { recursive: true });
|
|
9917
|
-
cpSync(skillsSrc, skillsDest, { recursive: true });
|
|
9918
|
-
const binDir = join21(pluginDir, "bin");
|
|
9919
|
-
mkdirSync13(binDir, { recursive: true });
|
|
9920
|
-
const binPath = join21(binDir, "morula");
|
|
9921
|
-
writeFileSync11(binPath, buildLauncherScript({ nodePath, packageRoot: paths.packageRoot }));
|
|
9922
|
-
chmodSync3(binPath, 493);
|
|
9923
|
-
const legacy = legacyAgentsPluginDir(opts.home);
|
|
9924
|
-
return {
|
|
9925
|
-
pluginDir,
|
|
9926
|
-
binPath,
|
|
9927
|
-
skills,
|
|
9928
|
-
version,
|
|
9929
|
-
node: nodePath,
|
|
9930
|
-
legacyDir: existsSync18(join21(legacy, MANIFEST_DIR_NAME)) ? legacy : null
|
|
9931
|
-
};
|
|
9932
|
-
}
|
|
9933
|
-
|
|
9934
|
-
// src/commands/setup.ts
|
|
9935
11563
|
var KNOWN_FLAGS = ["root", "help"];
|
|
9936
11564
|
async function setupCommand(context) {
|
|
9937
11565
|
rejectUnknownFlags(context.parsed.flags, KNOWN_FLAGS);
|