oasis_test_v2 2.2.12 → 2.2.14
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 +213 -25
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -201815,6 +201815,15 @@ var init_provision = __esm({
|
|
|
201815
201815
|
});
|
|
201816
201816
|
|
|
201817
201817
|
// ../connectors/src/prepare-job.ts
|
|
201818
|
+
function loginShellShadow(commandName) {
|
|
201819
|
+
const home2 = process.env["HOME"];
|
|
201820
|
+
if (!home2) return "";
|
|
201821
|
+
for (const dir of [(0, import_node_path17.join)(home2, ".local", "bin"), (0, import_node_path17.join)(home2, "bin")]) {
|
|
201822
|
+
const candidate = (0, import_node_path17.join)(dir, commandName);
|
|
201823
|
+
if ((0, import_node_fs11.existsSync)(candidate)) return candidate;
|
|
201824
|
+
}
|
|
201825
|
+
return "";
|
|
201826
|
+
}
|
|
201818
201827
|
async function prepareConnectorsForJob(job, deps) {
|
|
201819
201828
|
const creds = job.connectorCreds ?? [];
|
|
201820
201829
|
const remoteWrappers = job.wrapperPaths ?? [];
|
|
@@ -201850,7 +201859,16 @@ async function prepareConnectorsForJob(job, deps) {
|
|
|
201850
201859
|
localWrappers.push(staged);
|
|
201851
201860
|
status.wrapperReady = true;
|
|
201852
201861
|
}
|
|
201853
|
-
|
|
201862
|
+
const shadow = loginShellShadow(connector.commandName);
|
|
201863
|
+
if (shadow) {
|
|
201864
|
+
status.shadowedBy = shadow;
|
|
201865
|
+
log(
|
|
201866
|
+
"[node-connectors]",
|
|
201867
|
+
`\u26A0 ${slug6}: creds + wrapper \u5DF2\u843D\u5728\u672C\u673A\uFF0C\u4F46 **${shadow} \u4F1A\u5728\u767B\u5F55 shell \u91CC\u6321\u4F4F wrapper**\u2014\u2014Ubuntu \u7684 ~/.profile \u628A $HOME/.local/bin \u524D\u7F6E\u5230 PATH \u6700\u524D\u3002agent \u4E00\u65E6\u7528 \`bash -lc\` \u8C03 ${connector.commandName}\uFF0C\u8D70\u5230\u7684\u662F\u90A3\u4E2A\u5BBF\u4E3B\u526F\u672C\u3001\u914D\u7F6E\u76EE\u5F55\u53C8\u88AB\u9694\u79BB\u6210\u7A7A\u7684\uFF0C\u7ED3\u679C\u5C31\u662F\u300C\u672A\u914D\u7F6E\u300D\u3002\u5904\u7406\u529E\u6CD5\uFF1A\u5220\u6389 ${shadow}\uFF08\u8FDE\u63A5\u5668\u81EA\u5E26\u7684\u771F CLI \u5728 ~/.oasis/connector-tools/bin \u4E0B\uFF0C\u4E0D\u53D7\u5F71\u54CD\uFF09\u3002`
|
|
201868
|
+
);
|
|
201869
|
+
} else {
|
|
201870
|
+
log("[node-connectors]", `${slug6}: creds + wrapper \u5DF2\u843D\u5728\u672C\u673A`);
|
|
201871
|
+
}
|
|
201854
201872
|
} catch (err) {
|
|
201855
201873
|
status.error = String(err);
|
|
201856
201874
|
log(
|
|
@@ -203664,6 +203682,14 @@ async function runView(kernel, oplog, engineStore, blobs, name, artifactId, para
|
|
|
203664
203682
|
throw new Error(`\u672A\u77E5\u89C6\u56FE: ${name}`);
|
|
203665
203683
|
}
|
|
203666
203684
|
}
|
|
203685
|
+
function resolveListenTarget(env, pid, port) {
|
|
203686
|
+
const count2 = Number(env["LISTEN_FDS"] ?? "0");
|
|
203687
|
+
const listenPid = env["LISTEN_PID"];
|
|
203688
|
+
if (Number.isFinite(count2) && count2 >= 1 && (!listenPid || listenPid === String(pid))) {
|
|
203689
|
+
return { fd: 3 };
|
|
203690
|
+
}
|
|
203691
|
+
return { port: port ?? 0, host: "0.0.0.0" };
|
|
203692
|
+
}
|
|
203667
203693
|
async function startOasisServer(opts) {
|
|
203668
203694
|
const resolveActor = opts.resolveActor ?? defaultResolveActor;
|
|
203669
203695
|
const defaultDraftPlannerIssuesStore = new MemoryWorkorderDraftPlannerIssuesStore();
|
|
@@ -206756,6 +206782,52 @@ ${composed}`;
|
|
|
206756
206782
|
}
|
|
206757
206783
|
return;
|
|
206758
206784
|
}
|
|
206785
|
+
if (url.pathname.startsWith("/api/connectors/") && /\/auth\/(begin|rollback|commit)$/.test(url.pathname) && req.method === "POST") {
|
|
206786
|
+
const m2 = url.pathname.match(/^\/api\/connectors\/([^/]+)\/auth\/(begin|rollback|commit)$/);
|
|
206787
|
+
try {
|
|
206788
|
+
if (!m2) throw new Error("bad path");
|
|
206789
|
+
const connectorId = decodeURIComponent(m2[1]);
|
|
206790
|
+
const op = m2[2];
|
|
206791
|
+
const { actorId } = JSON.parse(rawBody || "{}");
|
|
206792
|
+
if (!actorId) {
|
|
206793
|
+
res.writeHead(400).end(JSON.stringify({ error: "actorId required" }));
|
|
206794
|
+
return;
|
|
206795
|
+
}
|
|
206796
|
+
const svc = await connectorActorsService();
|
|
206797
|
+
if (!svc) throw new Error("actors service unavailable");
|
|
206798
|
+
const cacheKey = `${currentCompanyId ?? ""}::${actorId}::${connectorId}`;
|
|
206799
|
+
const now = Date.now();
|
|
206800
|
+
for (const [k2, v2] of connectorAuthSnapshots) {
|
|
206801
|
+
if (now - v2.at > CONNECTOR_AUTH_SNAPSHOT_TTL_MS) connectorAuthSnapshots.delete(k2);
|
|
206802
|
+
}
|
|
206803
|
+
if (op === "begin") {
|
|
206804
|
+
const rows = await svc.snapshotActorConnectorVars(actorId, connectorId);
|
|
206805
|
+
connectorAuthSnapshots.set(cacheKey, { rows, at: now });
|
|
206806
|
+
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ ok: true, snapshot: rows.length }));
|
|
206807
|
+
return;
|
|
206808
|
+
}
|
|
206809
|
+
if (op === "commit") {
|
|
206810
|
+
connectorAuthSnapshots.delete(cacheKey);
|
|
206811
|
+
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ ok: true }));
|
|
206812
|
+
return;
|
|
206813
|
+
}
|
|
206814
|
+
const snap = connectorAuthSnapshots.get(cacheKey);
|
|
206815
|
+
if (!snap) {
|
|
206816
|
+
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ ok: false, reason: "no_snapshot" }));
|
|
206817
|
+
return;
|
|
206818
|
+
}
|
|
206819
|
+
const out = await svc.restoreActorConnectorVars(
|
|
206820
|
+
actorId,
|
|
206821
|
+
connectorId,
|
|
206822
|
+
snap.rows
|
|
206823
|
+
);
|
|
206824
|
+
connectorAuthSnapshots.delete(cacheKey);
|
|
206825
|
+
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ ok: true, ...out }));
|
|
206826
|
+
} catch (e) {
|
|
206827
|
+
res.writeHead(500).end(JSON.stringify({ error: String(e) }));
|
|
206828
|
+
}
|
|
206829
|
+
return;
|
|
206830
|
+
}
|
|
206759
206831
|
if (req.method === "POST" && /^\/api\/connectors\/[^/]+\/disconnect$/.test(url.pathname)) {
|
|
206760
206832
|
const connId = decodeURIComponent(url.pathname.split("/")[3] ?? "");
|
|
206761
206833
|
const svc = await connectorActorsService();
|
|
@@ -207193,7 +207265,7 @@ ${composed}`;
|
|
|
207193
207265
|
grantedFrom = "installation";
|
|
207194
207266
|
}
|
|
207195
207267
|
}
|
|
207196
|
-
const missing = missingAppPermissions(effective);
|
|
207268
|
+
const missing = missingAppPermissions({ ...meta.permissions, ...effective });
|
|
207197
207269
|
const requestedAll = Object.entries(meta.permissions).filter(([, level]) => Boolean(level)).map(([key, level]) => ({ key, level: String(level) })).sort((a, b2) => a.key.localeCompare(b2.key));
|
|
207198
207270
|
const grantedKeys = new Set(
|
|
207199
207271
|
Object.entries(effective).filter(([, level]) => Boolean(level)).map(([k2]) => k2)
|
|
@@ -207218,13 +207290,19 @@ ${composed}`;
|
|
|
207218
207290
|
*/
|
|
207219
207291
|
otherInstallations: installs.filter((i) => i.installationId !== installationId).map((i) => ({ account: i.account, count: Object.keys(i.permissions).length })),
|
|
207220
207292
|
/**
|
|
207221
|
-
*
|
|
207222
|
-
*
|
|
207223
|
-
*
|
|
207224
|
-
* ⚠ 差集只说明「这一处安装现在给不了」,不等于「人没批」——同一个 App 装多处时
|
|
207225
|
-
* 批准是逐处的(见 `otherInstallations`)。界面别把这层推断说死。
|
|
207293
|
+
* ⚠ 保留只为**兼容老前端**(新卡片不再读它):新口径不分「已授权 / 申请中」,
|
|
207294
|
+
* 见 `detected`。这一格哪天确认没有旧客户端在读了就可以删。
|
|
207226
207295
|
*/
|
|
207227
207296
|
awaitingApproval: requestedAll.filter((r) => !grantedKeys.has(r.key)).map((r) => r.key),
|
|
207297
|
+
/**
|
|
207298
|
+
* **检测到的全部权限**(App 申请的 ∪ 真拿得到的),英文原名 + 级别,按名排序。
|
|
207299
|
+
* 界面就照这一份平铺成一排 chip,不分组、不计数、不解释审批状态。
|
|
207300
|
+
*/
|
|
207301
|
+
detected: [.../* @__PURE__ */ new Set([...requestedAll.map((r) => r.key), ...grantedKeys])].sort((a, b2) => a.localeCompare(b2)).map((key) => ({
|
|
207302
|
+
key,
|
|
207303
|
+
// 真给的级别优先;只在 App 侧见过的才回落成它申请的级别。
|
|
207304
|
+
level: String(effective[key] ?? meta.permissions[key] ?? "")
|
|
207305
|
+
})),
|
|
207228
207306
|
/**
|
|
207229
207307
|
* agent **真正拿得到**的全部权限(优先读 token 自己的权限位),一律用 GitHub 的英文原名 + 级别
|
|
207230
207308
|
* (2026-09-10 发起人:「所有权限都用英文原文,不区分是不是八项权限」)。
|
|
@@ -207630,7 +207708,7 @@ ${composed}`;
|
|
|
207630
207708
|
});
|
|
207631
207709
|
httpServer.keepAliveTimeout = 65e3;
|
|
207632
207710
|
httpServer.headersTimeout = 66e3;
|
|
207633
|
-
await new Promise((resolve10) => httpServer.listen(
|
|
207711
|
+
await new Promise((resolve10) => httpServer.listen(resolveListenTarget(process.env, process.pid, opts.port), resolve10));
|
|
207634
207712
|
const address = httpServer.address();
|
|
207635
207713
|
if (address === null || typeof address === "string") throw new Error("listen failed");
|
|
207636
207714
|
const baseUrl = `http://127.0.0.1:${address.port}`;
|
|
@@ -207649,19 +207727,46 @@ ${composed}`;
|
|
|
207649
207727
|
* 委派域没装配时返回 null,调用方照常。
|
|
207650
207728
|
*/
|
|
207651
207729
|
settleDelegationFromSweep: (childSessionId, reason, companyId, opts2) => delegationService?.settleFromSweep(childSessionId, reason, companyId, opts2) ?? Promise.resolve(null),
|
|
207652
|
-
|
|
207730
|
+
/**
|
|
207731
|
+
* @param opts.drainMs **优雅停机预算**(毫秒)。缺省 0 = 老行为:立刻掐断所有连接后关闭。
|
|
207732
|
+
*
|
|
207733
|
+
* 给了预算就改成「排空」:先 `close()` 停收新连接、`closeIdleConnections()` 放掉空闲的
|
|
207734
|
+
* keep-alive,**在途请求让它跑完**;预算到点还没走完的(SSE 这类长连接本来就不会自己结束)
|
|
207735
|
+
* 再 `closeAllConnections()` 强断。前端的流有重连(`hydrateAndAttach`),被强断能自愈;
|
|
207736
|
+
* 普通请求跑到一半被掐则是实打实的失败,所以要给它们这几百毫秒。
|
|
207737
|
+
*
|
|
207738
|
+
* 生产上这条由 `oasis serve` 的 SIGTERM 处理器传(见 cli.ts)——现在 systemd 是硬杀
|
|
207739
|
+
* (journal: `code=exited, status=143`),在途请求当场断。
|
|
207740
|
+
*/
|
|
207741
|
+
close: async (opts2) => {
|
|
207653
207742
|
if (broadcastTimer) clearInterval(broadcastTimer);
|
|
207654
207743
|
if (pendingSystemNoticesRecoveryTimer) clearInterval(pendingSystemNoticesRecoveryTimer);
|
|
207655
207744
|
await knowledgeDomain?.close();
|
|
207656
207745
|
channelWsSupervisor?.stop();
|
|
207746
|
+
const drainMs = opts2?.drainMs ?? 0;
|
|
207657
207747
|
await new Promise((resolve10, reject) => {
|
|
207658
|
-
|
|
207659
|
-
|
|
207748
|
+
let sweep;
|
|
207749
|
+
let force;
|
|
207750
|
+
httpServer.close((err) => {
|
|
207751
|
+
if (sweep) clearInterval(sweep);
|
|
207752
|
+
if (force) clearTimeout(force);
|
|
207753
|
+
if (err) reject(err);
|
|
207754
|
+
else resolve10();
|
|
207755
|
+
});
|
|
207756
|
+
if (drainMs > 0) {
|
|
207757
|
+
httpServer.closeIdleConnections();
|
|
207758
|
+
sweep = setInterval(() => httpServer.closeIdleConnections(), 100);
|
|
207759
|
+
sweep.unref?.();
|
|
207760
|
+
force = setTimeout(() => httpServer.closeAllConnections(), drainMs);
|
|
207761
|
+
force.unref?.();
|
|
207762
|
+
} else {
|
|
207763
|
+
httpServer.closeAllConnections();
|
|
207764
|
+
}
|
|
207660
207765
|
});
|
|
207661
207766
|
}
|
|
207662
207767
|
};
|
|
207663
207768
|
}
|
|
207664
|
-
var http, import_node_crypto37, githubAppPending, defaultResolveActor, enc, CHAT_OUTPUT_LIMIT, STREAMING_PATHS, PLANNER_BLOCKING_CODES, WILL_VERBS, GOVERNANCE_COMMANDS, READ_ONLY_COMMANDS, isAgent, isHuman;
|
|
207769
|
+
var http, import_node_crypto37, githubAppPending, connectorAuthSnapshots, CONNECTOR_AUTH_SNAPSHOT_TTL_MS, defaultResolveActor, enc, CHAT_OUTPUT_LIMIT, STREAMING_PATHS, PLANNER_BLOCKING_CODES, WILL_VERBS, GOVERNANCE_COMMANDS, READ_ONLY_COMMANDS, isAgent, isHuman;
|
|
207665
207770
|
var init_server3 = __esm({
|
|
207666
207771
|
"../server/src/server.ts"() {
|
|
207667
207772
|
"use strict";
|
|
@@ -207719,6 +207824,8 @@ var init_server3 = __esm({
|
|
|
207719
207824
|
init_src8();
|
|
207720
207825
|
init_continuation();
|
|
207721
207826
|
githubAppPending = new PendingAppCreations();
|
|
207827
|
+
connectorAuthSnapshots = /* @__PURE__ */ new Map();
|
|
207828
|
+
CONNECTOR_AUTH_SNAPSHOT_TTL_MS = 30 * 60 * 1e3;
|
|
207722
207829
|
defaultResolveActor = (token) => token.startsWith("token:") ? token.slice("token:".length) : null;
|
|
207723
207830
|
enc = (s2) => new TextEncoder().encode(s2);
|
|
207724
207831
|
CHAT_OUTPUT_LIMIT = 16e3;
|
|
@@ -210765,6 +210872,18 @@ var init_projection = __esm({
|
|
|
210765
210872
|
function isUncategorizedProjectId(projectId2) {
|
|
210766
210873
|
return projectId2 === UNCATEGORIZED_PROJECT_ID;
|
|
210767
210874
|
}
|
|
210875
|
+
function latestUncategorizedActivityAt(candidates) {
|
|
210876
|
+
let bestIso = UNCATEGORIZED_UNKNOWN_TIME;
|
|
210877
|
+
let bestMs = Date.parse(UNCATEGORIZED_UNKNOWN_TIME);
|
|
210878
|
+
for (const iso6 of candidates) {
|
|
210879
|
+
if (!iso6) continue;
|
|
210880
|
+
const ms = Date.parse(iso6);
|
|
210881
|
+
if (Number.isNaN(ms) || ms <= bestMs) continue;
|
|
210882
|
+
bestMs = ms;
|
|
210883
|
+
bestIso = iso6;
|
|
210884
|
+
}
|
|
210885
|
+
return bestIso;
|
|
210886
|
+
}
|
|
210768
210887
|
async function bindWorkorderProject(deps) {
|
|
210769
210888
|
const { workOrderId, projectId: projectId2 } = deps;
|
|
210770
210889
|
if (!projectId2) return null;
|
|
@@ -211400,6 +211519,10 @@ var init_service4 = __esm({
|
|
|
211400
211519
|
async buildUncategorizedProject() {
|
|
211401
211520
|
const workorders = await this.listProjectWorkorders(UNCATEGORIZED_PROJECT_ID);
|
|
211402
211521
|
const artifactList = await this.aggregateUncategorizedArtifacts();
|
|
211522
|
+
const updatedAt = latestUncategorizedActivityAt([
|
|
211523
|
+
...workorders.map((workorder) => workorder.updatedAt),
|
|
211524
|
+
...artifactList.artifacts.map((artifact) => artifact.updatedAt)
|
|
211525
|
+
]);
|
|
211403
211526
|
return {
|
|
211404
211527
|
id: UNCATEGORIZED_PROJECT_ID,
|
|
211405
211528
|
name: UNCATEGORIZED_PROJECT_NAME,
|
|
@@ -211412,7 +211535,7 @@ var init_service4 = __esm({
|
|
|
211412
211535
|
// 出 null 保持「取不到就 null」的域内红线,也不把一个不存在的 actor id 塞进契约。
|
|
211413
211536
|
createdBy: null,
|
|
211414
211537
|
createdAt: UNCATEGORIZED_UNKNOWN_TIME,
|
|
211415
|
-
updatedAt
|
|
211538
|
+
updatedAt
|
|
211416
211539
|
};
|
|
211417
211540
|
}
|
|
211418
211541
|
async aggregateUncategorizedArtifacts() {
|
|
@@ -219564,6 +219687,46 @@ ${input.description}
|
|
|
219564
219687
|
listActorConnectorConnections(actorId) {
|
|
219565
219688
|
return this.opts.store.listActorConnectorConnections(actorId);
|
|
219566
219689
|
}
|
|
219690
|
+
/**
|
|
219691
|
+
* 授权流程**开始前**给这名员工在这个连接器下的全部个人变量拍一张快照(原样密文,不解密)。
|
|
219692
|
+
*
|
|
219693
|
+
* 为什么需要它(2026-09-10 发起人:「如果本身已经有连接器身份,如果重新连接没走完,
|
|
219694
|
+
* 则保留原身份不覆盖,之前重连到一半把原来的身份覆盖了」):
|
|
219695
|
+
* 「重新连接」是**边走边落库**的——飞书在「创建应用」那一步就把 `FEISHU_APP_ID` /
|
|
219696
|
+
* `FEISHU_APP_SECRET` 覆盖掉了,GitHub 在 manifest 回调那一步就把 `GITHUB_APP_*` 覆盖掉了,
|
|
219697
|
+
* 而这两步都在「用户完成授权」**之前**。人中途关掉弹窗,旧身份就已经没了。
|
|
219698
|
+
*
|
|
219699
|
+
* 前端此前的回滚只删「这次新增的键」(基线差集),**对已存在的键无能为力**——它拿不到旧值
|
|
219700
|
+
* (读侧永远只给掩码)。所以快照必须在服务端拍、也在服务端写回。
|
|
219701
|
+
*/
|
|
219702
|
+
async snapshotActorConnectorVars(actorId, connectorId) {
|
|
219703
|
+
const rows = await this.opts.store.listVariables(actorId);
|
|
219704
|
+
return rows.filter((v2) => v2.scope === "personal" && v2.actorId === actorId && v2.connectorId === connectorId);
|
|
219705
|
+
}
|
|
219706
|
+
/**
|
|
219707
|
+
* 把快照原样写回:**快照里有的恢复旧值,快照之后新冒出来的键删掉**。
|
|
219708
|
+
*
|
|
219709
|
+
* 密文原样搬运,不经解密再加密——少一次明文落到内存里,也不受密钥轮换影响。
|
|
219710
|
+
* 返回改了几行,调用方好把「回滚做了什么」如实说出来(回滚静默是上一版的毛病)。
|
|
219711
|
+
*/
|
|
219712
|
+
async restoreActorConnectorVars(actorId, connectorId, snapshot) {
|
|
219713
|
+
const keep = new Map(snapshot.map((r) => [r.key, r]));
|
|
219714
|
+
const now = await this.snapshotActorConnectorVars(actorId, connectorId);
|
|
219715
|
+
let removed = 0;
|
|
219716
|
+
for (const row of now) {
|
|
219717
|
+
if (keep.has(row.key)) continue;
|
|
219718
|
+
await this.deleteVariable(row.key, actorId);
|
|
219719
|
+
removed += 1;
|
|
219720
|
+
}
|
|
219721
|
+
let restored = 0;
|
|
219722
|
+
for (const row of keep.values()) {
|
|
219723
|
+
const current = now.find((r) => r.key === row.key);
|
|
219724
|
+
if (current && current.valueEncrypted === row.valueEncrypted) continue;
|
|
219725
|
+
await this.opts.store.putVariable({ ...row, updatedAt: this.now() });
|
|
219726
|
+
restored += 1;
|
|
219727
|
+
}
|
|
219728
|
+
return { restored, removed };
|
|
219729
|
+
}
|
|
219567
219730
|
/**
|
|
219568
219731
|
* 员工连接器面板所需状态:连接记录 + 组织级已连接集合 + 该员工已授权(有个人 connector 变量)集合。
|
|
219569
219732
|
* 前端据此算每个 connector 的 hasRecord / 有效 enabled / connected(actor 或 global)。
|
|
@@ -223250,8 +223413,12 @@ function toApiProject(project) {
|
|
|
223250
223413
|
// - 取不到创建人 → `null`(老的手建项目 / 无分类虚拟卡),前端显示「—」;
|
|
223251
223414
|
// - 有 id 但名册解析不到 → 对象在、`name`/`avatar` 为 `null`(**不回落 id 尾段**)。
|
|
223252
223415
|
created_by: project.createdBy ? toApiProjectActorRef(project.createdBy) : null,
|
|
223253
|
-
//
|
|
223254
|
-
//
|
|
223416
|
+
// 「时间不可知」哨兵(`UNCATEGORIZED_UNKNOWN_TIME`)→ `null`,前端见 null 显示「—」而不是
|
|
223417
|
+
// 一串远古时间。**只有无分类虚拟卡会命中**,真项目不受影响(建项目时两个字段都写真时间戳)。
|
|
223418
|
+
// 命中的两种情形(2026-09-10 起):
|
|
223419
|
+
// · `created_at`:恒命中——桶不存在「被创建」这回事;
|
|
223420
|
+
// · `updated_at`:只在桶里空无一物时命中;桶里有工单/产物时是真时间(见 service.ts
|
|
223421
|
+
// `buildUncategorizedProject` 与 `latestUncategorizedActivityAt`)。
|
|
223255
223422
|
created_at: project.createdAt === UNCATEGORIZED_UNKNOWN_TIME ? null : project.createdAt,
|
|
223256
223423
|
updated_at: project.updatedAt === UNCATEGORIZED_UNKNOWN_TIME ? null : project.updatedAt
|
|
223257
223424
|
};
|
|
@@ -234744,6 +234911,12 @@ function payloadStr(payload, key) {
|
|
|
234744
234911
|
}
|
|
234745
234912
|
return "";
|
|
234746
234913
|
}
|
|
234914
|
+
function workorderGoal(title, prefix, suffix) {
|
|
234915
|
+
const subject = Array.from(title.replace(/\s+/g, " ").trim());
|
|
234916
|
+
const budget = 50 - Array.from(prefix + suffix).length;
|
|
234917
|
+
const shortTitle = subject.length > budget ? `${subject.slice(0, budget - 1).join("")}\u2026` : subject.join("");
|
|
234918
|
+
return `${prefix}${shortTitle}${suffix}`;
|
|
234919
|
+
}
|
|
234747
234920
|
function buildPlan(structure, title, ctx, opts = {}) {
|
|
234748
234921
|
const nodeOwners = payloadNodeOwners(ctx.payload);
|
|
234749
234922
|
const nodeReviewers = payloadNodeReviewers(ctx.payload);
|
|
@@ -234803,9 +234976,7 @@ function bugFixResolve(ctx) {
|
|
|
234803
234976
|
artifactType: "diagnosis",
|
|
234804
234977
|
title,
|
|
234805
234978
|
brief,
|
|
234806
|
-
|
|
234807
|
-
// 但字段本身在内核里有设计意图(workorderSpec 读它),故填上而非留空。
|
|
234808
|
-
goal: title,
|
|
234979
|
+
goal: workorderGoal(title, "\u4FEE\u590D\u300C", "\u300D\uFF0C\u9A8C\u8BC1\u6240\u62A5\u73B0\u8C61\u4E0D\u518D\u51FA\u73B0\u3002"),
|
|
234809
234980
|
...manager ? { manager } : {},
|
|
234810
234981
|
acceptanceCriteria: ["\u6309\u63CF\u8FF0\u91CC\u7684\u590D\u73B0\u65B9\u5F0F\u9A8C\u8BC1\uFF1A\u6240\u62A5\u73B0\u8C61\u4E0D\u518D\u51FA\u73B0", "\u6539\u52A8\u6CA1\u6709\u660E\u663E\u6253\u574F\u76F4\u63A5\u76F8\u90BB\u7684\u8DEF\u5F84"],
|
|
234811
234982
|
plan
|
|
@@ -234836,7 +235007,7 @@ function adoptExternalResolve(ctx) {
|
|
|
234836
235007
|
artifactType: "research",
|
|
234837
235008
|
title,
|
|
234838
235009
|
brief,
|
|
234839
|
-
goal: title,
|
|
235010
|
+
goal: workorderGoal(title, "\u56F4\u7ED5\u300C", "\u300D\u501F\u9274\u5916\u90E8\u65B9\u6848\uFF0C\u5B8C\u6210\u843D\u5730\u5E76\u9A8C\u8BC1\u6548\u679C\u3002"),
|
|
234840
235011
|
...manager ? { manager } : {},
|
|
234841
235012
|
acceptanceCriteria: [
|
|
234842
235013
|
"\u843D\u5730\u65B9\u6848\u9010\u6761\u8BF4\u660E\u4E86\u4E3A\u4EC0\u4E48\u4E0D\u76F4\u63A5\u91C7\u7528\u5DF2\u77E5\u89E3\u6CD5\uFF0C\u5E76\u5199\u660E\u6211\u4EEC\u65B9\u6848\u7684\u4EE3\u4EF7",
|
|
@@ -234867,7 +235038,7 @@ function simpleTaskResolve(ctx) {
|
|
|
234867
235038
|
artifactType: "research",
|
|
234868
235039
|
title,
|
|
234869
235040
|
brief,
|
|
234870
|
-
goal: title,
|
|
235041
|
+
goal: workorderGoal(title, "\u5B8C\u6210\u300C", "\u300D\uFF0C\u4EA4\u4ED8\u53D1\u8D77\u4EBA\u53EF\u76F4\u63A5\u4F7F\u7528\u7684\u7ED3\u679C\u3002"),
|
|
234871
235042
|
...manager ? { manager } : {},
|
|
234872
235043
|
plan
|
|
234873
235044
|
};
|
|
@@ -263976,7 +264147,8 @@ ${nodeFault}` : "");
|
|
|
263976
264147
|
baseUrl: server.baseUrl,
|
|
263977
264148
|
operatorToken,
|
|
263978
264149
|
__coordinatorSpawnProbe: coordinatorSpawnProbe,
|
|
263979
|
-
close
|
|
264150
|
+
/** @param opts.drainMs 优雅停机排空预算,原样透传给 HTTP 层(见 server.ts 的 close)。缺省 = 老行为。 */
|
|
264151
|
+
close: async (opts2) => {
|
|
263980
264152
|
for (const timer of dispatchTimers) clearInterval(timer);
|
|
263981
264153
|
clearInterval(slaTimer);
|
|
263982
264154
|
escalationDeliveryWorker.stop();
|
|
@@ -263995,7 +264167,7 @@ ${nodeFault}` : "");
|
|
|
263995
264167
|
if (boardWatchTimer) clearInterval(boardWatchTimer);
|
|
263996
264168
|
clearInterval(serverHeartbeatTimer);
|
|
263997
264169
|
hub?.close();
|
|
263998
|
-
await server.close();
|
|
264170
|
+
await server.close(opts2 ?? {});
|
|
263999
264171
|
if (pgPool) await pgPool.end().catch(() => {
|
|
264000
264172
|
});
|
|
264001
264173
|
releaseServeLock(lock);
|
|
@@ -268214,9 +268386,25 @@ async function runCli(argv, println = console.log, progressln = console.error) {
|
|
|
268214
268386
|
println(`oasis serve \u5C31\u7EEA\uFF1Ahttp://0.0.0.0:${port2}\uFF08\u672C\u673A\u8BBF\u95EE\u7528 http://127.0.0.1:${port2}\uFF09`);
|
|
268215
268387
|
println(`operator token \u5DF2\u5199\u5165 ${path35.join(dir, "operator-token")}\uFF08\u672C\u673A CLI \u81EA\u52A8\u8BFB\u53D6\uFF09`);
|
|
268216
268388
|
if (flags.get("dispatch") === "true") println(`\u8C03\u5EA6\u5FAA\u73AF\u5DF2\u542F\u52A8\uFF08claude-code adapter\uFF09`);
|
|
268217
|
-
|
|
268389
|
+
const drainMs = Number(process.env["OASIS_SHUTDOWN_DRAIN_MS"] ?? "3000");
|
|
268390
|
+
const shutdownBudgetMs = drainMs + 5e3;
|
|
268391
|
+
await new Promise((resolve10) => {
|
|
268392
|
+
let stopping = false;
|
|
268393
|
+
const stop = (signal) => {
|
|
268394
|
+
if (stopping) return;
|
|
268395
|
+
stopping = true;
|
|
268396
|
+
println(`[serve] \u6536\u5230 ${signal}\uFF0C\u5F00\u59CB\u4F18\u96C5\u505C\u673A\uFF08\u6392\u7A7A\u9884\u7B97 ${drainMs}ms\uFF09\u2026`);
|
|
268397
|
+
const hardExit = setTimeout(() => {
|
|
268398
|
+
println(`[serve] \u505C\u673A\u603B\u9884\u7B97 ${shutdownBudgetMs}ms \u7528\u5C3D\uFF0C\u5F3A\u5236\u9000\u51FA`);
|
|
268399
|
+
process.exit(0);
|
|
268400
|
+
}, shutdownBudgetMs);
|
|
268401
|
+
hardExit.unref?.();
|
|
268402
|
+
void handle.close({ drainMs }).then(() => println("[serve] \u5DF2\u4F18\u96C5\u505C\u673A")).catch((err) => println(`[serve] \u505C\u673A\u8FC7\u7A0B\u51FA\u9519\uFF08\u7167\u5E38\u9000\u51FA\uFF09\uFF1A${String(err)}`)).finally(() => resolve10());
|
|
268403
|
+
};
|
|
268404
|
+
process.once("SIGTERM", () => stop("SIGTERM"));
|
|
268405
|
+
process.once("SIGINT", () => stop("SIGINT"));
|
|
268218
268406
|
});
|
|
268219
|
-
|
|
268407
|
+
process.exit(0);
|
|
268220
268408
|
}
|
|
268221
268409
|
const base = flags.get("server") ?? process.env["OASIS_SERVER"] ?? "http://127.0.0.1:7320";
|
|
268222
268410
|
const tokenFile = path35.join(dir, "operator-token");
|
|
@@ -271244,7 +271432,7 @@ function shimScript() {
|
|
|
271244
271432
|
}
|
|
271245
271433
|
|
|
271246
271434
|
// src/index.ts
|
|
271247
|
-
var PKG_VERSION = true ? "2.2.
|
|
271435
|
+
var PKG_VERSION = true ? "2.2.14" : "dev";
|
|
271248
271436
|
var LOCAL_BIN = localBin();
|
|
271249
271437
|
var NPM_PREFIX = npmPrefix();
|
|
271250
271438
|
var INSTANCE = DEFAULT_INSTANCE;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oasis_test_v2",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.14",
|
|
4
4
|
"description": "Oasis node daemon + CLI — background daemon, auto-start, full server CLI",
|
|
5
5
|
"bin": {
|
|
6
6
|
"oasis": "./dist/index.js"
|
|
@@ -26,6 +26,6 @@
|
|
|
26
26
|
"node": ">=20"
|
|
27
27
|
},
|
|
28
28
|
"oasisRelease": {
|
|
29
|
-
"sourceHead": "
|
|
29
|
+
"sourceHead": "c6b9e871786ca57b2d1b01293c368ed043db700f"
|
|
30
30
|
}
|
|
31
31
|
}
|