oasis_test 0.1.116 → 0.1.118
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 +992 -219
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1933,9 +1933,9 @@ function resolveNodeRef(ref2, candidates) {
|
|
|
1933
1933
|
const hit = candidates.find((n) => n.id === r);
|
|
1934
1934
|
return hit ? { ok: true, id: hit.id } : { ok: false, reason: "not_found" };
|
|
1935
1935
|
}
|
|
1936
|
-
const
|
|
1937
|
-
const type = (
|
|
1938
|
-
const title =
|
|
1936
|
+
const sep5 = r.indexOf(":");
|
|
1937
|
+
const type = (sep5 >= 0 ? r.slice(0, sep5) : r).trim().toLowerCase();
|
|
1938
|
+
const title = sep5 >= 0 ? r.slice(sep5 + 1).trim().toLowerCase() : null;
|
|
1939
1939
|
const matches = candidates.filter((n) => n.type.toLowerCase() === type && (title === null || (n.title ?? "").toLowerCase() === title));
|
|
1940
1940
|
if (matches.length === 0) return { ok: false, reason: "not_found" };
|
|
1941
1941
|
if (matches.length === 1) return { ok: true, id: matches[0].id };
|
|
@@ -2866,9 +2866,11 @@ var init_execution_continuity = __esm({
|
|
|
2866
2866
|
});
|
|
2867
2867
|
|
|
2868
2868
|
// ../contract/src/automation.ts
|
|
2869
|
+
var AUTOMATION_EVENT_NAMES;
|
|
2869
2870
|
var init_automation = __esm({
|
|
2870
2871
|
"../contract/src/automation.ts"() {
|
|
2871
2872
|
"use strict";
|
|
2873
|
+
AUTOMATION_EVENT_NAMES = ["workorder.completed"];
|
|
2872
2874
|
}
|
|
2873
2875
|
});
|
|
2874
2876
|
|
|
@@ -5726,8 +5728,8 @@ var init_dispatcher = __esm({
|
|
|
5726
5728
|
/** 从 produce jobKey 解析 artifactId:`produce::<artifactId>` 或 `produce::<artifactId>::<part>`。 */
|
|
5727
5729
|
artifactIdFromJobKey(jobKey) {
|
|
5728
5730
|
const rest = jobKey.slice(jobKey.indexOf("::") + 2);
|
|
5729
|
-
const
|
|
5730
|
-
return
|
|
5731
|
+
const sep5 = rest.indexOf("::");
|
|
5732
|
+
return sep5 >= 0 ? rest.slice(0, sep5) : rest;
|
|
5731
5733
|
}
|
|
5732
5734
|
/**
|
|
5733
5735
|
* 某节点的派发器运行时信号(stuckDiagnosis 的非 oplog 那半;proposal 协调者阻塞诊断 D1)。
|
|
@@ -7526,7 +7528,11 @@ function tryToRun(state, nodeId, ctx, events) {
|
|
|
7526
7528
|
nodeId,
|
|
7527
7529
|
workId,
|
|
7528
7530
|
reasons,
|
|
7529
|
-
|
|
7531
|
+
// ★ 基线(配套一):base=上一已验收版(latestAcceptId),parents[0]=最近一个有产出版
|
|
7532
|
+
//(latestProposedWorkId)。两者解耦:中间夹超时/零产出空壳时 base 仍在上一已验收版、parents 跳过空壳。
|
|
7533
|
+
// 不用 latestWorkId——它含空壳,会把基线钉到未验收/无产出的 work 上(isStale 比已验收 head,错)。
|
|
7534
|
+
continuesWorkId: node.latestAcceptId,
|
|
7535
|
+
parentWorkId: node.latestProposedWorkId,
|
|
7530
7536
|
// 工单 e4a43a42(接续只对短时重试成立):**已收口过的节点被重新激活 = 返工**(变更请求/驳回/上游
|
|
7531
7537
|
// 变更把已 accept 的节点重开)→ 起全新会话,别接续那场过时的旧对话;**从未收口的节点** = 首跑或
|
|
7532
7538
|
// 失败后重试,接续 WIP 是对的。判据 = 有没有 latest_accept。派发层据此决定 resumeSession。
|
|
@@ -7901,6 +7907,9 @@ CREATE TABLE IF NOT EXISTS ${schema}.works (
|
|
|
7901
7907
|
outcome_detail jsonb,
|
|
7902
7908
|
session_ref text,
|
|
7903
7909
|
continues_work_id text,
|
|
7910
|
+
-- \u2605 \u8C31\u7CFB\u4EB2\u672C\uFF08parents[0]\uFF09\uFF1Awork \u5EFA\u65F6\u7684\u300C\u6700\u8FD1\u4E00\u4E2A\u6709\u4EA7\u51FA\u7248\u300D\uFF08node.latest_proposed_work_id\uFF09\u3002
|
|
7911
|
+
-- \u4E0E continues_work_id\uFF08base=\u5DF2\u9A8C\u6536\u7248\uFF09\u89E3\u8026\u2014\u2014\u4E24\u8005\u8BED\u4E49\u4E0D\u540C\u3001\u53D6\u503C\u5E38\u5F02\uFF08\u4E2D\u95F4\u5939\u7A7A\u58F3\u65F6\u5C24\u5176\u5982\u6B64\uFF09\u3002
|
|
7912
|
+
parent_work_id text,
|
|
7904
7913
|
-- \u2605 work \u5C5E\u4E8E\u54EA\u6761\u9053\uFF08\u6B63\u5411\u3001\u663E\u5F0F\u4E8B\u5B9E\uFF09\uFF1A'main'=\u8282\u70B9\u4E3B\u94FE\u4EA7\u51FA / 'reply'=\u56DE\u4FE1\u8F6E\u3002\u53D6\u4EE3\u65E7\u7684\u5426\u5B9A\u5F0F\u5224\u636E
|
|
7905
7914
|
-- "reply_to_issue_id IS NULL"\u3002\u5B58\u91CF\u884C\u6309 reply_to_issue_id \u56DE\u586B\uFF08\u89C1\u4E0B\u65B9\u8FC1\u79FB\u6BB5\uFF09\u3002
|
|
7906
7915
|
lane text CHECK (lane IN ('main','reply')),
|
|
@@ -8078,6 +8087,10 @@ CREATE TABLE IF NOT EXISTS ${schema}.event_handler_log (
|
|
|
8078
8087
|
ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS retry_at timestamptz;
|
|
8079
8088
|
ALTER TABLE ${schema}.reviews ADD COLUMN IF NOT EXISTS retry_at timestamptz;
|
|
8080
8089
|
ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS status text;
|
|
8090
|
+
ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS parent_work_id text;
|
|
8091
|
+
-- \u5B58\u91CF\u56DE\u586B\uFF1Aparent_work_id \u7F3A\u7701\u6CBF\u7528 continues_work_id\uFF08\u65E7\u6865 parents=[continues_work_id] \u7684\u65E2\u6709\u8C31\u7CFB\uFF09\uFF0C
|
|
8092
|
+
-- \u65B0 work \u624D\u5199\u300C\u6700\u8FD1\u6709\u4EA7\u51FA\u7248\u300D\uFF08latest_proposed_work_id\uFF09\u5B9E\u73B0 base/parents \u89E3\u8026\u3002
|
|
8093
|
+
UPDATE ${schema}.works SET parent_work_id = continues_work_id WHERE parent_work_id IS NULL;
|
|
8081
8094
|
ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS agent_handoff_attempt_id text;
|
|
8082
8095
|
ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS business_handoff_status text;
|
|
8083
8096
|
ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS business_handoff_recorded_at timestamptz;
|
|
@@ -9427,7 +9440,7 @@ var require_utils2 = __commonJS({
|
|
|
9427
9440
|
var nodeCrypto = require("crypto");
|
|
9428
9441
|
module2.exports = {
|
|
9429
9442
|
postgresMd5PasswordHash,
|
|
9430
|
-
randomBytes:
|
|
9443
|
+
randomBytes: randomBytes8,
|
|
9431
9444
|
deriveKey,
|
|
9432
9445
|
sha256,
|
|
9433
9446
|
hashByName,
|
|
@@ -9437,7 +9450,7 @@ var require_utils2 = __commonJS({
|
|
|
9437
9450
|
var webCrypto = nodeCrypto.webcrypto || globalThis.crypto;
|
|
9438
9451
|
var subtleCrypto = webCrypto.subtle;
|
|
9439
9452
|
var textEncoder = new TextEncoder();
|
|
9440
|
-
function
|
|
9453
|
+
function randomBytes8(length) {
|
|
9441
9454
|
return webCrypto.getRandomValues(Buffer.alloc(length));
|
|
9442
9455
|
}
|
|
9443
9456
|
async function md5(string5) {
|
|
@@ -13569,6 +13582,7 @@ var init_store_postgres = __esm({
|
|
|
13569
13582
|
outcomeDetail: w2.outcome_detail,
|
|
13570
13583
|
sessionRef: w2.session_ref,
|
|
13571
13584
|
continuesWorkId: w2.continues_work_id,
|
|
13585
|
+
parentWorkId: w2.parent_work_id,
|
|
13572
13586
|
// ★ lane 兜底(存量行迁移前 / 迁移与回填之间的竞态窗):无值时按 reply_to_issue_id 派生,
|
|
13573
13587
|
// 保证 TS 侧 `Work.lane` 恒非空。reply_to_issue_id 在读写映射层可直接读(护栏白名单)。
|
|
13574
13588
|
lane: w2.lane ?? (w2.reply_to_issue_id == null ? "main" : "reply"),
|
|
@@ -13784,10 +13798,10 @@ var init_store_postgres = __esm({
|
|
|
13784
13798
|
await this.c.query(
|
|
13785
13799
|
`INSERT INTO ${this.s}.works
|
|
13786
13800
|
(id,workorder_id,node_id,assignee_actor_id,created_at,started_at,ended_at,dead_at,cancelled_at,retry_at,status,outcome,
|
|
13787
|
-
outcome_detail,session_ref,continues_work_id,reply_to_issue_id,output_version_no,
|
|
13801
|
+
outcome_detail,session_ref,continues_work_id,parent_work_id,reply_to_issue_id,output_version_no,
|
|
13788
13802
|
node_version,conclusion,proposal_reason,last_activity_at,frozen_issue_ids,agent_handoff_attempt_id,business_handoff_status,business_handoff_recorded_at,
|
|
13789
13803
|
acceptance_state,accepted_at,accepted_by,rejected_reason,override_by,override_reason,lane)
|
|
13790
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32)
|
|
13804
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33)
|
|
13791
13805
|
ON CONFLICT (id) DO NOTHING`,
|
|
13792
13806
|
[
|
|
13793
13807
|
w2.id,
|
|
@@ -13805,6 +13819,7 @@ var init_store_postgres = __esm({
|
|
|
13805
13819
|
w2.outcomeDetail ? JSON.stringify(w2.outcomeDetail) : null,
|
|
13806
13820
|
w2.sessionRef,
|
|
13807
13821
|
w2.continuesWorkId,
|
|
13822
|
+
w2.parentWorkId,
|
|
13808
13823
|
w2.replyToIssueId ?? null,
|
|
13809
13824
|
w2.outputVersionNo,
|
|
13810
13825
|
w2.nodeVersion,
|
|
@@ -14596,6 +14611,7 @@ var init_work = __esm({
|
|
|
14596
14611
|
outcomeDetail: null,
|
|
14597
14612
|
sessionRef: null,
|
|
14598
14613
|
continuesWorkId: e.continuesWorkId,
|
|
14614
|
+
parentWorkId: e.parentWorkId ?? null,
|
|
14599
14615
|
replyToIssueId: e.replyToIssueId,
|
|
14600
14616
|
outputVersionNo: null,
|
|
14601
14617
|
conclusion: null,
|
|
@@ -14634,6 +14650,7 @@ var init_work = __esm({
|
|
|
14634
14650
|
outcomeDetail: null,
|
|
14635
14651
|
sessionRef: null,
|
|
14636
14652
|
continuesWorkId: e.continuesWorkId,
|
|
14653
|
+
parentWorkId: e.parentWorkId ?? null,
|
|
14637
14654
|
outputVersionNo: null,
|
|
14638
14655
|
conclusion: null,
|
|
14639
14656
|
frozenIssueIds: frozen.length > 0 ? frozen : null,
|
|
@@ -15958,8 +15975,9 @@ function mapWorkStateToRevisionState(w2, artifactCount) {
|
|
|
15958
15975
|
case "success":
|
|
15959
15976
|
if (w2.acceptanceState === "accepted") return "merged";
|
|
15960
15977
|
if (w2.acceptanceState === "rejected") return "rejected";
|
|
15978
|
+
if (w2.conclusion == null) return "working";
|
|
15961
15979
|
return "proposed";
|
|
15962
|
-
//
|
|
15980
|
+
// 已 conclude、未裁决
|
|
15963
15981
|
// ★ ADR 0148:failed 尝试没有对应的 RevisionState(枚举只有 working/proposed/merged/rejected/
|
|
15964
15982
|
// superseded,改枚举会牵动契约与前端,见红线 4)——仍投影成 superseded 保持生命周期语义,但**内容
|
|
15965
15983
|
// 如实为 empty**(workToRevision 对无产出 work 给 contentKind="empty")。读面(CLI 版本列表)据此把
|
|
@@ -16037,7 +16055,9 @@ function workToRevision(w2, workInputs, artifactFiles) {
|
|
|
16037
16055
|
id: w2.id,
|
|
16038
16056
|
artifactId: w2.nodeId,
|
|
16039
16057
|
base: w2.continuesWorkId,
|
|
16040
|
-
|
|
16058
|
+
// ★ 配套一:base 与 parents 解耦——base=上一已验收版(continuesWorkId),parents[0]=最近一个有产出版
|
|
16059
|
+
//(parentWorkId)。两者可不同:中间夹超时/零产出空壳时 base 不动、parents 跳过空壳指向上一个有产出。
|
|
16060
|
+
parents: w2.parentWorkId ? [w2.parentWorkId] : [],
|
|
16041
16061
|
contentRef,
|
|
16042
16062
|
contentKind,
|
|
16043
16063
|
basedOn: Object.keys(basedOn).length > 0 ? basedOn : void 0,
|
|
@@ -16256,6 +16276,16 @@ function bridgeLoadReadModel(input) {
|
|
|
16256
16276
|
for (const w2 of allWorks) {
|
|
16257
16277
|
revisions.set(w2.id, workToRevision(w2, allWorkInputs, artifactFiles));
|
|
16258
16278
|
}
|
|
16279
|
+
for (const r of revisions.values()) {
|
|
16280
|
+
if (r.state !== "proposed") continue;
|
|
16281
|
+
for (const parentId of r.parents) {
|
|
16282
|
+
const parent = revisions.get(parentId);
|
|
16283
|
+
if (parent && parent.state === "proposed" && (parent.part ?? void 0) === (r.part ?? void 0)) {
|
|
16284
|
+
parent.state = "superseded";
|
|
16285
|
+
parent.supersededBy = r.id;
|
|
16286
|
+
}
|
|
16287
|
+
}
|
|
16288
|
+
}
|
|
16259
16289
|
const recipientsByIssue = /* @__PURE__ */ new Map();
|
|
16260
16290
|
for (const r of allIssueRecipients) {
|
|
16261
16291
|
const list = recipientsByIssue.get(r.issueId) ?? [];
|
|
@@ -17304,10 +17334,12 @@ var init_kernel_bridge = __esm({
|
|
|
17304
17334
|
nodeId: args.artifactId,
|
|
17305
17335
|
workId: revisionId,
|
|
17306
17336
|
reasons: [{ kind: "initial" }],
|
|
17307
|
-
// ★ base 三态:undefined
|
|
17308
|
-
//
|
|
17337
|
+
// ★ base 三态:undefined=缺省续写(基「上一已验收版」node.latestAcceptId——isStale 比 concludedHead,
|
|
17338
|
+
// 只有 latestAcceptId 对;旧口径 artifact.currentRev=latestWorkId 可能是一条未验收/空壳 work);null=并发
|
|
17309
17339
|
// 作者显式声明 base=∅(autoland stale 兜底:不落、留队);具体 revisionId=钉旧版。
|
|
17310
|
-
|
|
17340
|
+
// ★ parents[0]=「最近一个有产出版」node.latestProposedWorkId(跳过超时/零产出空壳),与 base 解耦。
|
|
17341
|
+
continuesWorkId: args.base !== void 0 ? args.base : nodeRow?.latestAcceptId ?? null,
|
|
17342
|
+
parentWorkId: nodeRow?.latestProposedWorkId ?? null
|
|
17311
17343
|
}
|
|
17312
17344
|
});
|
|
17313
17345
|
workId = revisionId;
|
|
@@ -40107,7 +40139,7 @@ var require_websocket = __commonJS({
|
|
|
40107
40139
|
var http2 = require("http");
|
|
40108
40140
|
var net = require("net");
|
|
40109
40141
|
var tls = require("tls");
|
|
40110
|
-
var { randomBytes:
|
|
40142
|
+
var { randomBytes: randomBytes8, createHash: createHash21 } = require("crypto");
|
|
40111
40143
|
var { Duplex, Readable } = require("stream");
|
|
40112
40144
|
var { URL: URL2 } = require("url");
|
|
40113
40145
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -40645,7 +40677,7 @@ var require_websocket = __commonJS({
|
|
|
40645
40677
|
}
|
|
40646
40678
|
}
|
|
40647
40679
|
const defaultPort = isSecure ? 443 : 80;
|
|
40648
|
-
const key =
|
|
40680
|
+
const key = randomBytes8(16).toString("base64");
|
|
40649
40681
|
const request2 = isSecure ? https.request : http2.request;
|
|
40650
40682
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
40651
40683
|
let perMessageDeflate;
|
|
@@ -40775,7 +40807,7 @@ var require_websocket = __commonJS({
|
|
|
40775
40807
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
40776
40808
|
return;
|
|
40777
40809
|
}
|
|
40778
|
-
const digest =
|
|
40810
|
+
const digest = createHash21("sha1").update(key + GUID).digest("base64");
|
|
40779
40811
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
40780
40812
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
40781
40813
|
return;
|
|
@@ -41144,7 +41176,7 @@ var require_websocket_server = __commonJS({
|
|
|
41144
41176
|
var EventEmitter = require("events");
|
|
41145
41177
|
var http2 = require("http");
|
|
41146
41178
|
var { Duplex } = require("stream");
|
|
41147
|
-
var { createHash:
|
|
41179
|
+
var { createHash: createHash21 } = require("crypto");
|
|
41148
41180
|
var extension2 = require_extension();
|
|
41149
41181
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
41150
41182
|
var subprotocol2 = require_subprotocol();
|
|
@@ -41451,7 +41483,7 @@ var require_websocket_server = __commonJS({
|
|
|
41451
41483
|
);
|
|
41452
41484
|
}
|
|
41453
41485
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
41454
|
-
const digest =
|
|
41486
|
+
const digest = createHash21("sha1").update(key + GUID).digest("base64");
|
|
41455
41487
|
const headers = [
|
|
41456
41488
|
"HTTP/1.1 101 Switching Protocols",
|
|
41457
41489
|
"Upgrade: websocket",
|
|
@@ -150077,9 +150109,9 @@ function agentEdgesFor(dependsOn, nodes, briefId, issues) {
|
|
|
150077
150109
|
edges.add(briefId);
|
|
150078
150110
|
continue;
|
|
150079
150111
|
}
|
|
150080
|
-
const
|
|
150081
|
-
const depType = (
|
|
150082
|
-
const depTitle =
|
|
150112
|
+
const sep5 = d.indexOf(":");
|
|
150113
|
+
const depType = (sep5 >= 0 ? d.slice(0, sep5) : d).trim().toLowerCase();
|
|
150114
|
+
const depTitle = sep5 >= 0 ? d.slice(sep5 + 1).trim().toLowerCase() : null;
|
|
150083
150115
|
const match = nodes.find((n) => n.type === depType && (depTitle === null || (n.title ?? "").toLowerCase() === depTitle));
|
|
150084
150116
|
if (match) edges.add(match.id);
|
|
150085
150117
|
else issues.push({ code: "planner_invalid", severity: "warning", message: `\u8282\u70B9\u4F9D\u8D56\u300C${dep}\u300D\u627E\u4E0D\u5230\u5BF9\u5E94\u4E0A\u6E38\uFF08\u6309 type:title \u5339\u914D\uFF09\uFF0C\u5DF2\u5FFD\u7565\u8FD9\u6761\u8FB9\u3002` });
|
|
@@ -153848,17 +153880,17 @@ function stageConnectorWrapper(connector) {
|
|
|
153848
153880
|
const cacheKey = `${commandName} ${scriptPath}`;
|
|
153849
153881
|
const cached3 = stagedBinDirs.get(cacheKey);
|
|
153850
153882
|
if (cached3 && (0, import_node_fs4.existsSync)((0, import_node_path4.join)(cached3, commandName))) return (0, import_node_path4.join)(cached3, commandName);
|
|
153851
|
-
const
|
|
153852
|
-
const link = (0, import_node_path4.join)(
|
|
153883
|
+
const binDir = (0, import_node_fs4.mkdtempSync)((0, import_node_path4.join)((0, import_node_os.tmpdir)(), `oasis-conn-${connector.config.slug}-`));
|
|
153884
|
+
const link = (0, import_node_path4.join)(binDir, commandName);
|
|
153853
153885
|
(0, import_node_fs4.symlinkSync)(scriptPath, link);
|
|
153854
153886
|
if (!(0, import_node_fs4.existsSync)(link)) {
|
|
153855
153887
|
try {
|
|
153856
|
-
(0, import_node_fs4.rmSync)(
|
|
153888
|
+
(0, import_node_fs4.rmSync)(binDir, { recursive: true, force: true });
|
|
153857
153889
|
} catch {
|
|
153858
153890
|
}
|
|
153859
153891
|
throw new Error(`connector ${connector.config.slug}: \u8F6F\u94FE\u5EFA\u5B8C\u4ECD\u4E0D\u53EF\u89E3\u6790\uFF08${link} \u2192 ${scriptPath}\uFF09`);
|
|
153860
153892
|
}
|
|
153861
|
-
stagedBinDirs.set(cacheKey,
|
|
153893
|
+
stagedBinDirs.set(cacheKey, binDir);
|
|
153862
153894
|
return link;
|
|
153863
153895
|
}
|
|
153864
153896
|
function isStagedWrapperUsable(link) {
|
|
@@ -158018,6 +158050,40 @@ var init_resume_classify = __esm({
|
|
|
158018
158050
|
}
|
|
158019
158051
|
});
|
|
158020
158052
|
|
|
158053
|
+
// ../adapters/src/_core/root-bypass.ts
|
|
158054
|
+
function decideRootBypass(sources) {
|
|
158055
|
+
if (sources.uid !== 0) return { inject: false, reason: "not-root" };
|
|
158056
|
+
if (sources.env["IS_SANDBOX"] === "1") return { inject: false, reason: "already-sandboxed" };
|
|
158057
|
+
if (sources.env["CLAUDE_CODE_BUBBLEWRAP"]) return { inject: false, reason: "bubblewrap" };
|
|
158058
|
+
const sw = (sources.env["OASIS_ALLOW_ROOT_BYPASS"] ?? "").trim().toLowerCase();
|
|
158059
|
+
if (OFF_VALUES.has(sw)) return { inject: false, reason: "disabled" };
|
|
158060
|
+
return { inject: true, reason: "root-needs-sandbox-flag" };
|
|
158061
|
+
}
|
|
158062
|
+
function rootBypassEnv(runtime, sources = { uid: process.getuid?.(), env: process.env }) {
|
|
158063
|
+
const d = decideRootBypass(sources);
|
|
158064
|
+
if (d.inject || d.reason === "disabled") {
|
|
158065
|
+
const key = `${runtime}:${d.reason}`;
|
|
158066
|
+
if (!loggedOnce.has(key)) {
|
|
158067
|
+
loggedOnce.add(key);
|
|
158068
|
+
console.warn(`[adapter:${runtime}] ` + JSON.stringify({
|
|
158069
|
+
event: "root-bypass",
|
|
158070
|
+
injected: d.inject,
|
|
158071
|
+
reason: d.reason,
|
|
158072
|
+
note: d.inject ? "\u4EE5 root \u8FD0\u884C\uFF1A\u6CE8\u5165 IS_SANDBOX=1 \u653E\u884C bypassPermissions\uFF08\u5173\u95ED\uFF1AOASIS_ALLOW_ROOT_BYPASS=off\uFF09" : "\u4EE5 root \u8FD0\u884C\u4E14 OASIS_ALLOW_ROOT_BYPASS=off\uFF1ACLI \u5C06\u62D2\u7EDD bypassPermissions\uFF0C\u4F1A\u8BDD\u4F1A\u7ACB\u5373\u9000\u51FA"
|
|
158073
|
+
}));
|
|
158074
|
+
}
|
|
158075
|
+
}
|
|
158076
|
+
return d.inject ? { IS_SANDBOX: "1" } : {};
|
|
158077
|
+
}
|
|
158078
|
+
var OFF_VALUES, loggedOnce;
|
|
158079
|
+
var init_root_bypass = __esm({
|
|
158080
|
+
"../adapters/src/_core/root-bypass.ts"() {
|
|
158081
|
+
"use strict";
|
|
158082
|
+
OFF_VALUES = /* @__PURE__ */ new Set(["off", "0", "false", "no"]);
|
|
158083
|
+
loggedOnce = /* @__PURE__ */ new Set();
|
|
158084
|
+
}
|
|
158085
|
+
});
|
|
158086
|
+
|
|
158021
158087
|
// ../adapters/src/_core/transcript.ts
|
|
158022
158088
|
function openTranscript(file) {
|
|
158023
158089
|
const out = fs8.createWriteStream(file);
|
|
@@ -158268,6 +158334,7 @@ var init_claude_code = __esm({
|
|
|
158268
158334
|
init_materialize_bundle();
|
|
158269
158335
|
init_container_runtime();
|
|
158270
158336
|
init_resume_classify();
|
|
158337
|
+
init_root_bypass();
|
|
158271
158338
|
init_session_paths();
|
|
158272
158339
|
init_transcript();
|
|
158273
158340
|
init_filter_extra_args();
|
|
@@ -158427,7 +158494,11 @@ var init_claude_code = __esm({
|
|
|
158427
158494
|
OASIS_SERVER: job.server.url,
|
|
158428
158495
|
OASIS_TOKEN: job.actorToken,
|
|
158429
158496
|
// OTel 遥测(默认关;resolveOtelEnv 决定开不开,runId 进 resource 属性自然归位到本次 run)。
|
|
158430
|
-
...otelEnv
|
|
158497
|
+
...otelEnv,
|
|
158498
|
+
// root 下放行 bypassPermissions(见 _core/root-bypass.ts:claude 的闸认 IS_SANDBOX=1)。
|
|
158499
|
+
// 铺在最后=系统强制层(同 OASIS_SERVER/TOKEN):员工 env 或工单 executionEnv 里的陈旧值
|
|
158500
|
+
// 不得把它顶掉。非 root / 宿主已给 / 显式关闭时是空对象,行为逐字节不变。
|
|
158501
|
+
...rootBypassEnv("claude-code")
|
|
158431
158502
|
},
|
|
158432
158503
|
stdio: ["pipe", "pipe", "pipe"]
|
|
158433
158504
|
});
|
|
@@ -161493,14 +161564,17 @@ async function materialize(job, cfg) {
|
|
|
161493
161564
|
const task = materializeBundle(dir, job.bundle);
|
|
161494
161565
|
return { id, dir, task };
|
|
161495
161566
|
}
|
|
161496
|
-
function buildEnv(job,
|
|
161567
|
+
function buildEnv(job, cfg) {
|
|
161497
161568
|
return {
|
|
161498
161569
|
...scrubLeakyEnv(process.env),
|
|
161499
|
-
...
|
|
161570
|
+
...cfg.env,
|
|
161500
161571
|
...job.env,
|
|
161501
161572
|
...job.wrapperPaths?.length ? { PATH: `${job.wrapperPaths.map((p2) => path12.dirname(p2)).join(path12.delimiter)}${path12.delimiter}${job.env?.["PATH"] ?? process.env["PATH"] ?? ""}` } : {},
|
|
161502
161573
|
OASIS_SERVER: job.server.url,
|
|
161503
|
-
OASIS_TOKEN: job.actorToken
|
|
161574
|
+
OASIS_TOKEN: job.actorToken,
|
|
161575
|
+
// root 下放行 bypassPermissions(仅 claude 家族 runtime 声明;见 _core/root-bypass.ts)。
|
|
161576
|
+
// 与 OASIS_SERVER/TOKEN 同属系统强制层:员工 env 顶不掉。
|
|
161577
|
+
...cfg.rootBypassRuntime ? rootBypassEnv(cfg.rootBypassRuntime) : {}
|
|
161504
161578
|
};
|
|
161505
161579
|
}
|
|
161506
161580
|
function makeFinish(id, cfg, transcriptRefPtr, transcriptOut, dir, workdirKey, runtimeSessionId, exitCbs) {
|
|
@@ -161559,7 +161633,7 @@ async function runStreamJson(job, cfg) {
|
|
|
161559
161633
|
stdio: ["ignore", "pipe", "pipe"]
|
|
161560
161634
|
}) : (0, import_node_child_process11.spawn)(cfg.bin, args, {
|
|
161561
161635
|
cwd: dir,
|
|
161562
|
-
env: buildEnv(job, cfg
|
|
161636
|
+
env: buildEnv(job, cfg),
|
|
161563
161637
|
stdio: ["ignore", "pipe", "pipe"]
|
|
161564
161638
|
});
|
|
161565
161639
|
if (child.pid !== void 0 && job.workdirKey) lockWorkdir(dir, child.pid, "session", job.workdirKey);
|
|
@@ -161659,7 +161733,7 @@ async function runOneShotText(job, cfg) {
|
|
|
161659
161733
|
stdio: ["ignore", "pipe", "pipe"]
|
|
161660
161734
|
}) : (0, import_node_child_process11.spawn)(cfg.bin, args, {
|
|
161661
161735
|
cwd: dir,
|
|
161662
|
-
env: buildEnv(job, cfg
|
|
161736
|
+
env: buildEnv(job, cfg),
|
|
161663
161737
|
stdio: ["ignore", "pipe", "pipe"]
|
|
161664
161738
|
});
|
|
161665
161739
|
if (child.pid !== void 0 && job.workdirKey) lockWorkdir(dir, child.pid, "session", job.workdirKey);
|
|
@@ -161743,6 +161817,7 @@ var init_subprocess = __esm({
|
|
|
161743
161817
|
init_session_paths();
|
|
161744
161818
|
init_transcript();
|
|
161745
161819
|
init_filter_extra_args();
|
|
161820
|
+
init_root_bypass();
|
|
161746
161821
|
}
|
|
161747
161822
|
});
|
|
161748
161823
|
|
|
@@ -161859,7 +161934,10 @@ var init_codebuddy = __esm({
|
|
|
161859
161934
|
workRoot: opts.workRoot,
|
|
161860
161935
|
cleanupWorkdir: opts.cleanupWorkdir,
|
|
161861
161936
|
env: opts.env,
|
|
161862
|
-
extraArgs: opts.extraArgs
|
|
161937
|
+
extraArgs: opts.extraArgs,
|
|
161938
|
+
// CodeBuddy 是 Claude Code 分叉,走同一个 bypassPermissions flag,root 下大概率撞同一条闸
|
|
161939
|
+
// (claude 侧已实测)。声明后由 _core/root-bypass.ts 在 root 下注入 IS_SANDBOX=1。
|
|
161940
|
+
rootBypassRuntime: "codebuddy"
|
|
161863
161941
|
});
|
|
161864
161942
|
}
|
|
161865
161943
|
};
|
|
@@ -166463,10 +166541,10 @@ function createTokenIssuer(opts = {}) {
|
|
|
166463
166541
|
if (revoked.has(token)) return null;
|
|
166464
166542
|
if (!token.startsWith(SESSION_TOKEN_PREFIX)) return null;
|
|
166465
166543
|
const encoded = token.slice(SESSION_TOKEN_PREFIX.length);
|
|
166466
|
-
const
|
|
166467
|
-
if (
|
|
166468
|
-
const payload = encoded.slice(0,
|
|
166469
|
-
const actualSig = encoded.slice(
|
|
166544
|
+
const sep5 = encoded.indexOf(".");
|
|
166545
|
+
if (sep5 <= 0) return null;
|
|
166546
|
+
const payload = encoded.slice(0, sep5);
|
|
166547
|
+
const actualSig = encoded.slice(sep5 + 1);
|
|
166470
166548
|
const expectedSig = sign(secret, payload);
|
|
166471
166549
|
if (!signatureMatches(actualSig, expectedSig)) return null;
|
|
166472
166550
|
let claims;
|
|
@@ -168058,6 +168136,10 @@ var init_memory_automation_store = __esm({
|
|
|
168058
168136
|
hasSigningSecret: false
|
|
168059
168137
|
};
|
|
168060
168138
|
}
|
|
168139
|
+
if (input.kind === "event" && input.event) {
|
|
168140
|
+
t.enabled = false;
|
|
168141
|
+
t.event = { source: "project", projectId: input.event.projectId, events: [...input.event.events], baselineState: "aligning" };
|
|
168142
|
+
}
|
|
168061
168143
|
return t;
|
|
168062
168144
|
}
|
|
168063
168145
|
async replaceTriggers(automationId, triggers) {
|
|
@@ -168085,6 +168167,7 @@ var init_memory_automation_store = __esm({
|
|
|
168085
168167
|
if (t.webhook && patch.webhook && "eventFilters" in patch.webhook) {
|
|
168086
168168
|
t.webhook.eventFilters = patch.webhook.eventFilters ?? null;
|
|
168087
168169
|
}
|
|
168170
|
+
if (t.event && patch.event?.events) t.event.events = [...patch.event.events];
|
|
168088
168171
|
return structuredClone(t);
|
|
168089
168172
|
}
|
|
168090
168173
|
async deleteTrigger(triggerId) {
|
|
@@ -168113,6 +168196,33 @@ var init_memory_automation_store = __esm({
|
|
|
168113
168196
|
if (t) t.publishedBy = publishedBy;
|
|
168114
168197
|
}
|
|
168115
168198
|
}
|
|
168199
|
+
/* ---------- event 触发器(ADR 0163) ---------- */
|
|
168200
|
+
async listEventTriggers() {
|
|
168201
|
+
const out = [];
|
|
168202
|
+
for (const t of this.triggers.values()) {
|
|
168203
|
+
if (t.kind !== "event") continue;
|
|
168204
|
+
const a = this.automations.get(t.automationId);
|
|
168205
|
+
if (!a || !a.enabled || a.archivedAt) continue;
|
|
168206
|
+
out.push(structuredClone(t));
|
|
168207
|
+
}
|
|
168208
|
+
return out.sort((x2, y) => x2.id < y.id ? -1 : x2.id > y.id ? 1 : 0);
|
|
168209
|
+
}
|
|
168210
|
+
async completeEventBaseline(triggerId, _at) {
|
|
168211
|
+
const t = this.triggers.get(triggerId);
|
|
168212
|
+
if (!t?.event) return;
|
|
168213
|
+
t.event.baselineState = "ready";
|
|
168214
|
+
t.enabled = true;
|
|
168215
|
+
}
|
|
168216
|
+
async resetEventBaseline(triggerId) {
|
|
168217
|
+
const t = this.triggers.get(triggerId);
|
|
168218
|
+
if (!t?.event) return;
|
|
168219
|
+
t.event.baselineState = "aligning";
|
|
168220
|
+
t.enabled = false;
|
|
168221
|
+
}
|
|
168222
|
+
async findRunBySpawnedWorkspace(workspace) {
|
|
168223
|
+
const hits = [...this.runs.values()].filter((r) => r.spawnedWorkspaceId === workspace).sort((a, b2) => a.triggeredAt < b2.triggeredAt ? -1 : a.triggeredAt > b2.triggeredAt ? 1 : a.id < b2.id ? -1 : 1);
|
|
168224
|
+
return hits[0] ? structuredClone(hits[0]) : null;
|
|
168225
|
+
}
|
|
168116
168226
|
/* ---------- runs ---------- */
|
|
168117
168227
|
async createRun(run) {
|
|
168118
168228
|
if (run.triggerId && run.plannedAt) {
|
|
@@ -183313,7 +183423,11 @@ var init_readonly_bundle = __esm({
|
|
|
183313
183423
|
function makeSeededWorkorderCreator(deps) {
|
|
183314
183424
|
const genWorkspace = deps.genWorkspace ?? (() => `ws:wo-${(0, import_node_crypto33.randomUUID)().slice(0, 8)}`);
|
|
183315
183425
|
return async (input) => {
|
|
183316
|
-
const workspace = genWorkspace();
|
|
183426
|
+
const workspace = input.idempotencyKey ? workspaceForKey(input.idempotencyKey) : genWorkspace();
|
|
183427
|
+
if (input.idempotencyKey) {
|
|
183428
|
+
const existing = existingWorkorder(deps.kernel, workspace);
|
|
183429
|
+
if (existing) return existing;
|
|
183430
|
+
}
|
|
183317
183431
|
if (deps.artifactState && (input.projectId || deps.createProject)) {
|
|
183318
183432
|
await ensureWorkorderProject({
|
|
183319
183433
|
workOrderId: workspace,
|
|
@@ -183392,6 +183506,20 @@ function makeSeededWorkorderCreator(deps) {
|
|
|
183392
183506
|
return { workspace, rootArtifactId: briefId, spawned: planned.spawned };
|
|
183393
183507
|
};
|
|
183394
183508
|
}
|
|
183509
|
+
function workspaceForKey(key) {
|
|
183510
|
+
return `ws:wo-${(0, import_node_crypto33.createHash)("sha256").update(key).digest("hex").slice(0, 8)}`;
|
|
183511
|
+
}
|
|
183512
|
+
function existingWorkorder(kernel, workspace) {
|
|
183513
|
+
const arts = [...kernel.model.artifacts.values()].filter((a) => a.workspace === workspace);
|
|
183514
|
+
if (arts.length === 0) return null;
|
|
183515
|
+
const seed = arts.find((a) => a.inputs.length === 0) ?? arts[0];
|
|
183516
|
+
return {
|
|
183517
|
+
workspace,
|
|
183518
|
+
rootArtifactId: seed.id,
|
|
183519
|
+
// role 取自 planner 的规划结果、不是 artifact 上的字段——重试路径拿不到也无所谓(可省)。
|
|
183520
|
+
spawned: arts.map((a) => ({ id: a.id, type: a.type, owner: a.owner }))
|
|
183521
|
+
};
|
|
183522
|
+
}
|
|
183395
183523
|
var import_node_crypto33, enc2;
|
|
183396
183524
|
var init_create_seeded_workorder = __esm({
|
|
183397
183525
|
"../server/src/domains/collab/create-seeded-workorder.ts"() {
|
|
@@ -186076,6 +186204,7 @@ function automationSubstantiveChange(before, after) {
|
|
|
186076
186204
|
}
|
|
186077
186205
|
function triggerSubstantiveChange(before, after) {
|
|
186078
186206
|
if (before.enabled !== after.enabled) return true;
|
|
186207
|
+
if (!jsonEq(before.event?.events ?? null, after.event?.events ?? null)) return true;
|
|
186079
186208
|
if ((before.schedule?.cron ?? null) !== (after.schedule?.cron ?? null)) return true;
|
|
186080
186209
|
if ((before.schedule?.tz ?? null) !== (after.schedule?.tz ?? null)) return true;
|
|
186081
186210
|
const filtersOf = (t) => t.webhook?.eventFilters ?? null;
|
|
@@ -186927,6 +187056,17 @@ function renderDate(at, tz) {
|
|
|
186927
187056
|
return at.toISOString().slice(0, 10);
|
|
186928
187057
|
}
|
|
186929
187058
|
}
|
|
187059
|
+
function eventCommandKey(runId) {
|
|
187060
|
+
return `automation-event:${runId}`;
|
|
187061
|
+
}
|
|
187062
|
+
function eventDispatchState(run) {
|
|
187063
|
+
const r = run.result;
|
|
187064
|
+
if (r && typeof r === "object" && !Array.isArray(r)) {
|
|
187065
|
+
const d = r["eventDispatch"];
|
|
187066
|
+
if (d && typeof d === "object" && !Array.isArray(d)) return d;
|
|
187067
|
+
}
|
|
187068
|
+
return {};
|
|
187069
|
+
}
|
|
186930
187070
|
function clipReply(reply) {
|
|
186931
187071
|
const t = reply.trim();
|
|
186932
187072
|
return t.length > CHAT_REPLY_MAX ? `${t.slice(0, CHAT_REPLY_MAX)}
|
|
@@ -186958,7 +187098,7 @@ function appendSourceNote(brief, automation, source, atIso) {
|
|
|
186958
187098
|
---
|
|
186959
187099
|
\u672C\u5DE5\u5355\u7531\u81EA\u52A8\u5316\u300C${automation.name}\u300D\u4E8E ${atIso} \u89E6\u53D1\uFF08\u6765\u6E90\uFF1A${source}\uFF09\u3002\u5F00\u5DE5\u540E\u8BF7\u628A\u5DE5\u5355\u6807\u9898\u6539\u6210\u51C6\u786E\u53CD\u6620\u5B9E\u9645\u5DE5\u4F5C\u7684\u63CF\u8FF0\u3002`;
|
|
186960
187100
|
}
|
|
186961
|
-
var import_node_crypto36, DATE_TOKEN, AutomationsService, CHAT_REPLY_MAX;
|
|
187101
|
+
var import_node_crypto36, DATE_TOKEN, AutomationsService, EVENT_RETRY_BASE_MS, EVENT_RETRY_MAX_MS, EVENT_RETRY_MAX_ATTEMPTS, eventAttempts, eventLastAttemptAt, eventLastError, CHAT_REPLY_MAX;
|
|
186962
187102
|
var init_service8 = __esm({
|
|
186963
187103
|
"../server/src/domains/automations/service.ts"() {
|
|
186964
187104
|
"use strict";
|
|
@@ -187051,7 +187191,7 @@ var init_service8 = __esm({
|
|
|
187051
187191
|
await seedScheduleNextRuns(this.store, [await this.store.getTrigger(t.id) ?? t].filter(Boolean), this.now());
|
|
187052
187192
|
return await this.store.getTrigger(t.id);
|
|
187053
187193
|
}
|
|
187054
|
-
/** 单条触发器 PATCH。归因:比较持久化前后行,实质变更(enabled/cron/tz/eventFilters)只转移**该行**。 */
|
|
187194
|
+
/** 单条触发器 PATCH。归因:比较持久化前后行,实质变更(enabled/cron/tz/eventFilters/events)只转移**该行**。 */
|
|
187055
187195
|
async updateTrigger(triggerId, patch, editor) {
|
|
187056
187196
|
const before = await this.store.getTrigger(triggerId);
|
|
187057
187197
|
if (!before) throw new Error(`trigger not found: ${triggerId}`);
|
|
@@ -187062,6 +187202,9 @@ var init_service8 = __esm({
|
|
|
187062
187202
|
if (after.kind === "schedule") {
|
|
187063
187203
|
await seedScheduleNextRuns(this.store, [after], this.now());
|
|
187064
187204
|
}
|
|
187205
|
+
if (after.kind === "event" && patch.enabled === true && !before.enabled) {
|
|
187206
|
+
await this.store.resetEventBaseline(triggerId);
|
|
187207
|
+
}
|
|
187065
187208
|
return await this.store.getTrigger(triggerId);
|
|
187066
187209
|
}
|
|
187067
187210
|
async deleteTrigger(triggerId, editor) {
|
|
@@ -187141,7 +187284,17 @@ var init_service8 = __esm({
|
|
|
187141
187284
|
};
|
|
187142
187285
|
const { run, created } = await this.store.createRun(base);
|
|
187143
187286
|
if (!created) return run;
|
|
187144
|
-
|
|
187287
|
+
return this.dispatchRun(automation, trigger, run, source, opts);
|
|
187288
|
+
}
|
|
187289
|
+
/**
|
|
187290
|
+
* 对一条**已占位的 pending run** 做准入 + 派发。
|
|
187291
|
+
*
|
|
187292
|
+
* 与 `fire()` 拆开是因为 event 触发必须「先原子占位、再决定派不派」(ADR 0163 §4.2 第 4/5 步):
|
|
187293
|
+
* 占位赢家才做权限/自环准入,输的那个直接停手。reconciler 重试也复用这里——同一 runId、
|
|
187294
|
+
* 同一下游命令键,取回同一副作用。
|
|
187295
|
+
*/
|
|
187296
|
+
async dispatchRun(automation, trigger, run, source, opts = {}) {
|
|
187297
|
+
const skip = await this.admissionSkip(automation, source);
|
|
187145
187298
|
if (skip) return this.settleSkip(run, skip.reasonCode, skip.error);
|
|
187146
187299
|
if (automation.executionMode === "chat") {
|
|
187147
187300
|
return this.fireChat(automation, trigger, run, opts);
|
|
@@ -187161,6 +187314,14 @@ var init_service8 = __esm({
|
|
|
187161
187314
|
return settled ?? run;
|
|
187162
187315
|
} catch (e) {
|
|
187163
187316
|
const msg = e instanceof Error ? e.message : String(e);
|
|
187317
|
+
if (source === "event") {
|
|
187318
|
+
await this.store.settleRun(run.id, {
|
|
187319
|
+
status: "pending",
|
|
187320
|
+
result: { eventDispatch: { attempts: eventAttempts(run) + 1, lastAttemptAt: this.now().toISOString(), lastError: msg } },
|
|
187321
|
+
completedAt: null
|
|
187322
|
+
});
|
|
187323
|
+
return await this.store.getRun(run.id);
|
|
187324
|
+
}
|
|
187164
187325
|
await this.store.settleRun(run.id, {
|
|
187165
187326
|
status: "failed",
|
|
187166
187327
|
error: msg,
|
|
@@ -187194,12 +187355,93 @@ var init_service8 = __esm({
|
|
|
187194
187355
|
});
|
|
187195
187356
|
return run;
|
|
187196
187357
|
}
|
|
187197
|
-
|
|
187358
|
+
/* ---------- 订阅触发(event,ADR 0163) ---------- */
|
|
187359
|
+
/**
|
|
187360
|
+
* 能力位:这条自动化现在能不能用订阅触发。**唯一真值在这里**——详情响应把它原样带给前端,
|
|
187361
|
+
* 前端只消费不推断(否则前后端两套规则必漂移)。
|
|
187362
|
+
*/
|
|
187363
|
+
eventTriggerCapability(automation) {
|
|
187364
|
+
if (automation.executionMode === "chat" && !this.deps.chatSupportsIdempotency) {
|
|
187365
|
+
return { supported: false, reason: "chat_no_idempotency" };
|
|
187366
|
+
}
|
|
187367
|
+
return { supported: true };
|
|
187368
|
+
}
|
|
187369
|
+
/**
|
|
187370
|
+
* 事件消费键占位:用同一把键原子落一条 run。
|
|
187371
|
+
* 唯一约束 `(automation_id, idempotency_key)` 负责多实例竞争——`created=false` 的那个实例停手。
|
|
187372
|
+
*/
|
|
187373
|
+
claimEventRun(automation, trigger, key, envelope, status = "pending", reasonCode = null, error2 = null) {
|
|
187374
|
+
const nowIso = this.now().toISOString();
|
|
187375
|
+
return this.store.createRun({
|
|
187376
|
+
id: this.genId("arun"),
|
|
187377
|
+
automationId: automation.id,
|
|
187378
|
+
triggerId: trigger.id,
|
|
187379
|
+
source: "event",
|
|
187380
|
+
status,
|
|
187381
|
+
spawnedWorkspaceId: null,
|
|
187382
|
+
spawnedArtifactId: null,
|
|
187383
|
+
plannedAt: null,
|
|
187384
|
+
deliveryId: null,
|
|
187385
|
+
idempotencyKey: key,
|
|
187386
|
+
ruleVersionId: null,
|
|
187387
|
+
payload: envelope,
|
|
187388
|
+
result: null,
|
|
187389
|
+
error: error2,
|
|
187390
|
+
reasonCode,
|
|
187391
|
+
triggeredAt: nowIso,
|
|
187392
|
+
completedAt: status === "skipped" ? nowIso : null
|
|
187393
|
+
});
|
|
187394
|
+
}
|
|
187395
|
+
/** 把一条占位成功的 event run 派出去(准入 → 建单/开会话),命令键固定为 automation-event:<runId>。 */
|
|
187396
|
+
dispatchEventRun(automation, trigger, run) {
|
|
187397
|
+
return this.dispatchRun(automation, trigger, run, "event", {
|
|
187398
|
+
...run.payload != null ? { envelope: run.payload } : {},
|
|
187399
|
+
commandKey: eventCommandKey(run.id)
|
|
187400
|
+
});
|
|
187401
|
+
}
|
|
187402
|
+
/** 已占位但被准入拒绝:落 skipped + reasonCode(**留痕**,不是静默丢弃)。 */
|
|
187403
|
+
skipEventRun(run, reasonCode, error2) {
|
|
187404
|
+
return this.settleSkip(run, reasonCode, error2);
|
|
187405
|
+
}
|
|
187406
|
+
/**
|
|
187407
|
+
* event pending 的可恢复派发(ADR 0163 §4.4)。
|
|
187408
|
+
*
|
|
187409
|
+
* **不走**「pending 超时即 failed」那条分支:那条规则会把「占位了但还没产生副作用」的 run 直接
|
|
187410
|
+
* 判死,等于永久漏一次触发。这里按稳定 runId 重试同一下游命令键,有界退避;
|
|
187411
|
+
* 达到重试上限的基础设施错误才落 failed 并告警(该 run 仍占着事件键,不自动制造第二次业务执行)。
|
|
187412
|
+
*/
|
|
187413
|
+
async reconcileEventRun(run) {
|
|
187414
|
+
const attempts = eventAttempts(run);
|
|
187415
|
+
const nowMs = this.now().getTime();
|
|
187416
|
+
const lastAt = eventLastAttemptAt(run) ?? Date.parse(run.triggeredAt);
|
|
187417
|
+
if (attempts >= EVENT_RETRY_MAX_ATTEMPTS) {
|
|
187418
|
+
await this.store.settleRun(run.id, {
|
|
187419
|
+
status: "failed",
|
|
187420
|
+
error: `\u8BA2\u9605\u89E6\u53D1\u6D3E\u53D1\u91CD\u8BD5 ${attempts} \u6B21\u4ECD\u5931\u8D25\uFF1A${eventLastError(run) ?? "\u672A\u77E5\u9519\u8BEF"}`,
|
|
187421
|
+
reasonCode: "spawn_failed",
|
|
187422
|
+
completedAt: this.now().toISOString()
|
|
187423
|
+
});
|
|
187424
|
+
this.log?.(`[automations] \u26A0 \u8BA2\u9605\u89E6\u53D1 run ${run.id} \u8FBE\u91CD\u8BD5\u4E0A\u9650\u5DF2\u843D failed\u2014\u2014\u8BE5 run \u4ECD\u5360\u7740\u4E8B\u4EF6\u952E\uFF0C\u4EBA\u5DE5\u6062\u590D\u5FC5\u987B\u91CD\u8BD5\u540C\u4E00 runId`);
|
|
187425
|
+
return;
|
|
187426
|
+
}
|
|
187427
|
+
const backoffMs = attempts === 0 ? 0 : Math.min(EVENT_RETRY_BASE_MS * 2 ** (attempts - 1), EVENT_RETRY_MAX_MS);
|
|
187428
|
+
if (nowMs - lastAt < backoffMs) return;
|
|
187429
|
+
const automation = await this.store.getAutomation(run.automationId);
|
|
187430
|
+
if (!automation) return;
|
|
187431
|
+
const trigger = run.triggerId ? await this.store.getTrigger(run.triggerId) : null;
|
|
187432
|
+
await this.dispatchEventRun(automation, trigger, run).catch((e) => {
|
|
187433
|
+
this.log?.(`[automations] \u8BA2\u9605\u89E6\u53D1\u91CD\u8BD5\u5F02\u5E38 ${run.id}\uFF1A${String(e)}`);
|
|
187434
|
+
});
|
|
187435
|
+
}
|
|
187436
|
+
async admissionSkip(automation, source = "manual") {
|
|
187198
187437
|
if (automation.archivedAt) return { reasonCode: "automation_archived", error: "\u81EA\u52A8\u5316\u5DF2\u5F52\u6863" };
|
|
187199
187438
|
if (!automation.enabled) return { reasonCode: "automation_paused", error: "\u81EA\u52A8\u5316\u5DF2\u6682\u505C" };
|
|
187200
187439
|
if (automation.executionMode === "chat") {
|
|
187201
187440
|
if (!automation.agentId) return { reasonCode: "config_error", error: "chat \u6A21\u5F0F\u7F3A\u5C11\u6267\u884C\u4EBA\uFF08agentId\uFF09" };
|
|
187202
187441
|
if (!this.deps.runChat) return { reasonCode: "config_error", error: "\u672C\u5B9E\u4F8B\u672A\u63A5 chat \u6267\u884C" };
|
|
187442
|
+
if (source === "event" && !this.deps.chatSupportsIdempotency) {
|
|
187443
|
+
return { reasonCode: "config_error", error: "\u5BF9\u8BDD\u6267\u884C\u6682\u4E0D\u652F\u6301\u8BA2\u9605\u89E6\u53D1\uFF08chat \u9002\u914D\u5668\u7F3A\u6301\u4E45\u5E42\u7B49\u952E\uFF09" };
|
|
187444
|
+
}
|
|
187203
187445
|
if (this.deps.agentReady) {
|
|
187204
187446
|
const ready = await this.deps.agentReady(automation.agentId);
|
|
187205
187447
|
if (!ready.ok) return { reasonCode: ready.reason, error: ready.error };
|
|
@@ -187319,7 +187561,9 @@ var init_service8 = __esm({
|
|
|
187319
187561
|
...human ? { humanActorId: human } : {},
|
|
187320
187562
|
...tagId ? { tags: [tagId] } : {},
|
|
187321
187563
|
// 成本安全(ADR 0010):dispatch=false(默认)→ 建单即设 dispatch-hold,待人放行。
|
|
187322
|
-
...automation.dispatch ? {} : { dispatch: false }
|
|
187564
|
+
...automation.dispatch ? {} : { dispatch: false },
|
|
187565
|
+
// 稳定下游键(ADR 0163 §4.4):重试落在同一张工单上,占位后崩溃不会建出第二张。
|
|
187566
|
+
...opts.commandKey ? { idempotencyKey: opts.commandKey } : {}
|
|
187323
187567
|
});
|
|
187324
187568
|
return { workspace: result.workspace, rootArtifactId: result.rootArtifactId };
|
|
187325
187569
|
}
|
|
@@ -187362,6 +187606,10 @@ var init_service8 = __esm({
|
|
|
187362
187606
|
continue;
|
|
187363
187607
|
}
|
|
187364
187608
|
if (run.status === "pending") {
|
|
187609
|
+
if (run.source === "event") {
|
|
187610
|
+
await this.reconcileEventRun(run);
|
|
187611
|
+
continue;
|
|
187612
|
+
}
|
|
187365
187613
|
if (nowMs - Date.parse(run.triggeredAt) > 10 * 60 * 1e3) {
|
|
187366
187614
|
await this.store.settleRun(run.id, {
|
|
187367
187615
|
status: "failed",
|
|
@@ -187438,6 +187686,15 @@ var init_service8 = __esm({
|
|
|
187438
187686
|
}
|
|
187439
187687
|
}
|
|
187440
187688
|
};
|
|
187689
|
+
EVENT_RETRY_BASE_MS = 3e4;
|
|
187690
|
+
EVENT_RETRY_MAX_MS = 10 * 6e4;
|
|
187691
|
+
EVENT_RETRY_MAX_ATTEMPTS = 5;
|
|
187692
|
+
eventAttempts = (run) => eventDispatchState(run).attempts ?? 0;
|
|
187693
|
+
eventLastAttemptAt = (run) => {
|
|
187694
|
+
const at = eventDispatchState(run).lastAttemptAt;
|
|
187695
|
+
return at ? Date.parse(at) : null;
|
|
187696
|
+
};
|
|
187697
|
+
eventLastError = (run) => eventDispatchState(run).lastError ?? null;
|
|
187441
187698
|
CHAT_REPLY_MAX = 2e4;
|
|
187442
187699
|
}
|
|
187443
187700
|
});
|
|
@@ -187821,7 +188078,8 @@ function buildAutomationSummary(automation, triggers, recent, canWrite) {
|
|
|
187821
188078
|
lastRun,
|
|
187822
188079
|
nextRunAt,
|
|
187823
188080
|
recent: ordered.slice(0, RECENT_LIMIT).map(stripRunPayload),
|
|
187824
|
-
canWrite
|
|
188081
|
+
canWrite,
|
|
188082
|
+
capabilities: { eventTrigger: { supported: true } }
|
|
187825
188083
|
};
|
|
187826
188084
|
}
|
|
187827
188085
|
function deriveNextRunAt(triggers) {
|
|
@@ -187867,10 +188125,12 @@ function validateTriggers(raw) {
|
|
|
187867
188125
|
label: t.label ?? null,
|
|
187868
188126
|
webhook: { provider, ...eventFilters !== void 0 ? { eventFilters } : {} }
|
|
187869
188127
|
});
|
|
188128
|
+
} else if (t?.kind === "event") {
|
|
188129
|
+
out.push({ kind: "event", enabled: t.enabled ?? true, label: t.label ?? null, event: validateEventConfig(t.event) });
|
|
187870
188130
|
} else if (t?.kind === "manual") {
|
|
187871
188131
|
out.push({ kind: "manual", enabled: t.enabled ?? true, label: t.label ?? null });
|
|
187872
188132
|
} else {
|
|
187873
|
-
throw new ApiError(400, "BAD_REQUEST", "trigger.kind \u53EA\u80FD\u662F schedule\u3001webhook \u6216 manual");
|
|
188133
|
+
throw new ApiError(400, "BAD_REQUEST", "trigger.kind \u53EA\u80FD\u662F schedule\u3001webhook\u3001event \u6216 manual");
|
|
187874
188134
|
}
|
|
187875
188135
|
}
|
|
187876
188136
|
return out;
|
|
@@ -187892,6 +188152,28 @@ function validateEventFilters(raw) {
|
|
|
187892
188152
|
}
|
|
187893
188153
|
return out;
|
|
187894
188154
|
}
|
|
188155
|
+
function validateEventConfig(raw) {
|
|
188156
|
+
const cfg = raw ?? {};
|
|
188157
|
+
if (cfg.source !== void 0 && cfg.source !== "project") {
|
|
188158
|
+
throw new ApiError(400, "BAD_REQUEST", `\u4E0D\u652F\u6301\u7684\u4E8B\u4EF6\u6E90\uFF1A${String(cfg.source)}\uFF08\u672C\u671F\u53EA\u652F\u6301 project\uFF09`);
|
|
188159
|
+
}
|
|
188160
|
+
const projectId = typeof cfg.projectId === "string" ? cfg.projectId.trim() : "";
|
|
188161
|
+
if (!projectId) throw new ApiError(400, "BAD_REQUEST", "event \u89E6\u53D1\u5668\u9700\u8981 event.projectId");
|
|
188162
|
+
const events = normalizeEventNames(cfg.events);
|
|
188163
|
+
return { source: "project", projectId, events };
|
|
188164
|
+
}
|
|
188165
|
+
function normalizeEventNames(raw) {
|
|
188166
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
188167
|
+
throw new ApiError(400, "BAD_REQUEST", `event.events \u9700\u8981\u975E\u7A7A\u6570\u7EC4\uFF08\u53EF\u9009\u503C\uFF1A${AUTOMATION_EVENT_NAMES.join(", ")}\uFF09`);
|
|
188168
|
+
}
|
|
188169
|
+
const known = new Set(AUTOMATION_EVENT_NAMES);
|
|
188170
|
+
for (const e of raw) {
|
|
188171
|
+
if (typeof e !== "string" || !known.has(e)) {
|
|
188172
|
+
throw new ApiError(400, "UNKNOWN_EVENT", `\u4E0D\u652F\u6301\u7684\u4E8B\u4EF6\u7C7B\u578B\uFF1A${String(e)}\uFF08\u53EF\u9009\u503C\uFF1A${AUTOMATION_EVENT_NAMES.join(", ")}\uFF09`);
|
|
188173
|
+
}
|
|
188174
|
+
}
|
|
188175
|
+
return AUTOMATION_EVENT_NAMES.filter((n) => raw.includes(n));
|
|
188176
|
+
}
|
|
187895
188177
|
function assertValidCron(cron, tz) {
|
|
187896
188178
|
const v2 = validateCron(cron, tz);
|
|
187897
188179
|
if (!v2.ok) throw new ApiError(400, v2.code, v2.message);
|
|
@@ -187954,13 +188236,66 @@ function automationsDomain(opts) {
|
|
|
187954
188236
|
throw new ApiError(403, "NOT_ALLOWED", "\u53EA\u6709\u521B\u5EFA\u8005\u6216\u7BA1\u7406\u5458\u53EF\u4EE5\u7BA1\u7406\u534F\u4F5C\u8005");
|
|
187955
188237
|
}
|
|
187956
188238
|
};
|
|
188239
|
+
const decorateEventTriggers = async (automation, triggers, req) => {
|
|
188240
|
+
if (!triggers.some((t) => t.kind === "event")) return triggers;
|
|
188241
|
+
const cache = /* @__PURE__ */ new Map();
|
|
188242
|
+
const out = [];
|
|
188243
|
+
for (const t of triggers) {
|
|
188244
|
+
if (t.kind !== "event" || !t.event) {
|
|
188245
|
+
out.push(t);
|
|
188246
|
+
continue;
|
|
188247
|
+
}
|
|
188248
|
+
const pid = t.event.projectId;
|
|
188249
|
+
if (!cache.has(pid)) {
|
|
188250
|
+
cache.set(pid, await opts.projectAccess?.({ companyId: companyOf(req), projectId: pid, actor: req.auth.actor }) ?? null);
|
|
188251
|
+
}
|
|
188252
|
+
const project = cache.get(pid) ?? null;
|
|
188253
|
+
out.push({
|
|
188254
|
+
...t,
|
|
188255
|
+
event: { ...t.event, projectAvailable: project !== null, projectName: project?.name ?? null }
|
|
188256
|
+
});
|
|
188257
|
+
}
|
|
188258
|
+
return out;
|
|
188259
|
+
};
|
|
187957
188260
|
const summarize3 = async (automation, req) => {
|
|
187958
188261
|
const [triggers, recent, canWrite] = await Promise.all([
|
|
187959
188262
|
service.listTriggers(automation.id),
|
|
187960
188263
|
service.listRuns(automation.id, { limit: RECENT_LIMIT2 }),
|
|
187961
188264
|
canWriteOf(automation, req)
|
|
187962
188265
|
]);
|
|
187963
|
-
|
|
188266
|
+
const decorated = await decorateEventTriggers(automation, triggers, req);
|
|
188267
|
+
return {
|
|
188268
|
+
...buildAutomationSummary(automation, decorated, recent, canWrite),
|
|
188269
|
+
// 能力位服务端算(ADR 0163 §10):前端只消费不推断,否则前后端两套规则必漂移。
|
|
188270
|
+
capabilities: { eventTrigger: service.eventTriggerCapability(automation) }
|
|
188271
|
+
};
|
|
188272
|
+
};
|
|
188273
|
+
const assertEventTriggerWritable = async (mode, cfg, existing, req) => {
|
|
188274
|
+
if (!service.eventTriggerCapability({ executionMode: mode }).supported) {
|
|
188275
|
+
throw new ApiError(400, "EVENT_TRIGGER_UNSUPPORTED", "\u8BE5\u6267\u884C\u65B9\u5F0F\u6682\u4E0D\u652F\u6301\u8BA2\u9605\u89E6\u53D1\uFF08\u5BF9\u8BDD\u6267\u884C\u7F3A\u6301\u4E45\u5E42\u7B49\u952E\uFF09");
|
|
188276
|
+
}
|
|
188277
|
+
const project = await opts.projectAccess?.({ companyId: companyOf(req), projectId: cfg.projectId, actor: req.auth.actor });
|
|
188278
|
+
if (!project) throw new ApiError(404, "NOT_FOUND", `\u6CA1\u6709\u8FD9\u4E2A\u9879\u76EE\uFF1A${cfg.projectId}`);
|
|
188279
|
+
for (const other of existing) {
|
|
188280
|
+
if (other.projectId !== cfg.projectId) continue;
|
|
188281
|
+
const dup = cfg.events.find((e) => other.events.includes(e));
|
|
188282
|
+
if (dup) throw new ApiError(409, "DUPLICATE_SUBSCRIPTION", `\u8BE5\u9879\u76EE\u7684\u8FD9\u4E2A\u4E8B\u4EF6\u5DF2\u8BA2\u9605\uFF1A${dup}`);
|
|
188283
|
+
}
|
|
188284
|
+
};
|
|
188285
|
+
const assertEventTriggerInputs = async (mode, inputs, kept, req) => {
|
|
188286
|
+
const seen = [...kept];
|
|
188287
|
+
for (const input of inputs ?? []) {
|
|
188288
|
+
if (input.kind !== "event" || !input.event) continue;
|
|
188289
|
+
await assertEventTriggerWritable(mode, input.event, seen, req);
|
|
188290
|
+
seen.push(input.event);
|
|
188291
|
+
}
|
|
188292
|
+
};
|
|
188293
|
+
const assertModeKeepsEventTriggers = async (automation, effMode) => {
|
|
188294
|
+
if (service.eventTriggerCapability({ ...automation, executionMode: effMode }).supported) return;
|
|
188295
|
+
const hasEvent = (await service.listTriggers(automation.id)).some((t) => t.kind === "event");
|
|
188296
|
+
if (hasEvent) {
|
|
188297
|
+
throw new ApiError(400, "EVENT_TRIGGER_UNSUPPORTED", "\u8FD9\u6761\u81EA\u52A8\u5316\u6709\u8BA2\u9605\u89E6\u53D1\u5668\uFF0C\u6539\u6210\u5BF9\u8BDD\u6267\u884C\u4F1A\u5931\u53BB\u6301\u4E45\u5E42\u7B49\u4FDD\u8BC1\u2014\u2014\u8BF7\u5148\u5220\u9664\u8BA2\u9605\u89E6\u53D1\u5668");
|
|
188298
|
+
}
|
|
187964
188299
|
};
|
|
187965
188300
|
return (router) => {
|
|
187966
188301
|
router.get("/api/automations/templates", async () => {
|
|
@@ -188027,6 +188362,7 @@ function automationsDomain(opts) {
|
|
|
188027
188362
|
assertTitleTemplate(b2.titleTemplate);
|
|
188028
188363
|
const subscribers = assertActorIds(b2.subscribers, "subscribers");
|
|
188029
188364
|
const triggers = validateTriggers(b2.triggers);
|
|
188365
|
+
await assertEventTriggerInputs(mode, triggers, [], req);
|
|
188030
188366
|
const created = await service.createAutomation({
|
|
188031
188367
|
companyId: companyOf(req),
|
|
188032
188368
|
name: b2.name,
|
|
@@ -188060,6 +188396,9 @@ function automationsDomain(opts) {
|
|
|
188060
188396
|
assertTitleTemplate(b2.titleTemplate);
|
|
188061
188397
|
const subscribers = assertActorIds(b2.subscribers, "subscribers");
|
|
188062
188398
|
const triggers = validateTriggers(b2.triggers);
|
|
188399
|
+
const kept = triggers !== void 0 ? [] : (await service.listTriggers(automation.id)).filter((t) => t.kind === "event" && t.event).map((t) => t.event);
|
|
188400
|
+
await assertEventTriggerInputs(effMode, triggers, kept, req);
|
|
188401
|
+
if (triggers === void 0) await assertModeKeepsEventTriggers(automation, effMode);
|
|
188063
188402
|
const updated = await service.updateAutomation(
|
|
188064
188403
|
automation.id,
|
|
188065
188404
|
{
|
|
@@ -188108,6 +188447,8 @@ function automationsDomain(opts) {
|
|
|
188108
188447
|
const automation = await mustGet(req);
|
|
188109
188448
|
await requireWrite(automation, req);
|
|
188110
188449
|
const inputs = validateTriggers([req.body]) ?? [];
|
|
188450
|
+
const kept = (await service.listTriggers(automation.id)).filter((t) => t.kind === "event" && t.event).map((t) => t.event);
|
|
188451
|
+
await assertEventTriggerInputs(automation.executionMode, inputs, kept, req);
|
|
188111
188452
|
const trigger = await service.addTrigger(automation.id, inputs[0], req.auth.actor);
|
|
188112
188453
|
return { status: 201, body: trigger };
|
|
188113
188454
|
});
|
|
@@ -188137,6 +188478,17 @@ function automationsDomain(opts) {
|
|
|
188137
188478
|
const eventFilters = validateEventFilters(b2.webhook.eventFilters);
|
|
188138
188479
|
if (eventFilters !== void 0) patch.webhook = { eventFilters };
|
|
188139
188480
|
}
|
|
188481
|
+
if (b2.event !== void 0) {
|
|
188482
|
+
if (existing.kind !== "event") throw new ApiError(400, "BAD_REQUEST", "\u975E event \u89E6\u53D1\u5668\u4E0D\u63A5\u53D7 event \u5B57\u6BB5");
|
|
188483
|
+
const raw = b2.event;
|
|
188484
|
+
if (raw["projectId"] !== void 0 || raw["source"] !== void 0) {
|
|
188485
|
+
throw new ApiError(400, "BAD_REQUEST", "\u8BA2\u9605\u7684\u9879\u76EE\u4E0E\u4E8B\u4EF6\u6E90\u4E0D\u53EF\u4FEE\u6539\u2014\u2014\u8BF7\u5220\u9664\u8FD9\u6761\u89E6\u53D1\u5668\u540E\u91CD\u65B0\u6DFB\u52A0");
|
|
188486
|
+
}
|
|
188487
|
+
const events = normalizeEventNames(raw["events"]);
|
|
188488
|
+
const kept = (await service.listTriggers(automation.id)).filter((t) => t.kind === "event" && t.id !== existing.id && t.event).map((t) => t.event);
|
|
188489
|
+
await assertEventTriggerWritable(automation.executionMode, { projectId: existing.event.projectId, events }, kept, req);
|
|
188490
|
+
patch.event = { events };
|
|
188491
|
+
}
|
|
188140
188492
|
const updated = await service.updateTrigger(existing.id, patch, req.auth.actor);
|
|
188141
188493
|
return { status: 200, body: updated };
|
|
188142
188494
|
});
|
|
@@ -188218,6 +188570,7 @@ var RECENT_LIMIT2;
|
|
|
188218
188570
|
var init_routes9 = __esm({
|
|
188219
188571
|
"../server/src/domains/automations/routes.ts"() {
|
|
188220
188572
|
"use strict";
|
|
188573
|
+
init_src2();
|
|
188221
188574
|
init_templates();
|
|
188222
188575
|
init_router();
|
|
188223
188576
|
init_service8();
|
|
@@ -188230,17 +188583,25 @@ var init_routes9 = __esm({
|
|
|
188230
188583
|
});
|
|
188231
188584
|
|
|
188232
188585
|
// ../server/src/domains/automations/workorder-state.ts
|
|
188233
|
-
function
|
|
188586
|
+
function makeAutomationWorkorderSnapshotResolver(kernel) {
|
|
188234
188587
|
return async (workspace) => {
|
|
188235
188588
|
const arts = [...kernel.model.artifacts.values()].filter((a) => a.workspace === workspace);
|
|
188236
|
-
if (arts.length === 0) return "unknown";
|
|
188589
|
+
if (arts.length === 0) return { state: "unknown", members: [], rootArtifactId: null };
|
|
188237
188590
|
const seed = arts.find((a) => a.inputs.length === 0) ?? arts[0];
|
|
188238
|
-
|
|
188591
|
+
const members = arts.map((a) => ({ artifactId: String(a.id), currentRevisionId: a.currentRev ? String(a.currentRev) : null })).sort((x2, y) => x2.artifactId < y.artifactId ? -1 : x2.artifactId > y.artifactId ? 1 : 0);
|
|
188592
|
+
const rootArtifactId = String(seed.id);
|
|
188593
|
+
if (lifecycleOf(kernel.model, seed.id) === "sealed:cancelled") {
|
|
188594
|
+
return { state: "cancelled", members, rootArtifactId };
|
|
188595
|
+
}
|
|
188239
188596
|
const stage = deriveStage(arts.map((a) => nodeInfo(kernel.model, a)));
|
|
188240
|
-
if (stage === "concluded" || stage === "sealed") return "done";
|
|
188241
|
-
return "active";
|
|
188597
|
+
if (stage === "concluded" || stage === "sealed") return { state: "done", members, rootArtifactId };
|
|
188598
|
+
return { state: "active", members, rootArtifactId };
|
|
188242
188599
|
};
|
|
188243
188600
|
}
|
|
188601
|
+
function makeAutomationWorkorderStateResolver(kernel) {
|
|
188602
|
+
const snapshot = makeAutomationWorkorderSnapshotResolver(kernel);
|
|
188603
|
+
return async (workspace) => (await snapshot(workspace)).state;
|
|
188604
|
+
}
|
|
188244
188605
|
function makeAutomationReplyResolver(kernel, blobs) {
|
|
188245
188606
|
return async (workspace) => {
|
|
188246
188607
|
const model = kernel.model;
|
|
@@ -188304,7 +188665,12 @@ function createAutomationsDomain(opts) {
|
|
|
188304
188665
|
return {
|
|
188305
188666
|
service,
|
|
188306
188667
|
webhook,
|
|
188307
|
-
register: automationsDomain({
|
|
188668
|
+
register: automationsDomain({
|
|
188669
|
+
service,
|
|
188670
|
+
webhook,
|
|
188671
|
+
...opts.isAdmin ? { isAdmin: opts.isAdmin } : {},
|
|
188672
|
+
...opts.projectAccess ? { projectAccess: opts.projectAccess } : {}
|
|
188673
|
+
})
|
|
188308
188674
|
};
|
|
188309
188675
|
}
|
|
188310
188676
|
var init_automations = __esm({
|
|
@@ -188596,6 +188962,210 @@ var init_playbooks = __esm({
|
|
|
188596
188962
|
}
|
|
188597
188963
|
});
|
|
188598
188964
|
|
|
188965
|
+
// ../server/src/automations/project-event-poller.ts
|
|
188966
|
+
function completionFingerprint(workspace, members) {
|
|
188967
|
+
const canonical = JSON.stringify({
|
|
188968
|
+
workspace,
|
|
188969
|
+
members: [...members].sort((a, b2) => a.artifactId < b2.artifactId ? -1 : a.artifactId > b2.artifactId ? 1 : 0).map((m2) => ({ artifactId: m2.artifactId, currentRevisionId: m2.currentRevisionId ?? null }))
|
|
188970
|
+
});
|
|
188971
|
+
return (0, import_node_crypto39.createHash)("sha256").update(canonical).digest("hex");
|
|
188972
|
+
}
|
|
188973
|
+
function eventConsumptionKey(args) {
|
|
188974
|
+
const raw = `project-event:${EVENT_KEY_VERSION}:${args.triggerId}:${args.event}:${args.workspace}:${args.fingerprint}`;
|
|
188975
|
+
return raw.length <= 200 ? raw : `project-event:${EVENT_KEY_VERSION}:sha256:${(0, import_node_crypto39.createHash)("sha256").update(raw).digest("hex")}`;
|
|
188976
|
+
}
|
|
188977
|
+
var import_node_crypto39, EVENT_KEY_VERSION, EMPTY_METRICS, AUTOMATION_EVENT_HANDLERS, ProjectEventPoller;
|
|
188978
|
+
var init_project_event_poller = __esm({
|
|
188979
|
+
"../server/src/automations/project-event-poller.ts"() {
|
|
188980
|
+
"use strict";
|
|
188981
|
+
import_node_crypto39 = require("node:crypto");
|
|
188982
|
+
EVENT_KEY_VERSION = "v1";
|
|
188983
|
+
EMPTY_METRICS = () => ({
|
|
188984
|
+
scanDurationMs: 0,
|
|
188985
|
+
projectsScanned: 0,
|
|
188986
|
+
workordersScanned: 0,
|
|
188987
|
+
newEvents: 0,
|
|
188988
|
+
duplicateEvents: 0,
|
|
188989
|
+
skippedEvents: 0,
|
|
188990
|
+
baselineEvents: 0,
|
|
188991
|
+
pollErrors: 0,
|
|
188992
|
+
oldestPendingAgeMs: null
|
|
188993
|
+
});
|
|
188994
|
+
AUTOMATION_EVENT_HANDLERS = {
|
|
188995
|
+
// 完成判据必须来自 D3 resolver(snapshot.state),不在这里复制 deriveStage。
|
|
188996
|
+
// active / unknown 是「还没完成」,cancelled 永不产生 completed——四态里只有 done 触发。
|
|
188997
|
+
"workorder.completed": {
|
|
188998
|
+
matches: (snapshot) => snapshot.state === "done",
|
|
188999
|
+
fingerprint: (workspace, snapshot) => completionFingerprint(workspace, snapshot.members)
|
|
189000
|
+
}
|
|
189001
|
+
};
|
|
189002
|
+
ProjectEventPoller = class {
|
|
189003
|
+
deps;
|
|
189004
|
+
now;
|
|
189005
|
+
log;
|
|
189006
|
+
batchLimit;
|
|
189007
|
+
inFlight = false;
|
|
189008
|
+
lastMetrics = EMPTY_METRICS();
|
|
189009
|
+
constructor(deps) {
|
|
189010
|
+
this.deps = deps;
|
|
189011
|
+
this.now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
189012
|
+
if (deps.log) this.log = deps.log;
|
|
189013
|
+
this.batchLimit = deps.batchLimit ?? 200;
|
|
189014
|
+
}
|
|
189015
|
+
metrics() {
|
|
189016
|
+
return { ...this.lastMetrics };
|
|
189017
|
+
}
|
|
189018
|
+
/** 一轮扫描。单实例用 inFlight 防重入;多实例无需选主——并发由数据库唯一键裁决。 */
|
|
189019
|
+
async tick() {
|
|
189020
|
+
if (this.inFlight) return this.lastMetrics;
|
|
189021
|
+
this.inFlight = true;
|
|
189022
|
+
const startedAt = this.now().getTime();
|
|
189023
|
+
const m2 = EMPTY_METRICS();
|
|
189024
|
+
try {
|
|
189025
|
+
const triggers = await this.deps.store.listEventTriggers();
|
|
189026
|
+
if (triggers.length === 0) return this.lastMetrics = { ...m2, scanDurationMs: this.now().getTime() - startedAt };
|
|
189027
|
+
const snapshots2 = /* @__PURE__ */ new Map();
|
|
189028
|
+
let budget = this.batchLimit;
|
|
189029
|
+
for (const trigger of triggers) {
|
|
189030
|
+
if (budget <= 0) break;
|
|
189031
|
+
const config2 = trigger.event;
|
|
189032
|
+
if (!config2?.projectId || config2.events.length === 0) continue;
|
|
189033
|
+
const automation = await this.deps.store.getAutomation(trigger.automationId);
|
|
189034
|
+
if (!automation) continue;
|
|
189035
|
+
const groupKey = `${automation.companyId}\0${config2.projectId}`;
|
|
189036
|
+
let group = snapshots2.get(groupKey);
|
|
189037
|
+
if (!group) {
|
|
189038
|
+
try {
|
|
189039
|
+
group = await this.scanProject(automation.companyId, config2.projectId);
|
|
189040
|
+
m2.projectsScanned += 1;
|
|
189041
|
+
m2.workordersScanned += group.length;
|
|
189042
|
+
} catch (e) {
|
|
189043
|
+
m2.pollErrors += 1;
|
|
189044
|
+
this.log?.(`[project-event-poller] \u626B\u63CF\u9879\u76EE\u5931\u8D25 ${config2.projectId}\uFF1A${String(e)}`);
|
|
189045
|
+
group = [];
|
|
189046
|
+
}
|
|
189047
|
+
snapshots2.set(groupKey, group);
|
|
189048
|
+
}
|
|
189049
|
+
try {
|
|
189050
|
+
budget -= await this.evaluateTrigger(automation, trigger, group, m2, budget);
|
|
189051
|
+
} catch (e) {
|
|
189052
|
+
m2.pollErrors += 1;
|
|
189053
|
+
this.log?.(`[project-event-poller] \u89E6\u53D1\u5668\u6C42\u503C\u5931\u8D25 ${trigger.id}\uFF1A${String(e)}`);
|
|
189054
|
+
}
|
|
189055
|
+
}
|
|
189056
|
+
m2.oldestPendingAgeMs = await this.oldestPendingAge();
|
|
189057
|
+
} catch (e) {
|
|
189058
|
+
m2.pollErrors += 1;
|
|
189059
|
+
this.log?.(`[project-event-poller] tick \u5931\u8D25\uFF1A${String(e)}`);
|
|
189060
|
+
} finally {
|
|
189061
|
+
m2.scanDurationMs = this.now().getTime() - startedAt;
|
|
189062
|
+
this.lastMetrics = m2;
|
|
189063
|
+
this.inFlight = false;
|
|
189064
|
+
}
|
|
189065
|
+
if (m2.newEvents || m2.skippedEvents || m2.baselineEvents || m2.pollErrors) {
|
|
189066
|
+
this.log?.(
|
|
189067
|
+
`[project-event-poller] \u9879\u76EE ${m2.projectsScanned} / \u5DE5\u5355 ${m2.workordersScanned} \u2192 \u65B0\u4E8B\u4EF6 ${m2.newEvents}\u3001\u91CD\u590D ${m2.duplicateEvents}\u3001\u8DF3\u8FC7 ${m2.skippedEvents}\u3001\u57FA\u7EBF ${m2.baselineEvents}\u3001\u9519\u8BEF ${m2.pollErrors}\uFF08${m2.scanDurationMs}ms\uFF09`
|
|
189068
|
+
);
|
|
189069
|
+
}
|
|
189070
|
+
return this.lastMetrics;
|
|
189071
|
+
}
|
|
189072
|
+
async scanProject(companyId, projectId) {
|
|
189073
|
+
const workspaces = await this.deps.listProjectWorkspaces({ companyId, projectId });
|
|
189074
|
+
const out = [];
|
|
189075
|
+
for (const workspace of workspaces) {
|
|
189076
|
+
out.push({ workspace, snapshot: await this.deps.workorderSnapshot({ companyId, workspace }) });
|
|
189077
|
+
}
|
|
189078
|
+
return out;
|
|
189079
|
+
}
|
|
189080
|
+
/** 返回本触发器消耗掉的预算条数。 */
|
|
189081
|
+
async evaluateTrigger(automation, trigger, candidates, m2, budget) {
|
|
189082
|
+
const config2 = trigger.event;
|
|
189083
|
+
const baselining = config2.baselineState !== "ready";
|
|
189084
|
+
let used = 0;
|
|
189085
|
+
for (const event of config2.events) {
|
|
189086
|
+
const handler = AUTOMATION_EVENT_HANDLERS[event];
|
|
189087
|
+
if (!handler) continue;
|
|
189088
|
+
for (const { workspace, snapshot } of candidates) {
|
|
189089
|
+
if (!handler.matches(snapshot)) continue;
|
|
189090
|
+
if (used >= budget) return used;
|
|
189091
|
+
const fingerprint = handler.fingerprint(workspace, snapshot);
|
|
189092
|
+
const key = eventConsumptionKey({ triggerId: trigger.id, event, workspace, fingerprint });
|
|
189093
|
+
const envelope = {
|
|
189094
|
+
event,
|
|
189095
|
+
companyId: automation.companyId,
|
|
189096
|
+
projectId: config2.projectId,
|
|
189097
|
+
workspace,
|
|
189098
|
+
rootArtifactId: snapshot.rootArtifactId,
|
|
189099
|
+
completionFingerprint: fingerprint,
|
|
189100
|
+
observedAt: this.now().toISOString(),
|
|
189101
|
+
...await this.originAutomationOf(workspace)
|
|
189102
|
+
};
|
|
189103
|
+
used += 1;
|
|
189104
|
+
if (baselining) {
|
|
189105
|
+
const { created: created2 } = await this.deps.service.claimEventRun(
|
|
189106
|
+
automation,
|
|
189107
|
+
trigger,
|
|
189108
|
+
key,
|
|
189109
|
+
envelope,
|
|
189110
|
+
"skipped",
|
|
189111
|
+
"subscription_baseline",
|
|
189112
|
+
"\u8BA2\u9605\u57FA\u7EBF\u5BF9\u9F50\uFF1A\u8BA2\u9605\u751F\u6548\u524D\u5DF2\u5B8C\u6210\uFF0C\u4E0D\u56DE\u653E\u5386\u53F2"
|
|
189113
|
+
);
|
|
189114
|
+
if (created2) m2.baselineEvents += 1;
|
|
189115
|
+
else m2.duplicateEvents += 1;
|
|
189116
|
+
continue;
|
|
189117
|
+
}
|
|
189118
|
+
const { run, created } = await this.deps.service.claimEventRun(automation, trigger, key, envelope);
|
|
189119
|
+
if (!created) {
|
|
189120
|
+
m2.duplicateEvents += 1;
|
|
189121
|
+
continue;
|
|
189122
|
+
}
|
|
189123
|
+
const denial = await this.admit(automation, trigger, envelope);
|
|
189124
|
+
if (denial) {
|
|
189125
|
+
await this.deps.service.skipEventRun(run, denial.reasonCode, denial.error);
|
|
189126
|
+
m2.skippedEvents += 1;
|
|
189127
|
+
continue;
|
|
189128
|
+
}
|
|
189129
|
+
await this.deps.service.dispatchEventRun(automation, trigger, run);
|
|
189130
|
+
m2.newEvents += 1;
|
|
189131
|
+
}
|
|
189132
|
+
}
|
|
189133
|
+
if (baselining) {
|
|
189134
|
+
await this.deps.store.completeEventBaseline(trigger.id, this.now().toISOString());
|
|
189135
|
+
}
|
|
189136
|
+
return used;
|
|
189137
|
+
}
|
|
189138
|
+
/** 权限复查 + 自环准入。两条都只在**占位成功之后**做,因为拒绝也要留痕在同一把键上。 */
|
|
189139
|
+
async admit(automation, trigger, envelope) {
|
|
189140
|
+
if (envelope.originAutomationId && envelope.originAutomationId === automation.id) {
|
|
189141
|
+
return { reasonCode: "self_trigger_blocked", error: "\u81EA\u89E6\u53D1\u5DF2\u963B\u65AD\uFF1A\u8FD9\u4E2A\u5DE5\u5355\u662F\u672C\u81EA\u52A8\u5316\u81EA\u5DF1\u5EFA\u7684" };
|
|
189142
|
+
}
|
|
189143
|
+
const accountable = trigger.publishedBy ?? (await this.deps.store.latestRuleVersion(automation.id))?.publishedBy ?? automation.createdBy ?? null;
|
|
189144
|
+
const readable = await this.deps.projectReadable({
|
|
189145
|
+
companyId: automation.companyId,
|
|
189146
|
+
projectId: envelope.projectId,
|
|
189147
|
+
actor: accountable
|
|
189148
|
+
});
|
|
189149
|
+
if (!readable) {
|
|
189150
|
+
return { reasonCode: "project_access_revoked", error: `\u8BA2\u9605\u9879\u76EE\u5DF2\u4E0D\u53EF\u8BBF\u95EE\uFF1A${envelope.projectId}` };
|
|
189151
|
+
}
|
|
189152
|
+
return null;
|
|
189153
|
+
}
|
|
189154
|
+
async originAutomationOf(workspace) {
|
|
189155
|
+
const origin = await this.deps.store.findRunBySpawnedWorkspace(workspace).catch(() => null);
|
|
189156
|
+
return origin ? { originAutomationId: origin.automationId } : {};
|
|
189157
|
+
}
|
|
189158
|
+
async oldestPendingAge() {
|
|
189159
|
+
const unsettled = await this.deps.store.listUnsettledRuns(50).catch(() => []);
|
|
189160
|
+
const pending = unsettled.filter((r) => r.source === "event" && r.status === "pending");
|
|
189161
|
+
if (pending.length === 0) return null;
|
|
189162
|
+
const oldest = Math.min(...pending.map((r) => Date.parse(r.triggeredAt)));
|
|
189163
|
+
return this.now().getTime() - oldest;
|
|
189164
|
+
}
|
|
189165
|
+
};
|
|
189166
|
+
}
|
|
189167
|
+
});
|
|
189168
|
+
|
|
188599
189169
|
// ../server/src/daemon-adapter.ts
|
|
188600
189170
|
function graceFromEnv(fallback) {
|
|
188601
189171
|
const v2 = process.env["OASIS_NODE_LOST_GRACE_MS"];
|
|
@@ -188623,11 +189193,11 @@ function remoteAppendInput(hub, nodeId, dispatchId, entry, input, timeoutMs) {
|
|
|
188623
189193
|
entry.appendWaiters.push({ resolve: resolve10, timer });
|
|
188624
189194
|
});
|
|
188625
189195
|
}
|
|
188626
|
-
var
|
|
189196
|
+
var import_node_crypto40, STASH_TTL_MS, STASH_MAX_FRAMES_PER_ID, STASH_MAX_IDS, DaemonHubAdapter, BindingRouterAdapter, WorkdirBridge;
|
|
188627
189197
|
var init_daemon_adapter = __esm({
|
|
188628
189198
|
"../server/src/daemon-adapter.ts"() {
|
|
188629
189199
|
"use strict";
|
|
188630
|
-
|
|
189200
|
+
import_node_crypto40 = require("node:crypto");
|
|
188631
189201
|
STASH_TTL_MS = 5 * 6e4;
|
|
188632
189202
|
STASH_MAX_FRAMES_PER_ID = 500;
|
|
188633
189203
|
STASH_MAX_IDS = 50;
|
|
@@ -188823,7 +189393,7 @@ var init_daemon_adapter = __esm({
|
|
|
188823
189393
|
async spawn(job) {
|
|
188824
189394
|
const nodeId = job.binding?.nodeId;
|
|
188825
189395
|
if (!nodeId) throw new Error("DaemonHubAdapter \u9700\u8981 job.binding.nodeId\uFF08\u8DEF\u7531\u9519\u8BEF\uFF09");
|
|
188826
|
-
const dispatchId = job.dispatchId ?? `dispatch:${(0,
|
|
189396
|
+
const dispatchId = job.dispatchId ?? `dispatch:${(0, import_node_crypto40.randomUUID)()}`;
|
|
188827
189397
|
const entry = {
|
|
188828
189398
|
nodeId,
|
|
188829
189399
|
...job.binding?.runtimeKind ? { runtimeKind: job.binding.runtimeKind } : {},
|
|
@@ -188979,7 +189549,7 @@ var init_daemon_adapter = __esm({
|
|
|
188979
189549
|
}));
|
|
188980
189550
|
}
|
|
188981
189551
|
request(nodeId, buildFrame) {
|
|
188982
|
-
const requestId = (0,
|
|
189552
|
+
const requestId = (0, import_node_crypto40.randomUUID)();
|
|
188983
189553
|
const sent = this.hub.dispatch(nodeId, buildFrame(requestId));
|
|
188984
189554
|
if (!sent) return Promise.resolve({ ok: false, code: "NODE_OFFLINE" });
|
|
188985
189555
|
return new Promise((resolve10) => {
|
|
@@ -189087,13 +189657,13 @@ function classifyPage(page, lastPushedHash, serviceAccount) {
|
|
|
189087
189657
|
if (sha(body) === lastPushedHash) return { kind: "echo" };
|
|
189088
189658
|
return { kind: "human-edit", content: body, updatedBy: page.updatedBy };
|
|
189089
189659
|
}
|
|
189090
|
-
var
|
|
189660
|
+
var import_node_crypto41, sha, enc3, dec, MirrorEngine, MapMirrorIdentities;
|
|
189091
189661
|
var init_engine = __esm({
|
|
189092
189662
|
"../server/src/mirror/engine.ts"() {
|
|
189093
189663
|
"use strict";
|
|
189094
|
-
|
|
189664
|
+
import_node_crypto41 = require("node:crypto");
|
|
189095
189665
|
init_src2();
|
|
189096
|
-
sha = (s2) => (0,
|
|
189666
|
+
sha = (s2) => (0, import_node_crypto41.createHash)("sha256").update(s2, "utf8").digest("hex");
|
|
189097
189667
|
enc3 = (s2) => new TextEncoder().encode(s2);
|
|
189098
189668
|
dec = (b2) => new TextDecoder().decode(b2);
|
|
189099
189669
|
MirrorEngine = class {
|
|
@@ -189300,11 +189870,11 @@ function humanExitCause(exit) {
|
|
|
189300
189870
|
const base = (exit.reason ? label[exit.reason] : void 0) ?? `\u4F1A\u8BDD\u5F02\u5E38\u7ED3\u675F\uFF08${exit.reason ?? "\u65E0\u9000\u51FA\u4FE1\u606F"}\uFF09`;
|
|
189301
189871
|
return exit.errorMessage ? `${base}\uFF1A${exit.errorMessage.slice(0, 200)}` : base;
|
|
189302
189872
|
}
|
|
189303
|
-
var
|
|
189873
|
+
var import_node_crypto42, AUTONOMOUS_ETHOS, CONVERSATIONAL_ETHOS, SHARED_GRAPH_BODY, AUTONOMOUS_RECOVERY_TOOLS, HIGH_RISK_COMMANDS, COORDINATOR_SYSTEM_PROMPT, CONVERSATIONAL_RECOVERY_TOOLS, CONVERSATIONAL_ESCALATION_TOOLS, CONVERSATIONAL_MANAGER_BODY, MACHINE_EXIT_CODES, CoordinatorWorker;
|
|
189304
189874
|
var init_worker = __esm({
|
|
189305
189875
|
"../server/src/coordinator/worker.ts"() {
|
|
189306
189876
|
"use strict";
|
|
189307
|
-
|
|
189877
|
+
import_node_crypto42 = require("node:crypto");
|
|
189308
189878
|
init_src5();
|
|
189309
189879
|
init_identity();
|
|
189310
189880
|
AUTONOMOUS_ETHOS = `\u4F60\u662F\u8FD9\u4E2A\u5DE5\u5355\u7684**\u7BA1\u7406\u8005**\u2014\u2014\u804C\u8D23\u662F\u8BA9\u5B83\u987A\u7545\u8DD1\u5B8C\u3002\u7CFB\u7EDF\u5728\u67D0\u4E2A\u8282\u70B9\u5361\u4F4F\u3001\u673A\u68B0\u5206\u8BCA\u786E\u8BA4"\u9700\u8981\u4F60"\u65F6\u5524\u8D77\u4F60\uFF08\u65E0\u4EBA\u5728\u573A\uFF0C\u4F60\u8FD9\u4E00\u8F6E\u628A\u80FD\u505A\u7684\u505A\u6389\uFF09\u3002\u4F60\u7684\u624B\u6BB5\u5F88\u5BBD\uFF1A
|
|
@@ -189551,7 +190121,7 @@ ${HIGH_RISK_COMMANDS}`;
|
|
|
189551
190121
|
return this.deps.kernel.model.lastSeq.get(artifactId) ?? 0;
|
|
189552
190122
|
}
|
|
189553
190123
|
evaluationKey(kind, artifactId, evidence) {
|
|
189554
|
-
const fingerprint = (0,
|
|
190124
|
+
const fingerprint = (0, import_node_crypto42.createHash)("sha256").update(JSON.stringify(evidence)).digest("hex");
|
|
189555
190125
|
return `${kind}:${artifactId}:${fingerprint}`;
|
|
189556
190126
|
}
|
|
189557
190127
|
artifactEvidence(id) {
|
|
@@ -189928,8 +190498,8 @@ ${ctx.nodeFault}
|
|
|
189928
190498
|
this.deps.log?.(`[coordinator] ${workspace} \u5168\u5C40\u4E0A\u4E0B\u6587\u8BFB\u53D6\u5931\u8D25\uFF0C\u7EE7\u7EED\u6CBF\u7528\u539F\u534F\u8C03\u8005\u4EFB\u52A1\uFF1A${String(error2)}`);
|
|
189929
190499
|
}
|
|
189930
190500
|
}
|
|
189931
|
-
const runtimeSessionId = opts?.resumeSessionId ?? (0,
|
|
189932
|
-
const coordinatorRunId = `coordinate:${(0,
|
|
190501
|
+
const runtimeSessionId = opts?.resumeSessionId ?? (0, import_node_crypto42.randomUUID)();
|
|
190502
|
+
const coordinatorRunId = `coordinate:${(0, import_node_crypto42.randomUUID)()}`;
|
|
189933
190503
|
const taskWithContext = coordinatorContext ? [
|
|
189934
190504
|
task,
|
|
189935
190505
|
``,
|
|
@@ -189970,7 +190540,7 @@ ${ctx.nodeFault}
|
|
|
189970
190540
|
const startedAtMs = Date.now();
|
|
189971
190541
|
let trajectoryStarted = false;
|
|
189972
190542
|
if (this.deps.trajectory) {
|
|
189973
|
-
const bundleManifest = Object.fromEntries(Object.entries(job.bundle.files).map(([name, content]) => [name, (0,
|
|
190543
|
+
const bundleManifest = Object.fromEntries(Object.entries(job.bundle.files).map(([name, content]) => [name, (0, import_node_crypto42.createHash)("sha256").update(content).digest("hex")]));
|
|
189974
190544
|
const session = {
|
|
189975
190545
|
runId: coordinatorRunId,
|
|
189976
190546
|
runtimeSessionId,
|
|
@@ -190371,6 +190941,7 @@ var init_src10 = __esm({
|
|
|
190371
190941
|
init_playbooks();
|
|
190372
190942
|
init_overrides();
|
|
190373
190943
|
init_scheduler();
|
|
190944
|
+
init_project_event_poller();
|
|
190374
190945
|
init_daemon_adapter();
|
|
190375
190946
|
init_dispatch_fence();
|
|
190376
190947
|
init_node_health();
|
|
@@ -190391,11 +190962,11 @@ var init_src10 = __esm({
|
|
|
190391
190962
|
});
|
|
190392
190963
|
|
|
190393
190964
|
// ../storage/src/postgres.ts
|
|
190394
|
-
var
|
|
190965
|
+
var import_node_crypto43, ident3, isUniqueViolation, PostgresOplogStore, PostgresBlobStore;
|
|
190395
190966
|
var init_postgres = __esm({
|
|
190396
190967
|
"../storage/src/postgres.ts"() {
|
|
190397
190968
|
"use strict";
|
|
190398
|
-
|
|
190969
|
+
import_node_crypto43 = require("node:crypto");
|
|
190399
190970
|
init_esm();
|
|
190400
190971
|
init_src2();
|
|
190401
190972
|
ident3 = (s2) => {
|
|
@@ -190574,7 +191145,7 @@ var init_postgres = __esm({
|
|
|
190574
191145
|
return new _PostgresBlobStore(pool, schema);
|
|
190575
191146
|
}
|
|
190576
191147
|
async put(bytes) {
|
|
190577
|
-
const hash = (0,
|
|
191148
|
+
const hash = (0, import_node_crypto43.createHash)("sha256").update(bytes).digest("hex");
|
|
190578
191149
|
await this.pool.query(
|
|
190579
191150
|
`INSERT INTO ${this.t} (hash, bytes, size, content_type) VALUES ($1, $2, $3, $4) ON CONFLICT (hash) DO NOTHING`,
|
|
190580
191151
|
[hash, Buffer.from(bytes), bytes.byteLength, sniffContentType(bytes) ?? null]
|
|
@@ -194843,6 +195414,14 @@ var init_postgres_trace = __esm({
|
|
|
194843
195414
|
});
|
|
194844
195415
|
|
|
194845
195416
|
// ../storage/src/postgres-automations.ts
|
|
195417
|
+
function storedEventConfig(t) {
|
|
195418
|
+
return {
|
|
195419
|
+
source: "project",
|
|
195420
|
+
projectId: t.event?.projectId ?? "",
|
|
195421
|
+
events: t.event?.events ?? [],
|
|
195422
|
+
baselineAt: null
|
|
195423
|
+
};
|
|
195424
|
+
}
|
|
194846
195425
|
function buildTrigger(automationId, input) {
|
|
194847
195426
|
const t = {
|
|
194848
195427
|
id: genId("trig"),
|
|
@@ -194863,6 +195442,10 @@ function buildTrigger(automationId, input) {
|
|
|
194863
195442
|
hasSigningSecret: false
|
|
194864
195443
|
};
|
|
194865
195444
|
}
|
|
195445
|
+
if (input.kind === "event" && input.event) {
|
|
195446
|
+
t.enabled = false;
|
|
195447
|
+
t.event = { source: "project", projectId: input.event.projectId, events: [...input.event.events], baselineState: "aligning" };
|
|
195448
|
+
}
|
|
194866
195449
|
return t;
|
|
194867
195450
|
}
|
|
194868
195451
|
function genWebhookToken2() {
|
|
@@ -195166,7 +195749,8 @@ var init_postgres_automations = __esm({
|
|
|
195166
195749
|
t.schedule?.claimedAt ?? null,
|
|
195167
195750
|
t.webhook?.token ?? null,
|
|
195168
195751
|
t.webhook?.provider ?? null,
|
|
195169
|
-
|
|
195752
|
+
// event_filters 一列两用(ADR 0163 §3.2:不新增列):webhook 存过滤数组,event 存订阅配置对象。
|
|
195753
|
+
t.kind === "event" ? JSON.stringify(storedEventConfig(t)) : t.webhook?.eventFilters != null ? JSON.stringify(t.webhook.eventFilters) : null
|
|
195170
195754
|
]
|
|
195171
195755
|
);
|
|
195172
195756
|
}
|
|
@@ -195212,6 +195796,10 @@ var init_postgres_automations = __esm({
|
|
|
195212
195796
|
if (patch.webhook && "eventFilters" in patch.webhook) {
|
|
195213
195797
|
push("event_filters", patch.webhook.eventFilters != null ? JSON.stringify(patch.webhook.eventFilters) : null, "::jsonb");
|
|
195214
195798
|
}
|
|
195799
|
+
if (patch.event?.events) {
|
|
195800
|
+
args.push(JSON.stringify(patch.event.events));
|
|
195801
|
+
sets.push(`event_filters=jsonb_set(COALESCE(event_filters, '{}'::jsonb), '{events}', $${args.length}::jsonb, true)`);
|
|
195802
|
+
}
|
|
195215
195803
|
if (sets.length === 0) return await this.getTrigger(triggerId);
|
|
195216
195804
|
args.push(triggerId);
|
|
195217
195805
|
const r = await this.pool.query(
|
|
@@ -195498,6 +196086,42 @@ var init_postgres_automations = __esm({
|
|
|
195498
196086
|
);
|
|
195499
196087
|
return r.rows.map(rowToTrigger);
|
|
195500
196088
|
}
|
|
196089
|
+
/* ---------- event 触发器(ADR 0163) ---------- */
|
|
196090
|
+
async listEventTriggers() {
|
|
196091
|
+
const r = await this.pool.query(
|
|
196092
|
+
`SELECT t.* FROM ${this.s}.automation_triggers t
|
|
196093
|
+
JOIN ${this.s}.automations a ON a.id = t.automation_id
|
|
196094
|
+
WHERE t.kind = 'event' AND a.enabled AND a.archived_at IS NULL
|
|
196095
|
+
ORDER BY t.id`
|
|
196096
|
+
);
|
|
196097
|
+
return r.rows.map(rowToTrigger);
|
|
196098
|
+
}
|
|
196099
|
+
async completeEventBaseline(triggerId, at) {
|
|
196100
|
+
await this.pool.query(
|
|
196101
|
+
`UPDATE ${this.s}.automation_triggers
|
|
196102
|
+
SET event_filters = jsonb_set(COALESCE(event_filters, '{}'::jsonb), '{baselineAt}', to_jsonb($2::text), true),
|
|
196103
|
+
enabled = true
|
|
196104
|
+
WHERE id = $1 AND kind = 'event'`,
|
|
196105
|
+
[triggerId, at]
|
|
196106
|
+
);
|
|
196107
|
+
}
|
|
196108
|
+
async resetEventBaseline(triggerId) {
|
|
196109
|
+
await this.pool.query(
|
|
196110
|
+
`UPDATE ${this.s}.automation_triggers
|
|
196111
|
+
SET event_filters = jsonb_set(COALESCE(event_filters, '{}'::jsonb), '{baselineAt}', 'null'::jsonb, true),
|
|
196112
|
+
enabled = false
|
|
196113
|
+
WHERE id = $1 AND kind = 'event'`,
|
|
196114
|
+
[triggerId]
|
|
196115
|
+
);
|
|
196116
|
+
}
|
|
196117
|
+
async findRunBySpawnedWorkspace(workspace) {
|
|
196118
|
+
const r = await this.pool.query(
|
|
196119
|
+
`SELECT * FROM ${this.s}.automation_runs WHERE spawned_workspace_id = $1
|
|
196120
|
+
ORDER BY triggered_at ASC, id ASC LIMIT 1`,
|
|
196121
|
+
[workspace]
|
|
196122
|
+
);
|
|
196123
|
+
return r.rows[0] ? rowToRun2(r.rows[0]) : null;
|
|
196124
|
+
}
|
|
195501
196125
|
};
|
|
195502
196126
|
rowToAutomation = (row) => ({
|
|
195503
196127
|
id: row.id,
|
|
@@ -195546,6 +196170,15 @@ var init_postgres_automations = __esm({
|
|
|
195546
196170
|
hasSigningSecret: row.signing_secret != null
|
|
195547
196171
|
};
|
|
195548
196172
|
}
|
|
196173
|
+
if (t.kind === "event") {
|
|
196174
|
+
const raw = row.event_filters ?? null;
|
|
196175
|
+
t.event = {
|
|
196176
|
+
source: "project",
|
|
196177
|
+
projectId: raw?.projectId ?? "",
|
|
196178
|
+
events: raw?.events ?? [],
|
|
196179
|
+
baselineState: raw?.baselineAt ? "ready" : "aligning"
|
|
196180
|
+
};
|
|
196181
|
+
}
|
|
195549
196182
|
return t;
|
|
195550
196183
|
};
|
|
195551
196184
|
rowToRun2 = (row) => ({
|
|
@@ -195909,11 +196542,11 @@ var init_postgres_chat_sessions = __esm({
|
|
|
195909
196542
|
});
|
|
195910
196543
|
|
|
195911
196544
|
// ../storage/src/postgres-nodes.ts
|
|
195912
|
-
var
|
|
196545
|
+
var import_node_crypto44, ident12, PostgresNodeStore, PostgresNodeTokenStore, rowToNode, rowToRuntime;
|
|
195913
196546
|
var init_postgres_nodes = __esm({
|
|
195914
196547
|
"../storage/src/postgres-nodes.ts"() {
|
|
195915
196548
|
"use strict";
|
|
195916
|
-
|
|
196549
|
+
import_node_crypto44 = require("node:crypto");
|
|
195917
196550
|
init_esm();
|
|
195918
196551
|
ident12 = (s2) => {
|
|
195919
196552
|
if (!/^[a-z_][a-z0-9_]*$/.test(s2)) throw new Error(`invalid schema name: ${s2}`);
|
|
@@ -196062,7 +196695,7 @@ var init_postgres_nodes = __esm({
|
|
|
196062
196695
|
return store;
|
|
196063
196696
|
}
|
|
196064
196697
|
issue(nodeId) {
|
|
196065
|
-
const token = `ont_${(0,
|
|
196698
|
+
const token = `ont_${(0, import_node_crypto44.randomBytes)(24).toString("base64url")}`;
|
|
196066
196699
|
this.cache.set(token, nodeId);
|
|
196067
196700
|
void this.pool.query(`INSERT INTO ${this.s}.node_tokens (token,node_id) VALUES ($1,$2)`, [token, nodeId]);
|
|
196068
196701
|
return token;
|
|
@@ -196274,11 +196907,11 @@ var init_postgres_type_registry = __esm({
|
|
|
196274
196907
|
});
|
|
196275
196908
|
|
|
196276
196909
|
// ../storage/src/postgres-actor-memory.ts
|
|
196277
|
-
var
|
|
196910
|
+
var import_node_crypto45, matchClause, ident15, PostgresActorMemoryStore, rowToIndexEntry, rowToRecord;
|
|
196278
196911
|
var init_postgres_actor_memory = __esm({
|
|
196279
196912
|
"../storage/src/postgres-actor-memory.ts"() {
|
|
196280
196913
|
"use strict";
|
|
196281
|
-
|
|
196914
|
+
import_node_crypto45 = require("node:crypto");
|
|
196282
196915
|
init_src2();
|
|
196283
196916
|
matchClause = (q) => q.requireMatch && q.keywords.length > 0 ? " WHERE m > 0" : "";
|
|
196284
196917
|
ident15 = (s2) => {
|
|
@@ -196483,7 +197116,7 @@ var init_postgres_actor_memory = __esm({
|
|
|
196483
197116
|
}
|
|
196484
197117
|
async write(input, now) {
|
|
196485
197118
|
if (input.memId === void 0) {
|
|
196486
|
-
const memId = `mem:${(0,
|
|
197119
|
+
const memId = `mem:${(0, import_node_crypto45.randomUUID)()}`;
|
|
196487
197120
|
const r2 = await this.pool.query(
|
|
196488
197121
|
`INSERT INTO ${this.s}.actor_memories
|
|
196489
197122
|
(mem_id, actor_id, project_id, keywords, content, version, created_at, updated_at, accessed_at, source_artifact_id, source_session_id)
|
|
@@ -196996,19 +197629,20 @@ var fs38 = __toESM(require("node:fs"));
|
|
|
196996
197629
|
var os13 = __toESM(require("node:os"));
|
|
196997
197630
|
var path32 = __toESM(require("node:path"));
|
|
196998
197631
|
var import_node_child_process19 = require("node:child_process");
|
|
197632
|
+
var import_node_crypto52 = require("node:crypto");
|
|
196999
197633
|
|
|
197000
197634
|
// ../cli/src/cli.ts
|
|
197001
197635
|
var fs34 = __toESM(require("node:fs"), 1);
|
|
197002
197636
|
var os11 = __toESM(require("node:os"), 1);
|
|
197003
197637
|
var path28 = __toESM(require("node:path"), 1);
|
|
197004
|
-
var
|
|
197638
|
+
var import_node_crypto50 = require("node:crypto");
|
|
197005
197639
|
|
|
197006
197640
|
// ../cli/src/serve.ts
|
|
197007
197641
|
var fs30 = __toESM(require("node:fs"), 1);
|
|
197008
197642
|
var os7 = __toESM(require("node:os"), 1);
|
|
197009
197643
|
var path24 = __toESM(require("node:path"), 1);
|
|
197010
197644
|
var import_node_child_process15 = require("node:child_process");
|
|
197011
|
-
var
|
|
197645
|
+
var import_node_crypto46 = require("node:crypto");
|
|
197012
197646
|
var import_node_url7 = require("node:url");
|
|
197013
197647
|
init_src5();
|
|
197014
197648
|
init_src10();
|
|
@@ -197452,8 +198086,8 @@ exec ${JSON.stringify(tsx)} ${JSON.stringify(main)} "$@"
|
|
|
197452
198086
|
return wrapper;
|
|
197453
198087
|
}
|
|
197454
198088
|
function splitIdentityFiles3(prompt) {
|
|
197455
|
-
const
|
|
197456
|
-
const parts = prompt.split(
|
|
198089
|
+
const sep5 = /<!-- FILE: (.+?) -->\n?/g;
|
|
198090
|
+
const parts = prompt.split(sep5);
|
|
197457
198091
|
if (parts.length === 1) return { "identity/AGENTS.md": prompt };
|
|
197458
198092
|
const out = {};
|
|
197459
198093
|
for (let i = 1; i < parts.length; i += 2) {
|
|
@@ -198131,7 +198765,7 @@ async function startServe(opts) {
|
|
|
198131
198765
|
// dispatchChat 收流、done 后落助手消息并回报最终回复。会话在控制台 chat 页可回看。
|
|
198132
198766
|
runChat: async ({ agentId, humanActorId, title, prompt }, onDone) => {
|
|
198133
198767
|
if (!automationDispatchChat) throw new Error("chat \u6D3E\u53D1\u5C1A\u672A\u5C31\u7EEA\uFF08serve \u542F\u52A8\u4E2D\uFF09");
|
|
198134
|
-
const chatSessionId = (0,
|
|
198768
|
+
const chatSessionId = (0, import_node_crypto46.randomUUID)();
|
|
198135
198769
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
198136
198770
|
await chatSessionStore.createSession({
|
|
198137
198771
|
id: chatSessionId,
|
|
@@ -198143,7 +198777,7 @@ async function startServe(opts) {
|
|
|
198143
198777
|
touchedAt: nowIso,
|
|
198144
198778
|
createdAt: nowIso
|
|
198145
198779
|
}).catch(() => void 0);
|
|
198146
|
-
await chatSessionStore.appendMessage({ id: (0,
|
|
198780
|
+
await chatSessionStore.appendMessage({ id: (0, import_node_crypto46.randomUUID)(), sessionId: chatSessionId, role: "user", content: prompt, createdAt: nowIso }).catch(() => void 0);
|
|
198147
198781
|
const session = await automationDispatchChat({ actorId: agentId, message: prompt, chatSessionId });
|
|
198148
198782
|
let buf = "";
|
|
198149
198783
|
session.onOutput((c) => {
|
|
@@ -198152,7 +198786,7 @@ async function startServe(opts) {
|
|
|
198152
198786
|
void session.done.then(async () => {
|
|
198153
198787
|
if (buf || session.runId) {
|
|
198154
198788
|
await chatSessionStore.appendMessage({
|
|
198155
|
-
id: (0,
|
|
198789
|
+
id: (0, import_node_crypto46.randomUUID)(),
|
|
198156
198790
|
sessionId: chatSessionId,
|
|
198157
198791
|
role: "assistant",
|
|
198158
198792
|
content: buf,
|
|
@@ -198166,6 +198800,10 @@ async function startServe(opts) {
|
|
|
198166
198800
|
});
|
|
198167
198801
|
return { chatSessionId };
|
|
198168
198802
|
},
|
|
198803
|
+
// 订阅触发 × chat 的 fail-closed 开关(ADR 0163 §4.4)。当前 runChat 每次 randomUUID 建会话、
|
|
198804
|
+
// 没有任何持久去重:占位后崩溃重试会多开一个会话。**在 chat 适配器补上持久幂等键之前保持 false**,
|
|
198805
|
+
// 由此 event+chat 在配置期就被拒(能力位 supported=false),而不是上线后偶发重复开会话。
|
|
198806
|
+
chatSupportsIdempotency: false,
|
|
198169
198807
|
// chat 准入(≈ multica AgentReadiness):在岗 + 有 active runtime 绑定,注定失败的触发别去开会话。
|
|
198170
198808
|
agentReady: async (agentId) => {
|
|
198171
198809
|
const actor = await registryStore.getActor(agentId);
|
|
@@ -198183,6 +198821,13 @@ async function startServe(opts) {
|
|
|
198183
198821
|
const member = await controlPlaneStore.getMember(companyId, actor);
|
|
198184
198822
|
return member?.role === "owner" || member?.role === "admin";
|
|
198185
198823
|
},
|
|
198824
|
+
// 订阅项目可读判据(ADR 0163 §5.2):复用 projects 域的可见性口径,automation 侧不复制成员规则。
|
|
198825
|
+
// 现状:projects 域对登录成员不做逐项目读限制,故「可读」= 项目存在;项目被删/查不到 → null → 404。
|
|
198826
|
+
// 哪天 projects 加了成员级可见性,改这一个闭包即可,automation 侧不动。
|
|
198827
|
+
projectAccess: async ({ projectId }) => {
|
|
198828
|
+
const project = await projectStateStore.getProject(projectId);
|
|
198829
|
+
return project ? { id: project.id, name: project.name } : null;
|
|
198830
|
+
},
|
|
198186
198831
|
log: (m2) => console.log(m2)
|
|
198187
198832
|
});
|
|
198188
198833
|
const controlPlaneStore = pgDsn && pgPool ? await PostgresControlPlaneStore.open(pgPool, pgSchema) : await FileControlPlaneStore.open(path24.join(opts.dir, "control-plane.json"));
|
|
@@ -198604,7 +199249,7 @@ async function startServe(opts) {
|
|
|
198604
199249
|
}));
|
|
198605
199250
|
}
|
|
198606
199251
|
const limits = { wallClockMs: wallClockForKind("planner") };
|
|
198607
|
-
const artifactId = `artifact:planner:${(0,
|
|
199252
|
+
const artifactId = `artifact:planner:${(0, import_node_crypto46.randomUUID)()}`;
|
|
198608
199253
|
const handle = await chatRemoteAdapter.spawn({
|
|
198609
199254
|
actor: planner.id,
|
|
198610
199255
|
// companyId 同协调者:planner 用的就是默认公司的 actors 服务,漏签会让它一调公司域端点就 404。
|
|
@@ -199021,10 +199666,10 @@ async function startServe(opts) {
|
|
|
199021
199666
|
{ nodeId: priorChatSession?.runtimeId, runtimeKind: priorChatSession?.runtimeKind },
|
|
199022
199667
|
{ nodeId: binding.nodeId, runtimeKind: binding.runtimeKind }
|
|
199023
199668
|
) : false;
|
|
199024
|
-
const runtimeSessionId = sessionId ?? (0,
|
|
199669
|
+
const runtimeSessionId = sessionId ?? (0, import_node_crypto46.randomUUID)();
|
|
199025
199670
|
const resumeRuntimeSession = Boolean(sessionId);
|
|
199026
|
-
const traceRunId = `chat-run:${(0,
|
|
199027
|
-
const artifactId = `artifact:chat:${(0,
|
|
199671
|
+
const traceRunId = `chat-run:${(0, import_node_crypto46.randomUUID)()}`;
|
|
199672
|
+
const artifactId = `artifact:chat:${(0, import_node_crypto46.randomUUID)()}`;
|
|
199028
199673
|
const traceStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
199029
199674
|
let lastProgressTouchMs = 0;
|
|
199030
199675
|
const PROGRESS_TOUCH_THROTTLE_MS2 = 2e4;
|
|
@@ -199677,7 +200322,7 @@ async function startServe(opts) {
|
|
|
199677
200322
|
console.log(`[new-engine] dispatchWork ${workId} \u662F human work\u2014\u2014\u4E0D\u6D3E runtime\uFF0C\u7B49\u5F85\u4EBA\u5DE5\u5904\u7406`);
|
|
199678
200323
|
return;
|
|
199679
200324
|
}
|
|
199680
|
-
const ledgerId = `dispatch:${(0,
|
|
200325
|
+
const ledgerId = `dispatch:${(0, import_node_crypto46.randomUUID)()}`;
|
|
199681
200326
|
try {
|
|
199682
200327
|
await dispatchLedger.insertOpen({
|
|
199683
200328
|
id: ledgerId,
|
|
@@ -199753,7 +200398,7 @@ async function startServe(opts) {
|
|
|
199753
200398
|
const kernelModel = newKernel.model;
|
|
199754
200399
|
const art = kernelModel.artifacts.get(nodeId);
|
|
199755
200400
|
const revWid = art?.workspace ?? "";
|
|
199756
|
-
const ledgerId = `dispatch:${(0,
|
|
200401
|
+
const ledgerId = `dispatch:${(0, import_node_crypto46.randomUUID)()}`;
|
|
199757
200402
|
try {
|
|
199758
200403
|
await dispatchLedger.insertOpen({
|
|
199759
200404
|
id: ledgerId,
|
|
@@ -200875,6 +201520,31 @@ ${st.detail}
|
|
|
200875
201520
|
const automationTimer = setInterval(() => {
|
|
200876
201521
|
void automationScheduler.tick().catch((err) => console.error(`[automation-scheduler] tick \u5931\u8D25: ${String(err)}`));
|
|
200877
201522
|
}, 3e4);
|
|
201523
|
+
const eventPollMs = Number(process.env["OASIS_AUTOMATION_EVENT_POLL_MS"] ?? 1e4);
|
|
201524
|
+
let automationEventTimer;
|
|
201525
|
+
if (Number.isFinite(eventPollMs) && eventPollMs > 0) {
|
|
201526
|
+
const eventPoller = new ProjectEventPoller({
|
|
201527
|
+
store: automationStore,
|
|
201528
|
+
service: automations.service,
|
|
201529
|
+
log: (m2) => console.log(m2),
|
|
201530
|
+
batchLimit: envNum("OASIS_AUTOMATION_EVENT_BATCH", 200),
|
|
201531
|
+
// 工单→项目绑定是合法薄旁存(B5),不是内核事实——列项目下的工单只认它。
|
|
201532
|
+
listProjectWorkspaces: async ({ projectId }) => (await artifactStateStore.listWorkspaceBindings()).filter((b2) => b2.projectId === projectId).map((b2) => b2.workOrderId),
|
|
201533
|
+
// 完成判据必须复用 D3 resolver(不复制 deriveStage),且与成员快照同一次读取。
|
|
201534
|
+
workorderSnapshot: async ({ companyId, workspace }) => {
|
|
201535
|
+
const engine2 = await getEngine(companyId);
|
|
201536
|
+
return makeAutomationWorkorderSnapshotResolver(engine2.kernel)(workspace);
|
|
201537
|
+
},
|
|
201538
|
+
// 触发时复查归因主体的项目读取权(与配置期同一口径)。
|
|
201539
|
+
projectReadable: async ({ projectId }) => await projectStateStore.getProject(projectId) !== null
|
|
201540
|
+
});
|
|
201541
|
+
automationEventTimer = setInterval(() => {
|
|
201542
|
+
void eventPoller.tick().catch((err) => console.error(`[project-event-poller] tick \u5931\u8D25: ${String(err)}`));
|
|
201543
|
+
}, eventPollMs);
|
|
201544
|
+
console.log(`[serve] \u9879\u76EE\u4E8B\u4EF6\u8BA2\u9605\u8F6E\u8BE2\u5668\u5DF2\u542F\u7528\uFF08\u6BCF ${Math.round(eventPollMs / 1e3)}s \u4E00\u8F6E\uFF1BOASIS_AUTOMATION_EVENT_POLL_MS=0 \u53EF\u5173\uFF09`);
|
|
201545
|
+
} else {
|
|
201546
|
+
console.log("[serve] \u9879\u76EE\u4E8B\u4EF6\u8BA2\u9605\u8F6E\u8BE2\u5668\u5DF2\u88AB OASIS_AUTOMATION_EVENT_POLL_MS \u663E\u5F0F\u5173\u95ED");
|
|
201547
|
+
}
|
|
200878
201548
|
let boardWatchTimer;
|
|
200879
201549
|
const boardWatchFlag = (process.env["OASIS_BOARD_WATCH"] ?? "").trim().toLowerCase();
|
|
200880
201550
|
if (!["0", "false", "off"].includes(boardWatchFlag)) {
|
|
@@ -200900,6 +201570,7 @@ ${st.detail}
|
|
|
200900
201570
|
if (orphanSweepTimer) clearInterval(orphanSweepTimer);
|
|
200901
201571
|
if (zeroOutputBreakerTimer) clearInterval(zeroOutputBreakerTimer);
|
|
200902
201572
|
clearInterval(automationTimer);
|
|
201573
|
+
if (automationEventTimer) clearInterval(automationEventTimer);
|
|
200903
201574
|
if (boardWatchTimer) clearInterval(boardWatchTimer);
|
|
200904
201575
|
clearInterval(serverHeartbeatTimer);
|
|
200905
201576
|
hub?.close();
|
|
@@ -200967,7 +201638,7 @@ var SessionManager = class {
|
|
|
200967
201638
|
// ../cli/src/daemon/ws-client.ts
|
|
200968
201639
|
init_wrapper();
|
|
200969
201640
|
var import_node_os9 = require("node:os");
|
|
200970
|
-
var
|
|
201641
|
+
var import_node_crypto47 = require("node:crypto");
|
|
200971
201642
|
init_src8();
|
|
200972
201643
|
init_src7();
|
|
200973
201644
|
|
|
@@ -201592,7 +202263,7 @@ var DaemonWsClient = class {
|
|
|
201592
202263
|
*/
|
|
201593
202264
|
async queryArtifactStates(artifactIds, timeoutMs = 15e3) {
|
|
201594
202265
|
if (artifactIds.length === 0) return {};
|
|
201595
|
-
const requestId = (0,
|
|
202266
|
+
const requestId = (0, import_node_crypto47.randomUUID)();
|
|
201596
202267
|
return new Promise((resolve10, reject) => {
|
|
201597
202268
|
const timer = setTimeout(() => {
|
|
201598
202269
|
this.gcPending.delete(requestId);
|
|
@@ -201929,7 +202600,7 @@ function forwardSignal(child, sig, killGroup = (pgid, signal) => {
|
|
|
201929
202600
|
// ../cli/src/daemon/machine-id.ts
|
|
201930
202601
|
var import_node_child_process17 = require("node:child_process");
|
|
201931
202602
|
var import_node_fs21 = require("node:fs");
|
|
201932
|
-
var
|
|
202603
|
+
var import_node_crypto48 = require("node:crypto");
|
|
201933
202604
|
var import_node_os12 = require("node:os");
|
|
201934
202605
|
function linuxMachineId() {
|
|
201935
202606
|
for (const p2 of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
@@ -201995,7 +202666,7 @@ var defaultSources = {
|
|
|
201995
202666
|
function resolveNodeId(sources = {}) {
|
|
201996
202667
|
const s2 = { ...defaultSources, ...sources };
|
|
201997
202668
|
const material = `${s2.machineFingerprint()}:${s2.osUser()}`;
|
|
201998
|
-
const digest = (0,
|
|
202669
|
+
const digest = (0, import_node_crypto48.createHash)("sha256").update(material).digest("hex").slice(0, 12);
|
|
201999
202670
|
return `node-${digest}`;
|
|
202000
202671
|
}
|
|
202001
202672
|
|
|
@@ -202329,17 +203000,22 @@ var COMMAND_DECLS = {
|
|
|
202329
203000
|
},
|
|
202330
203001
|
"automation-trigger-add": {
|
|
202331
203002
|
group: "\u81EA\u52A8\u5316",
|
|
202332
|
-
usage: "oasis automation-trigger-add <id> --kind schedule|webhook [--cron <expr> --tz <tz>] [--provider generic|github] [--label <\u540D>]",
|
|
202333
|
-
description: "\u7ED9\u81EA\u52A8\u5316\u8FFD\u52A0\u4E00\u6761\u89E6\u53D1\u5668\uFF08\u4E00\u6761\u81EA\u52A8\u5316\u53EF\u6302\u591A\u4E2A\uFF09\u3002",
|
|
203003
|
+
usage: "oasis automation-trigger-add <id> --kind schedule|webhook|event [--cron <expr> --tz <tz>] [--provider generic|github] [--project <projectId> --event workorder.completed] [--label <\u540D>]",
|
|
203004
|
+
description: "\u7ED9\u81EA\u52A8\u5316\u8FFD\u52A0\u4E00\u6761\u89E6\u53D1\u5668\uFF08\u4E00\u6761\u81EA\u52A8\u5316\u53EF\u6302\u591A\u4E2A\uFF09\u3002event=\u8BA2\u9605\u9879\u76EE\uFF1A\u8BE5\u9879\u76EE\u4E0B\u5DE5\u5355\u5B8C\u6210\u65F6\u89E6\u53D1\u4E00\u6B21\uFF1B\u65B0\u5EFA\u540E\u5148\u8DD1\u4E00\u6B21\u57FA\u7EBF\u5BF9\u9F50\uFF08\u4E0D\u56DE\u653E\u5386\u53F2\uFF09\uFF0C\u5BF9\u9F50\u5B8C\u81EA\u52A8\u542F\u7528\u3002",
|
|
202334
203005
|
positional: [{ name: "<id>", required: true, desc: "\u81EA\u52A8\u5316 id" }],
|
|
202335
203006
|
flags: [
|
|
202336
|
-
{ name: "kind", desc: "schedule|webhook\uFF08\u9ED8\u8BA4 schedule\uFF09" },
|
|
203007
|
+
{ name: "kind", desc: "schedule|webhook|event\uFF08\u9ED8\u8BA4 schedule\uFF09" },
|
|
202337
203008
|
{ name: "cron", desc: "5 \u5B57\u6BB5 cron\uFF08schedule \u5FC5\u586B\uFF09" },
|
|
202338
203009
|
{ name: "tz", desc: "IANA \u65F6\u533A\uFF08\u9ED8\u8BA4 UTC\uFF09" },
|
|
202339
203010
|
{ name: "provider", desc: "webhook provider\uFF1Ageneric|github" },
|
|
203011
|
+
{ name: "project", desc: "\u8BA2\u9605\u7684\u9879\u76EE id\uFF08event \u5FC5\u586B\uFF1Bschedule/webhook \u4E0D\u63A5\u53D7\uFF09" },
|
|
203012
|
+
{ name: "event", desc: "\u8BA2\u9605\u7684\u4E8B\u4EF6\u540D\uFF08event \u5FC5\u586B\uFF0C\u53EF\u91CD\u590D\u6216\u9017\u53F7\u5206\u9694\uFF1B\u672C\u671F\u53EA\u6709 workorder.completed\uFF09" },
|
|
202340
203013
|
{ name: "label", desc: "\u663E\u793A\u540D" }
|
|
202341
203014
|
],
|
|
202342
|
-
examples: [
|
|
203015
|
+
examples: [
|
|
203016
|
+
'oasis automation-trigger-add auto_abc123 --kind schedule --cron "0 18 * * 5" --tz Asia/Shanghai',
|
|
203017
|
+
"oasis automation-trigger-add auto_abc123 --kind event --project oasis --event workorder.completed"
|
|
203018
|
+
]
|
|
202343
203019
|
},
|
|
202344
203020
|
"automation-trigger-update": {
|
|
202345
203021
|
group: "\u81EA\u52A8\u5316",
|
|
@@ -203607,13 +204283,13 @@ function ownFlagsFromDecl(command) {
|
|
|
203607
204283
|
}
|
|
203608
204284
|
|
|
203609
204285
|
// ../cli/src/connector-effect-runner.ts
|
|
203610
|
-
var
|
|
204286
|
+
var import_node_crypto49 = require("node:crypto");
|
|
203611
204287
|
var import_node_child_process18 = require("node:child_process");
|
|
203612
204288
|
init_src8();
|
|
203613
204289
|
function stableKey(effect) {
|
|
203614
204290
|
const keyFields = Object.fromEntries(Object.entries(effect.keyFields).sort(([a], [b2]) => a.localeCompare(b2)));
|
|
203615
204291
|
const canonical = JSON.stringify({ kind: effect.kind, target: effect.target, keyFields });
|
|
203616
|
-
return `sha256:${(0,
|
|
204292
|
+
return `sha256:${(0, import_node_crypto49.createHash)("sha256").update(canonical).digest("hex")}`;
|
|
203617
204293
|
}
|
|
203618
204294
|
async function runConnectorEffect(connector, toolArgs, deps) {
|
|
203619
204295
|
const effect = connector.describeExternalEffect?.(toolArgs) ?? null;
|
|
@@ -204208,7 +204884,7 @@ async function runCli(argv, println = console.log, progressln = console.error) {
|
|
|
204208
204884
|
const store = createNodeTokenStore(path28.join(dir, "node-tokens.json"));
|
|
204209
204885
|
const sub = positional[0];
|
|
204210
204886
|
if (sub === "issue") {
|
|
204211
|
-
const id = flags.get("id") ?? `node-${(0,
|
|
204887
|
+
const id = flags.get("id") ?? `node-${(0, import_node_crypto50.randomUUID)()}`;
|
|
204212
204888
|
const token2 = store.issue(id);
|
|
204213
204889
|
println(token2);
|
|
204214
204890
|
process.stderr.write(
|
|
@@ -204821,10 +205497,36 @@ ${p2.ref} ${p2.name}`);
|
|
|
204821
205497
|
break;
|
|
204822
205498
|
}
|
|
204823
205499
|
case "automation-trigger-add": {
|
|
204824
|
-
const id = needPos(positional, 0, "oasis automation-trigger-add <id> --kind schedule|webhook ...");
|
|
205500
|
+
const id = needPos(positional, 0, "oasis automation-trigger-add <id> --kind schedule|webhook|event ...");
|
|
204825
205501
|
const kind = flags.get("kind") ?? "schedule";
|
|
204826
|
-
|
|
204827
|
-
|
|
205502
|
+
if (kind !== "schedule" && kind !== "webhook" && kind !== "event") {
|
|
205503
|
+
throw new Error(`--kind \u53EA\u80FD\u662F schedule\u3001webhook \u6216 event\uFF08\u6536\u5230 ${kind}\uFF09`);
|
|
205504
|
+
}
|
|
205505
|
+
if (kind !== "event" && (flags.has("project") || flags.has("event"))) {
|
|
205506
|
+
throw new Error(`--project / --event \u53EA\u7528\u4E8E --kind event\uFF08\u5F53\u524D --kind ${kind}\uFF09`);
|
|
205507
|
+
}
|
|
205508
|
+
if (kind === "event" && (flags.has("cron") || flags.has("tz") || flags.has("provider"))) {
|
|
205509
|
+
throw new Error("--kind event \u4E0D\u63A5\u53D7 --cron / --tz / --provider");
|
|
205510
|
+
}
|
|
205511
|
+
const label = flags.has("label") ? { label: flags.get("label") } : {};
|
|
205512
|
+
let body;
|
|
205513
|
+
if (kind === "schedule") {
|
|
205514
|
+
body = { kind, schedule: { cron: need(flags, "cron"), tz: flags.get("tz") ?? "UTC" }, ...label };
|
|
205515
|
+
} else if (kind === "webhook") {
|
|
205516
|
+
body = { kind, webhook: { provider: flags.get("provider") ?? "generic" }, ...label };
|
|
205517
|
+
} else {
|
|
205518
|
+
const events = (flags.get("event") ?? "").split(",").map((x2) => x2.trim()).filter(Boolean);
|
|
205519
|
+
if (!events.length) throw new Error("--kind event \u9700\u8981\u81F3\u5C11\u4E00\u4E2A --event\uFF08\u5982 --event workorder.completed\uFF09");
|
|
205520
|
+
body = { kind, event: { source: "project", projectId: need(flags, "project"), events }, ...label };
|
|
205521
|
+
}
|
|
205522
|
+
const created = await api.post(
|
|
205523
|
+
`/api/automations/${encodeURIComponent(id)}/triggers`,
|
|
205524
|
+
body
|
|
205525
|
+
);
|
|
205526
|
+
println(JSON.stringify(created, null, 2));
|
|
205527
|
+
if (kind === "event" && created.event?.baselineState === "aligning") {
|
|
205528
|
+
println("\u2139 \u8BA2\u9605\u5DF2\u5EFA\uFF0C\u6B63\u5728\u5BF9\u9F50\u57FA\u7EBF\uFF1A\u628A\u9879\u76EE\u91CC**\u5DF2\u5B8C\u6210**\u7684\u5DE5\u5355\u8BB0\u4E3A\u300C\u5DF2\u77E5\u300D\uFF0C\u5BF9\u9F50\u5B8C\u81EA\u52A8\u542F\u7528\u3002\u5386\u53F2\u5DE5\u5355\u4E0D\u4F1A\u88AB\u8865\u89E6\u53D1\u3002");
|
|
205529
|
+
}
|
|
204828
205530
|
break;
|
|
204829
205531
|
}
|
|
204830
205532
|
case "automation-trigger-update": {
|
|
@@ -206558,8 +207260,8 @@ function sanitizeDaemonPath(rawPath) {
|
|
|
206558
207260
|
return !SESSION_SCOPED_PREFIXES.some((p2) => base.startsWith(p2));
|
|
206559
207261
|
}).join(":");
|
|
206560
207262
|
}
|
|
206561
|
-
function installRuntimeAssets(srcRoot,
|
|
206562
|
-
if (path29.resolve(srcRoot) === path29.resolve(
|
|
207263
|
+
function installRuntimeAssets(srcRoot, binDir) {
|
|
207264
|
+
if (path29.resolve(srcRoot) === path29.resolve(binDir)) return 0;
|
|
206563
207265
|
let copied = 0;
|
|
206564
207266
|
let entries;
|
|
206565
207267
|
try {
|
|
@@ -206577,7 +207279,7 @@ function installRuntimeAssets(srcRoot, binDir2) {
|
|
|
206577
207279
|
continue;
|
|
206578
207280
|
}
|
|
206579
207281
|
if (files.length === 0) continue;
|
|
206580
|
-
const destDir = path29.join(
|
|
207282
|
+
const destDir = path29.join(binDir, entry.name);
|
|
206581
207283
|
fs35.mkdirSync(destDir, { recursive: true });
|
|
206582
207284
|
for (const f2 of files) {
|
|
206583
207285
|
const dest = path29.join(destDir, f2);
|
|
@@ -206588,10 +207290,10 @@ function installRuntimeAssets(srcRoot, binDir2) {
|
|
|
206588
207290
|
}
|
|
206589
207291
|
return copied;
|
|
206590
207292
|
}
|
|
206591
|
-
function syncRuntimeAssets(candidateRoots,
|
|
206592
|
-
fs35.mkdirSync(
|
|
207293
|
+
function syncRuntimeAssets(candidateRoots, binDir) {
|
|
207294
|
+
fs35.mkdirSync(binDir, { recursive: true });
|
|
206593
207295
|
for (const root of candidateRoots) {
|
|
206594
|
-
const copied = installRuntimeAssets(root,
|
|
207296
|
+
const copied = installRuntimeAssets(root, binDir);
|
|
206595
207297
|
if (copied > 0) return { from: root, copied };
|
|
206596
207298
|
}
|
|
206597
207299
|
return null;
|
|
@@ -206604,12 +207306,9 @@ var path30 = __toESM(require("node:path"));
|
|
|
206604
207306
|
var DEFAULT_INSTANCE = "default";
|
|
206605
207307
|
var home = () => os12.homedir();
|
|
206606
207308
|
var oasisDir = () => path30.join(home(), ".oasis");
|
|
206607
|
-
var binDir = () => path30.join(oasisDir(), "bin");
|
|
206608
|
-
var binFile = () => path30.join(binDir(), "oasis.js");
|
|
206609
|
-
var npmPrefix = () => path30.join(oasisDir(), "npm-global");
|
|
206610
207309
|
var versionsDir = () => path30.join(oasisDir(), "versions");
|
|
206611
|
-
var versionDir = (
|
|
206612
|
-
var
|
|
207310
|
+
var versionDir = (label) => path30.join(versionsDir(), label);
|
|
207311
|
+
var npmPrefix = () => path30.join(oasisDir(), "npm-global");
|
|
206613
207312
|
var localBin = () => path30.join(home(), ".local", "bin", "oasis");
|
|
206614
207313
|
var instancesRoot = () => path30.join(oasisDir(), "instances");
|
|
206615
207314
|
function isValidInstanceName(name) {
|
|
@@ -206625,7 +207324,11 @@ function instancePaths(name) {
|
|
|
206625
207324
|
dir,
|
|
206626
207325
|
configFile: path30.join(dir, "node-config.json"),
|
|
206627
207326
|
pidFile: path30.join(dir, "node.pid"),
|
|
206628
|
-
logFile: path30.join(dir, "node.log")
|
|
207327
|
+
logFile: path30.join(dir, "node.log"),
|
|
207328
|
+
binDir: path30.join(dir, "bin"),
|
|
207329
|
+
binFile: path30.join(dir, "bin", "oasis.js"),
|
|
207330
|
+
currentLink: path30.join(dir, "current"),
|
|
207331
|
+
sessionsDir: path30.join(dir, "sessions")
|
|
206629
207332
|
};
|
|
206630
207333
|
}
|
|
206631
207334
|
function listInstances() {
|
|
@@ -206644,6 +207347,16 @@ function listInstances() {
|
|
|
206644
207347
|
}
|
|
206645
207348
|
return found;
|
|
206646
207349
|
}
|
|
207350
|
+
function referencedVersionLabels() {
|
|
207351
|
+
const labels = /* @__PURE__ */ new Set();
|
|
207352
|
+
for (const name of listInstances()) {
|
|
207353
|
+
try {
|
|
207354
|
+
labels.add(path30.basename(fs36.realpathSync(instancePaths(name).currentLink)));
|
|
207355
|
+
} catch {
|
|
207356
|
+
}
|
|
207357
|
+
}
|
|
207358
|
+
return [...labels];
|
|
207359
|
+
}
|
|
206647
207360
|
function normalizeServerUrl(raw) {
|
|
206648
207361
|
try {
|
|
206649
207362
|
const u = new URL(raw);
|
|
@@ -206656,24 +207369,26 @@ function normalizeServerUrl(raw) {
|
|
|
206656
207369
|
return raw.trim().replace(/\/+$/, "").toLowerCase();
|
|
206657
207370
|
}
|
|
206658
207371
|
}
|
|
206659
|
-
function hostSlug(raw) {
|
|
206660
|
-
let host;
|
|
206661
|
-
try {
|
|
206662
|
-
host = new URL(raw).hostname;
|
|
206663
|
-
} catch {
|
|
206664
|
-
host = raw.replace(/^[a-z]+:\/\//i, "").split("/")[0] ?? raw;
|
|
206665
|
-
}
|
|
206666
|
-
const slug6 = host.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
206667
|
-
return slug6 || "node";
|
|
206668
|
-
}
|
|
206669
207372
|
var systemdUnitName = (name) => `oasis-node@${name}.service`;
|
|
206670
207373
|
var launchdLabel = (name) => name === DEFAULT_INSTANCE ? "com.oasis.node" : `com.oasis.node.${name}`;
|
|
206671
207374
|
var LEGACY_SYSTEMD_UNIT = "oasis-node.service";
|
|
206672
207375
|
var LEGACY_LAUNCHD_LABEL = "com.oasis.node";
|
|
206673
207376
|
|
|
206674
207377
|
// src/install.ts
|
|
207378
|
+
var import_node_crypto51 = require("node:crypto");
|
|
206675
207379
|
var fs37 = __toESM(require("node:fs"));
|
|
206676
207380
|
var path31 = __toESM(require("node:path"));
|
|
207381
|
+
function versionLabel(sourceFile, pkgVersion, npmPrefixDir) {
|
|
207382
|
+
if (isFromNpmPrefix(sourceFile, npmPrefixDir)) return pkgVersion;
|
|
207383
|
+
const digest = (0, import_node_crypto51.createHash)("sha256").update(fs37.readFileSync(sourceFile)).digest("hex").slice(0, 8);
|
|
207384
|
+
return `${pkgVersion}+local.${digest}`;
|
|
207385
|
+
}
|
|
207386
|
+
function isFromNpmPrefix(sourceFile, npmPrefixDir) {
|
|
207387
|
+
const src = realpathOrSelf(sourceFile);
|
|
207388
|
+
const root = realpathOrSelf(path31.join(npmPrefixDir, "lib", "node_modules"));
|
|
207389
|
+
return src.startsWith(root + path31.sep);
|
|
207390
|
+
}
|
|
207391
|
+
var isLocalLabel = (label) => label.includes("+local.");
|
|
206677
207392
|
function installBinaryAtomic(sourceFile, targetPath) {
|
|
206678
207393
|
if (realpathOrSelf(sourceFile) === realpathOrSelf(targetPath)) return;
|
|
206679
207394
|
fs37.mkdirSync(path31.dirname(targetPath), { recursive: true });
|
|
@@ -206715,8 +207430,9 @@ function pointCurrentAt(linkPath, targetDir) {
|
|
|
206715
207430
|
}
|
|
206716
207431
|
}
|
|
206717
207432
|
function gcVersions(versionsDirPath, opts) {
|
|
206718
|
-
const
|
|
206719
|
-
const
|
|
207433
|
+
const keep = new Set(opts.keep);
|
|
207434
|
+
const keepSpare = opts.keepSpare ?? 1;
|
|
207435
|
+
const minAgeMs = opts.minAgeMs ?? 60 * 60 * 1e3;
|
|
206720
207436
|
const now = opts.now ?? Date.now();
|
|
206721
207437
|
let entries;
|
|
206722
207438
|
try {
|
|
@@ -206724,7 +207440,7 @@ function gcVersions(versionsDirPath, opts) {
|
|
|
206724
207440
|
} catch {
|
|
206725
207441
|
return [];
|
|
206726
207442
|
}
|
|
206727
|
-
const candidates = entries.filter((name) => name
|
|
207443
|
+
const candidates = entries.filter((name) => !keep.has(name) && !name.includes(".tmp.")).map((name) => {
|
|
206728
207444
|
const full = path31.join(versionsDirPath, name);
|
|
206729
207445
|
try {
|
|
206730
207446
|
const st = fs37.statSync(full);
|
|
@@ -206734,7 +207450,7 @@ function gcVersions(versionsDirPath, opts) {
|
|
|
206734
207450
|
}
|
|
206735
207451
|
}).filter((x2) => x2 !== null).sort((a, b2) => b2.mtime - a.mtime);
|
|
206736
207452
|
const deleted = [];
|
|
206737
|
-
for (const c of candidates.slice(
|
|
207453
|
+
for (const c of candidates.slice(keepSpare)) {
|
|
206738
207454
|
if (now - c.mtime < minAgeMs) continue;
|
|
206739
207455
|
try {
|
|
206740
207456
|
fs37.rmSync(c.full, { recursive: true, force: true });
|
|
@@ -206756,9 +207472,7 @@ function shimScript() {
|
|
|
206756
207472
|
}
|
|
206757
207473
|
|
|
206758
207474
|
// src/index.ts
|
|
206759
|
-
var PKG_VERSION = true ? "0.1.
|
|
206760
|
-
var BIN_DIR = binDir();
|
|
206761
|
-
var BIN_FILE = binFile();
|
|
207475
|
+
var PKG_VERSION = true ? "0.1.118" : "dev";
|
|
206762
207476
|
var LOCAL_BIN = localBin();
|
|
206763
207477
|
var NPM_PREFIX = npmPrefix();
|
|
206764
207478
|
var INSTANCE = DEFAULT_INSTANCE;
|
|
@@ -206766,19 +207480,28 @@ var INST = instancePaths(DEFAULT_INSTANCE);
|
|
|
206766
207480
|
var CONFIG_FILE = INST.configFile;
|
|
206767
207481
|
var PID_FILE = INST.pidFile;
|
|
206768
207482
|
var LOG_FILE = INST.logFile;
|
|
207483
|
+
var BIN_DIR = INST.binDir;
|
|
207484
|
+
var BIN_FILE = INST.binFile;
|
|
206769
207485
|
function useInstance(name) {
|
|
206770
207486
|
INSTANCE = name;
|
|
206771
207487
|
INST = instancePaths(name);
|
|
206772
207488
|
CONFIG_FILE = INST.configFile;
|
|
206773
207489
|
PID_FILE = INST.pidFile;
|
|
206774
207490
|
LOG_FILE = INST.logFile;
|
|
207491
|
+
BIN_DIR = INST.binDir;
|
|
207492
|
+
BIN_FILE = INST.binFile;
|
|
206775
207493
|
}
|
|
206776
207494
|
var PKG_NAME = "oasis_test";
|
|
206777
207495
|
var parseFlags = (argv) => {
|
|
206778
207496
|
const flags = /* @__PURE__ */ new Map();
|
|
206779
207497
|
for (let i = 0; i < argv.length; i++) {
|
|
206780
207498
|
const a = argv[i];
|
|
206781
|
-
if (a.startsWith("--"))
|
|
207499
|
+
if (!a.startsWith("--")) continue;
|
|
207500
|
+
const next = argv[i + 1];
|
|
207501
|
+
if (next !== void 0 && !next.startsWith("--")) {
|
|
207502
|
+
flags.set(a.slice(2), next);
|
|
207503
|
+
i++;
|
|
207504
|
+
} else flags.set(a.slice(2), "");
|
|
206782
207505
|
}
|
|
206783
207506
|
return flags;
|
|
206784
207507
|
};
|
|
@@ -206831,22 +207554,24 @@ function ensureRuntimeAssets(targetDir = BIN_DIR) {
|
|
|
206831
207554
|
console.error(`[oasis] connector runtime assets: \u4ECE ${synced.from} \u540C\u6B65\u4E86 ${synced.copied} \u4E2A .sh \u5230 ${targetDir}`);
|
|
206832
207555
|
}
|
|
206833
207556
|
}
|
|
206834
|
-
function installSoftware() {
|
|
207557
|
+
function installSoftware(label) {
|
|
206835
207558
|
const src = process.argv[1];
|
|
206836
|
-
const vDir = versionDir(
|
|
207559
|
+
const vDir = versionDir(label);
|
|
206837
207560
|
installBinaryAtomic(src, path32.join(vDir, "oasis.js"));
|
|
206838
207561
|
ensureRuntimeAssets(vDir);
|
|
206839
|
-
pointCurrentAt(currentLink(), vDir);
|
|
206840
207562
|
installBinaryAtomic(src, BIN_FILE);
|
|
206841
207563
|
ensureRuntimeAssets(BIN_DIR);
|
|
207564
|
+
pointCurrentAt(INST.currentLink, vDir);
|
|
206842
207565
|
fs38.mkdirSync(path32.dirname(LOCAL_BIN), { recursive: true });
|
|
206843
207566
|
fs38.writeFileSync(LOCAL_BIN, shimScript(), { mode: 493 });
|
|
206844
|
-
const
|
|
206845
|
-
|
|
207567
|
+
const keep = [.../* @__PURE__ */ new Set([label, ...referencedVersionLabels()])];
|
|
207568
|
+
const removed = gcVersions(versionsDir(), { keep });
|
|
207569
|
+
if (removed.length) console.error(`[oasis] \u7248\u672C GC\uFF1A\u5220\u9664 ${removed.join(", ")}\uFF08\u5728\u7528\uFF1A${keep.join(", ")}\uFF09`);
|
|
207570
|
+
return label;
|
|
206846
207571
|
}
|
|
206847
207572
|
function setupAutostart(name) {
|
|
206848
207573
|
const inst = instancePaths(name);
|
|
206849
|
-
const cmd = `${process.execPath} "${
|
|
207574
|
+
const cmd = `${process.execPath} "${inst.binFile}" --_daemon --instance ${name}`;
|
|
206850
207575
|
const home2 = os13.homedir();
|
|
206851
207576
|
const pnpmHome = process.env["PNPM_HOME"] || path32.join(home2, ".local", "share", "pnpm");
|
|
206852
207577
|
const extraBins = [
|
|
@@ -206866,9 +207591,13 @@ function setupAutostart(name) {
|
|
|
206866
207591
|
`Description=Oasis Node Daemon (${name})`,
|
|
206867
207592
|
"After=network.target",
|
|
206868
207593
|
"",
|
|
207594
|
+
// OASIS_CLI_HOME 由 daemon 继承给它 spawn 的 agent 会话——非 default 实例的会话因此
|
|
207595
|
+
// 用的是**本实例**的 CLI 版本,而不是 PATH 上那个默认指向 default 的 shim。
|
|
207596
|
+
// (台阶 3 的 D4 后半截会把它细到「每个会话钉 spawn 那一刻的版本」,这里先钉到实例。)
|
|
206869
207597
|
"[Service]",
|
|
206870
207598
|
`ExecStart=${cmd}`,
|
|
206871
207599
|
`Environment=PATH=${daemonPath}`,
|
|
207600
|
+
`Environment=OASIS_CLI_HOME=${inst.currentLink}`,
|
|
206872
207601
|
`StandardOutput=append:${inst.logFile}`,
|
|
206873
207602
|
`StandardError=append:${inst.logFile}`,
|
|
206874
207603
|
"Restart=on-failure",
|
|
@@ -206916,9 +207645,12 @@ function setupAutostart(name) {
|
|
|
206916
207645
|
function spawnDaemon() {
|
|
206917
207646
|
fs38.mkdirSync(path32.dirname(LOG_FILE), { recursive: true });
|
|
206918
207647
|
const log3 = fs38.openSync(LOG_FILE, "a");
|
|
206919
|
-
const child = (0, import_node_child_process19.spawn)(process.execPath, [
|
|
207648
|
+
const child = (0, import_node_child_process19.spawn)(process.execPath, [INST.binFile, "--_daemon", "--instance", INSTANCE], {
|
|
206920
207649
|
detached: true,
|
|
206921
|
-
stdio: ["ignore", log3, log3]
|
|
207650
|
+
stdio: ["ignore", log3, log3],
|
|
207651
|
+
// 与 systemd unit 里那条 Environment 等价——detached 兜底路径不能少,否则同一实例在两条
|
|
207652
|
+
// 启动路径下 spawn 出的会话会用不同版本的 CLI。
|
|
207653
|
+
env: { ...process.env, OASIS_CLI_HOME: INST.currentLink }
|
|
206922
207654
|
});
|
|
206923
207655
|
child.unref();
|
|
206924
207656
|
return child.pid ?? 0;
|
|
@@ -206966,7 +207698,7 @@ function selfUpdate() {
|
|
|
206966
207698
|
if (!cfg?.serverUrl) throw new Error("no config \u2014 run `oasis daemon start` first");
|
|
206967
207699
|
const nameArg = cfg.name ? ` --name '${cfg.name.replace(/'/g, "'\\''")}'` : "";
|
|
206968
207700
|
const oasisBin = path32.join(NPM_PREFIX, "bin", "oasis");
|
|
206969
|
-
const startCmd = `"${oasisBin}" daemon start --instance ${INSTANCE} --server-ws '${cfg.serverUrl}'${nameArg}`;
|
|
207701
|
+
const startCmd = `"${oasisBin}" daemon start --upgrade --instance ${INSTANCE} --server-ws '${cfg.serverUrl}'${nameArg}`;
|
|
206970
207702
|
const OFFICIAL = "https://registry.npmjs.org/";
|
|
206971
207703
|
const installOfficial = `npm install -g --prefer-online --registry '${OFFICIAL}' --prefix '${NPM_PREFIX}' '${PKG_NAME}@latest'`;
|
|
206972
207704
|
const installFallback = `npm install -g --prefer-online --prefix '${NPM_PREFIX}' '${PKG_NAME}@latest'`;
|
|
@@ -206992,36 +207724,110 @@ function selfUpdate() {
|
|
|
206992
207724
|
child.unref();
|
|
206993
207725
|
}
|
|
206994
207726
|
}
|
|
206995
|
-
function resolveInstance(explicit
|
|
207727
|
+
function resolveInstance(explicit) {
|
|
207728
|
+
if (!explicit) return DEFAULT_INSTANCE;
|
|
207729
|
+
if (!isValidInstanceName(explicit)) {
|
|
207730
|
+
console.error(`[oasis] \u975E\u6CD5\u5B9E\u4F8B\u540D "${explicit}"\uFF1A\u53EA\u5141\u8BB8\u5C0F\u5199\u5B57\u6BCD/\u6570\u5B57/.-_\uFF0C\u4E14\u4EE5\u5B57\u6BCD\u6570\u5B57\u5F00\u5934`);
|
|
207731
|
+
process.exit(1);
|
|
207732
|
+
}
|
|
207733
|
+
return explicit;
|
|
207734
|
+
}
|
|
207735
|
+
function currentLabelOf(inst) {
|
|
207736
|
+
try {
|
|
207737
|
+
return path32.basename(fs38.realpathSync(inst.currentLink));
|
|
207738
|
+
} catch {
|
|
207739
|
+
return null;
|
|
207740
|
+
}
|
|
207741
|
+
}
|
|
207742
|
+
function instanceHasSoftware(inst) {
|
|
207743
|
+
return fs38.existsSync(inst.binFile) && currentLabelOf(inst) !== null;
|
|
207744
|
+
}
|
|
207745
|
+
function newInstanceName() {
|
|
207746
|
+
const existing = new Set(listInstances());
|
|
207747
|
+
if (!existing.has(DEFAULT_INSTANCE)) return DEFAULT_INSTANCE;
|
|
207748
|
+
for (; ; ) {
|
|
207749
|
+
const n = `inst-${(0, import_node_crypto52.randomBytes)(3).toString("hex")}`;
|
|
207750
|
+
if (!existing.has(n)) return n;
|
|
207751
|
+
}
|
|
207752
|
+
}
|
|
207753
|
+
function planStart(explicit, serverUrl) {
|
|
206996
207754
|
if (explicit) {
|
|
206997
|
-
|
|
206998
|
-
|
|
206999
|
-
process.exit(1);
|
|
207000
|
-
}
|
|
207001
|
-
return explicit;
|
|
207755
|
+
const name = resolveInstance(explicit);
|
|
207756
|
+
return { mode: fs38.existsSync(instancePaths(name).configFile) ? "resume" : "new", names: [name] };
|
|
207002
207757
|
}
|
|
207003
|
-
const
|
|
207004
|
-
if (
|
|
207005
|
-
if (existing.length === 1) return existing[0];
|
|
207006
|
-
const demand = (why) => {
|
|
207007
|
-
console.error(`[oasis] \u672C\u673A\u6709 ${existing.length} \u4E2A daemon \u5B9E\u4F8B\uFF08${existing.join(", ")}\uFF09\uFF1A${why}`);
|
|
207008
|
-
console.error(` \u8BF7\u7528 --instance <name> \u663E\u5F0F\u6307\u5B9A\u3002`);
|
|
207009
|
-
process.exit(1);
|
|
207010
|
-
};
|
|
207011
|
-
if (!serverUrl) return demand("\u6CA1\u7ED9 --server-ws\uFF0C\u65E0\u4ECE\u5224\u65AD\u4F60\u8981\u54EA\u4E00\u4E2A");
|
|
207758
|
+
const all = listInstances();
|
|
207759
|
+
if (all.length === 0) return { mode: "new", names: [DEFAULT_INSTANCE] };
|
|
207012
207760
|
const want = normalizeServerUrl(serverUrl);
|
|
207013
|
-
const
|
|
207761
|
+
const hits = all.filter((n) => {
|
|
207014
207762
|
const c = readConfigAt(instancePaths(n).configFile);
|
|
207015
207763
|
return !!c?.serverUrl && normalizeServerUrl(c.serverUrl) === want;
|
|
207016
207764
|
});
|
|
207017
|
-
if (
|
|
207018
|
-
|
|
207019
|
-
|
|
207020
|
-
|
|
207021
|
-
|
|
207022
|
-
|
|
207765
|
+
if (hits.length === 0) return { mode: "new", names: [newInstanceName()] };
|
|
207766
|
+
return { mode: "resume", names: hits };
|
|
207767
|
+
}
|
|
207768
|
+
async function startOneInstance(o) {
|
|
207769
|
+
const { serverUrl, upgrade, flags } = o;
|
|
207770
|
+
const prev = readConfig();
|
|
207771
|
+
if (prev?.serverUrl && normalizeServerUrl(prev.serverUrl) !== normalizeServerUrl(serverUrl)) {
|
|
207772
|
+
console.error(`[oasis] \u26A0 \u5B9E\u4F8B "${INSTANCE}" \u539F\u672C\u8FDE ${prev.serverUrl}\uFF0C\u672C\u6B21\u5C06\u6539\u8FDE ${serverUrl}\u2014\u2014\u8FD9\u662F**\u8FC1\u79FB**\u4E0D\u662F\u65B0\u589E\u3002`);
|
|
207773
|
+
console.error(`[oasis] \u60F3\u8BA9\u4E24\u4E2A\u63A7\u5236\u9762\u5E76\u5B58\uFF0C\u8BF7\u4E2D\u6B62\uFF08Ctrl-C\uFF09\u5E76\u6539\u7528\uFF1A`);
|
|
207774
|
+
console.error(`[oasis] oasis daemon start --instance <\u65B0\u540D\u5B57> --server-ws ${serverUrl} --enroll-token <ent_...>`);
|
|
207775
|
+
}
|
|
207776
|
+
const serverChanged = !!prev?.serverUrl && prev.serverUrl !== serverUrl;
|
|
207777
|
+
let nodeId = serverChanged ? void 0 : prev?.nodeId;
|
|
207778
|
+
let token = serverChanged ? flags.get("token") ?? process.env["OASIS_NODE_TOKEN"] : flags.get("token") ?? process.env["OASIS_NODE_TOKEN"] ?? prev?.token;
|
|
207779
|
+
const enrollToken = o.mode === "new" ? flags.get("enroll-token") ?? process.env["OASIS_ENROLL_TOKEN"] : void 0;
|
|
207780
|
+
const derivedNodeId = derivedNodeIdFor(INSTANCE);
|
|
207781
|
+
if (enrollToken) {
|
|
207782
|
+
const res = await fetch(`${httpBase(serverUrl)}/api/nodes/exchange`, {
|
|
207783
|
+
method: "POST",
|
|
207784
|
+
headers: { "content-type": "application/json" },
|
|
207785
|
+
body: JSON.stringify({ enrollToken, nodeId: derivedNodeId, hostname: os13.hostname() })
|
|
207786
|
+
});
|
|
207787
|
+
if (!res.ok) throw new Error(`enroll token exchange failed: HTTP ${res.status}`);
|
|
207788
|
+
const body = await res.json();
|
|
207789
|
+
token = body.token;
|
|
207790
|
+
nodeId = body.nodeId;
|
|
207791
|
+
} else if (prev?.nodeId && prev?.token && !serverChanged) {
|
|
207792
|
+
nodeId = prev.nodeId;
|
|
207793
|
+
token = prev.token;
|
|
207794
|
+
}
|
|
207795
|
+
if (!token || !nodeId) {
|
|
207796
|
+
console.error("Usage: oasis daemon start --server-ws <wss://...> (--enroll-token <ent_...> | --token <ont_...>) [--name <name>] [--instance <name>] [--upgrade]");
|
|
207797
|
+
process.exit(1);
|
|
207798
|
+
}
|
|
207799
|
+
const name = o.applyName ? flags.get("name") ?? prev?.name : prev?.name;
|
|
207800
|
+
const nameChanged = prev?.name !== void 0 && name !== prev.name;
|
|
207801
|
+
saveConfig({ serverUrl, token, nodeId, ...name ? { name } : {}, reportRuntimesOnNextStart: true });
|
|
207802
|
+
const before = currentLabelOf(INST);
|
|
207803
|
+
let label = before;
|
|
207804
|
+
if (!instanceHasSoftware(INST) || upgrade) {
|
|
207805
|
+
const next = versionLabel(process.argv[1], PKG_VERSION, NPM_PREFIX);
|
|
207806
|
+
if (before && isLocalLabel(before) && before !== next) {
|
|
207807
|
+
console.log(` \u26A0 \u5373\u5C06\u7528 ${next} \u8986\u76D6\u672C\u5B9E\u4F8B\u6B63\u5728\u8DD1\u7684\u672C\u5730\u6784\u5EFA ${before}`);
|
|
207808
|
+
}
|
|
207809
|
+
label = installSoftware(next);
|
|
207810
|
+
} else {
|
|
207811
|
+
fs38.mkdirSync(path32.dirname(LOCAL_BIN), { recursive: true });
|
|
207812
|
+
fs38.writeFileSync(LOCAL_BIN, shimScript(), { mode: 493 });
|
|
207023
207813
|
}
|
|
207024
|
-
|
|
207814
|
+
const pidNow = readPid();
|
|
207815
|
+
const alive = !!pidNow && isRunning2(pidNow);
|
|
207816
|
+
if (alive && label === before && !serverChanged && !nameChanged) {
|
|
207817
|
+
console.log(`\u2713 ${INSTANCE}\uFF1A\u5DF2\u5728\u8DD1\uFF08PID ${pidNow}\uFF0C${label ?? "?"}\uFF09\uFF0C\u7248\u672C\u4E0E\u914D\u7F6E\u5747\u672A\u53D8\uFF0C\u672A\u91CD\u542F`);
|
|
207818
|
+
return;
|
|
207819
|
+
}
|
|
207820
|
+
stopDaemon(INSTANCE);
|
|
207821
|
+
const tookOver = setupAutostart(INSTANCE);
|
|
207822
|
+
const suffix = nameChanged ? ` [name updated: ${prev.name} \u2192 ${name}]` : "";
|
|
207823
|
+
if (!tookOver) {
|
|
207824
|
+
const pid = spawnDaemon();
|
|
207825
|
+
console.log(`\u2713 daemon started (instance ${INSTANCE}, PID ${pid})${suffix}`);
|
|
207826
|
+
} else {
|
|
207827
|
+
console.log(`\u2713 daemon started via systemd (${systemdUnitName(INSTANCE)})${suffix}`);
|
|
207828
|
+
}
|
|
207829
|
+
console.log(` instance: ${INSTANCE} nodeId: ${nodeId} version: ${label ?? "?"} config: ${INST.configFile}`);
|
|
207830
|
+
console.log(` logs: tail -f ${LOG_FILE}`);
|
|
207025
207831
|
}
|
|
207026
207832
|
function derivedNodeIdFor(name) {
|
|
207027
207833
|
if (name === DEFAULT_INSTANCE) return resolveNodeId();
|
|
@@ -207047,12 +207853,7 @@ void (async () => {
|
|
|
207047
207853
|
}
|
|
207048
207854
|
const flags = parseFlags(dargs);
|
|
207049
207855
|
if (cmd === "--_daemon") {
|
|
207050
|
-
|
|
207051
|
-
if (!isValidInstanceName(daemonInstance)) {
|
|
207052
|
-
console.error(`[oasis] \u975E\u6CD5\u5B9E\u4F8B\u540D "${daemonInstance}"`);
|
|
207053
|
-
process.exit(1);
|
|
207054
|
-
}
|
|
207055
|
-
useInstance(daemonInstance);
|
|
207856
|
+
useInstance(resolveInstance(flags.get("instance")));
|
|
207056
207857
|
const cfg = readConfig();
|
|
207057
207858
|
if (!cfg) {
|
|
207058
207859
|
console.error("[oasis] no config \u2014 run `oasis daemon start` first");
|
|
@@ -207132,69 +207933,37 @@ void (async () => {
|
|
|
207132
207933
|
return;
|
|
207133
207934
|
}
|
|
207134
207935
|
if (dsub === "start") {
|
|
207936
|
+
const explicit = flags.get("instance") || void 0;
|
|
207937
|
+
const upgrade = flags.has("upgrade");
|
|
207135
207938
|
const serverUrlFlag = flags.get("server-ws") ?? process.env["OASIS_SERVER_WS"];
|
|
207136
|
-
|
|
207137
|
-
const
|
|
207138
|
-
const serverUrl = serverUrlFlag ?? prev?.serverUrl;
|
|
207939
|
+
const probe = explicit ?? listInstances()[0] ?? DEFAULT_INSTANCE;
|
|
207940
|
+
const serverUrl = serverUrlFlag ?? readConfigAt(instancePaths(probe).configFile)?.serverUrl;
|
|
207139
207941
|
if (!serverUrl) {
|
|
207140
|
-
console.error("Usage: oasis daemon start --server-ws <wss://...> (--enroll-token <ent_...> | --token <ont_...>) [--name <name>] [--instance <name>]");
|
|
207942
|
+
console.error("Usage: oasis daemon start --server-ws <wss://...> (--enroll-token <ent_...> | --token <ont_...>) [--name <name>] [--instance <name>] [--upgrade]");
|
|
207141
207943
|
process.exit(1);
|
|
207142
207944
|
}
|
|
207143
|
-
|
|
207144
|
-
|
|
207145
|
-
console.
|
|
207146
|
-
|
|
207147
|
-
|
|
207148
|
-
const serverChanged = !!prev?.serverUrl && prev.serverUrl !== serverUrl;
|
|
207149
|
-
let nodeId = serverChanged ? void 0 : prev?.nodeId;
|
|
207150
|
-
let token = serverChanged ? flags.get("token") ?? process.env["OASIS_NODE_TOKEN"] : flags.get("token") ?? process.env["OASIS_NODE_TOKEN"] ?? prev?.token;
|
|
207151
|
-
const enrollToken = flags.get("enroll-token") ?? process.env["OASIS_ENROLL_TOKEN"];
|
|
207152
|
-
const derivedNodeId = derivedNodeIdFor(INSTANCE);
|
|
207153
|
-
if (enrollToken) {
|
|
207154
|
-
const res = await fetch(`${httpBase(serverUrl)}/api/nodes/exchange`, {
|
|
207155
|
-
method: "POST",
|
|
207156
|
-
headers: { "content-type": "application/json" },
|
|
207157
|
-
body: JSON.stringify({ enrollToken, nodeId: derivedNodeId, hostname: os13.hostname() })
|
|
207158
|
-
});
|
|
207159
|
-
if (!res.ok) throw new Error(`enroll token exchange failed: HTTP ${res.status}`);
|
|
207160
|
-
const body = await res.json();
|
|
207161
|
-
token = body.token;
|
|
207162
|
-
nodeId = body.nodeId;
|
|
207163
|
-
} else if (prev?.nodeId && prev?.token && !serverChanged) {
|
|
207164
|
-
nodeId = prev.nodeId;
|
|
207165
|
-
token = prev.token;
|
|
207166
|
-
}
|
|
207167
|
-
if (!token || !nodeId) {
|
|
207168
|
-
console.error("Usage: oasis daemon start --server-ws <wss://...> (--enroll-token <ent_...> | --token <ont_...>) [--name <name>] [--instance <name>]");
|
|
207169
|
-
process.exit(1);
|
|
207945
|
+
const plan = planStart(explicit, serverUrl);
|
|
207946
|
+
if (plan.mode === "resume" && plan.names.length > 1) {
|
|
207947
|
+
console.log(`\u2192 \u672C\u673A\u6709 ${plan.names.length} \u4E2A\u5B9E\u4F8B\u8FDE\u7740\u8BE5\u670D\u52A1\u7AEF\uFF0C\u9010\u4E2A\u6062\u590D\uFF1A${plan.names.join(", ")}`);
|
|
207948
|
+
} else if (plan.mode === "new" && !explicit && listInstances().length > 0) {
|
|
207949
|
+
console.log(`\u2192 \u672C\u673A\u5DF2\u6709\u5B9E\u4F8B\uFF08${listInstances().join(", ")}\uFF09\uFF0C\u4F46\u90FD\u4E0D\u8FDE\u8BE5\u670D\u52A1\u7AEF\uFF1B\u65B0\u5EFA\u5B9E\u4F8B ${plan.names[0]}`);
|
|
207170
207950
|
}
|
|
207171
|
-
const name
|
|
207172
|
-
|
|
207173
|
-
|
|
207174
|
-
installSoftware();
|
|
207175
|
-
stopDaemon(INSTANCE);
|
|
207176
|
-
const tookOver = setupAutostart(INSTANCE);
|
|
207177
|
-
const suffix = `${nameChanged ? ` [name updated: ${prev.name} \u2192 ${name}]` : ""}`;
|
|
207178
|
-
if (!tookOver) {
|
|
207179
|
-
const pid = spawnDaemon();
|
|
207180
|
-
console.log(`\u2713 daemon started (instance ${INSTANCE}, PID ${pid})${suffix}`);
|
|
207181
|
-
} else {
|
|
207182
|
-
console.log(`\u2713 daemon started via systemd (${systemdUnitName(INSTANCE)})${suffix}`);
|
|
207951
|
+
for (const name of plan.names) {
|
|
207952
|
+
useInstance(name);
|
|
207953
|
+
await startOneInstance({ mode: plan.mode, serverUrl, upgrade, flags, applyName: plan.names.length === 1 });
|
|
207183
207954
|
}
|
|
207184
|
-
console.log(` instance: ${INSTANCE} nodeId: ${nodeId} config: ${INST.configFile}`);
|
|
207185
|
-
console.log(` logs: tail -f ${LOG_FILE}`);
|
|
207186
207955
|
if (!process.env["PATH"]?.includes(path32.dirname(LOCAL_BIN)))
|
|
207187
207956
|
console.log(` add to PATH: echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc`);
|
|
207188
207957
|
return;
|
|
207189
207958
|
}
|
|
207190
207959
|
if (dsub === "stop") {
|
|
207191
|
-
useInstance(resolveInstance(flags.get("instance")
|
|
207960
|
+
useInstance(resolveInstance(flags.get("instance")));
|
|
207192
207961
|
stopDaemon(INSTANCE);
|
|
207193
207962
|
console.log(`\u2713 stopped (instance ${INSTANCE})`);
|
|
207194
207963
|
return;
|
|
207195
207964
|
}
|
|
207196
207965
|
if (dsub === "restart") {
|
|
207197
|
-
useInstance(resolveInstance(flags.get("instance")
|
|
207966
|
+
useInstance(resolveInstance(flags.get("instance")));
|
|
207198
207967
|
stopDaemon(INSTANCE);
|
|
207199
207968
|
if (!readConfig()) {
|
|
207200
207969
|
console.error("no config \u2014 run `oasis daemon start` first");
|
|
@@ -207205,11 +207974,15 @@ void (async () => {
|
|
|
207205
207974
|
return;
|
|
207206
207975
|
}
|
|
207207
207976
|
if (dsub === "update") {
|
|
207208
|
-
useInstance(resolveInstance(flags.get("instance")
|
|
207977
|
+
useInstance(resolveInstance(flags.get("instance")));
|
|
207209
207978
|
if (!readConfig()?.serverUrl) {
|
|
207210
207979
|
console.error("no config \u2014 run `oasis daemon start` first");
|
|
207211
207980
|
process.exit(1);
|
|
207212
207981
|
}
|
|
207982
|
+
const cur = currentLabelOf(INST);
|
|
207983
|
+
if (cur && isLocalLabel(cur)) {
|
|
207984
|
+
console.log(` \u26A0 \u672C\u5B9E\u4F8B\u73B0\u5728\u8DD1\u7684\u662F\u672C\u5730\u6784\u5EFA ${cur}\uFF0C\u672C\u6B21\u66F4\u65B0\u4F1A\u7528 ${PKG_NAME}@latest \u8986\u76D6\u5B83\u3002`);
|
|
207985
|
+
}
|
|
207213
207986
|
console.log(`\u2192 \u66F4\u65B0 ${PKG_NAME} \u5230\u6700\u65B0\u7248\u672C\u5E76\u91CD\u542F\u8282\u70B9\u2026`);
|
|
207214
207987
|
selfUpdate();
|
|
207215
207988
|
console.log(`\u2713 \u66F4\u65B0\u5DF2\u89E6\u53D1\uFF08\u540E\u53F0\u62C9\u53D6 ${PKG_NAME}@latest \u5E76\u91CD\u542F\uFF09\uFF1B\u67E5\u770B\u65E5\u5FD7\uFF1Atail -f ${LOG_FILE}`);
|