oasis_test 0.1.117 → 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 +909 -156
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -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
|
|
|
@@ -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",
|
|
@@ -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
|
};
|
|
@@ -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();
|
|
@@ -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": {
|
|
@@ -206673,12 +207375,12 @@ var LEGACY_SYSTEMD_UNIT = "oasis-node.service";
|
|
|
206673
207375
|
var LEGACY_LAUNCHD_LABEL = "com.oasis.node";
|
|
206674
207376
|
|
|
206675
207377
|
// src/install.ts
|
|
206676
|
-
var
|
|
207378
|
+
var import_node_crypto51 = require("node:crypto");
|
|
206677
207379
|
var fs37 = __toESM(require("node:fs"));
|
|
206678
207380
|
var path31 = __toESM(require("node:path"));
|
|
206679
207381
|
function versionLabel(sourceFile, pkgVersion, npmPrefixDir) {
|
|
206680
207382
|
if (isFromNpmPrefix(sourceFile, npmPrefixDir)) return pkgVersion;
|
|
206681
|
-
const digest = (0,
|
|
207383
|
+
const digest = (0, import_node_crypto51.createHash)("sha256").update(fs37.readFileSync(sourceFile)).digest("hex").slice(0, 8);
|
|
206682
207384
|
return `${pkgVersion}+local.${digest}`;
|
|
206683
207385
|
}
|
|
206684
207386
|
function isFromNpmPrefix(sourceFile, npmPrefixDir) {
|
|
@@ -206770,7 +207472,7 @@ function shimScript() {
|
|
|
206770
207472
|
}
|
|
206771
207473
|
|
|
206772
207474
|
// src/index.ts
|
|
206773
|
-
var PKG_VERSION = true ? "0.1.
|
|
207475
|
+
var PKG_VERSION = true ? "0.1.118" : "dev";
|
|
206774
207476
|
var LOCAL_BIN = localBin();
|
|
206775
207477
|
var NPM_PREFIX = npmPrefix();
|
|
206776
207478
|
var INSTANCE = DEFAULT_INSTANCE;
|
|
@@ -206852,9 +207554,8 @@ function ensureRuntimeAssets(targetDir = BIN_DIR) {
|
|
|
206852
207554
|
console.error(`[oasis] connector runtime assets: \u4ECE ${synced.from} \u540C\u6B65\u4E86 ${synced.copied} \u4E2A .sh \u5230 ${targetDir}`);
|
|
206853
207555
|
}
|
|
206854
207556
|
}
|
|
206855
|
-
function installSoftware() {
|
|
207557
|
+
function installSoftware(label) {
|
|
206856
207558
|
const src = process.argv[1];
|
|
206857
|
-
const label = versionLabel(src, PKG_VERSION, NPM_PREFIX);
|
|
206858
207559
|
const vDir = versionDir(label);
|
|
206859
207560
|
installBinaryAtomic(src, path32.join(vDir, "oasis.js"));
|
|
206860
207561
|
ensureRuntimeAssets(vDir);
|
|
@@ -206992,20 +207693,12 @@ function stopDaemon(name) {
|
|
|
206992
207693
|
}
|
|
206993
207694
|
}
|
|
206994
207695
|
}
|
|
206995
|
-
function selfUpdate(
|
|
207696
|
+
function selfUpdate() {
|
|
206996
207697
|
const cfg = readConfig();
|
|
206997
207698
|
if (!cfg?.serverUrl) throw new Error("no config \u2014 run `oasis daemon start` first");
|
|
206998
|
-
if (cfg.pinnedVersion && !opts.force) {
|
|
206999
|
-
console.error(`[oasis] \u5B9E\u4F8B ${INSTANCE} \u5DF2\u9489\u5728 ${cfg.pinnedVersion}\uFF0C\u8DF3\u8FC7\u81EA\u66F4\u65B0\uFF08\u8981\u89E3\u9664\uFF1Aoasis daemon update --instance ${INSTANCE} --force\uFF09`);
|
|
207000
|
-
return false;
|
|
207001
|
-
}
|
|
207002
|
-
if (cfg.pinnedVersion && opts.force) {
|
|
207003
|
-
saveConfig({ ...cfg, pinnedVersion: void 0 });
|
|
207004
|
-
console.error(`[oasis] --force\uFF1A\u5DF2\u89E3\u9664 ${INSTANCE} \u7684\u7248\u672C\u9489\u6B7B\uFF08\u539F ${cfg.pinnedVersion}\uFF09`);
|
|
207005
|
-
}
|
|
207006
207699
|
const nameArg = cfg.name ? ` --name '${cfg.name.replace(/'/g, "'\\''")}'` : "";
|
|
207007
207700
|
const oasisBin = path32.join(NPM_PREFIX, "bin", "oasis");
|
|
207008
|
-
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}`;
|
|
207009
207702
|
const OFFICIAL = "https://registry.npmjs.org/";
|
|
207010
207703
|
const installOfficial = `npm install -g --prefer-online --registry '${OFFICIAL}' --prefix '${NPM_PREFIX}' '${PKG_NAME}@latest'`;
|
|
207011
207704
|
const installFallback = `npm install -g --prefer-online --prefix '${NPM_PREFIX}' '${PKG_NAME}@latest'`;
|
|
@@ -207030,7 +207723,6 @@ function selfUpdate(opts = {}) {
|
|
|
207030
207723
|
const child = (0, import_node_child_process19.spawn)("sh", ["-c", inner], { detached: true, stdio: ["ignore", log3, log3] });
|
|
207031
207724
|
child.unref();
|
|
207032
207725
|
}
|
|
207033
|
-
return true;
|
|
207034
207726
|
}
|
|
207035
207727
|
function resolveInstance(explicit) {
|
|
207036
207728
|
if (!explicit) return DEFAULT_INSTANCE;
|
|
@@ -207040,6 +207732,103 @@ function resolveInstance(explicit) {
|
|
|
207040
207732
|
}
|
|
207041
207733
|
return explicit;
|
|
207042
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) {
|
|
207754
|
+
if (explicit) {
|
|
207755
|
+
const name = resolveInstance(explicit);
|
|
207756
|
+
return { mode: fs38.existsSync(instancePaths(name).configFile) ? "resume" : "new", names: [name] };
|
|
207757
|
+
}
|
|
207758
|
+
const all = listInstances();
|
|
207759
|
+
if (all.length === 0) return { mode: "new", names: [DEFAULT_INSTANCE] };
|
|
207760
|
+
const want = normalizeServerUrl(serverUrl);
|
|
207761
|
+
const hits = all.filter((n) => {
|
|
207762
|
+
const c = readConfigAt(instancePaths(n).configFile);
|
|
207763
|
+
return !!c?.serverUrl && normalizeServerUrl(c.serverUrl) === want;
|
|
207764
|
+
});
|
|
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 });
|
|
207813
|
+
}
|
|
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}`);
|
|
207831
|
+
}
|
|
207043
207832
|
function derivedNodeIdFor(name) {
|
|
207044
207833
|
if (name === DEFAULT_INSTANCE) return resolveNodeId();
|
|
207045
207834
|
const user = (() => {
|
|
@@ -207144,61 +207933,25 @@ void (async () => {
|
|
|
207144
207933
|
return;
|
|
207145
207934
|
}
|
|
207146
207935
|
if (dsub === "start") {
|
|
207936
|
+
const explicit = flags.get("instance") || void 0;
|
|
207937
|
+
const upgrade = flags.has("upgrade");
|
|
207147
207938
|
const serverUrlFlag = flags.get("server-ws") ?? process.env["OASIS_SERVER_WS"];
|
|
207148
|
-
|
|
207149
|
-
const
|
|
207150
|
-
const serverUrl = serverUrlFlag ?? prev?.serverUrl;
|
|
207939
|
+
const probe = explicit ?? listInstances()[0] ?? DEFAULT_INSTANCE;
|
|
207940
|
+
const serverUrl = serverUrlFlag ?? readConfigAt(instancePaths(probe).configFile)?.serverUrl;
|
|
207151
207941
|
if (!serverUrl) {
|
|
207152
|
-
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]");
|
|
207153
207943
|
process.exit(1);
|
|
207154
207944
|
}
|
|
207155
|
-
|
|
207156
|
-
|
|
207157
|
-
console.
|
|
207158
|
-
|
|
207159
|
-
|
|
207160
|
-
const serverChanged = !!prev?.serverUrl && prev.serverUrl !== serverUrl;
|
|
207161
|
-
let nodeId = serverChanged ? void 0 : prev?.nodeId;
|
|
207162
|
-
let token = serverChanged ? flags.get("token") ?? process.env["OASIS_NODE_TOKEN"] : flags.get("token") ?? process.env["OASIS_NODE_TOKEN"] ?? prev?.token;
|
|
207163
|
-
const enrollToken = flags.get("enroll-token") ?? process.env["OASIS_ENROLL_TOKEN"];
|
|
207164
|
-
const derivedNodeId = derivedNodeIdFor(INSTANCE);
|
|
207165
|
-
if (enrollToken) {
|
|
207166
|
-
const res = await fetch(`${httpBase(serverUrl)}/api/nodes/exchange`, {
|
|
207167
|
-
method: "POST",
|
|
207168
|
-
headers: { "content-type": "application/json" },
|
|
207169
|
-
body: JSON.stringify({ enrollToken, nodeId: derivedNodeId, hostname: os13.hostname() })
|
|
207170
|
-
});
|
|
207171
|
-
if (!res.ok) throw new Error(`enroll token exchange failed: HTTP ${res.status}`);
|
|
207172
|
-
const body = await res.json();
|
|
207173
|
-
token = body.token;
|
|
207174
|
-
nodeId = body.nodeId;
|
|
207175
|
-
} else if (prev?.nodeId && prev?.token && !serverChanged) {
|
|
207176
|
-
nodeId = prev.nodeId;
|
|
207177
|
-
token = prev.token;
|
|
207178
|
-
}
|
|
207179
|
-
if (!token || !nodeId) {
|
|
207180
|
-
console.error("Usage: oasis daemon start --server-ws <wss://...> (--enroll-token <ent_...> | --token <ont_...>) [--name <name>] [--instance <name>]");
|
|
207181
|
-
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]}`);
|
|
207182
207950
|
}
|
|
207183
|
-
const name
|
|
207184
|
-
|
|
207185
|
-
|
|
207186
|
-
const installedLabel = installSoftware();
|
|
207187
|
-
if (isLocalLabel(installedLabel)) {
|
|
207188
|
-
saveConfig({ ...readConfig(), pinnedVersion: installedLabel });
|
|
207189
|
-
console.log(` \u672C\u5730\u6784\u5EFA \u2192 \u5DF2\u9489\u7248\u672C ${installedLabel}\uFF08\u81EA\u66F4\u65B0\u5BF9\u672C\u5B9E\u4F8B\u5931\u6548\uFF1B\u89E3\u9664\uFF1Aoasis daemon update --instance ${INSTANCE} --force\uFF09`);
|
|
207951
|
+
for (const name of plan.names) {
|
|
207952
|
+
useInstance(name);
|
|
207953
|
+
await startOneInstance({ mode: plan.mode, serverUrl, upgrade, flags, applyName: plan.names.length === 1 });
|
|
207190
207954
|
}
|
|
207191
|
-
stopDaemon(INSTANCE);
|
|
207192
|
-
const tookOver = setupAutostart(INSTANCE);
|
|
207193
|
-
const suffix = `${nameChanged ? ` [name updated: ${prev.name} \u2192 ${name}]` : ""}`;
|
|
207194
|
-
if (!tookOver) {
|
|
207195
|
-
const pid = spawnDaemon();
|
|
207196
|
-
console.log(`\u2713 daemon started (instance ${INSTANCE}, PID ${pid})${suffix}`);
|
|
207197
|
-
} else {
|
|
207198
|
-
console.log(`\u2713 daemon started via systemd (${systemdUnitName(INSTANCE)})${suffix}`);
|
|
207199
|
-
}
|
|
207200
|
-
console.log(` instance: ${INSTANCE} nodeId: ${nodeId} config: ${INST.configFile}`);
|
|
207201
|
-
console.log(` logs: tail -f ${LOG_FILE}`);
|
|
207202
207955
|
if (!process.env["PATH"]?.includes(path32.dirname(LOCAL_BIN)))
|
|
207203
207956
|
console.log(` add to PATH: echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc`);
|
|
207204
207957
|
return;
|
|
@@ -207226,13 +207979,13 @@ void (async () => {
|
|
|
207226
207979
|
console.error("no config \u2014 run `oasis daemon start` first");
|
|
207227
207980
|
process.exit(1);
|
|
207228
207981
|
}
|
|
207229
|
-
const
|
|
207230
|
-
|
|
207231
|
-
|
|
207232
|
-
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}`);
|
|
207233
|
-
} else {
|
|
207234
|
-
console.log(`\uFF08\u672A\u89E6\u53D1\u66F4\u65B0\u2014\u2014\u672C\u5B9E\u4F8B\u5DF2\u9489\u7248\u672C\uFF09`);
|
|
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`);
|
|
207235
207985
|
}
|
|
207986
|
+
console.log(`\u2192 \u66F4\u65B0 ${PKG_NAME} \u5230\u6700\u65B0\u7248\u672C\u5E76\u91CD\u542F\u8282\u70B9\u2026`);
|
|
207987
|
+
selfUpdate();
|
|
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}`);
|
|
207236
207989
|
return;
|
|
207237
207990
|
}
|
|
207238
207991
|
if (dsub === "status") {
|