@arcships/morula-runtime 0.1.0-alpha.7 → 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 +2042 -528
- 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(", ")}`);
|
|
@@ -4615,7 +5291,7 @@ function buildDaemonRepoUrl(repoFullName, baseUrl) {
|
|
|
4615
5291
|
function ensureRepo(workdir, tenantId, repo, env, remoteUrl, daemonRemoteUrl) {
|
|
4616
5292
|
const repoDir = join11(workdir, tenantId, repo);
|
|
4617
5293
|
const fetchUrl = daemonRemoteUrl ?? remoteUrl ?? buildDaemonRepoUrl(repo);
|
|
4618
|
-
if (
|
|
5294
|
+
if (existsSync7(join11(repoDir, ".git"))) {
|
|
4619
5295
|
if (remoteUrl)
|
|
4620
5296
|
syncRemoteUrl(repoDir, remoteUrl, env);
|
|
4621
5297
|
try {
|
|
@@ -4660,7 +5336,7 @@ function syncRemoteUrl(repoDir, remoteUrl, env) {
|
|
|
4660
5336
|
function isLinkedWorktree(repoDir) {
|
|
4661
5337
|
try {
|
|
4662
5338
|
const gitDir = execFileSync6("git", ["-C", repoDir, "rev-parse", "--absolute-git-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
4663
|
-
return !!gitDir &&
|
|
5339
|
+
return !!gitDir && existsSync7(join11(gitDir, "commondir"));
|
|
4664
5340
|
} catch {
|
|
4665
5341
|
return false;
|
|
4666
5342
|
}
|
|
@@ -4698,7 +5374,7 @@ function ensureGitIdentity(repoDir, personaName) {
|
|
|
4698
5374
|
}
|
|
4699
5375
|
|
|
4700
5376
|
// src/daemon/outbox.ts
|
|
4701
|
-
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";
|
|
4702
5378
|
import { homedir as homedir9 } from "node:os";
|
|
4703
5379
|
import { join as join12 } from "node:path";
|
|
4704
5380
|
var OUTBOX_RETRY_SCHEDULE = [1e3, 4e3, 16e3, 6e4, 3e5];
|
|
@@ -4714,14 +5390,14 @@ function writeOutbox(entry, configDir) {
|
|
|
4714
5390
|
}
|
|
4715
5391
|
function readOutboxEntries(configDir) {
|
|
4716
5392
|
const dir = outboxDir(configDir);
|
|
4717
|
-
if (!
|
|
5393
|
+
if (!existsSync8(dir))
|
|
4718
5394
|
return [];
|
|
4719
5395
|
const entries = [];
|
|
4720
5396
|
for (const file of readdirSync2(dir)) {
|
|
4721
5397
|
if (!file.endsWith(".json"))
|
|
4722
5398
|
continue;
|
|
4723
5399
|
try {
|
|
4724
|
-
const raw =
|
|
5400
|
+
const raw = readFileSync5(join12(dir, file), "utf8");
|
|
4725
5401
|
const parsed = JSON.parse(raw);
|
|
4726
5402
|
if (parsed && typeof parsed.taskId === "string")
|
|
4727
5403
|
entries.push(parsed);
|
|
@@ -4731,7 +5407,7 @@ function readOutboxEntries(configDir) {
|
|
|
4731
5407
|
return entries;
|
|
4732
5408
|
}
|
|
4733
5409
|
function removeOutbox(taskId, configDir) {
|
|
4734
|
-
|
|
5410
|
+
rmSync7(join12(outboxDir(configDir), `${taskId}.json`), { force: true });
|
|
4735
5411
|
}
|
|
4736
5412
|
function staleDir(configDir) {
|
|
4737
5413
|
return join12(outboxDir(configDir), "stale");
|
|
@@ -4754,13 +5430,113 @@ function outboxAge(entry) {
|
|
|
4754
5430
|
return Number.isFinite(created) ? Date.now() - created : Number.POSITIVE_INFINITY;
|
|
4755
5431
|
}
|
|
4756
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
|
+
|
|
4757
5533
|
// src/daemon/acp/acp-event-mapper.ts
|
|
4758
5534
|
var AcpEventMapper = class {
|
|
4759
5535
|
lastUsage = void 0;
|
|
4760
5536
|
reset() {
|
|
4761
5537
|
this.lastUsage = void 0;
|
|
4762
5538
|
}
|
|
4763
|
-
/** 取当前 usage
|
|
5539
|
+
/** 取当前 usage(回放/观测用;**不再**作为 tokenUsage 附着——token 总量以 prompt 响应的 usage 为准) */
|
|
4764
5540
|
get latestUsage() {
|
|
4765
5541
|
return this.lastUsage;
|
|
4766
5542
|
}
|
|
@@ -4786,6 +5562,13 @@ var AcpEventMapper = class {
|
|
|
4786
5562
|
}
|
|
4787
5563
|
break;
|
|
4788
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
|
+
}
|
|
4789
5572
|
case "tool_call": {
|
|
4790
5573
|
messages.push({
|
|
4791
5574
|
eventType: "tool:started",
|
|
@@ -4813,11 +5596,13 @@ var AcpEventMapper = class {
|
|
|
4813
5596
|
break;
|
|
4814
5597
|
}
|
|
4815
5598
|
case "usage_update": {
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
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
|
|
4820
5603
|
};
|
|
5604
|
+
this.lastUsage = usage2;
|
|
5605
|
+
messages.push({ eventType: "usage:context", payload: { usage: usage2 } });
|
|
4821
5606
|
break;
|
|
4822
5607
|
}
|
|
4823
5608
|
default:
|
|
@@ -4913,14 +5698,9 @@ function normalizeOptionId(decision) {
|
|
|
4913
5698
|
}
|
|
4914
5699
|
|
|
4915
5700
|
// src/daemon/acp/dim-acp-executor.ts
|
|
4916
|
-
var DEFAULT_TIMEOUT_MS2 = 30 * 60 * 1e3;
|
|
4917
|
-
var DEFAULT_IDLE_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
4918
|
-
var DEFAULT_TOOL_IDLE_TIMEOUT_MS = 20 * 60 * 1e3;
|
|
4919
|
-
var DEFAULT_MAX_TURNS = 8;
|
|
4920
|
-
var DEFAULT_TASK_WALL_CLOCK_MS = 2 * 60 * 60 * 1e3;
|
|
4921
5701
|
var DEFAULT_STEER_SAFETY_MS = 30 * 1e3;
|
|
4922
5702
|
var PROMPT_RPC_TIMEOUT_GRACE_MS = 1e4;
|
|
4923
|
-
var
|
|
5703
|
+
var STEER_POLL_TICK_MS = 15e3;
|
|
4924
5704
|
var KILL_GRACE_MS = 3e3;
|
|
4925
5705
|
function resolveSteerSafetyMs(raw = process.env.MORULA_STEER_SAFETY_MS) {
|
|
4926
5706
|
if (raw === void 0 || raw === "")
|
|
@@ -5063,7 +5843,7 @@ var DimAcpExecutor = class {
|
|
|
5063
5843
|
/** 每次 run 前由调用方注入(daemon 单例池) */
|
|
5064
5844
|
pool = null;
|
|
5065
5845
|
async run(opts) {
|
|
5066
|
-
const timeout = opts.timeoutMs
|
|
5846
|
+
const timeout = opts.timeoutMs && opts.timeoutMs > 0 ? opts.timeoutMs : 0;
|
|
5067
5847
|
const mapper = new AcpEventMapper();
|
|
5068
5848
|
const pool = opts.pool ?? this.pool;
|
|
5069
5849
|
return await new Promise((resolve2) => {
|
|
@@ -5076,9 +5856,7 @@ var DimAcpExecutor = class {
|
|
|
5076
5856
|
let turnNo = 1;
|
|
5077
5857
|
const inFlightToolCalls = /* @__PURE__ */ new Map();
|
|
5078
5858
|
const inFlightTools = () => inFlightToolCalls.size;
|
|
5079
|
-
let
|
|
5080
|
-
let watchdogTimer = null;
|
|
5081
|
-
let watchdogFired = false;
|
|
5859
|
+
let steerPollTimer = null;
|
|
5082
5860
|
let resumeFailed;
|
|
5083
5861
|
const killTree = (c) => {
|
|
5084
5862
|
if (!c)
|
|
@@ -5089,10 +5867,10 @@ var DimAcpExecutor = class {
|
|
|
5089
5867
|
c.kill();
|
|
5090
5868
|
}
|
|
5091
5869
|
};
|
|
5092
|
-
const
|
|
5093
|
-
if (
|
|
5094
|
-
clearInterval(
|
|
5095
|
-
|
|
5870
|
+
const stopSteerPoll = () => {
|
|
5871
|
+
if (steerPollTimer) {
|
|
5872
|
+
clearInterval(steerPollTimer);
|
|
5873
|
+
steerPollTimer = null;
|
|
5096
5874
|
}
|
|
5097
5875
|
};
|
|
5098
5876
|
let abortHandler = () => {
|
|
@@ -5103,7 +5881,7 @@ var DimAcpExecutor = class {
|
|
|
5103
5881
|
settled = true;
|
|
5104
5882
|
if (timer)
|
|
5105
5883
|
clearTimeout(timer);
|
|
5106
|
-
|
|
5884
|
+
stopSteerPoll();
|
|
5107
5885
|
if (ownsConnection && conn) {
|
|
5108
5886
|
void conn.close().catch(() => {
|
|
5109
5887
|
});
|
|
@@ -5128,45 +5906,17 @@ var DimAcpExecutor = class {
|
|
|
5128
5906
|
finish({ ok: false, error: "\u4EFB\u52A1\u88AB\u53D6\u6D88", sessionId: sessionId || void 0 });
|
|
5129
5907
|
}
|
|
5130
5908
|
};
|
|
5131
|
-
const
|
|
5132
|
-
|
|
5133
|
-
if (idle <= 0)
|
|
5134
|
-
return;
|
|
5135
|
-
lastActivityAt = Date.now();
|
|
5136
|
-
inFlightToolCalls.clear();
|
|
5137
|
-
watchdogTimer = setInterval(() => {
|
|
5909
|
+
const startSteerPoll = () => {
|
|
5910
|
+
steerPollTimer = setInterval(() => {
|
|
5138
5911
|
maybeInterruptAtSafePoint();
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
const budget = inFlightTools() > 0 ? toolBudget : idle;
|
|
5142
|
-
if (Date.now() - lastActivityAt > budget) {
|
|
5143
|
-
watchdogFired = true;
|
|
5144
|
-
if (sessionId && conn)
|
|
5145
|
-
conn.cancel(sessionId);
|
|
5146
|
-
killTree(conn);
|
|
5147
|
-
finish({
|
|
5148
|
-
ok: false,
|
|
5149
|
-
error: `daemon: idle watchdog fired\uFF08${Math.round(budget / 1e3)}s \u65E0\u6D3B\u52A8\uFF09`,
|
|
5150
|
-
sessionId: sessionId || void 0,
|
|
5151
|
-
turns: turnNo
|
|
5152
|
-
});
|
|
5153
|
-
}
|
|
5154
|
-
}, WATCHDOG_TICK_MS);
|
|
5155
|
-
watchdogTimer.unref?.();
|
|
5156
|
-
};
|
|
5157
|
-
const markActivity = () => {
|
|
5158
|
-
lastActivityAt = Date.now();
|
|
5912
|
+
}, STEER_POLL_TICK_MS);
|
|
5913
|
+
steerPollTimer.unref?.();
|
|
5159
5914
|
};
|
|
5160
5915
|
let activeSteer = null;
|
|
5161
5916
|
let steerWaitingSince = 0;
|
|
5162
|
-
let wallClockFired = false;
|
|
5163
5917
|
let interruptedTurns = 0;
|
|
5164
5918
|
let turnKind = "initial";
|
|
5165
5919
|
const steerSafetyMs = resolveSteerSafetyMs();
|
|
5166
|
-
const taskStartedAt = Date.now();
|
|
5167
|
-
const maxTurns = Math.max(1, Math.trunc(opts.steer?.maxTurns ?? DEFAULT_MAX_TURNS));
|
|
5168
|
-
const taskWallClockMs = opts.steer?.wallClockMs ?? 0;
|
|
5169
|
-
const wallClockExceeded = () => taskWallClockMs > 0 && Date.now() - taskStartedAt > taskWallClockMs;
|
|
5170
5920
|
function maybeInterruptAtSafePoint() {
|
|
5171
5921
|
if (!opts.steer || activeSteer || !conn || !sessionId)
|
|
5172
5922
|
return;
|
|
@@ -5190,15 +5940,6 @@ var DimAcpExecutor = class {
|
|
|
5190
5940
|
`);
|
|
5191
5941
|
conn.cancel(sessionId);
|
|
5192
5942
|
}
|
|
5193
|
-
function enforceTaskWallClock() {
|
|
5194
|
-
if (wallClockFired || !wallClockExceeded())
|
|
5195
|
-
return;
|
|
5196
|
-
wallClockFired = true;
|
|
5197
|
-
process.stderr.write(`[daemon] \u4EFB\u52A1\u5899\u949F\u8D85\u9650\uFF08${Math.round(taskWallClockMs / 1e3)}s\uFF09\uFF0C\u7EC8\u6B62\u672C\u8F6E\u5E76\u8981\u6C42\u4EBA\u4ECB\u5165
|
|
5198
|
-
`);
|
|
5199
|
-
if (sessionId && conn)
|
|
5200
|
-
conn.cancel(sessionId);
|
|
5201
|
-
}
|
|
5202
5943
|
function emitAbortedTools() {
|
|
5203
5944
|
const aborted = [];
|
|
5204
5945
|
for (const [callId, toolName] of inFlightToolCalls) {
|
|
@@ -5237,8 +5978,12 @@ var DimAcpExecutor = class {
|
|
|
5237
5978
|
});
|
|
5238
5979
|
setup().then(async () => {
|
|
5239
5980
|
const armTurnTimer = () => {
|
|
5240
|
-
if (timer)
|
|
5981
|
+
if (timer) {
|
|
5241
5982
|
clearTimeout(timer);
|
|
5983
|
+
timer = void 0;
|
|
5984
|
+
}
|
|
5985
|
+
if (timeout <= 0)
|
|
5986
|
+
return;
|
|
5242
5987
|
timer = setTimeout(() => {
|
|
5243
5988
|
if (sessionId && conn)
|
|
5244
5989
|
conn.cancel(sessionId);
|
|
@@ -5254,7 +5999,7 @@ var DimAcpExecutor = class {
|
|
|
5254
5999
|
};
|
|
5255
6000
|
armTurnTimer();
|
|
5256
6001
|
opts.signal?.addEventListener("abort", abortHandler, { once: true });
|
|
5257
|
-
|
|
6002
|
+
startSteerPoll();
|
|
5258
6003
|
const bridge = new AcpPermissionBridge();
|
|
5259
6004
|
conn.setPermissionHandler(bridge.createHandler(opts.approval, opts.approvalMode === "manual"));
|
|
5260
6005
|
const sessionParams = { cwd: opts.cwd, mcpServers: [] };
|
|
@@ -5304,7 +6049,7 @@ var DimAcpExecutor = class {
|
|
|
5304
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
|
|
5305
6050
|
`);
|
|
5306
6051
|
} else {
|
|
5307
|
-
await conn.
|
|
6052
|
+
await conn.setConfigOption(sess.sessionId, "model", modelId).catch((err) => {
|
|
5308
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}
|
|
5309
6054
|
`);
|
|
5310
6055
|
});
|
|
@@ -5314,7 +6059,6 @@ var DimAcpExecutor = class {
|
|
|
5314
6059
|
});
|
|
5315
6060
|
sessionId = sess.sessionId;
|
|
5316
6061
|
pool?.touch();
|
|
5317
|
-
markActivity();
|
|
5318
6062
|
opts.onEvent?.(mapper.readyMessage(sessionId), "");
|
|
5319
6063
|
const reopenSessionAfterInterrupt = async () => {
|
|
5320
6064
|
try {
|
|
@@ -5345,7 +6089,6 @@ var DimAcpExecutor = class {
|
|
|
5345
6089
|
activeSteer = null;
|
|
5346
6090
|
opts.steer?.onTurnStarted?.({ turnNo, kind: turnKind });
|
|
5347
6091
|
const result = await conn.prompt(sessionId, promptText, (update) => {
|
|
5348
|
-
markActivity();
|
|
5349
6092
|
pool?.touch();
|
|
5350
6093
|
const { messages } = mapper.map(sessionId, update);
|
|
5351
6094
|
for (const msg of messages) {
|
|
@@ -5378,37 +6121,18 @@ var DimAcpExecutor = class {
|
|
|
5378
6121
|
}
|
|
5379
6122
|
}
|
|
5380
6123
|
maybeInterruptAtSafePoint();
|
|
5381
|
-
}, timeout + PROMPT_RPC_TIMEOUT_GRACE_MS);
|
|
6124
|
+
}, timeout > 0 ? timeout + PROMPT_RPC_TIMEOUT_GRACE_MS : 0);
|
|
5382
6125
|
if (settled)
|
|
5383
6126
|
return;
|
|
5384
6127
|
pool?.touch();
|
|
5385
|
-
|
|
5386
|
-
const usage2 = result.usage ?? mapper.latestUsage;
|
|
6128
|
+
const usage2 = result.usage ?? void 0;
|
|
5387
6129
|
const cancelled = result.stopReason === "cancelled";
|
|
5388
6130
|
const base = { sessionId, turns: turnNo, interrupted: interruptedTurns > 0 };
|
|
5389
|
-
if (wallClockFired || wallClockExceeded()) {
|
|
5390
|
-
const abortedTools = emitAbortedTools();
|
|
5391
|
-
opts.steer?.onTurnInterrupted?.({ turnNo, reason: "wall-clock", abortedTools });
|
|
5392
|
-
killTree(conn);
|
|
5393
|
-
finish({
|
|
5394
|
-
...base,
|
|
5395
|
-
ok: false,
|
|
5396
|
-
error: `\u5DF2\u8FBE\u4EFB\u52A1\u5899\u949F\u4E0A\u9650\uFF08${Math.round(taskWallClockMs / 1e3)}s\uFF09\uFF0C\u5DF2\u7EC8\u6B62\u672C\u8F6E\uFF0C\u8BF7\u4EBA\u4ECB\u5165`
|
|
5397
|
-
});
|
|
5398
|
-
return;
|
|
5399
|
-
}
|
|
5400
6131
|
if (cancelled && activeSteer) {
|
|
5401
6132
|
interruptedTurns += 1;
|
|
5402
6133
|
const abortedTools = emitAbortedTools();
|
|
5403
6134
|
const nextTurn = turnNo + 1;
|
|
5404
|
-
|
|
5405
|
-
opts.steer?.onTurnInterrupted?.({ turnNo, reason: withinBudget ? "steer" : "max-turns", abortedTools });
|
|
5406
|
-
if (!withinBudget) {
|
|
5407
|
-
process.stderr.write(`[daemon] \u5DF2\u8FBE\u6700\u5927\u56DE\u5408\u6570 ${maxTurns}\uFF0C\u5269\u4F59\u6253\u65AD\u8BF7\u6C42\u4EA4\u7531\u4E0B\u4E00\u8F6E
|
|
5408
|
-
`);
|
|
5409
|
-
finish({ ...base, ok: true, text: finalText, tokenUsage: usage2 });
|
|
5410
|
-
return;
|
|
5411
|
-
}
|
|
6135
|
+
opts.steer?.onTurnInterrupted?.({ turnNo, reason: "steer", abortedTools });
|
|
5412
6136
|
turnKind = "after-interrupt";
|
|
5413
6137
|
promptText = opts.steer.consume({ turnNo: nextTurn, sessionReset });
|
|
5414
6138
|
turnNo = nextTurn;
|
|
@@ -5431,7 +6155,7 @@ var DimAcpExecutor = class {
|
|
|
5431
6155
|
return;
|
|
5432
6156
|
}
|
|
5433
6157
|
const leftover = activeSteer ?? opts.steer?.peek() ?? null;
|
|
5434
|
-
if (leftover
|
|
6158
|
+
if (leftover) {
|
|
5435
6159
|
const nextTurn = turnNo + 1;
|
|
5436
6160
|
turnKind = "appended";
|
|
5437
6161
|
promptText = opts.steer.consume({ turnNo: nextTurn, sessionReset });
|
|
@@ -5532,7 +6256,15 @@ function createDimEventNormalizer(opts) {
|
|
|
5532
6256
|
let currentMessageId = null;
|
|
5533
6257
|
let currentText = "";
|
|
5534
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
|
+
};
|
|
5535
6266
|
const startMessage = (messageId) => {
|
|
6267
|
+
flushUserMessage();
|
|
5536
6268
|
if (currentMessageId !== null && currentText.trim().length > 0) {
|
|
5537
6269
|
opts.onEvent({ type: "assistant_message_completed", messageId: currentMessageId, text: currentText });
|
|
5538
6270
|
}
|
|
@@ -5545,7 +6277,22 @@ function createDimEventNormalizer(opts) {
|
|
|
5545
6277
|
const eventType = msg.eventType;
|
|
5546
6278
|
if (!eventType)
|
|
5547
6279
|
return;
|
|
6280
|
+
if (eventType !== "user_message:delta")
|
|
6281
|
+
flushUserMessage();
|
|
5548
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
|
+
}
|
|
5549
6296
|
case "message:started": {
|
|
5550
6297
|
const messageId = extractMessageId(msg.payload) ?? `msg-${msg.payload?.messageId ?? "run"}`;
|
|
5551
6298
|
startMessage(messageId);
|
|
@@ -5560,9 +6307,12 @@ function createDimEventNormalizer(opts) {
|
|
|
5560
6307
|
currentText = "";
|
|
5561
6308
|
break;
|
|
5562
6309
|
}
|
|
5563
|
-
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 });
|
|
5564
6314
|
break;
|
|
5565
|
-
|
|
6315
|
+
}
|
|
5566
6316
|
case "text:delta": {
|
|
5567
6317
|
const delta = msg.payload?.delta ?? "";
|
|
5568
6318
|
if (!delta)
|
|
@@ -5636,6 +6386,7 @@ function createDimEventNormalizer(opts) {
|
|
|
5636
6386
|
break;
|
|
5637
6387
|
}
|
|
5638
6388
|
case "run:ended": {
|
|
6389
|
+
flushUserMessage();
|
|
5639
6390
|
if (currentMessageId !== null && currentText.trim().length > 0) {
|
|
5640
6391
|
opts.onEvent({ type: "assistant_message_completed", messageId: currentMessageId, text: currentText });
|
|
5641
6392
|
} else if (!hasBoundarySignal && finalText.trim()) {
|
|
@@ -5673,7 +6424,11 @@ function normalizeUsage(raw) {
|
|
|
5673
6424
|
return {
|
|
5674
6425
|
inputTokens: typeof u.inputTokens === "number" ? u.inputTokens : void 0,
|
|
5675
6426
|
outputTokens: typeof u.outputTokens === "number" ? u.outputTokens : void 0,
|
|
5676
|
-
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
|
|
5677
6432
|
};
|
|
5678
6433
|
}
|
|
5679
6434
|
var DIM_CAPABILITIES = {
|
|
@@ -5728,8 +6483,8 @@ var DimAgentAdapter = class {
|
|
|
5728
6483
|
}
|
|
5729
6484
|
}
|
|
5730
6485
|
async probe() {
|
|
5731
|
-
const { existsSync:
|
|
5732
|
-
const installed =
|
|
6486
|
+
const { existsSync: existsSync18 } = await import("node:fs");
|
|
6487
|
+
const installed = existsSync18(this.dimPath) || this.dimPath === "dim";
|
|
5733
6488
|
let authStatus = installed ? "unknown" : "error";
|
|
5734
6489
|
let lastProbeErrorCode = installed ? void 0 : "DIM_CLI_NOT_FOUND";
|
|
5735
6490
|
let lastProbeError = installed ? void 0 : `\u672A\u627E\u5230 dim CLI\uFF08${this.dimPath}\uFF09`;
|
|
@@ -6021,19 +6776,19 @@ function pickLegacyDimModels(inventory) {
|
|
|
6021
6776
|
|
|
6022
6777
|
// src/daemon/repo-cache.ts
|
|
6023
6778
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
6024
|
-
import { existsSync as existsSync11, mkdirSync as
|
|
6025
|
-
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";
|
|
6026
6781
|
function ownerOf(repoFullName) {
|
|
6027
6782
|
const idx = repoFullName.indexOf("/");
|
|
6028
6783
|
return idx > 0 ? repoFullName.slice(0, idx) : "_default";
|
|
6029
6784
|
}
|
|
6030
6785
|
function buildRepoCachePath(workdir, tenantId, repoFullName) {
|
|
6031
|
-
return
|
|
6786
|
+
return join15(workdir, "repos", tenantId, ownerOf(repoFullName), repoFullName);
|
|
6032
6787
|
}
|
|
6033
6788
|
function openRepoCache(workdir, tenantId, repoFullName, env, agentRemoteUrl, daemonRemoteUrl) {
|
|
6034
6789
|
const repoCachePath = buildRepoCachePath(workdir, tenantId, repoFullName);
|
|
6035
6790
|
const currentOrigin = () => {
|
|
6036
|
-
if (!existsSync11(
|
|
6791
|
+
if (!existsSync11(join15(repoCachePath, ".git")))
|
|
6037
6792
|
return null;
|
|
6038
6793
|
try {
|
|
6039
6794
|
return execFileSync7("git", ["-C", repoCachePath, "remote", "get-url", "origin"], { encoding: "utf8", env, stdio: "pipe" }).trim() || null;
|
|
@@ -6046,11 +6801,11 @@ function openRepoCache(workdir, tenantId, repoFullName, env, agentRemoteUrl, dae
|
|
|
6046
6801
|
return {
|
|
6047
6802
|
repoCachePath,
|
|
6048
6803
|
ensure() {
|
|
6049
|
-
if (existsSync11(
|
|
6804
|
+
if (existsSync11(join15(repoCachePath, ".git"))) {
|
|
6050
6805
|
syncRemoteUrl(repoCachePath, originUrl(), env);
|
|
6051
6806
|
return;
|
|
6052
6807
|
}
|
|
6053
|
-
|
|
6808
|
+
mkdirSync9(dirname7(repoCachePath), { recursive: true });
|
|
6054
6809
|
const cloneUrl = fetchUrl();
|
|
6055
6810
|
try {
|
|
6056
6811
|
execFileSync7("git", ["clone", "--filter=blob:none", cloneUrl, repoCachePath], { env, stdio: "pipe" });
|
|
@@ -6086,24 +6841,24 @@ function gitErrorText2(err) {
|
|
|
6086
6841
|
// src/daemon/repo-checkout-service.ts
|
|
6087
6842
|
import { createServer as createServer3 } from "node:http";
|
|
6088
6843
|
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
6089
|
-
import { existsSync as existsSync14, mkdirSync as
|
|
6090
|
-
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";
|
|
6091
6846
|
|
|
6092
6847
|
// src/daemon/worktree-manager.ts
|
|
6093
6848
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
6094
|
-
import { existsSync as existsSync13, mkdirSync as
|
|
6095
|
-
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";
|
|
6096
6851
|
|
|
6097
6852
|
// src/daemon/workspace-marker.ts
|
|
6098
|
-
import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as
|
|
6099
|
-
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";
|
|
6100
6855
|
var WORKTREE_MARKER_FILE = ".morula-worktree.json";
|
|
6101
6856
|
var WORKTREE_MARKER_SCHEMA = 1;
|
|
6102
6857
|
function markerPath(worktreePath) {
|
|
6103
|
-
return
|
|
6858
|
+
return join16(worktreePath, WORKTREE_MARKER_FILE);
|
|
6104
6859
|
}
|
|
6105
6860
|
function writeWorktreeMarker(worktreePath, marker) {
|
|
6106
|
-
|
|
6861
|
+
writeFileSync7(markerPath(worktreePath), `${JSON.stringify(marker, null, 2)}
|
|
6107
6862
|
`, "utf8");
|
|
6108
6863
|
}
|
|
6109
6864
|
function readWorktreeMarker(worktreePath) {
|
|
@@ -6135,14 +6890,14 @@ function buildTaskRootPath(workdir, tenantId, runtimeId, workContextId, code) {
|
|
|
6135
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");
|
|
6136
6891
|
}
|
|
6137
6892
|
const dirName = code ? `${safeDirName(code)}-${workContextId.slice(0, 8)}` : workContextId;
|
|
6138
|
-
return
|
|
6893
|
+
return join17(workdir, "worktrees", tenantId, runtimeSegment, dirName);
|
|
6139
6894
|
}
|
|
6140
6895
|
function buildWorktreePath(workdir, tenantId, runtimeId, workContextId, code, repoFullName) {
|
|
6141
|
-
return
|
|
6896
|
+
return join17(buildTaskRootPath(workdir, tenantId, runtimeId, workContextId, code), safeDirName(repoFullName));
|
|
6142
6897
|
}
|
|
6143
6898
|
function buildLegacyWorktreePath(workdir, tenantId, workContextId, code) {
|
|
6144
6899
|
const dirName = code ? `${safeDirName(code)}-${workContextId.slice(0, 8)}` : workContextId;
|
|
6145
|
-
return
|
|
6900
|
+
return join17(workdir, "worktrees", tenantId, dirName);
|
|
6146
6901
|
}
|
|
6147
6902
|
var legacyPathWarned = /* @__PURE__ */ new Set();
|
|
6148
6903
|
function warnLegacyWorktreeResidue(legacyPath) {
|
|
@@ -6153,13 +6908,13 @@ function warnLegacyWorktreeResidue(legacyPath) {
|
|
|
6153
6908
|
}
|
|
6154
6909
|
var legacyLayoutWarned = /* @__PURE__ */ new Set();
|
|
6155
6910
|
function warnLegacyLayoutWorktreeResidue(taskRoot, worktreePath) {
|
|
6156
|
-
if (legacyLayoutWarned.has(taskRoot) || existsSync13(worktreePath) || !existsSync13(
|
|
6911
|
+
if (legacyLayoutWarned.has(taskRoot) || existsSync13(worktreePath) || !existsSync13(join17(taskRoot, WORKTREE_MARKER_FILE)))
|
|
6157
6912
|
return;
|
|
6158
6913
|
legacyLayoutWarned.add(taskRoot);
|
|
6159
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`);
|
|
6160
6915
|
}
|
|
6161
6916
|
function ensureMarkerIgnoredAndCommitted(worktreePath, env) {
|
|
6162
|
-
const gitignorePath =
|
|
6917
|
+
const gitignorePath = join17(worktreePath, ".gitignore");
|
|
6163
6918
|
const existing = existsSync13(gitignorePath) ? readFileSync9(gitignorePath, "utf8") : "";
|
|
6164
6919
|
const requiredLines = [WORKTREE_MARKER_FILE];
|
|
6165
6920
|
const missing = requiredLines.filter((l) => !existing.split("\n").some((line) => line.trim() === l));
|
|
@@ -6167,7 +6922,7 @@ function ensureMarkerIgnoredAndCommitted(worktreePath, env) {
|
|
|
6167
6922
|
const next = `${existing.trimEnd() ? `${existing.trimEnd()}
|
|
6168
6923
|
` : ""}${missing.join("\n")}
|
|
6169
6924
|
`;
|
|
6170
|
-
|
|
6925
|
+
writeFileSync8(gitignorePath, next, "utf8");
|
|
6171
6926
|
try {
|
|
6172
6927
|
execFileSync8("git", ["-C", worktreePath, "add", ".gitignore"], { env, stdio: "pipe" });
|
|
6173
6928
|
execFileSync8(
|
|
@@ -6257,7 +7012,7 @@ function isIndexLockConflict(err) {
|
|
|
6257
7012
|
return /index\.lock/i.test(text) || /另一个 git 进程|another git process/i.test(text);
|
|
6258
7013
|
}
|
|
6259
7014
|
function worktreeIndexLockPath(repoCachePath, worktreePath) {
|
|
6260
|
-
return
|
|
7015
|
+
return join17(repoCachePath, ".git", "worktrees", basename(worktreePath), "index.lock");
|
|
6261
7016
|
}
|
|
6262
7017
|
function indexLockPathFromError(text) {
|
|
6263
7018
|
const m = text.match(/[^\s'‘’"]*index\.lock/);
|
|
@@ -6311,7 +7066,7 @@ var foreignResidueHits = /* @__PURE__ */ new Map();
|
|
|
6311
7066
|
function inspectForeignDirectory(dir) {
|
|
6312
7067
|
let entries = [];
|
|
6313
7068
|
try {
|
|
6314
|
-
entries =
|
|
7069
|
+
entries = readdirSync4(dir, { withFileTypes: true });
|
|
6315
7070
|
} catch {
|
|
6316
7071
|
}
|
|
6317
7072
|
const topNames = entries.slice(0, 20).map((e) => `${e.name}${e.isDirectory() ? "/" : ""}`);
|
|
@@ -6325,7 +7080,7 @@ function inspectForeignDirectory(dir) {
|
|
|
6325
7080
|
return;
|
|
6326
7081
|
let children = [];
|
|
6327
7082
|
try {
|
|
6328
|
-
children =
|
|
7083
|
+
children = readdirSync4(p, { withFileTypes: true });
|
|
6329
7084
|
} catch {
|
|
6330
7085
|
return;
|
|
6331
7086
|
}
|
|
@@ -6333,9 +7088,9 @@ function inspectForeignDirectory(dir) {
|
|
|
6333
7088
|
if (count >= FOREIGN_RESIDUE_SCAN_LIMIT)
|
|
6334
7089
|
return;
|
|
6335
7090
|
count++;
|
|
6336
|
-
const full =
|
|
7091
|
+
const full = join17(p, e.name);
|
|
6337
7092
|
try {
|
|
6338
|
-
const st =
|
|
7093
|
+
const st = statSync3(full);
|
|
6339
7094
|
bytes += st.size;
|
|
6340
7095
|
minMs = Math.min(minMs, st.mtimeMs);
|
|
6341
7096
|
maxMs = Math.max(maxMs, st.mtimeMs);
|
|
@@ -6421,7 +7176,7 @@ function prepareWorktree(input) {
|
|
|
6421
7176
|
if (!isGitRepo) {
|
|
6422
7177
|
const forensics = inspectForeignDirectory(worktreePath);
|
|
6423
7178
|
try {
|
|
6424
|
-
|
|
7179
|
+
rmSync9(worktreePath, { recursive: true, force: true });
|
|
6425
7180
|
} catch {
|
|
6426
7181
|
reportForeignResidue(worktreePath, forensics, false);
|
|
6427
7182
|
throw new Error(`WORKTREE_RESIDUE: \u76EE\u5F55 ${worktreePath} \u6B8B\u7559\u4E14\u6E05\u7406\u5931\u8D25\uFF0C\u8BF7\u4EBA\u5DE5\u5904\u7406`);
|
|
@@ -6477,7 +7232,7 @@ function prepareWorktree(input) {
|
|
|
6477
7232
|
}
|
|
6478
7233
|
}
|
|
6479
7234
|
if (!existsSync13(worktreePath)) {
|
|
6480
|
-
|
|
7235
|
+
mkdirSync10(join17(worktreePath, ".."), { recursive: true });
|
|
6481
7236
|
addWorktreeWithRetry(cache.repoCachePath, worktreePath, `origin/${input.baseBranch}`);
|
|
6482
7237
|
writeWorktreeMarker(worktreePath, {
|
|
6483
7238
|
schemaVersion: 1,
|
|
@@ -6494,7 +7249,7 @@ function prepareWorktree(input) {
|
|
|
6494
7249
|
{
|
|
6495
7250
|
const dotMorulaRescue = `${worktreePath}.morula-rescue`;
|
|
6496
7251
|
if (existsSync13(dotMorulaRescue)) {
|
|
6497
|
-
const dotMorulaDir =
|
|
7252
|
+
const dotMorulaDir = join17(worktreePath, ".morula");
|
|
6498
7253
|
if (!existsSync13(dotMorulaDir)) {
|
|
6499
7254
|
try {
|
|
6500
7255
|
renameSync2(dotMorulaRescue, dotMorulaDir);
|
|
@@ -6503,7 +7258,7 @@ function prepareWorktree(input) {
|
|
|
6503
7258
|
console.warn(`[daemon] worktree \u6F02\u79FB\u81EA\u52A8\u6062\u590D: .morula/ \u5F52\u8FD8\u5931\u8D25\uFF0C\u5907\u4EFD\u4FDD\u7559\u5728 ${dotMorulaRescue}`);
|
|
6504
7259
|
}
|
|
6505
7260
|
} else {
|
|
6506
|
-
|
|
7261
|
+
rmSync9(dotMorulaRescue, { recursive: true, force: true });
|
|
6507
7262
|
}
|
|
6508
7263
|
}
|
|
6509
7264
|
}
|
|
@@ -6523,10 +7278,10 @@ function prepareWorktree(input) {
|
|
|
6523
7278
|
};
|
|
6524
7279
|
}
|
|
6525
7280
|
function resolveWorkspaceLayout(root, expected) {
|
|
6526
|
-
const primaryDir =
|
|
6527
|
-
if (existsSync13(
|
|
7281
|
+
const primaryDir = join17(root, safeDirName(expected.repoFullName));
|
|
7282
|
+
if (existsSync13(join17(primaryDir, WORKTREE_MARKER_FILE)))
|
|
6528
7283
|
return { primaryDir, legacy: false };
|
|
6529
|
-
if (existsSync13(
|
|
7284
|
+
if (existsSync13(join17(root, WORKTREE_MARKER_FILE)))
|
|
6530
7285
|
return { primaryDir: root, legacy: true };
|
|
6531
7286
|
return null;
|
|
6532
7287
|
}
|
|
@@ -6550,7 +7305,7 @@ function validateRemovable(taskRoot, expected, repoCachePath) {
|
|
|
6550
7305
|
).join("\n");
|
|
6551
7306
|
if (statusRelevant.length > 0)
|
|
6552
7307
|
return "WORKTREE_DIRTY";
|
|
6553
|
-
const additionalActivity = legacy ? findActivityInReposDir(
|
|
7308
|
+
const additionalActivity = legacy ? findActivityInReposDir(join17(primaryDir, ".morula", "repos")) : findAdditionalRepoActivity(taskRoot, { excludeDirs: [primaryDir] });
|
|
6554
7309
|
if (additionalActivity)
|
|
6555
7310
|
return "WORKTREE_DIRTY";
|
|
6556
7311
|
try {
|
|
@@ -6593,13 +7348,13 @@ function findActivityInReposDir(reposDir) {
|
|
|
6593
7348
|
return null;
|
|
6594
7349
|
let names = [];
|
|
6595
7350
|
try {
|
|
6596
|
-
names =
|
|
7351
|
+
names = readdirSync4(reposDir);
|
|
6597
7352
|
} catch {
|
|
6598
7353
|
return null;
|
|
6599
7354
|
}
|
|
6600
7355
|
for (const name of names) {
|
|
6601
|
-
const repoPath =
|
|
6602
|
-
if (!existsSync13(
|
|
7356
|
+
const repoPath = join17(reposDir, name);
|
|
7357
|
+
if (!existsSync13(join17(repoPath, ".git")))
|
|
6603
7358
|
continue;
|
|
6604
7359
|
if (repoHasActivity(repoPath))
|
|
6605
7360
|
return repoPath;
|
|
@@ -6610,20 +7365,20 @@ function findAdditionalRepoActivity(taskRoot, options = {}) {
|
|
|
6610
7365
|
const excluded = new Set(options.excludeDirs ?? []);
|
|
6611
7366
|
let entries = [];
|
|
6612
7367
|
try {
|
|
6613
|
-
entries =
|
|
7368
|
+
entries = readdirSync4(taskRoot, { withFileTypes: true });
|
|
6614
7369
|
} catch {
|
|
6615
7370
|
return null;
|
|
6616
7371
|
}
|
|
6617
|
-
const candidates = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) =>
|
|
6618
|
-
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");
|
|
6619
7374
|
if (existsSync13(legacyReposDir)) {
|
|
6620
7375
|
try {
|
|
6621
|
-
candidates.push(...
|
|
7376
|
+
candidates.push(...readdirSync4(legacyReposDir).map((n) => join17(legacyReposDir, n)));
|
|
6622
7377
|
} catch {
|
|
6623
7378
|
}
|
|
6624
7379
|
}
|
|
6625
7380
|
for (const repoPath of candidates) {
|
|
6626
|
-
if (!existsSync13(
|
|
7381
|
+
if (!existsSync13(join17(repoPath, ".git")))
|
|
6627
7382
|
continue;
|
|
6628
7383
|
if (repoHasActivity(repoPath))
|
|
6629
7384
|
return repoPath;
|
|
@@ -6631,18 +7386,18 @@ function findAdditionalRepoActivity(taskRoot, options = {}) {
|
|
|
6631
7386
|
return null;
|
|
6632
7387
|
}
|
|
6633
7388
|
function cleanupInjectedSkills(dir) {
|
|
6634
|
-
const injectedSkillsRoot =
|
|
7389
|
+
const injectedSkillsRoot = join17(dir, ".agents", "skills");
|
|
6635
7390
|
if (!existsSync13(injectedSkillsRoot))
|
|
6636
7391
|
return;
|
|
6637
7392
|
try {
|
|
6638
|
-
for (const name of
|
|
7393
|
+
for (const name of readdirSync4(injectedSkillsRoot)) {
|
|
6639
7394
|
if (name === COLLABORATION_SKILL_NAME || /^[\w-]{1,64}$/.test(name)) {
|
|
6640
|
-
|
|
7395
|
+
rmSync9(join17(injectedSkillsRoot, name), { recursive: true, force: true });
|
|
6641
7396
|
}
|
|
6642
7397
|
}
|
|
6643
|
-
if (
|
|
6644
|
-
|
|
6645
|
-
|
|
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 });
|
|
6646
7401
|
}
|
|
6647
7402
|
} catch {
|
|
6648
7403
|
}
|
|
@@ -6651,15 +7406,15 @@ function collectSiblingGitCommonDirs(taskRoot, excludeDir) {
|
|
|
6651
7406
|
const dirs = /* @__PURE__ */ new Set();
|
|
6652
7407
|
let entries = [];
|
|
6653
7408
|
try {
|
|
6654
|
-
entries =
|
|
7409
|
+
entries = readdirSync4(taskRoot, { withFileTypes: true });
|
|
6655
7410
|
} catch {
|
|
6656
7411
|
return [];
|
|
6657
7412
|
}
|
|
6658
7413
|
for (const e of entries) {
|
|
6659
7414
|
if (!e.isDirectory() || e.name.startsWith("."))
|
|
6660
7415
|
continue;
|
|
6661
|
-
const repoPath =
|
|
6662
|
-
if (repoPath === excludeDir || !existsSync13(
|
|
7416
|
+
const repoPath = join17(taskRoot, e.name);
|
|
7417
|
+
if (repoPath === excludeDir || !existsSync13(join17(repoPath, ".git")))
|
|
6663
7418
|
continue;
|
|
6664
7419
|
try {
|
|
6665
7420
|
const commonDir = execFileSync8("git", ["-C", repoPath, "rev-parse", "--path-format=absolute", "--git-common-dir"], { encoding: "utf8", stdio: "pipe" }).trim();
|
|
@@ -6676,13 +7431,13 @@ function removeTaskWorkspace(repoCachePath, taskRoot, expected) {
|
|
|
6676
7431
|
throw new Error("MARKER_MISMATCH");
|
|
6677
7432
|
const { primaryDir, legacy } = layout;
|
|
6678
7433
|
if (!legacy) {
|
|
6679
|
-
if (primaryDir !==
|
|
7434
|
+
if (primaryDir !== join17(taskRoot, safeDirName(expected.repoFullName)) || !isManagedWorkspaceRoot(taskRoot))
|
|
6680
7435
|
throw new Error("GIT_REMOVE_FAILED");
|
|
6681
7436
|
}
|
|
6682
7437
|
cleanupInjectedSkills(legacy ? primaryDir : taskRoot);
|
|
6683
7438
|
const siblingCommonDirs = legacy ? [] : collectSiblingGitCommonDirs(taskRoot, primaryDir);
|
|
6684
7439
|
try {
|
|
6685
|
-
|
|
7440
|
+
rmSync9(taskRoot, { recursive: true, force: true });
|
|
6686
7441
|
} catch {
|
|
6687
7442
|
throw new Error("GIT_REMOVE_FAILED");
|
|
6688
7443
|
}
|
|
@@ -6706,7 +7461,7 @@ function removeWorktree(repoCachePath, worktreePath) {
|
|
|
6706
7461
|
}
|
|
6707
7462
|
|
|
6708
7463
|
// src/daemon/repo-checkout-service.ts
|
|
6709
|
-
var CHECKOUT_JSON_FILE =
|
|
7464
|
+
var CHECKOUT_JSON_FILE = join18(".morula", "checkout.json");
|
|
6710
7465
|
var LocalRepoCheckoutService = class {
|
|
6711
7466
|
constructor(opts) {
|
|
6712
7467
|
this.opts = opts;
|
|
@@ -6794,9 +7549,9 @@ var LocalRepoCheckoutService = class {
|
|
|
6794
7549
|
return;
|
|
6795
7550
|
this.tasks.set(taskId, { workdir, checkToken, primaryRepoDir });
|
|
6796
7551
|
try {
|
|
6797
|
-
const dir =
|
|
6798
|
-
|
|
6799
|
-
|
|
7552
|
+
const dir = join18(workdir, ".morula");
|
|
7553
|
+
mkdirSync11(dir, { recursive: true, mode: 448 });
|
|
7554
|
+
writeFileSync9(join18(dir, "checkout.json"), JSON.stringify({
|
|
6800
7555
|
taskId,
|
|
6801
7556
|
port: this.boundPort,
|
|
6802
7557
|
checkToken
|
|
@@ -6960,11 +7715,11 @@ function isReadOnlyRepoDir(dirName) {
|
|
|
6960
7715
|
return READ_ONLY_DIR_RE.test(dirName) || dirName.startsWith(LEGACY_READ_ONLY_DIR_PREFIX);
|
|
6961
7716
|
}
|
|
6962
7717
|
function resolveRepoTargetDir(taskRoot, base) {
|
|
6963
|
-
const direct =
|
|
6964
|
-
if (!existsSync14(direct) || existsSync14(
|
|
7718
|
+
const direct = join18(taskRoot, base);
|
|
7719
|
+
if (!existsSync14(direct) || existsSync14(join18(direct, ".git")))
|
|
6965
7720
|
return direct;
|
|
6966
7721
|
for (let i = 2; i < 100; i++) {
|
|
6967
|
-
const candidate =
|
|
7722
|
+
const candidate = join18(taskRoot, `${base}-${i}`);
|
|
6968
7723
|
if (!existsSync14(candidate))
|
|
6969
7724
|
return candidate;
|
|
6970
7725
|
}
|
|
@@ -6973,7 +7728,7 @@ function resolveRepoTargetDir(taskRoot, base) {
|
|
|
6973
7728
|
function materializeReadWorktree(input) {
|
|
6974
7729
|
const worktreePath = resolveRepoTargetDir(input.taskRoot, `${safeRepoDirName(input.repoFullName)}${READ_ONLY_REPO_DIR_SUFFIX}`);
|
|
6975
7730
|
const targetRef = resolveReadRef(input.repoCachePath, input.env, input.ref, input.baseBranch);
|
|
6976
|
-
if (existsSync14(
|
|
7731
|
+
if (existsSync14(join18(worktreePath, ".git"))) {
|
|
6977
7732
|
const current = currentBranchName(worktreePath);
|
|
6978
7733
|
if (current)
|
|
6979
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`);
|
|
@@ -6981,7 +7736,7 @@ function materializeReadWorktree(input) {
|
|
|
6981
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");
|
|
6982
7737
|
execFileSync9("git", ["-C", worktreePath, "checkout", "--detach", targetRef], { env: input.env, stdio: "pipe" });
|
|
6983
7738
|
} else {
|
|
6984
|
-
|
|
7739
|
+
mkdirSync11(join18(worktreePath, ".."), { recursive: true });
|
|
6985
7740
|
execFileSync9("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", worktreePath, targetRef], {
|
|
6986
7741
|
env: input.env,
|
|
6987
7742
|
stdio: "pipe"
|
|
@@ -7049,11 +7804,11 @@ function listMaterializedRepoDirs(taskRoot, options = {}) {
|
|
|
7049
7804
|
const excluded = new Set(options.excludeDirs ?? []);
|
|
7050
7805
|
let entries;
|
|
7051
7806
|
try {
|
|
7052
|
-
entries =
|
|
7807
|
+
entries = readdirSync5(taskRoot, { withFileTypes: true });
|
|
7053
7808
|
} catch {
|
|
7054
7809
|
return [];
|
|
7055
7810
|
}
|
|
7056
|
-
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();
|
|
7057
7812
|
}
|
|
7058
7813
|
function parseRepoFullNameFromRemoteUrl(url) {
|
|
7059
7814
|
const raw = url?.trim();
|
|
@@ -7075,7 +7830,7 @@ function parseRepoFullNameFromRemoteUrl(url) {
|
|
|
7075
7830
|
}
|
|
7076
7831
|
function collectMaterializedRepoDeliveryTargets(input) {
|
|
7077
7832
|
const excludedNames = new Set(input.excludeRepoFullNames?.filter(Boolean) ?? []);
|
|
7078
|
-
const excludedDirs = [...excludedNames].map((name) =>
|
|
7833
|
+
const excludedDirs = [...excludedNames].map((name) => join18(input.taskRoot, safeRepoDirName(name)));
|
|
7079
7834
|
const recordsByDir = /* @__PURE__ */ new Map();
|
|
7080
7835
|
const recordsByDirName = /* @__PURE__ */ new Map();
|
|
7081
7836
|
for (const [repoFullName, record] of input.prepared ?? []) {
|
|
@@ -7138,7 +7893,7 @@ function syncGitIdentity(primaryDir, targetDir, env) {
|
|
|
7138
7893
|
}
|
|
7139
7894
|
}
|
|
7140
7895
|
function materializeAdditionalRepo(input) {
|
|
7141
|
-
if (existsSync14(
|
|
7896
|
+
if (existsSync14(join18(input.targetPath, ".git"))) {
|
|
7142
7897
|
const current = currentBranchName(input.targetPath);
|
|
7143
7898
|
if (!current && !hasLocalChanges2(input.targetPath, input.env)) {
|
|
7144
7899
|
const targetRef = resolveReadRef(input.repoCachePath, input.env, void 0, input.baseBranch);
|
|
@@ -7158,7 +7913,7 @@ function materializeAdditionalRepo(input) {
|
|
|
7158
7913
|
}
|
|
7159
7914
|
if (!baseRef)
|
|
7160
7915
|
throw new Error("BASE_REF_NOT_FOUND: repo cache \u65E0\u53EF\u7528\u8FDC\u7AEF\u5F15\u7528\uFF08origin/<base> / origin/main / origin/master\uFF09");
|
|
7161
|
-
|
|
7916
|
+
mkdirSync11(join18(input.targetPath, ".."), { recursive: true });
|
|
7162
7917
|
execFileSync9("git", ["-C", input.repoCachePath, "worktree", "add", "--detach", input.targetPath, baseRef], {
|
|
7163
7918
|
env: input.env,
|
|
7164
7919
|
stdio: "pipe"
|
|
@@ -7304,6 +8059,811 @@ function buildStageContract(stageCode, source) {
|
|
|
7304
8059
|
return STAGE_CONTRACTS[stageCode] ?? "";
|
|
7305
8060
|
}
|
|
7306
8061
|
|
|
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");
|
|
8101
|
+
}
|
|
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
|
+
|
|
7307
8867
|
// src/daemon/ws-client.ts
|
|
7308
8868
|
import { createHash as createHash2 } from "node:crypto";
|
|
7309
8869
|
import { io } from "socket.io-client";
|
|
@@ -7440,6 +9000,12 @@ var DaemonWsClient = class {
|
|
|
7440
9000
|
if (event)
|
|
7441
9001
|
handlers.onTaskMessage(event);
|
|
7442
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
|
+
}
|
|
7443
9009
|
socket.on("connect_error", (error) => {
|
|
7444
9010
|
this.onConnectError(socket, error);
|
|
7445
9011
|
});
|
|
@@ -7622,6 +9188,12 @@ var DaemonRunner = class {
|
|
|
7622
9188
|
isolatedDimEnv = {};
|
|
7623
9189
|
/** ACP 连接池(ACP-only:daemon 启动即建,懒 spawn,首个任务才起 dim acp 进程) */
|
|
7624
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;
|
|
7625
9197
|
/** LOCAL-AGENT-REPO-READ-001:本地 repo checkout 端点(agent 本轮内读仓库) */
|
|
7626
9198
|
repoCheckout = null;
|
|
7627
9199
|
async start() {
|
|
@@ -7657,6 +9229,15 @@ var DaemonRunner = class {
|
|
|
7657
9229
|
env: this.isolatedDimEnv
|
|
7658
9230
|
});
|
|
7659
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`);
|
|
7660
9241
|
this.repoCheckout = new LocalRepoCheckoutService({
|
|
7661
9242
|
api: this.api,
|
|
7662
9243
|
cfg: cfg2 ?? loadDaemonConfig(this.store),
|
|
@@ -7675,7 +9256,7 @@ var DaemonRunner = class {
|
|
|
7675
9256
|
this.log(`[daemon] dim CLI: ${this.dimPath}`);
|
|
7676
9257
|
let binaryMtime = "unknown";
|
|
7677
9258
|
try {
|
|
7678
|
-
binaryMtime =
|
|
9259
|
+
binaryMtime = statSync4(process.execPath).mtime.toISOString();
|
|
7679
9260
|
} catch (_) {
|
|
7680
9261
|
}
|
|
7681
9262
|
this.log(`[daemon] binary=${process.execPath} mtime=${binaryMtime} v=${BUILD_VERSION2}`);
|
|
@@ -7741,6 +9322,11 @@ var DaemonRunner = class {
|
|
|
7741
9322
|
});
|
|
7742
9323
|
this.acpPool = null;
|
|
7743
9324
|
}
|
|
9325
|
+
if (this.sessionHost) {
|
|
9326
|
+
void this.sessionHost.closeAll().catch(() => {
|
|
9327
|
+
});
|
|
9328
|
+
this.sessionHost = null;
|
|
9329
|
+
}
|
|
7744
9330
|
this.repoCheckout?.close();
|
|
7745
9331
|
this.repoCheckout = null;
|
|
7746
9332
|
}
|
|
@@ -8077,9 +9663,68 @@ var DaemonRunner = class {
|
|
|
8077
9663
|
// P1:重连后房间已重放 → 立即按 afterSeq 重新补齐(断连期间的帧不会补发,只能拉)
|
|
8078
9664
|
onTaskResubscribed: (taskId) => {
|
|
8079
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);
|
|
8080
9672
|
}
|
|
8081
9673
|
};
|
|
8082
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
|
+
}
|
|
8083
9728
|
/**
|
|
8084
9729
|
* 派发事件入口(P0-b,issues/agent-task-false-failure-on-daemon-fetch-error.md):
|
|
8085
9730
|
* 服务端把派发事件发到 **tenant 房间**(daemon-executor.service.ts `sendToTenant`,WS 无 per-runtime
|
|
@@ -8306,7 +9951,13 @@ var DaemonRunner = class {
|
|
|
8306
9951
|
// 本任务的 AgentTask ID:`morula repo mr create`(服务端建 MR 通道)等机器协议据此定位任务;
|
|
8307
9952
|
// agent 不需要也不应自己拼(issues/mr-created-outside-bound-project.md)
|
|
8308
9953
|
...taskId ? { MORULA_TASK_ID: taskId } : {},
|
|
8309
|
-
...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()
|
|
8310
9961
|
};
|
|
8311
9962
|
let repoDir = cfg.workdir;
|
|
8312
9963
|
let taskRoot = cfg.workdir;
|
|
@@ -8360,12 +10011,12 @@ var DaemonRunner = class {
|
|
|
8360
10011
|
});
|
|
8361
10012
|
if (spaceToken?.token)
|
|
8362
10013
|
env = { ...env, ...buildRepoEnv(spaceToken.token, spaceToken.baseUrl) };
|
|
8363
|
-
repoDir =
|
|
10014
|
+
repoDir = join20(cfg.workdir, "scratch", taskId);
|
|
8364
10015
|
taskRoot = repoDir;
|
|
8365
|
-
|
|
8366
|
-
const readme =
|
|
10016
|
+
mkdirSync13(repoDir, { recursive: true });
|
|
10017
|
+
const readme = join20(repoDir, "README.md");
|
|
8367
10018
|
if (!existsSync15(readme)) {
|
|
8368
|
-
|
|
10019
|
+
writeFileSync11(
|
|
8369
10020
|
readme,
|
|
8370
10021
|
`# \u9694\u79BB\u5DE5\u4F5C\u533A\uFF08\u4EFB\u52A1 ${taskId}\uFF09
|
|
8371
10022
|
|
|
@@ -8373,7 +10024,8 @@ var DaemonRunner = class {
|
|
|
8373
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
|
|
8374
10025
|
|
|
8375
10026
|
## \u8BFB\u53D6\u4ED3\u5E93\u5185\u5BB9\uFF08\u91CD\u8981\uFF09
|
|
8376
|
-
\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
|
|
8377
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
|
|
8378
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
|
|
8379
10031
|
`,
|
|
@@ -8549,8 +10201,6 @@ var DaemonRunner = class {
|
|
|
8549
10201
|
*/
|
|
8550
10202
|
async runOnce(taskId, detail, taskRoot, provider, providerKey, requestedModelId, env, repoBaseUrl, signal) {
|
|
8551
10203
|
const streamChunks = [];
|
|
8552
|
-
let textBuf = "";
|
|
8553
|
-
let textTimer = null;
|
|
8554
10204
|
let thoughtBuf = "";
|
|
8555
10205
|
let thoughtTimer = null;
|
|
8556
10206
|
let streamFlushing = false;
|
|
@@ -8649,6 +10299,16 @@ var DaemonRunner = class {
|
|
|
8649
10299
|
void flushStreamChunks(false);
|
|
8650
10300
|
}
|
|
8651
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}`) });
|
|
8652
10312
|
const resumeSessionId = providerKey === "dim" ? detail.dimSessionId ?? detail.lastRun?.dimSessionId ?? null : null;
|
|
8653
10313
|
const resumeWorkDir = detail.dimWorkDir ?? detail.lastRun?.dimWorkDir ?? null;
|
|
8654
10314
|
const canResume = !!resumeSessionId && !!resumeWorkDir && existsSync15(resumeWorkDir);
|
|
@@ -8695,16 +10355,19 @@ var DaemonRunner = class {
|
|
|
8695
10355
|
}
|
|
8696
10356
|
}
|
|
8697
10357
|
if (event.type === "tool_call_started") {
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
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(() => {
|
|
8706
10370
|
});
|
|
8707
|
-
void flushStreamChunks(true);
|
|
8708
10371
|
}
|
|
8709
10372
|
if (event.type === "tool_call_completed") {
|
|
8710
10373
|
streamChunks.push({
|
|
@@ -8720,17 +10383,7 @@ var DaemonRunner = class {
|
|
|
8720
10383
|
void flushStreamChunks(true);
|
|
8721
10384
|
}
|
|
8722
10385
|
if (event.type === "assistant_text_delta") {
|
|
8723
|
-
|
|
8724
|
-
if (!textTimer) {
|
|
8725
|
-
textTimer = setTimeout(() => {
|
|
8726
|
-
textTimer = null;
|
|
8727
|
-
if (textBuf) {
|
|
8728
|
-
streamChunks.push({ chunkType: "text", seqHint: nextChunkSeq(), data: { text: textBuf } });
|
|
8729
|
-
textBuf = "";
|
|
8730
|
-
void flushStreamChunks(false);
|
|
8731
|
-
}
|
|
8732
|
-
}, 200);
|
|
8733
|
-
}
|
|
10386
|
+
textStream.push(event.text);
|
|
8734
10387
|
}
|
|
8735
10388
|
if (event.type === "usage") {
|
|
8736
10389
|
streamChunks.push({ chunkType: "usage", seqHint: nextChunkSeq(), data: event.usage });
|
|
@@ -8748,14 +10401,8 @@ var DaemonRunner = class {
|
|
|
8748
10401
|
}
|
|
8749
10402
|
};
|
|
8750
10403
|
const result = await provider.run(input);
|
|
8751
|
-
|
|
8752
|
-
|
|
8753
|
-
textTimer = null;
|
|
8754
|
-
}
|
|
8755
|
-
if (textBuf) {
|
|
8756
|
-
streamChunks.push({ chunkType: "text", seqHint: nextChunkSeq(), data: { text: textBuf } });
|
|
8757
|
-
textBuf = "";
|
|
8758
|
-
}
|
|
10404
|
+
await textStream.flushParagraph();
|
|
10405
|
+
textStream.dispose();
|
|
8759
10406
|
if (thoughtTimer) {
|
|
8760
10407
|
clearTimeout(thoughtTimer);
|
|
8761
10408
|
thoughtTimer = null;
|
|
@@ -8925,7 +10572,7 @@ var DaemonRunner = class {
|
|
|
8925
10572
|
await this.api.submitCleanupResult(item.id, this.runtimeId, { ok: true });
|
|
8926
10573
|
return;
|
|
8927
10574
|
}
|
|
8928
|
-
const managedRoot =
|
|
10575
|
+
const managedRoot = join20(cfg.workdir, "worktrees", this.tenantId);
|
|
8929
10576
|
if (!wc.worktreePath.startsWith(`${managedRoot}/`)) {
|
|
8930
10577
|
await this.api.submitCleanupResult(item.id, this.runtimeId, {
|
|
8931
10578
|
ok: false,
|
|
@@ -8973,38 +10620,38 @@ function isDebugLogEnabled() {
|
|
|
8973
10620
|
var spaceTokenFailureLogged = false;
|
|
8974
10621
|
function writeCustomSkills(targetDir, skills, log) {
|
|
8975
10622
|
const NAME_RE = /^[\w-]{1,64}$/;
|
|
8976
|
-
const base =
|
|
10623
|
+
const base = join20(targetDir, ".agents", "skills");
|
|
8977
10624
|
for (const skill of skills) {
|
|
8978
10625
|
const name = skill.name.trim();
|
|
8979
10626
|
if (!NAME_RE.test(name)) {
|
|
8980
10627
|
log?.(`[daemon] \u8DF3\u8FC7\u975E\u6CD5 skill \u540D\u79F0: ${JSON.stringify(name)}`);
|
|
8981
10628
|
continue;
|
|
8982
10629
|
}
|
|
8983
|
-
const dir =
|
|
8984
|
-
if (
|
|
10630
|
+
const dir = join20(base, name);
|
|
10631
|
+
if (dirname8(dir) !== base) {
|
|
8985
10632
|
log?.(`[daemon] \u8DF3\u8FC7\u8D8A\u754C skill \u8DEF\u5F84: ${JSON.stringify(name)}`);
|
|
8986
10633
|
continue;
|
|
8987
10634
|
}
|
|
8988
|
-
|
|
10635
|
+
mkdirSync13(dir, { recursive: true });
|
|
8989
10636
|
const meta = ["---", `name: ${name}`];
|
|
8990
10637
|
if (skill.description?.trim()) {
|
|
8991
10638
|
meta.push(`description: ${skill.description.trim().replace(/\n/g, " ")}`);
|
|
8992
10639
|
}
|
|
8993
10640
|
meta.push("---", "");
|
|
8994
|
-
|
|
10641
|
+
writeFileSync11(join20(dir, "SKILL.md"), `${meta.join("\n")}
|
|
8995
10642
|
${skill.content ?? ""}`, "utf8");
|
|
8996
10643
|
}
|
|
8997
10644
|
}
|
|
8998
10645
|
function writeCollaborationSkill(targetDir, repo, repoBaseUrl, log, baseBranch, provider = repoBaseUrl ? "gitlab" : "github") {
|
|
8999
|
-
const dir =
|
|
9000
|
-
const refsDir =
|
|
9001
|
-
|
|
10646
|
+
const dir = join20(targetDir, ".agents", "skills", COLLABORATION_SKILL_NAME);
|
|
10647
|
+
const refsDir = join20(dir, "references");
|
|
10648
|
+
mkdirSync13(refsDir, { recursive: true });
|
|
9002
10649
|
const isGitlab = provider === "gitlab" && !!repoBaseUrl;
|
|
9003
10650
|
const front = isGitlab ? buildGitlabSkillFrontmatter() : COLLABORATION_SKILL_FRONTMATTER;
|
|
9004
10651
|
const body = isGitlab ? buildGitlabSkillBody(repo ?? "", repoBaseUrl, baseBranch) : buildGithubSkillBody(baseBranch);
|
|
9005
|
-
|
|
10652
|
+
writeFileSync11(join20(dir, "SKILL.md"), `${front}
|
|
9006
10653
|
${body}`, "utf8");
|
|
9007
|
-
|
|
10654
|
+
writeFileSync11(join20(refsDir, "working-on-issues-source-map.md"), COLLABORATION_SKILL_SOURCE_MAP, "utf8");
|
|
9008
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/`);
|
|
9009
10656
|
}
|
|
9010
10657
|
function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundBaseBranch) {
|
|
@@ -9035,8 +10682,8 @@ function resolveContractPlatform(availableRepos, boundRepo, boundBaseUrl, boundB
|
|
|
9035
10682
|
};
|
|
9036
10683
|
}
|
|
9037
10684
|
function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
|
|
9038
|
-
const briefDir =
|
|
9039
|
-
|
|
10685
|
+
const briefDir = join20(targetDir, ".agent_context");
|
|
10686
|
+
mkdirSync13(briefDir, { recursive: true });
|
|
9040
10687
|
const brief = `# \u4EFB\u52A1\u4E0A\u4E0B\u6587\uFF08Task Context\uFF09
|
|
9041
10688
|
|
|
9042
10689
|
- \u672C AgentTask \u4EFB\u52A1 ID\uFF1A\`${taskId}\`
|
|
@@ -9045,7 +10692,7 @@ function writeTaskContextBrief(targetDir, taskId, detail, repo, log) {
|
|
|
9045
10692
|
---
|
|
9046
10693
|
|
|
9047
10694
|
${buildTaskPromptWithContract(detail, void 0)}`;
|
|
9048
|
-
|
|
10695
|
+
writeFileSync11(join20(briefDir, "task-context.md"), brief, "utf8");
|
|
9049
10696
|
log?.(`[daemon] \u5DF2\u6CE8\u5165\u4EFB\u52A1\u4E0A\u4E0B\u6587 brief \u5230 .agent_context/task-context.md`);
|
|
9050
10697
|
}
|
|
9051
10698
|
var PROJECT_AGENTS_MD_MAX_CHARS = 1e5;
|
|
@@ -9059,8 +10706,8 @@ function writeProjectAgentsMd(targetDir, agentsMd, log) {
|
|
|
9059
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`);
|
|
9060
10707
|
content = content.slice(0, PROJECT_AGENTS_MD_MAX_CHARS);
|
|
9061
10708
|
}
|
|
9062
|
-
|
|
9063
|
-
|
|
10709
|
+
mkdirSync13(targetDir, { recursive: true });
|
|
10710
|
+
writeFileSync11(join20(targetDir, "AGENTS.md"), `${content}
|
|
9064
10711
|
`, "utf8");
|
|
9065
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`);
|
|
9066
10713
|
return true;
|
|
@@ -9080,52 +10727,20 @@ function resolveRepo(detail) {
|
|
|
9080
10727
|
}
|
|
9081
10728
|
function buildTaskPromptWithContract(detail, repoBaseUrl) {
|
|
9082
10729
|
const prompt = detail.taskPrompt ?? "";
|
|
9083
|
-
|
|
9084
|
-
const
|
|
9085
|
-
const
|
|
9086
|
-
const
|
|
9087
|
-
const multiRepoList = availableRepos.length > 1 ? availableRepos.map((r) => `- ${r.repoFullName}\uFF08\u9ED8\u8BA4\u5206\u652F\uFF1A${r.defaultBranch || "main"}\uFF09`).join("\n") : "";
|
|
9088
|
-
const multiRepoGuide = availableRepos.length > 1 ? `\u3010\u591A\u4ED3\u5E93\u4EFB\u52A1\u3011\u672C\u4EFB\u52A1\u7A7A\u95F4\u7ED1\u5B9A\u4E86\u591A\u4E2A\u4ED3\u5E93\uFF08\u6E05\u5355\u89C1\u4E0B\uFF09\uFF0C\u6D89\u53CA\u54EA\u4E9B\u4ED3\u5E93\u7531\u4F60\u7ED3\u5408\u4EFB\u52A1\u63CF\u8FF0\u5224\u65AD\u6267\u884C\uFF08\u4EFB\u52A1\u63CF\u8FF0\u300C\u6D89\u53CA\u4ED3\u5E93\u300D\u6BB5\u4E3A\u521D\u59CB\u5224\u65AD\uFF09\uFF1B\u6539\u52A8\u53D1\u751F\u5728\u54EA\u4E2A\u4ED3\u5E93\uFF0C\u5C31\u5728\u54EA\u4E2A\u4ED3\u5E93\u5185\u5EFA\u5206\u652F\u5E76\u63D0 PR/MR\uFF0C\u6BCF\u4E2A\u4ED3\u5E93\u7684 PR \u90FD\u4F1A\u88AB\u670D\u52A1\u7AEF\u81EA\u52A8\u5173\u8054\u5230\u672C\u4EFB\u52A1\u3002
|
|
9089
|
-
\u53EF\u7528\u4ED3\u5E93\u6E05\u5355\uFF1A
|
|
9090
|
-
${multiRepoList}
|
|
9091
|
-
|
|
9092
|
-
` : "";
|
|
9093
|
-
let repoHint = "";
|
|
9094
|
-
const repoRefs = Array.isArray(detail.contextSnapshot?.repoRefs) ? detail.contextSnapshot.repoRefs : [];
|
|
9095
|
-
if (repoRefs.length > 0) {
|
|
9096
|
-
const list = repoRefs.map((r) => r.repoFullName).filter(Boolean).join("\u3001");
|
|
9097
|
-
repoHint = `\u7528\u6237 @ \u6307\u5B9A\u8BFB\u53D6\u4ED3\u5E93\uFF1A${list}\uFF08\u53EA\u8BFB\u67E5\u8BC1\uFF0C\u4E0D\u5EFA\u5206\u652F\u3001\u4E0D\u6539\u4EE3\u7801\uFF1B\u7269\u5316\u65B9\u5F0F\u89C1 \`morula-working-on-issues\` skill \xA70.5\uFF09\u3002
|
|
9098
|
-
|
|
9099
|
-
`;
|
|
9100
|
-
} else if (repo) {
|
|
9101
|
-
repoHint = `${multiRepoGuide}\u53EF\u7528\u4ED3\u5E93\uFF1A${repo}\uFF08\u7A7A\u95F4\u7ED1\u5B9A\u4ED3\u5E93\uFF1B\u7269\u5316/\u5206\u652F/MR \u64CD\u4F5C\u65B9\u5F0F\u89C1 \`morula-working-on-issues\` skill \xA70.5\uFF09\u3002
|
|
9102
|
-
|
|
9103
|
-
`;
|
|
9104
|
-
} else if (repoResolution === "MULTI_REPO_AGENT_DECIDES") {
|
|
9105
|
-
repoHint = `${multiRepoGuide}\u53EF\u7528\u4ED3\u5E93\uFF1A${availableRepos.map((r) => r.repoFullName).join("\u3001")}\uFF08\u4EFB\u52A1\u7A7A\u95F4\u7ED1\u5B9A\uFF1B\u7269\u5316/\u5206\u652F/MR \u64CD\u4F5C\u65B9\u5F0F\u89C1 \`morula-working-on-issues\` skill \xA70.5\uFF09\u3002
|
|
9106
|
-
|
|
9107
|
-
`;
|
|
9108
|
-
} else if (repoResolution === "REPO_SELECTION_REQUIRED") {
|
|
9109
|
-
const snapCandidates = Array.isArray(detail.contextSnapshot?.repoCandidates) ? detail.contextSnapshot.repoCandidates : [];
|
|
9110
|
-
const candidates = snapCandidates.length > 0 ? snapCandidates : availableRepos.map((r) => r.repoFullName).filter((n) => Boolean(n));
|
|
9111
|
-
repoHint = candidates.length > 0 ? `\u7A7A\u95F4\u5173\u8054\u591A\u4E2A\u4ED3\u5E93\uFF0C\u9700\u8981\u5411\u7528\u6237\u786E\u8BA4\u8981\u64CD\u4F5C\u7684\u4ED3\u5E93\uFF08\u5019\u9009\uFF1A${candidates.join("\u3001")}\uFF09\u3002
|
|
9112
|
-
|
|
9113
|
-
` : `\u7A7A\u95F4\u5173\u8054\u591A\u4E2A\u4ED3\u5E93\uFF0C\u9700\u8981\u5411\u7528\u6237\u786E\u8BA4\u8981\u64CD\u4F5C\u7684\u4ED3\u5E93\u3002
|
|
9114
|
-
|
|
9115
|
-
`;
|
|
9116
|
-
} else if (!repo && (repoResolution === "NO_CONNECTION" || !repoResolution)) {
|
|
9117
|
-
if (availableRepos.length > 0) {
|
|
9118
|
-
const list = availableRepos.map((r) => r.repoFullName).filter(Boolean).join("\u3001");
|
|
9119
|
-
repoHint = `\u53EF\u7528\u4ED3\u5E93\uFF1A${list}\uFF08\u4EFB\u52A1\u7A7A\u95F4\u7ED1\u5B9A\uFF1B\u53EA\u8BFB\u67E5\u8BC1\u65B9\u5F0F\u89C1 \`morula-working-on-issues\` skill \xA70.5\uFF09\u3002
|
|
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
|
|
9120
10734
|
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
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
|
+
` : ""}`;
|
|
9124
10738
|
const toolsHint = `## \u5DE5\u4F5C\u533A\u4E0E\u5DE5\u5177
|
|
9125
10739
|
|
|
9126
|
-
- \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
|
|
9127
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
|
|
9128
|
-
- \u4EFB\u52A1\u8BE6\u60C5\uFF08\u4EFB\u52A1\u7F16\u53F7 / \u5173\u8054\u5BF9\u8C61 / \u4E1A\u52A1\u76EE\u6807 / \u8BC4\u8BBA / PR\uFF09\uFF1A\`morula task get\`\uFF08\u65E0\u53C2\uFF0CtaskId \u7531\u6267\u884C\u73AF\u5883\u63D0\u4F9B\uFF09\
|
|
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
|
|
9129
10744
|
|
|
9130
10745
|
`;
|
|
9131
10746
|
const taskSource = detail.contextSnapshot?.source;
|
|
@@ -9133,19 +10748,12 @@ ${multiRepoList}
|
|
|
9133
10748
|
const stageContractBlock = stageContract ? `${stageContract}
|
|
9134
10749
|
|
|
9135
10750
|
` : "";
|
|
9136
|
-
const
|
|
9137
|
-
|
|
9138
|
-
if (snapCtx?.triggerSource === "comment_mention" && snapCtx.commentId) {
|
|
9139
|
-
extraHint += `\u672C\u4EFB\u52A1\u7531\u8BC4\u8BBA\u89E6\u53D1\uFF08\u6700\u7EC8\u603B\u7ED3\u7531\u670D\u52A1\u7AEF\u81EA\u52A8\u4F5C\u4E3A\u8BE5\u89E6\u53D1\u8BC4\u8BBA\u7684\u56DE\u590D\u53D1\u51FA\uFF0C\u65E0\u9700\u81EA\u884C\u8BC4\u8BBA\uFF09\u3002
|
|
9140
|
-
|
|
9141
|
-
`;
|
|
9142
|
-
}
|
|
9143
|
-
if (snapCtx?.handoffNote && typeof snapCtx.handoffNote === "string" && snapCtx.handoffNote.trim()) {
|
|
9144
|
-
extraHint += `\u6307\u6D3E\u4EA4\u4ED8\u8981\u6C42\uFF08\u7528\u6237\u663E\u5F0F\u6307\u5B9A\uFF0C\u5FC5\u987B\u6EE1\u8DB3\uFF09\uFF1A**${snapCtx.handoffNote.trim()}**
|
|
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}**
|
|
9145
10753
|
|
|
9146
|
-
|
|
9147
|
-
}
|
|
9148
|
-
|
|
10754
|
+
` : "";
|
|
10755
|
+
const head = `${stageContractBlock}${keysHint}
|
|
10756
|
+
${toolsHint}${extraHint}`;
|
|
9149
10757
|
return head ? `${head}========== \u4F60\u7684\u76EE\u6807 ==========
|
|
9150
10758
|
${prompt}` : `${prompt}`;
|
|
9151
10759
|
}
|
|
@@ -9158,7 +10766,7 @@ async function reportRepoTargets(taskId, targets, env, ops) {
|
|
|
9158
10766
|
}
|
|
9159
10767
|
async function reportOneRepoTarget(taskId, target, env, ops) {
|
|
9160
10768
|
const execEnv = mergeExecEnv(env);
|
|
9161
|
-
if (!existsSync15(target.dir) || !existsSync15(
|
|
10769
|
+
if (!existsSync15(target.dir) || !existsSync15(join20(target.dir, ".git"))) {
|
|
9162
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`);
|
|
9163
10771
|
return;
|
|
9164
10772
|
}
|
|
@@ -9327,8 +10935,8 @@ Task ref: ${taskCode}\uFF08\u7531 morula \u81EA\u52A8\u8865\u5145\uFF0C\u7528\u4
|
|
|
9327
10935
|
// src/commands/doctor.ts
|
|
9328
10936
|
init_dist2();
|
|
9329
10937
|
import { existsSync as existsSync16, readFileSync as readFileSync10, realpathSync } from "node:fs";
|
|
9330
|
-
import { homedir as
|
|
9331
|
-
import { dirname as
|
|
10938
|
+
import { homedir as homedir12 } from "node:os";
|
|
10939
|
+
import { dirname as dirname9, join as join21 } from "node:path";
|
|
9332
10940
|
init_daemon_config();
|
|
9333
10941
|
init_daemon_lock();
|
|
9334
10942
|
init_dim_locate();
|
|
@@ -9348,10 +10956,10 @@ async function doctorCommand(context) {
|
|
|
9348
10956
|
async function collectDoctor(context, probe) {
|
|
9349
10957
|
const checks = [];
|
|
9350
10958
|
const env = context.env;
|
|
9351
|
-
const configDir = env.MORULA_CONFIG_DIR ??
|
|
9352
|
-
const configPath = env.MORULA_CONFIG ??
|
|
9353
|
-
const daemonPath =
|
|
9354
|
-
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");
|
|
9355
10963
|
const contextName = context.runtime.context ?? null;
|
|
9356
10964
|
const baseUrl = context.runtime.baseUrl ?? null;
|
|
9357
10965
|
const tokenPresent = Boolean(context.runtime.token);
|
|
@@ -9365,12 +10973,11 @@ async function collectDoctor(context, probe) {
|
|
|
9365
10973
|
configReadable = false;
|
|
9366
10974
|
}
|
|
9367
10975
|
}
|
|
9368
|
-
const lockPath =
|
|
10976
|
+
const lockPath = join21(daemonPath, DAEMON_LOCK_FILE);
|
|
9369
10977
|
const lock = probeDaemonLock(lockPath);
|
|
9370
|
-
const pidFile =
|
|
9371
|
-
const filePid =
|
|
9372
|
-
const
|
|
9373
|
-
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;
|
|
9374
10981
|
const cfg = loadDaemonConfig(context.store);
|
|
9375
10982
|
const dim = safeResolveDim();
|
|
9376
10983
|
const dimPath = dim.path;
|
|
@@ -9538,12 +11145,12 @@ function detectInstallMode() {
|
|
|
9538
11145
|
|
|
9539
11146
|
// src/commands/daemon.ts
|
|
9540
11147
|
function configFilePath(env) {
|
|
9541
|
-
return env.MORULA_CONFIG ??
|
|
11148
|
+
return env.MORULA_CONFIG ?? join22(env.MORULA_CONFIG_DIR ?? join22(homedir13(), ".morula-cli"), "config.yaml");
|
|
9542
11149
|
}
|
|
9543
11150
|
function daemonDir(configPath) {
|
|
9544
|
-
return
|
|
11151
|
+
return join22(dirname10(configPath), "daemon");
|
|
9545
11152
|
}
|
|
9546
|
-
var PID_FILE =
|
|
11153
|
+
var PID_FILE = DAEMON_PID_FILE;
|
|
9547
11154
|
var LOG_FILE = "daemon.log";
|
|
9548
11155
|
async function daemonCommand(context) {
|
|
9549
11156
|
const { action } = context.parsed;
|
|
@@ -9735,8 +11342,8 @@ async function run2(context) {
|
|
|
9735
11342
|
saveDaemonConfig(context.store, { name, workdir, maxConcurrent });
|
|
9736
11343
|
}
|
|
9737
11344
|
const resolvedWorkdir = workdir ?? loadDaemonConfig(context.store)?.workdir ?? DEFAULT_WORKDIR;
|
|
9738
|
-
|
|
9739
|
-
const lockPath =
|
|
11345
|
+
mkdirSync14(resolvedWorkdir, { recursive: true });
|
|
11346
|
+
const lockPath = join22(daemonDir(configFilePath(context.env)), DAEMON_LOCK_FILE);
|
|
9740
11347
|
const acquired = acquireDaemonLock(lockPath, { workdir: resolvedWorkdir });
|
|
9741
11348
|
if (!acquired.ok) {
|
|
9742
11349
|
const pid = acquired.existing?.pid;
|
|
@@ -9810,10 +11417,10 @@ async function start(context) {
|
|
|
9810
11417
|
}
|
|
9811
11418
|
const dir = daemonDir(configFilePath(context.env));
|
|
9812
11419
|
if (!dryRun)
|
|
9813
|
-
|
|
9814
|
-
const pidFile =
|
|
9815
|
-
const logFile =
|
|
9816
|
-
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);
|
|
9817
11424
|
const lock = probeDaemonLock(lockPath);
|
|
9818
11425
|
if (lock.state === "active") {
|
|
9819
11426
|
throw new CliError(
|
|
@@ -9832,7 +11439,7 @@ async function start(context) {
|
|
|
9832
11439
|
});
|
|
9833
11440
|
}
|
|
9834
11441
|
if (!dryRun)
|
|
9835
|
-
|
|
11442
|
+
rmSync10(pidFile, { force: true });
|
|
9836
11443
|
}
|
|
9837
11444
|
const env = {
|
|
9838
11445
|
...process.env,
|
|
@@ -9870,12 +11477,12 @@ async function start(context) {
|
|
|
9870
11477
|
const { spawnDaemonProcess: spawnDaemonProcess2 } = await Promise.resolve().then(() => (init_spawn(), spawn_exports));
|
|
9871
11478
|
const result = spawnDaemonProcess2({ logFile, env });
|
|
9872
11479
|
if (result.error || result.pid <= 0) {
|
|
9873
|
-
|
|
11480
|
+
rmSync10(pidFile, { force: true });
|
|
9874
11481
|
throw new CliError("DAEMON_START_FAILED", EXIT.OTHER, `[daemon] \u542F\u52A8\u5931\u8D25: ${result.error ?? `pid=${result.pid}`}`, false, {
|
|
9875
11482
|
hint: `tail -n 50 ${logFile}`
|
|
9876
11483
|
});
|
|
9877
11484
|
}
|
|
9878
|
-
|
|
11485
|
+
writeFileSync12(pidFile, String(result.pid), { mode: 384 });
|
|
9879
11486
|
context.err(`[daemon] \u5DF2\u540E\u53F0\u542F\u52A8\uFF08pid=${result.pid}\uFF09\uFF0C\u65E5\u5FD7: ${logFile}`);
|
|
9880
11487
|
return { running: true, pid: result.pid, logFile, lockFile: lockPath };
|
|
9881
11488
|
}
|
|
@@ -9883,8 +11490,8 @@ async function stop(context) {
|
|
|
9883
11490
|
rejectUnknownFlags(context.parsed.flags, ["dry-run"]);
|
|
9884
11491
|
const dryRun = isDryRun(context);
|
|
9885
11492
|
const dir = daemonDir(configFilePath(context.env));
|
|
9886
|
-
const pidFile =
|
|
9887
|
-
const lockPath =
|
|
11493
|
+
const pidFile = join22(dir, PID_FILE);
|
|
11494
|
+
const lockPath = join22(dir, DAEMON_LOCK_FILE);
|
|
9888
11495
|
const lock = probeDaemonLock(lockPath);
|
|
9889
11496
|
const lockPid = lock.state === "absent" ? null : lock.record?.pid ?? null;
|
|
9890
11497
|
const filePid = existsSync17(pidFile) ? Number(readFileSync11(pidFile, "utf8").trim()) : null;
|
|
@@ -9897,7 +11504,7 @@ async function stop(context) {
|
|
|
9897
11504
|
};
|
|
9898
11505
|
}
|
|
9899
11506
|
if (!pid) {
|
|
9900
|
-
|
|
11507
|
+
rmSync10(pidFile, { force: true });
|
|
9901
11508
|
releaseDaemonLock(lockPath);
|
|
9902
11509
|
return { running: false, message: "daemon \u672A\u5728\u8FD0\u884C\uFF08\u65E0\u5B58\u6D3B pid\uFF09" };
|
|
9903
11510
|
}
|
|
@@ -9906,7 +11513,7 @@ async function stop(context) {
|
|
|
9906
11513
|
releaseDaemonLock(lockPath);
|
|
9907
11514
|
else
|
|
9908
11515
|
releaseDaemonLock(lockPath, pid);
|
|
9909
|
-
|
|
11516
|
+
rmSync10(pidFile, { force: true });
|
|
9910
11517
|
if (result.outcome === "killed")
|
|
9911
11518
|
context.err(`[daemon] pid=${pid} \u672A\u54CD\u5E94 SIGTERM\uFF0C\u5DF2 SIGKILL\uFF08\u9501\u5DF2\u91CA\u653E\uFF09`);
|
|
9912
11519
|
return { running: false, stoppedPid: pid, outcome: result.outcome };
|
|
@@ -9915,21 +11522,17 @@ async function status2(context) {
|
|
|
9915
11522
|
rejectUnknownFlags(context.parsed.flags, []);
|
|
9916
11523
|
const cfg = loadDaemonConfig(context.store);
|
|
9917
11524
|
const dir = daemonDir(configFilePath(context.env));
|
|
9918
|
-
const
|
|
9919
|
-
const
|
|
9920
|
-
const lock = probeDaemonLock(lockPath);
|
|
9921
|
-
const lockPid = lock.state === "active" ? lock.record.pid : null;
|
|
9922
|
-
const filePid = existsSync17(pidFile) ? Number(readFileSync11(pidFile, "utf8").trim() || 0) : 0;
|
|
9923
|
-
const pid = lockPid ?? (isProcessAlive(filePid) ? filePid : null);
|
|
11525
|
+
const lockPath = join22(dir, DAEMON_LOCK_FILE);
|
|
11526
|
+
const probe = probeDaemonInstance(dirname10(configFilePath(context.env)));
|
|
9924
11527
|
return {
|
|
9925
|
-
running:
|
|
9926
|
-
pid,
|
|
11528
|
+
running: probe.running,
|
|
11529
|
+
pid: probe.pid,
|
|
9927
11530
|
daemonId: cfg?.daemonId ?? null,
|
|
9928
11531
|
runtimeId: cfg?.runtimeId ?? null,
|
|
9929
11532
|
name: cfg?.name ?? null,
|
|
9930
11533
|
workdir: cfg?.workdir ?? DEFAULT_WORKDIR,
|
|
9931
11534
|
maxConcurrent: cfg?.maxConcurrent ?? DEFAULT_MAX_CONCURRENT,
|
|
9932
|
-
logFile:
|
|
11535
|
+
logFile: join22(dir, LOG_FILE),
|
|
9933
11536
|
lockFile: lockPath
|
|
9934
11537
|
};
|
|
9935
11538
|
}
|
|
@@ -9957,95 +11560,6 @@ function parseMaxConcurrent(context) {
|
|
|
9957
11560
|
|
|
9958
11561
|
// src/commands/setup.ts
|
|
9959
11562
|
init_dist2();
|
|
9960
|
-
|
|
9961
|
-
// src/daemon/plugin-register.ts
|
|
9962
|
-
import { chmodSync as chmodSync3, cpSync, existsSync as existsSync18, mkdirSync as mkdirSync13, readdirSync as readdirSync5, readFileSync as readFileSync12, rmSync as rmSync9, statSync as statSync4, writeFileSync as writeFileSync11 } from "node:fs";
|
|
9963
|
-
import { homedir as homedir13 } from "node:os";
|
|
9964
|
-
import { dirname as dirname10, join as join21 } from "node:path";
|
|
9965
|
-
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
9966
|
-
var PLUGIN_NAME = "morula-cli";
|
|
9967
|
-
var MANIFEST_DIR_NAME = ".codex-plugin";
|
|
9968
|
-
function legacyAgentsPluginDir(home = homedir13()) {
|
|
9969
|
-
return join21(home, ".agents", "plugins", PLUGIN_NAME);
|
|
9970
|
-
}
|
|
9971
|
-
function resolveDimcodeHome(env = process.env, home = homedir13()) {
|
|
9972
|
-
const fromEnv = env.DIMCODE_HOME?.trim();
|
|
9973
|
-
return fromEnv || join21(home, ".dimcode", "v2");
|
|
9974
|
-
}
|
|
9975
|
-
function defaultPluginsRoot(env = process.env, home = homedir13()) {
|
|
9976
|
-
return join21(resolveDimcodeHome(env, home), "plugins");
|
|
9977
|
-
}
|
|
9978
|
-
function resolvePackagePaths(entryUrl) {
|
|
9979
|
-
const distDir = dirname10(fileURLToPath2(entryUrl));
|
|
9980
|
-
return {
|
|
9981
|
-
packageRoot: dirname10(distDir),
|
|
9982
|
-
distDir,
|
|
9983
|
-
assetsDir: join21(distDir, "plugin")
|
|
9984
|
-
};
|
|
9985
|
-
}
|
|
9986
|
-
function buildLauncherScript(opts) {
|
|
9987
|
-
const entry = join21(opts.packageRoot, "dist", "morula.js");
|
|
9988
|
-
return `#!/bin/sh
|
|
9989
|
-
# Morula \u4E1A\u52A1 CLI \u542F\u52A8\u5668\uFF08\u7531 \`morula-runtime setup\` \u751F\u6210\uFF1BNode \u5347\u7EA7\u540E\u91CD\u8DD1 setup \u5237\u65B0\uFF09\u3002
|
|
9990
|
-
# \u4E0D\u4F9D\u8D56\u8C03\u7528\u65B9 PATH \u91CC\u7684 node\uFF08agent \u6267\u884C\u73AF\u5883 PATH \u5E38\u88AB\u88C1\u526A\uFF09\uFF0C\u4EC5\u4F5C\u515C\u5E95\u3002
|
|
9991
|
-
NODE=${JSON.stringify(opts.nodePath)}
|
|
9992
|
-
[ -x "$NODE" ] || NODE="$(command -v node 2>/dev/null || true)"
|
|
9993
|
-
if [ -z "$NODE" ]; then
|
|
9994
|
-
echo '{"code":"ERROR","message":"node not found; reinstall or re-run: morula-runtime setup"}' >&2
|
|
9995
|
-
exit 127
|
|
9996
|
-
fi
|
|
9997
|
-
exec "$NODE" ${JSON.stringify(entry)} "$@"
|
|
9998
|
-
`;
|
|
9999
|
-
}
|
|
10000
|
-
function assetsReady(paths) {
|
|
10001
|
-
return existsSync18(join21(paths.assetsDir, "plugin.json")) && existsSync18(join21(paths.assetsDir, "skills"));
|
|
10002
|
-
}
|
|
10003
|
-
function listSkillNames(assetsDir) {
|
|
10004
|
-
const dir = join21(assetsDir, "skills");
|
|
10005
|
-
if (!existsSync18(dir))
|
|
10006
|
-
return [];
|
|
10007
|
-
return readdirSync5(dir).filter((name) => {
|
|
10008
|
-
try {
|
|
10009
|
-
return statSync4(join21(dir, name)).isDirectory();
|
|
10010
|
-
} catch {
|
|
10011
|
-
return false;
|
|
10012
|
-
}
|
|
10013
|
-
}).sort();
|
|
10014
|
-
}
|
|
10015
|
-
function registerPlugin(opts) {
|
|
10016
|
-
const { pluginsRoot, paths, version } = opts;
|
|
10017
|
-
const nodePath = opts.nodePath ?? process.execPath;
|
|
10018
|
-
const pluginDir = join21(pluginsRoot, PLUGIN_NAME);
|
|
10019
|
-
if (!assetsReady(paths))
|
|
10020
|
-
throw new Error(`plugin assets not found at ${paths.assetsDir}; run \`pnpm run build:dist\` (dev) or reinstall the package`);
|
|
10021
|
-
const skillsSrc = join21(paths.assetsDir, "skills");
|
|
10022
|
-
const skills = listSkillNames(paths.assetsDir);
|
|
10023
|
-
const manifestDir = join21(pluginDir, MANIFEST_DIR_NAME);
|
|
10024
|
-
mkdirSync13(manifestDir, { recursive: true });
|
|
10025
|
-
const manifest = JSON.parse(readFileSync12(join21(paths.assetsDir, "plugin.json"), "utf8"));
|
|
10026
|
-
writeFileSync11(join21(manifestDir, "plugin.json"), `${JSON.stringify({ ...manifest, version }, null, 2)}
|
|
10027
|
-
`);
|
|
10028
|
-
const skillsDest = join21(pluginDir, "skills");
|
|
10029
|
-
rmSync9(skillsDest, { recursive: true, force: true });
|
|
10030
|
-
mkdirSync13(skillsDest, { recursive: true });
|
|
10031
|
-
cpSync(skillsSrc, skillsDest, { recursive: true });
|
|
10032
|
-
const binDir = join21(pluginDir, "bin");
|
|
10033
|
-
mkdirSync13(binDir, { recursive: true });
|
|
10034
|
-
const binPath = join21(binDir, "morula");
|
|
10035
|
-
writeFileSync11(binPath, buildLauncherScript({ nodePath, packageRoot: paths.packageRoot }));
|
|
10036
|
-
chmodSync3(binPath, 493);
|
|
10037
|
-
const legacy = legacyAgentsPluginDir(opts.home);
|
|
10038
|
-
return {
|
|
10039
|
-
pluginDir,
|
|
10040
|
-
binPath,
|
|
10041
|
-
skills,
|
|
10042
|
-
version,
|
|
10043
|
-
node: nodePath,
|
|
10044
|
-
legacyDir: existsSync18(join21(legacy, MANIFEST_DIR_NAME)) ? legacy : null
|
|
10045
|
-
};
|
|
10046
|
-
}
|
|
10047
|
-
|
|
10048
|
-
// src/commands/setup.ts
|
|
10049
11563
|
var KNOWN_FLAGS = ["root", "help"];
|
|
10050
11564
|
async function setupCommand(context) {
|
|
10051
11565
|
rejectUnknownFlags(context.parsed.flags, KNOWN_FLAGS);
|