@deepseek-ai/dsh-app-boot 0.1.7-alpha.1 → 0.1.7-alpha.2
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/README.i18n.yaml +2 -2
- package/README.md +2 -1
- package/README.zh.md +2 -1
- package/lib/index.js +36 -12
- package/lib/types/index.d.ts +21 -8
- package/package.json +18 -18
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/boot/app-boot/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 70a8e235ac5846061ea47e79e7dc575881110a50
|
|
6
|
+
README.zh.md: 9849b033e01827a4c50a22b72ea20deebb0fdd85
|
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ installFailLoud('dsh')
|
|
|
40
40
|
const ctx = await boot('dsh', resolveConfigPath(argv[2], process.env.DSH_SNAPSHOT))
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
With that entry point, startup keeps every plugin that can activate. An enabled failed plugin produces a labelled warning. A failed required entry makes startup dispose the whole app and exit nonzero; required ids absent from a profile and disabled required entries do not affect startup. The global required list covers shared Agent execution, application endpoints, and Web bootstrap/transport: `agent-loop`, `webserver`, `modules`, `connection`, `headless-runner`, `acp`, and `sdk-jsonrpc-server`.
|
|
43
|
+
`installFailLoud` writes one labelled `util.inspect` diagnostic to stderr for an unhandled rejection or an uncaught exception, awaits the surface's release hook under a fixed timeout, and exits 1; control never returns to the failed operation, because only the throw site knows which state is intact, and the event loop runs only until the release settles or times out. With that entry point, startup keeps every plugin that can activate. An enabled failed plugin produces a labelled warning. A failed required entry makes startup dispose the whole app and exit nonzero; required ids absent from a profile and disabled required entries do not affect startup. The global required list covers shared Agent execution, application endpoints, and Web bootstrap/transport: `agent-loop`, `webserver`, `modules`, `connection`, `headless-runner`, `acp`, and `sdk-jsonrpc-server`.
|
|
44
44
|
|
|
45
45
|
<a id="profiles"></a>
|
|
46
46
|
### Profiles
|
|
@@ -97,6 +97,7 @@ After the Loader settles, app-boot warns when only optional entries are inactive
|
|
|
97
97
|
| An injected service is unavailable | Warn; continue while the entry waits for its dependencies | Stop startup | Keep the entry waiting; adding the missing provider can activate it |
|
|
98
98
|
| HTTP port binding fails | Warn; continue without that endpoint | Stop startup | Keep the process running without the failed endpoint; corrected config can restore it |
|
|
99
99
|
| Detached asynchronous work outside the `apply()` return Promise produces an unhandled rejection | Fatal: dispose the app and exit nonzero | Fatal: dispose the app and exit nonzero | Fatal: dispose the app and exit nonzero, regardless of entry id |
|
|
100
|
+
| A synchronous callback (a stream `'data'` listener, a timer) throws an uncaught exception at any point in the process lifetime | Fatal: dispose the app and exit nonzero; the failed operation is not resumed | Fatal: dispose the app and exit nonzero; the failed operation is not resumed | Fatal: dispose the app and exit nonzero, regardless of entry id |
|
|
100
101
|
| Entry is absent or explicitly disabled | Ignore it | Ignore it | Do not activate it; no required-startup audit |
|
|
101
102
|
|
|
102
103
|
The required list above includes `modules` and `connection`; Web startup cannot succeed when either enabled entry fails. Failure of an optional provider can also prevent a required consumer from activating. Schema rejection before an existing entry updates is not a transactional rollback of sibling changes.
|
package/README.zh.md
CHANGED
|
@@ -40,7 +40,7 @@ installFailLoud('dsh')
|
|
|
40
40
|
const ctx = await boot('dsh', resolveConfigPath(argv[2], process.env.DSH_SNAPSHOT))
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
`installFailLoud` 会为未处理 rejection 或未捕获异常向 stderr 写一条带标签的 `util.inspect` 诊断,在固定超时内等待界面的 release 钩子,然后以 1 退出;控制流不会回到失败的操作,因为只有抛出点知道哪些状态仍然完整,事件循环只运行到 release 结束或超时。有了这个入口,启动会保留所有能够激活的插件。启用但失败的插件会产生带标签的警告。required entry 失败时,启动会拆卸整个应用并以非零码退出;profile 中不存在的 required id 和已禁用的 required entry 不影响启动。全局 required list 覆盖共享 Agent 执行、应用 endpoint,以及 Web 启动与传输:`agent-loop`、`webserver`、`modules`、`connection`、`headless-runner`、`acp` 和 `sdk-jsonrpc-server`。
|
|
44
44
|
|
|
45
45
|
<a id="profiles"></a>
|
|
46
46
|
### Profile
|
|
@@ -97,6 +97,7 @@ Loader 结算后,app-boot 在仅 optional 条目未激活时输出警告。如
|
|
|
97
97
|
| 注入的服务不可用 | 警告;继续,条目等待依赖 | 终止启动 | 条目继续等待;补上缺失的提供方后可以激活 |
|
|
98
98
|
| HTTP 端口绑定失败 | 警告;继续,但该端点不可用 | 终止启动 | 进程继续运行,但失败的端点不可用;修正配置后可以恢复 |
|
|
99
99
|
| 脱离 `apply()` 返回 Promise 的异步任务产生未处理 rejection | 致命错误:释放应用并以非零码退出 | 致命错误:释放应用并以非零码退出 | 致命错误:释放应用并以非零码退出,与条目 id 无关 |
|
|
100
|
+
| 同步回调(流的 `'data'` 监听器、定时器)在进程生命周期任一时刻抛出未捕获异常 | 致命错误:释放应用并以非零码退出;失败的操作不会被恢复 | 致命错误:释放应用并以非零码退出;失败的操作不会被恢复 | 致命错误:释放应用并以非零码退出,与条目 id 无关 |
|
|
100
101
|
| 条目缺失或被显式禁用 | 忽略 | 忽略 | 不激活该条目;不执行 required 启动审计 |
|
|
101
102
|
|
|
102
103
|
上面的 required 列表包含 `modules` 与 `connection`;只要其中一个已启用条目失败,Web 就无法成功启动。Optional 提供方失败也可能使 required 消费方无法激活。现有条目的新配置在更新前被 schema 校验拒绝,并不等于对兄弟插件的变更做事务回滚。
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire, isBuiltin } from "node:module";
|
|
2
2
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { parseEnv } from "node:util";
|
|
4
|
+
import { inspect, parseEnv } from "node:util";
|
|
5
5
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
|
|
6
6
|
import * as yaml from "js-yaml";
|
|
7
7
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
@@ -3363,11 +3363,22 @@ async function observeLoaderRejectionCheckpoint(reasons) {
|
|
|
3363
3363
|
*/
|
|
3364
3364
|
const FAIL_LOUD_RELEASE_TIMEOUT_MS = 2e3;
|
|
3365
3365
|
/**
|
|
3366
|
-
* Install before boot to turn
|
|
3367
|
-
*
|
|
3368
|
-
* {@link auditStartupEntries} is
|
|
3369
|
-
*
|
|
3370
|
-
*
|
|
3366
|
+
* Install before boot to turn an unhandled rejection or an uncaught exception,
|
|
3367
|
+
* at any point in the process lifetime, into one labelled stderr diagnostic and
|
|
3368
|
+
* `exit(1)`. A rejection already included by {@link auditStartupEntries} is
|
|
3369
|
+
* ignored during its process checkpoint; every other rejection and every
|
|
3370
|
+
* uncaught exception remains fatal. Control never returns to the failed
|
|
3371
|
+
* operation after either: only the throw site knows which state is intact, and
|
|
3372
|
+
* a listener that threw mid-update (a stream `'data'` handler, a half-applied
|
|
3373
|
+
* registry write) leaves silently wrong results behind if it were resumed. The
|
|
3374
|
+
* event loop keeps running only until the release hook settles or times out.
|
|
3375
|
+
* Stdout remains untouched for ACP; the returned function removes both handlers.
|
|
3376
|
+
*
|
|
3377
|
+
* The diagnostic is `util.inspect(err)`, not `err.stack`: a `node:fs` error's
|
|
3378
|
+
* `code`, `syscall`, and `path` and any `cause` chain are enumerable properties
|
|
3379
|
+
* that the stack line omits, and they are what a crash report needs. Once a
|
|
3380
|
+
* handler is installed Node prints nothing of its own, so this line is the
|
|
3381
|
+
* only record of the failure.
|
|
3371
3382
|
*
|
|
3372
3383
|
* The Loader mounts entries concurrently, so a surface that owns the terminal
|
|
3373
3384
|
* can already hold it when a sibling entry rejects. Exiting straight from the
|
|
@@ -3389,15 +3400,17 @@ const FAIL_LOUD_RELEASE_TIMEOUT_MS = 2e3;
|
|
|
3389
3400
|
* @param release - optional teardown awaited before exit, used by a
|
|
3390
3401
|
* terminal-owning surface to restore the terminal. Its own failure is
|
|
3391
3402
|
* swallowed because the pending fatal exit already owns the outcome.
|
|
3392
|
-
* @returns the uninstaller that removes
|
|
3403
|
+
* @returns the uninstaller that removes both handlers.
|
|
3393
3404
|
*/
|
|
3394
3405
|
function installFailLoud(binName, proc = process, release) {
|
|
3395
3406
|
let exiting = false;
|
|
3396
|
-
const
|
|
3397
|
-
if (assembledActivationRejections.has(err)) return;
|
|
3407
|
+
const report = (err, label) => {
|
|
3398
3408
|
if (exiting) return;
|
|
3399
3409
|
exiting = true;
|
|
3400
|
-
proc.stderr.write(`${binName}:
|
|
3410
|
+
proc.stderr.write(`${binName}: ${label}: ${inspect(err, {
|
|
3411
|
+
depth: 4,
|
|
3412
|
+
maxArrayLength: 50
|
|
3413
|
+
})}\n`);
|
|
3401
3414
|
if (release === void 0) {
|
|
3402
3415
|
proc.exit(1);
|
|
3403
3416
|
return;
|
|
@@ -3413,8 +3426,19 @@ function installFailLoud(binName, proc = process, release) {
|
|
|
3413
3426
|
proc.exit(1);
|
|
3414
3427
|
})();
|
|
3415
3428
|
};
|
|
3416
|
-
const
|
|
3417
|
-
|
|
3429
|
+
const onRejection = (err) => {
|
|
3430
|
+
if (assembledActivationRejections.has(err)) return;
|
|
3431
|
+
report(err, "fatal load failure");
|
|
3432
|
+
};
|
|
3433
|
+
const onException = (err) => {
|
|
3434
|
+
report(err, "fatal uncaught exception");
|
|
3435
|
+
};
|
|
3436
|
+
const uninstall = () => {
|
|
3437
|
+
proc.off("unhandledRejection", onRejection);
|
|
3438
|
+
proc.off("uncaughtException", onException);
|
|
3439
|
+
};
|
|
3440
|
+
proc.on("unhandledRejection", onRejection);
|
|
3441
|
+
proc.on("uncaughtException", onException);
|
|
3418
3442
|
return uninstall;
|
|
3419
3443
|
}
|
|
3420
3444
|
/**
|
package/lib/types/index.d.ts
CHANGED
|
@@ -144,13 +144,15 @@ export declare function renderConfigDump(binName: string, absoluteConfigPath: st
|
|
|
144
144
|
* entry creation was in flight.
|
|
145
145
|
*/
|
|
146
146
|
export declare function mountRootInclude(ctx: Context, absoluteConfigPath: string, patches?: readonly PatchOptions[], bareModuleBaseUrl?: string): Promise<Entry | undefined>;
|
|
147
|
+
/** The two process events {@link installFailLoud} turns into a fatal exit. */
|
|
148
|
+
export type FailLoudEvent = 'unhandledRejection' | 'uncaughtException';
|
|
147
149
|
/**
|
|
148
150
|
* The slice of `process` {@link installFailLoud} needs — injectable so tests
|
|
149
151
|
* exercise the handler without registering on (or exiting) the real process.
|
|
150
152
|
*/
|
|
151
153
|
export interface FailLoudProcess {
|
|
152
|
-
on(event:
|
|
153
|
-
off(event:
|
|
154
|
+
on(event: FailLoudEvent, handler: (err: unknown) => void): unknown;
|
|
155
|
+
off(event: FailLoudEvent, handler: (err: unknown) => void): unknown;
|
|
154
156
|
stderr: {
|
|
155
157
|
write(chunk: string): unknown;
|
|
156
158
|
};
|
|
@@ -167,11 +169,22 @@ export interface FailLoudProcess {
|
|
|
167
169
|
*/
|
|
168
170
|
export declare const FAIL_LOUD_RELEASE_TIMEOUT_MS = 2000;
|
|
169
171
|
/**
|
|
170
|
-
* Install before boot to turn
|
|
171
|
-
*
|
|
172
|
-
* {@link auditStartupEntries} is
|
|
173
|
-
*
|
|
174
|
-
*
|
|
172
|
+
* Install before boot to turn an unhandled rejection or an uncaught exception,
|
|
173
|
+
* at any point in the process lifetime, into one labelled stderr diagnostic and
|
|
174
|
+
* `exit(1)`. A rejection already included by {@link auditStartupEntries} is
|
|
175
|
+
* ignored during its process checkpoint; every other rejection and every
|
|
176
|
+
* uncaught exception remains fatal. Control never returns to the failed
|
|
177
|
+
* operation after either: only the throw site knows which state is intact, and
|
|
178
|
+
* a listener that threw mid-update (a stream `'data'` handler, a half-applied
|
|
179
|
+
* registry write) leaves silently wrong results behind if it were resumed. The
|
|
180
|
+
* event loop keeps running only until the release hook settles or times out.
|
|
181
|
+
* Stdout remains untouched for ACP; the returned function removes both handlers.
|
|
182
|
+
*
|
|
183
|
+
* The diagnostic is `util.inspect(err)`, not `err.stack`: a `node:fs` error's
|
|
184
|
+
* `code`, `syscall`, and `path` and any `cause` chain are enumerable properties
|
|
185
|
+
* that the stack line omits, and they are what a crash report needs. Once a
|
|
186
|
+
* handler is installed Node prints nothing of its own, so this line is the
|
|
187
|
+
* only record of the failure.
|
|
175
188
|
*
|
|
176
189
|
* The Loader mounts entries concurrently, so a surface that owns the terminal
|
|
177
190
|
* can already hold it when a sibling entry rejects. Exiting straight from the
|
|
@@ -193,7 +206,7 @@ export declare const FAIL_LOUD_RELEASE_TIMEOUT_MS = 2000;
|
|
|
193
206
|
* @param release - optional teardown awaited before exit, used by a
|
|
194
207
|
* terminal-owning surface to restore the terminal. Its own failure is
|
|
195
208
|
* swallowed because the pending fatal exit already owns the outcome.
|
|
196
|
-
* @returns the uninstaller that removes
|
|
209
|
+
* @returns the uninstaller that removes both handlers.
|
|
197
210
|
*/
|
|
198
211
|
export declare function installFailLoud(binName: string, proc?: FailLoudProcess, release?: () => Promise<void> | void): () => void;
|
|
199
212
|
interface InactiveEntry {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-app-boot",
|
|
3
3
|
"description": "Shared boot glue for the app bins: .env loading, fail-loud Loader guards, snapshot-aware config resolution, and the Loader boot sequence",
|
|
4
|
-
"version": "0.1.7-alpha.
|
|
4
|
+
"version": "0.1.7-alpha.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -37,27 +37,27 @@
|
|
|
37
37
|
"js-yaml": "^4.2.0",
|
|
38
38
|
"node-addon-require-builtin": "^0.1.6",
|
|
39
39
|
"resolve.exports": "^2.0.3",
|
|
40
|
-
"@deepseek-ai/dsh-package-manifest": "
|
|
40
|
+
"@deepseek-ai/dsh-package-manifest": "0.1.7-alpha.2"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@deepseek-ai/cordis-plugin-
|
|
44
|
-
"@deepseek-ai/cordis-plugin-
|
|
45
|
-
"@deepseek-ai/
|
|
46
|
-
"@deepseek-ai/dsh-
|
|
47
|
-
"@deepseek-ai/
|
|
48
|
-
"@deepseek-ai/dsh-
|
|
49
|
-
"@deepseek-ai/cordis
|
|
43
|
+
"@deepseek-ai/cordis-plugin-group": "~1.0.4",
|
|
44
|
+
"@deepseek-ai/cordis-plugin-include": "~1.0.9",
|
|
45
|
+
"@deepseek-ai/cordis-plugin-loader": "~1.0.5",
|
|
46
|
+
"@deepseek-ai/dsh-launch-environment": "0.1.7-alpha.2",
|
|
47
|
+
"@deepseek-ai/dsh-home-paths": "0.1.7-alpha.2",
|
|
48
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.7-alpha.2",
|
|
49
|
+
"@deepseek-ai/cordis": "~4.0.4"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/js-yaml": "^4.0.9",
|
|
53
|
-
"@deepseek-ai/schemastery": "
|
|
54
|
-
"@deepseek-ai/cordis-plugin-
|
|
55
|
-
"@deepseek-ai/cordis-plugin-
|
|
56
|
-
"@deepseek-ai/cordis-plugin-
|
|
57
|
-
"@deepseek-ai/
|
|
58
|
-
"@deepseek-ai/
|
|
59
|
-
"@deepseek-ai/dsh-
|
|
60
|
-
"@deepseek-ai/dsh-system-prompt": "
|
|
61
|
-
"@deepseek-ai/cordis": "
|
|
53
|
+
"@deepseek-ai/schemastery": "~3.18.4",
|
|
54
|
+
"@deepseek-ai/cordis-plugin-include": "~1.0.9",
|
|
55
|
+
"@deepseek-ai/cordis-plugin-group": "~1.0.4",
|
|
56
|
+
"@deepseek-ai/cordis-plugin-timer": "~1.1.6",
|
|
57
|
+
"@deepseek-ai/cordis-plugin-loader": "~1.0.5",
|
|
58
|
+
"@deepseek-ai/dsh-home-paths": "0.1.7-alpha.2",
|
|
59
|
+
"@deepseek-ai/dsh-launch-environment": "0.1.7-alpha.2",
|
|
60
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.7-alpha.2",
|
|
61
|
+
"@deepseek-ai/cordis": "~4.0.4"
|
|
62
62
|
}
|
|
63
63
|
}
|