@qlingzzy/qling 1.2.0 → 1.2.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/CHANGELOG.md +17 -0
- package/README.en.md +3 -3
- package/README.md +3 -3
- package/dist/agent/durable-session-supervisor.d.ts.map +1 -0
- package/dist/{session → agent}/durable-session-supervisor.js +3 -3
- package/dist/daemon.js +1 -1
- package/dist/execution/failure-classifier.d.ts.map +1 -1
- package/dist/execution/failure-classifier.js +4 -1
- package/dist/index.js +10 -0
- package/dist/repl.d.ts +3 -0
- package/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +4 -1
- package/dist/session/session-scheduler.d.ts.map +1 -1
- package/dist/session/session-scheduler.js +9 -1
- package/dist/skills/registry.js +1 -1
- package/dist/slash-ports.d.ts +24 -0
- package/dist/slash-ports.d.ts.map +1 -0
- package/dist/slash-ports.js +31 -0
- package/dist/tui/streaming-repl.d.ts +4 -0
- package/dist/tui/streaming-repl.d.ts.map +1 -1
- package/dist/tui/streaming-repl.js +5 -2
- package/dist/tui/streaming-tui.d.ts +4 -0
- package/dist/tui/streaming-tui.d.ts.map +1 -1
- package/dist/tui/streaming-tui.js +34 -6
- package/docs/install.md +12 -6
- package/package.json +3 -2
- package/scripts/build-portable-win.mjs +183 -37
- package/scripts/dep-layers.mjs +1 -0
- package/scripts/sync-winget-sha.mjs +56 -0
- package/scripts/validate-packaging.mjs +52 -14
- package/scripts/write-scoop-manifest.mjs +69 -0
- package/dist/session/durable-session-supervisor.d.ts.map +0 -1
- /package/dist/{session → agent}/durable-session-supervisor.d.ts +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## v1.2.2 (2026-07-15)
|
|
6
|
+
|
|
7
|
+
- Fix CRLF skill frontmatter parsing on Windows CI.
|
|
8
|
+
- Normalize Windows and POSIX target paths consistently in recovery fingerprints.
|
|
9
|
+
- Persist session task state atomically to prevent partial JSON reads.
|
|
10
|
+
- Install locked production dependencies in Windows portable builds and verify the staged launcher with `--version` and `doctor` before packaging.
|
|
11
|
+
- Provision Playwright Chromium for the required Linux dashboard smoke test.
|
|
12
|
+
|
|
13
|
+
## v1.2.1 (2026-07-15)
|
|
14
|
+
|
|
15
|
+
### Official distribution + strict layers + self-contained portable
|
|
16
|
+
|
|
17
|
+
- **分层**:清零反向依赖;`ci:check` 启用 `dep:layers --strict`;`installSlashPorts` + `DurableSessionSupervisor` 上提 `agent/`。
|
|
18
|
+
- **便携包**:`build:portable-win` 内嵌 Node 运行时(`runtime/node.exe`),无需本机安装 Node。
|
|
19
|
+
- **Scoop/winget**:manifest 指向 GitHub Release portable zip;多文件 winget 清单;官方 PR 按审阅修正 64bit 结构与 hash 格式。
|
|
20
|
+
- **公共 Scoop bucket**:https://github.com/Zzy-min/scoop-qling
|
|
21
|
+
|
|
5
22
|
## v1.2.0 (2026-07-14)
|
|
6
23
|
|
|
7
24
|
### Phase 8.0 — Sprint 5 分层治理 + 分发资产
|
package/README.en.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](#requirements)
|
|
4
4
|
[](LICENSE)
|
|
5
|
-
[](CHANGELOG.md)
|
|
6
6
|
[](https://github.com/Zzy-min/qling/releases)
|
|
7
7
|
|
|
8
8
|
**Qling** is a **local-first** AI Agent CLI workbench. It keeps sessions, tools, skills, permissions, diagnostics, long-running missions, and recovery controls in one auditable terminal console.
|
|
@@ -178,14 +178,14 @@ QLING_EVAL_LLM=1 npm run eval:llm
|
|
|
178
178
|
| Channel | Status |
|
|
179
179
|
|---------|--------|
|
|
180
180
|
| Source + bootstrap | Ready |
|
|
181
|
-
| npm `@qlingzzy/qling` | **Published** `1.2.
|
|
181
|
+
| npm `@qlingzzy/qling` | **Published** `1.2.2` — install with `--registry https://registry.npmjs.org/` if your default is a mirror |
|
|
182
182
|
| Scoop | Draft + real tarball SHA256; not in official buckets |
|
|
183
183
|
| winget | Draft YAML only (needs portable zip + SHA256) |
|
|
184
184
|
| Portable zip / single binary | Not yet |
|
|
185
185
|
|
|
186
186
|
## Version
|
|
187
187
|
|
|
188
|
-
- Package: `1.2.
|
|
188
|
+
- Package: `1.2.2` (see latest tag on GitHub for hotfixes)
|
|
189
189
|
- Changelog: [CHANGELOG.md](CHANGELOG.md)
|
|
190
190
|
- Specs / plans: `docs/superpowers/`
|
|
191
191
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](#环境要求)
|
|
4
4
|
[](LICENSE)
|
|
5
|
-
[](CHANGELOG.md)
|
|
6
6
|
|
|
7
7
|
[English](README.en.md) · [安装指南](docs/install.md) · [CHANGELOG](CHANGELOG.md)
|
|
8
8
|
|
|
@@ -63,7 +63,7 @@ qling
|
|
|
63
63
|
# 或从 GitHub 直接全局安装
|
|
64
64
|
npm install -g github:Zzy-min/qling
|
|
65
65
|
|
|
66
|
-
# npm 官方包(已发布 1.2.
|
|
66
|
+
# npm 官方包(已发布 1.2.2;作用域名,CLI 仍是 qling)
|
|
67
67
|
# 若默认 registry 是国内镜像,请显式指定官方源
|
|
68
68
|
npm install -g @qlingzzy/qling --registry https://registry.npmjs.org/
|
|
69
69
|
```
|
|
@@ -402,7 +402,7 @@ npm audit --registry=https://registry.npmjs.org --audit-level=high
|
|
|
402
402
|
|
|
403
403
|
## 版本与变更
|
|
404
404
|
|
|
405
|
-
- 当前版本:`1.2.
|
|
405
|
+
- 当前版本:`1.2.2`
|
|
406
406
|
|
|
407
407
|
- 完整变更历史:见 [CHANGELOG.md](CHANGELOG.md)
|
|
408
408
|
- 安装与分发:见 [docs/install.md](docs/install.md)、[packaging/](packaging/)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable-session-supervisor.d.ts","sourceRoot":"","sources":["../../src/agent/durable-session-supervisor.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED,qBAAa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA4B;IAChD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqB;IACrD,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,+BAA+B;IAMpD,KAAK,IAAI,IAAI;IAQP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAOb,IAAI;YAyBJ,iBAAiB;YAcjB,iBAAiB;YAiBjB,sBAAsB;YAiCtB,cAAc;YAoCd,iBAAiB;YAyBjB,WAAW;CA8B1B"}
|
|
@@ -4,9 +4,9 @@ import * as path from "path";
|
|
|
4
4
|
import { AgentLoop } from "../agent-loop.js";
|
|
5
5
|
import { applyConfigToProcessEnv, loadQlingConfig } from "../config.js";
|
|
6
6
|
import { buildToolRegistry } from "../tools/index.js";
|
|
7
|
-
import { SessionGoalController } from "
|
|
8
|
-
import { SessionGoalManager } from "
|
|
9
|
-
import { SessionScheduler } from "
|
|
7
|
+
import { SessionGoalController } from "../session/goal-controller.js";
|
|
8
|
+
import { SessionGoalManager } from "../session/session-goal-manager.js";
|
|
9
|
+
import { SessionScheduler } from "../session/session-scheduler.js";
|
|
10
10
|
export class DurableSessionSupervisor {
|
|
11
11
|
stateDir;
|
|
12
12
|
tickIntervalMs;
|
package/dist/daemon.js
CHANGED
|
@@ -13,7 +13,7 @@ import dotenv from "dotenv";
|
|
|
13
13
|
import { SessionScheduler } from "./session/session-scheduler.js";
|
|
14
14
|
import { SessionGoalController } from "./session/goal-controller.js";
|
|
15
15
|
import { SessionGoalManager } from "./session/session-goal-manager.js";
|
|
16
|
-
import { DurableSessionSupervisor } from "./
|
|
16
|
+
import { DurableSessionSupervisor } from "./agent/durable-session-supervisor.js";
|
|
17
17
|
import { formatDaemonVersion } from "./package-version.js";
|
|
18
18
|
const HOME_DIR = os.homedir();
|
|
19
19
|
const DEFAULT_STATE_DIR = path.join(HOME_DIR, ".qling");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"failure-classifier.d.ts","sourceRoot":"","sources":["../../src/execution/failure-classifier.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAmB,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEzE,UAAU,cAAc;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"failure-classifier.d.ts","sourceRoot":"","sources":["../../src/execution/failure-classifier.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAmB,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEzE,UAAU,cAAc;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAqBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE,cAAmB,GAAG,qBAAqB,CAgCnG;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,IAAI,CAAC,qBAAqB,EAAE,aAAa,CAAC,GAAG,MAAM,CAUpG"}
|
|
@@ -13,6 +13,9 @@ function normalizeMessage(value) {
|
|
|
13
13
|
.replace(/\s+/g, " ")
|
|
14
14
|
.trim();
|
|
15
15
|
}
|
|
16
|
+
function portableBasename(value) {
|
|
17
|
+
return value.includes("\\") ? path.win32.basename(value) : path.posix.basename(value);
|
|
18
|
+
}
|
|
16
19
|
export function classifyFailure(error, context = {}) {
|
|
17
20
|
const message = errorMessage(error);
|
|
18
21
|
const lower = message.toLowerCase();
|
|
@@ -54,7 +57,7 @@ export function classifyFailure(error, context = {}) {
|
|
|
54
57
|
return { ...classification, fingerprint: createFailureFingerprint(classification) };
|
|
55
58
|
}
|
|
56
59
|
export function createFailureFingerprint(failure) {
|
|
57
|
-
const target = failure.targetPath ?
|
|
60
|
+
const target = failure.targetPath ? portableBasename(failure.targetPath).toLowerCase() : "";
|
|
58
61
|
const stable = [
|
|
59
62
|
failure.category,
|
|
60
63
|
failure.tool ?? "",
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,8 @@ import axios from "axios";
|
|
|
15
15
|
import { AgentLoop } from "./agent-loop.js";
|
|
16
16
|
import { Repl } from "./repl.js";
|
|
17
17
|
import { StreamingREPL } from "./tui/streaming-repl.js";
|
|
18
|
+
import { installSlashPorts } from "./slash-ports.js";
|
|
19
|
+
import { handleSlashCommand, findSlashCompletion, formatSlashCommandPanel, formatGroupedSlashPanel, } from "./commands/index.js";
|
|
18
20
|
import { buildHelpText, formatCliError, parseCliArgs } from "./cli/startup-contract.js";
|
|
19
21
|
import { runBootstrap } from "./cli/bootstrap.js";
|
|
20
22
|
import { applyConfigToProcessEnv, loadQlingConfig } from "./config.js";
|
|
@@ -1111,6 +1113,14 @@ async function main() {
|
|
|
1111
1113
|
process.exit(1);
|
|
1112
1114
|
}
|
|
1113
1115
|
}
|
|
1116
|
+
installSlashPorts({
|
|
1117
|
+
handleSlashCommand,
|
|
1118
|
+
ui: {
|
|
1119
|
+
findSlashCompletion,
|
|
1120
|
+
formatSlashCommandPanel,
|
|
1121
|
+
formatGroupedSlashPanel,
|
|
1122
|
+
},
|
|
1123
|
+
});
|
|
1114
1124
|
if (decision.mode === "chat") {
|
|
1115
1125
|
const repl = new StreamingREPL(agent, {
|
|
1116
1126
|
continueSession: decision.global.continueSession,
|
package/dist/repl.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AgentLoop } from "./agent-loop.js";
|
|
2
|
+
import { type SlashCommandHandler } from "./slash-ports.js";
|
|
2
3
|
export type ReplInputResult = "continue" | "exit";
|
|
3
4
|
export declare class Repl {
|
|
4
5
|
private agent;
|
|
@@ -10,9 +11,11 @@ export declare class Repl {
|
|
|
10
11
|
private controllersSessionId;
|
|
11
12
|
private immediatePrompt;
|
|
12
13
|
private runningScheduledTask;
|
|
14
|
+
private readonly handleSlashCommandOverride?;
|
|
13
15
|
constructor(agent?: AgentLoop, options?: {
|
|
14
16
|
resumeSession?: string;
|
|
15
17
|
continueSession?: boolean;
|
|
18
|
+
handleSlashCommand?: SlashCommandHandler;
|
|
16
19
|
});
|
|
17
20
|
start(): Promise<void>;
|
|
18
21
|
private loop;
|
package/dist/repl.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,kBAAkB,CAAC;AAM1B,MAAM,MAAM,eAAe,GAAG,UAAU,GAAG,MAAM,CAAC;AAMlD,qBAAa,IAAI;IACf,OAAO,CAAC,KAAK,CAAY;IACzB,OAAO,CAAC,EAAE,CAAqB;IAC/B,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAU;IAC1C,OAAO,CAAC,SAAS,CAAiC;IAClD,OAAO,CAAC,cAAc,CAAsC;IAC5D,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAAC,CAAsB;gBAGhE,KAAK,CAAC,EAAE,SAAS,EACjB,OAAO,GAAE;QACP,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,kBAAkB,CAAC,EAAE,mBAAmB,CAAC;KACrC;IAYF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAqBd,IAAI;IAeZ,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;YAgFhD,aAAa;YA8Bb,6BAA6B;YAqC7B,gCAAgC;IAoB9C,OAAO,CAAC,kBAAkB;YAkBZ,iCAAiC;YAwBjC,sBAAsB;IAepC,OAAO,CAAC,+BAA+B;YAezB,uBAAuB;IAWrC,OAAO,CAAC,oBAAoB;YASd,wBAAwB;IAatC,OAAO,CAAC,iBAAiB;YAKX,6BAA6B;YAwC7B,4BAA4B;YAS5B,aAAa;YAWb,uBAAuB;YAMvB,sBAAsB;IAcpC,OAAO,CAAC,0BAA0B;IAIlC,OAAO,CAAC,2BAA2B;IAInC,OAAO,CAAC,8BAA8B;YAYxB,6BAA6B;CAS5C"}
|
package/dist/repl.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// ============================================================
|
|
5
5
|
import * as readline from "readline";
|
|
6
6
|
import { AgentLoop } from "./agent-loop.js";
|
|
7
|
-
import {
|
|
7
|
+
import { resolveSlashHandler, } from "./slash-ports.js";
|
|
8
8
|
import { SessionGoalController } from "./session/goal-controller.js";
|
|
9
9
|
import { SessionGoalManager } from "./session/session-goal-manager.js";
|
|
10
10
|
import { SessionScheduler } from "./session/session-scheduler.js";
|
|
@@ -18,10 +18,12 @@ export class Repl {
|
|
|
18
18
|
controllersSessionId = null;
|
|
19
19
|
immediatePrompt = null;
|
|
20
20
|
runningScheduledTask = false;
|
|
21
|
+
handleSlashCommandOverride;
|
|
21
22
|
constructor(agent, options = {}) {
|
|
22
23
|
this.agent = agent ?? new AgentLoop();
|
|
23
24
|
this.startupResumeTarget = options.resumeSession;
|
|
24
25
|
this.startupContinue = options.continueSession ?? false;
|
|
26
|
+
this.handleSlashCommandOverride = options.handleSlashCommand;
|
|
25
27
|
this.rl = readline.createInterface({
|
|
26
28
|
input: process.stdin,
|
|
27
29
|
output: process.stdout,
|
|
@@ -114,6 +116,7 @@ export class Repl {
|
|
|
114
116
|
if (trimmedInput.startsWith("/")) {
|
|
115
117
|
await this.ensureLocalSessionControllers();
|
|
116
118
|
this.immediatePrompt = null;
|
|
119
|
+
const handleSlashCommand = await resolveSlashHandler(this.handleSlashCommandOverride);
|
|
117
120
|
const handledSlashCommand = await handleSlashCommand(trimmedInput, this.createSlashContext());
|
|
118
121
|
if (handledSlashCommand) {
|
|
119
122
|
if (this.immediatePrompt) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-scheduler.d.ts","sourceRoot":"","sources":["../../src/session/session-scheduler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"session-scheduler.d.ts","sourceRoot":"","sources":["../../src/session/session-scheduler.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AACrC,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,CAAC;AAClD,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,CAAC;AACvG,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,QAAQ,CAAC;AAErD,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAMD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;IAC7D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;IAC3C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;IACxD,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,KAAK,CAA+C;gBAEhD,OAAO,EAAE,uBAAuB;IAStC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B,KAAK,IAAI,IAAI;IAOP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAO3B,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAIvB,cAAc,CAAC,OAAO,EAAE;QAC5B,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,eAAe,CAAC;QACtB,MAAM,CAAC,EAAE,iBAAiB,CAAC;KAC5B,GAAG,OAAO,CAAC,WAAW,CAAC;IAqBlB,SAAS,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAOnC,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAU5C,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAiBjC,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC;IA4E9C,OAAO,CAAC,cAAc;YAQR,SAAS;YAaT,SAAS;CAYxB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "fs/promises";
|
|
2
2
|
import { existsSync } from "fs";
|
|
3
3
|
import * as path from "path";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
4
5
|
function cloneTask(task) {
|
|
5
6
|
return { ...task };
|
|
6
7
|
}
|
|
@@ -190,7 +191,14 @@ export class SessionScheduler {
|
|
|
190
191
|
const payload = Array.from(this.tasks.values())
|
|
191
192
|
.sort((a, b) => a.createdAt - b.createdAt)
|
|
192
193
|
.map(cloneTask);
|
|
193
|
-
|
|
194
|
+
const tempFile = `${this.stateFile}.${process.pid}.${randomUUID()}.tmp`;
|
|
195
|
+
await fs.writeFile(tempFile, JSON.stringify(payload, null, 2), "utf-8");
|
|
196
|
+
try {
|
|
197
|
+
await fs.rename(tempFile, this.stateFile);
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
await fs.rm(tempFile, { force: true }).catch(() => undefined);
|
|
201
|
+
}
|
|
194
202
|
}
|
|
195
203
|
}
|
|
196
204
|
function sanitizeError(error) {
|
package/dist/skills/registry.js
CHANGED
|
@@ -92,7 +92,7 @@ export function parseFrontmatter(raw, filePath) {
|
|
|
92
92
|
triggers: [],
|
|
93
93
|
path: filePath,
|
|
94
94
|
};
|
|
95
|
-
const lines = raw.split("\n");
|
|
95
|
+
const lines = raw.replace(/\r\n?/g, "\n").split("\n");
|
|
96
96
|
const firstIdx = lines.findIndex((l) => l.trim() === "---");
|
|
97
97
|
if (firstIdx < 0)
|
|
98
98
|
return fallback;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SlashCommandContext } from "./slash-context.js";
|
|
2
|
+
export type SlashCommandHandler = (input: string, context: SlashCommandContext | Record<string, any>) => Promise<boolean>;
|
|
3
|
+
export interface SlashCatalogItem {
|
|
4
|
+
name: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
aliases?: string[];
|
|
7
|
+
}
|
|
8
|
+
export type FindSlashCompletion = (prefix: string, limit?: number) => SlashCatalogItem[];
|
|
9
|
+
export type FormatSlashPanel = (prefix: string, selectedIndex?: number, width?: number, limit?: number) => string[];
|
|
10
|
+
export type FormatGroupedSlashPanel = (width?: number) => string[];
|
|
11
|
+
export interface SlashUiPorts {
|
|
12
|
+
findSlashCompletion: FindSlashCompletion;
|
|
13
|
+
formatSlashCommandPanel: FormatSlashPanel;
|
|
14
|
+
formatGroupedSlashPanel: FormatGroupedSlashPanel;
|
|
15
|
+
}
|
|
16
|
+
export interface SlashRuntimePorts {
|
|
17
|
+
handleSlashCommand: SlashCommandHandler;
|
|
18
|
+
ui?: SlashUiPorts;
|
|
19
|
+
}
|
|
20
|
+
export declare function installSlashPorts(ports: SlashRuntimePorts): void;
|
|
21
|
+
export declare function getInstalledSlashPorts(): SlashRuntimePorts | null;
|
|
22
|
+
export declare function resolveSlashHandler(override?: SlashCommandHandler): Promise<SlashCommandHandler>;
|
|
23
|
+
export declare function resolveSlashUiPorts(override?: SlashUiPorts): Promise<SlashUiPorts>;
|
|
24
|
+
//# sourceMappingURL=slash-ports.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slash-ports.d.ts","sourceRoot":"","sources":["../src/slash-ports.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,MAAM,MAAM,mBAAmB,GAAG,CAChC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAC/C,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,gBAAgB,EAAE,CAAC;AACzF,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;AACpH,MAAM,MAAM,uBAAuB,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;AAEnE,MAAM,WAAW,YAAY;IAC3B,mBAAmB,EAAE,mBAAmB,CAAC;IACzC,uBAAuB,EAAE,gBAAgB,CAAC;IAC1C,uBAAuB,EAAE,uBAAuB,CAAC;CAClD;AAED,MAAM,WAAW,iBAAiB;IAChC,kBAAkB,EAAE,mBAAmB,CAAC;IACxC,EAAE,CAAC,EAAE,YAAY,CAAC;CACnB;AAKD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAEhE;AAED,wBAAgB,sBAAsB,IAAI,iBAAiB,GAAG,IAAI,CAEjE;AAED,wBAAsB,mBAAmB,CACvC,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,mBAAmB,CAAC,CAK9B;AAED,wBAAsB,mBAAmB,CAAC,QAAQ,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CASxF"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Slash ports — agent-runtime / presentation 依赖的抽象,避免直接 import cli/commands
|
|
3
|
+
// ============================================================
|
|
4
|
+
/** 由 cli 入口注入;未注入时 runtime 使用动态 import 回退(不形成静态分层边) */
|
|
5
|
+
let installedPorts = null;
|
|
6
|
+
export function installSlashPorts(ports) {
|
|
7
|
+
installedPorts = ports;
|
|
8
|
+
}
|
|
9
|
+
export function getInstalledSlashPorts() {
|
|
10
|
+
return installedPorts;
|
|
11
|
+
}
|
|
12
|
+
export async function resolveSlashHandler(override) {
|
|
13
|
+
if (override)
|
|
14
|
+
return override;
|
|
15
|
+
if (installedPorts?.handleSlashCommand)
|
|
16
|
+
return installedPorts.handleSlashCommand;
|
|
17
|
+
const mod = await import("./commands/index.js");
|
|
18
|
+
return mod.handleSlashCommand;
|
|
19
|
+
}
|
|
20
|
+
export async function resolveSlashUiPorts(override) {
|
|
21
|
+
if (override)
|
|
22
|
+
return override;
|
|
23
|
+
if (installedPorts?.ui)
|
|
24
|
+
return installedPorts.ui;
|
|
25
|
+
const mod = await import("./commands/index.js");
|
|
26
|
+
return {
|
|
27
|
+
findSlashCompletion: mod.findSlashCompletion,
|
|
28
|
+
formatSlashCommandPanel: mod.formatSlashCommandPanel,
|
|
29
|
+
formatGroupedSlashPanel: mod.formatGroupedSlashPanel,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AgentLoop } from "../agent-loop.js";
|
|
2
|
+
import { type SlashCommandHandler, type SlashUiPorts } from "../slash-ports.js";
|
|
2
3
|
export declare class StreamingREPL {
|
|
3
4
|
private ui;
|
|
4
5
|
private agent;
|
|
@@ -13,9 +14,12 @@ export declare class StreamingREPL {
|
|
|
13
14
|
private closed;
|
|
14
15
|
private onClose;
|
|
15
16
|
private lastExecutionStatus;
|
|
17
|
+
private readonly handleSlashCommandOverride?;
|
|
16
18
|
constructor(agent?: AgentLoop, options?: {
|
|
17
19
|
resumeSession?: string;
|
|
18
20
|
continueSession?: boolean;
|
|
21
|
+
handleSlashCommand?: SlashCommandHandler;
|
|
22
|
+
slashUi?: SlashUiPorts;
|
|
19
23
|
});
|
|
20
24
|
start(): Promise<void>;
|
|
21
25
|
stop(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streaming-repl.d.ts","sourceRoot":"","sources":["../../src/tui/streaming-repl.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"streaming-repl.d.ts","sourceRoot":"","sources":["../../src/tui/streaming-repl.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAI7C,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAS3B,qBAAa,aAAa;IACxB,OAAO,CAAC,EAAE,CAAW;IACrB,OAAO,CAAC,KAAK,CAAY;IACzB,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,cAAc,CAAyB;IAC/C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAU;IAC1C,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,iBAAiB,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmB;IAC9C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,mBAAmB,CAAM;IACjC,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAAC,CAAsB;gBAGhE,KAAK,CAAC,EAAE,SAAS,EACjB,OAAO,GAAE;QACP,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,kBAAkB,CAAC,EAAE,mBAAmB,CAAC;QACzC,OAAO,CAAC,EAAE,YAAY,CAAC;KACnB;IA0BF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAIb,KAAK;IAkBnB,OAAO,CAAC,eAAe;IA8DvB,OAAO,CAAC,aAAa;IAgCrB,OAAO,CAAC,kBAAkB;YAqDZ,gBAAgB;YAKhB,iBAAiB;YAoBjB,qBAAqB;YAQrB,uBAAuB;YAQvB,aAAa;YAsEb,eAAe;YASf,2BAA2B;IA4BzC,OAAO,CAAC,0BAA0B;YAkCpB,qBAAqB;IAiCnC,OAAO,CAAC,kBAAkB;YAWZ,6BAA6B;YAU7B,yBAAyB;YA2BzB,aAAa;CAsB5B"}
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
// ============================================================
|
|
11
11
|
import { AgentLoop } from "../agent-loop.js";
|
|
12
12
|
import { StreamUI } from "./streaming-tui.js";
|
|
13
|
-
import { handleSlashCommand } from "../commands/index.js";
|
|
14
13
|
import { SessionScheduler } from "../session/session-scheduler.js";
|
|
14
|
+
import { resolveSlashHandler, } from "../slash-ports.js";
|
|
15
15
|
import { SessionGoalManager } from "../session/session-goal-manager.js";
|
|
16
16
|
import { SessionGoalController } from "../session/goal-controller.js";
|
|
17
17
|
import { DaemonSessionApi } from "../session/daemon-session-api.js";
|
|
@@ -32,14 +32,16 @@ export class StreamingREPL {
|
|
|
32
32
|
closed = false;
|
|
33
33
|
onClose = null;
|
|
34
34
|
lastExecutionStatus = "";
|
|
35
|
+
handleSlashCommandOverride;
|
|
35
36
|
constructor(agent, options = {}) {
|
|
36
37
|
this.agent = agent ?? new AgentLoop();
|
|
37
38
|
const model = this.agent.getModel();
|
|
38
39
|
const toolsCount = this.agent.getToolCount();
|
|
39
|
-
this.ui = new StreamUI(model, toolsCount);
|
|
40
|
+
this.ui = new StreamUI(model, toolsCount, { slashUi: options.slashUi });
|
|
40
41
|
this.daemonSessionApi = new DaemonSessionApi();
|
|
41
42
|
this.startupResumeTarget = options.resumeSession;
|
|
42
43
|
this.startupContinue = options.continueSession ?? false;
|
|
44
|
+
this.handleSlashCommandOverride = options.handleSlashCommand;
|
|
43
45
|
this.inputQueue = new SerialInputQueue({
|
|
44
46
|
maxPending: 20,
|
|
45
47
|
onQueued: ({ pendingCount }) => {
|
|
@@ -402,6 +404,7 @@ export class StreamingREPL {
|
|
|
402
404
|
}
|
|
403
405
|
// v0.4 Slash Commands
|
|
404
406
|
this.immediatePrompt = null;
|
|
407
|
+
const handleSlashCommand = await resolveSlashHandler(this.handleSlashCommandOverride);
|
|
405
408
|
const handled = await handleSlashCommand(input, this.createSlashContext());
|
|
406
409
|
if (handled) {
|
|
407
410
|
// plan/model/permissions 等变更后刷新 chrome + statusline
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SlashUiPorts } from "../slash-ports.js";
|
|
1
2
|
import type { RecoveryState } from "../execution/types.js";
|
|
2
3
|
export type AgentEvent = {
|
|
3
4
|
type: "tool_start";
|
|
@@ -73,8 +74,11 @@ export declare class StreamUI {
|
|
|
73
74
|
private recoveryState;
|
|
74
75
|
private readonly now;
|
|
75
76
|
private readonly doubleCtrlCExitWindowMs;
|
|
77
|
+
private slashUi;
|
|
78
|
+
private readonly slashUiPromise;
|
|
76
79
|
constructor(model?: string, tools?: number, options?: {
|
|
77
80
|
now?: () => number;
|
|
81
|
+
slashUi?: SlashUiPorts;
|
|
78
82
|
});
|
|
79
83
|
start(): void;
|
|
80
84
|
stop(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streaming-tui.d.ts","sourceRoot":"","sources":["../../src/tui/streaming-tui.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"streaming-tui.d.ts","sourceRoot":"","sources":["../../src/tui/streaming-tui.ts"],"names":[],"mappings":"AAmBA,OAAO,EAEL,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAe3B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAgE3D,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC3F;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACxF;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAIzC,qBAAa,QAAQ;IACnB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,YAAY,CAQb;IACP,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,aAAa,CAAiD;IACtE,OAAO,CAAC,kBAAkB,CAAkB;IAC5C,OAAO,CAAC,WAAW,CAA0C;IAC7D,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,iBAAiB,CAAQ;IACjC,OAAO,CAAC,aAAa,CAA+B;IACpD,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,aAAa,CAAW;IAChC,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,6BAA6B,CAAmC;IACxE,OAAO,CAAC,kBAAkB,CAAmC;IAC7D,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,yBAAyB,CAAK;IACtC,OAAO,CAAC,wBAAwB,CAAK;IACrC,OAAO,CAAC,sBAAsB,CAAK;IACnC,OAAO,CAAC,4BAA4B,CAAK;IACzC,OAAO,CAAC,iBAAiB,CAAmC;IAC5D,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAS;IACjD,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAwB;gBAGrD,KAAK,GAAE,MAAwB,EAC/B,KAAK,GAAE,MAAU,EACjB,OAAO,GAAE;QAAE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,YAAY,CAAA;KAAO;IAgB9D,KAAK,IAAI,IAAI;IAQb,IAAI,IAAI,IAAI;IAkBZ,OAAO,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAIjD,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIxC,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAI5C,eAAe,CAAC,MAAM,EAAE;QACtB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,GAAG,IAAI;IAIR,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK7B,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,IAAI;IAKnD,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAOlC,OAAO,CAAC,kBAAkB;IAa1B,sBAAsB,IAAI,OAAO;IAIjC,uBAAuB,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;IAIhD,0BAA0B,IAAI,OAAO;IAKrC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI;IAInC,aAAa,CAAC,KAAK,GAAE,MAAgB,EAAE,UAAU,GAAE,MAAe,GAAG,IAAI;IAYzE,YAAY,IAAI,IAAI;IAQpB,OAAO,CAAC,WAAW;IAiCnB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,uBAAuB;IAO/B,OAAO,CAAC,oBAAoB;IA+C5B,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,8BAA8B;IAMtC,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,sBAAsB;IAe9B,OAAO,CAAC,UAAU;IA4BlB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,sBAAsB;IAI9B,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,eAAe;IA4EvB,OAAO,CAAC,iCAAiC;IAuBzC,OAAO,CAAC,uBAAuB;IAK/B,OAAO,CAAC,uBAAuB;IAI/B,OAAO,CAAC,qBAAqB;IAmB7B,OAAO,CAAC,4BAA4B;IAepC,UAAU,IAAI,IAAI;IASlB,iCAAiC;IACjC,uBAAuB,IAAI,OAAO;IAMlC,OAAO,CAAC,UAAU;IAyLlB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,SAAS;IAiBjB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,WAAW;IA6BnB,OAAO,CAAC,WAAW;IAwBnB,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,mBAAmB;IAU3B,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,eAAe;IAqBvB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAMpD,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAW1F,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAYvF,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAUlC,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAIzC,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAMtE,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAOhC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAQnC,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAOtE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAwB/B,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI/B,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAI3C,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;CAIrC"}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// appendFinal / appendError / appendState / appendDone
|
|
16
16
|
// ============================================================
|
|
17
17
|
import { default as stringWidth } from "string-width";
|
|
18
|
-
import {
|
|
18
|
+
import { resolveSlashUiPorts, } from "../slash-ports.js";
|
|
19
19
|
import { InputBuffer } from "./input-buffer.js";
|
|
20
20
|
import { formatProgressPulse } from "./progress.js";
|
|
21
21
|
import { formatWelcomeGuide, formatRoleHeader, formatResultHighlight, formatToolOutputCard, formatToolTimelineRow, formatTopBar, padVisible, truncateVisible, } from "./shell.js";
|
|
@@ -104,13 +104,26 @@ export class StreamUI {
|
|
|
104
104
|
recoveryState = null;
|
|
105
105
|
now;
|
|
106
106
|
doubleCtrlCExitWindowMs = 2_000;
|
|
107
|
+
slashUi = null;
|
|
108
|
+
slashUiPromise;
|
|
107
109
|
constructor(model = "deepseek-chat", tools = 0, options = {}) {
|
|
108
110
|
this.model = model;
|
|
109
111
|
this.tools = tools;
|
|
110
112
|
this.now = options.now ?? (() => Date.now());
|
|
113
|
+
if (options.slashUi) {
|
|
114
|
+
this.slashUi = options.slashUi;
|
|
115
|
+
this.slashUiPromise = Promise.resolve(options.slashUi);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
this.slashUiPromise = resolveSlashUiPorts().then((ports) => {
|
|
119
|
+
this.slashUi = ports;
|
|
120
|
+
return ports;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
111
123
|
}
|
|
112
124
|
start() {
|
|
113
125
|
this.running = true;
|
|
126
|
+
void this.slashUiPromise;
|
|
114
127
|
this.printHeader();
|
|
115
128
|
this.printInputBar();
|
|
116
129
|
this.setupInput();
|
|
@@ -486,15 +499,20 @@ export class StreamUI {
|
|
|
486
499
|
formatCurrentSlashCompletionHints() {
|
|
487
500
|
if (!this.isSlashCompletionActive(this.input.value))
|
|
488
501
|
return [];
|
|
489
|
-
const
|
|
502
|
+
const ports = this.slashUi;
|
|
503
|
+
if (!ports) {
|
|
504
|
+
void this.slashUiPromise;
|
|
505
|
+
return [];
|
|
506
|
+
}
|
|
507
|
+
const matches = ports.findSlashCompletion(this.input.value, 8);
|
|
490
508
|
if (matches.length > 0 && this.slashCompletionSelectedIndex >= matches.length) {
|
|
491
509
|
this.slashCompletionSelectedIndex = 0;
|
|
492
510
|
}
|
|
493
511
|
const inputVal = this.input.value || "";
|
|
494
512
|
if (inputVal === "/" || inputVal === "") {
|
|
495
|
-
return formatGroupedSlashPanel(process.stdout.columns || 80);
|
|
513
|
+
return ports.formatGroupedSlashPanel(process.stdout.columns || 80);
|
|
496
514
|
}
|
|
497
|
-
return formatSlashCommandPanel(inputVal, this.slashCompletionSelectedIndex, process.stdout.columns || 80, 8);
|
|
515
|
+
return ports.formatSlashCommandPanel(inputVal, this.slashCompletionSelectedIndex, process.stdout.columns || 80, 8);
|
|
498
516
|
}
|
|
499
517
|
isSlashCompletionPrefix(value) {
|
|
500
518
|
const text = value.trim();
|
|
@@ -506,7 +524,12 @@ export class StreamUI {
|
|
|
506
524
|
acceptSlashCompletion() {
|
|
507
525
|
if (!this.isSlashCompletionPrefix(this.input.value))
|
|
508
526
|
return false;
|
|
509
|
-
const
|
|
527
|
+
const ports = this.slashUi;
|
|
528
|
+
if (!ports) {
|
|
529
|
+
void this.slashUiPromise;
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
const matches = ports.findSlashCompletion(this.input.value, 8);
|
|
510
533
|
const completion = matches[this.slashCompletionSelectedIndex] ?? matches[0];
|
|
511
534
|
if (!completion)
|
|
512
535
|
return false;
|
|
@@ -521,7 +544,12 @@ export class StreamUI {
|
|
|
521
544
|
moveSlashCompletionSelection(delta) {
|
|
522
545
|
if (!this.isSlashCompletionPrefix(this.input.value))
|
|
523
546
|
return false;
|
|
524
|
-
const
|
|
547
|
+
const ports = this.slashUi;
|
|
548
|
+
if (!ports) {
|
|
549
|
+
void this.slashUiPromise;
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
const matches = ports.findSlashCompletion(this.input.value, 8);
|
|
525
553
|
if (matches.length <= 1)
|
|
526
554
|
return false;
|
|
527
555
|
this.slashCompletionSelectedIndex =
|
package/docs/install.md
CHANGED
|
@@ -89,13 +89,19 @@ qling
|
|
|
89
89
|
### D1. 本仓库自建 bucket(推荐试用)
|
|
90
90
|
|
|
91
91
|
```powershell
|
|
92
|
-
|
|
93
|
-
scoop bucket add qling
|
|
94
|
-
scoop install qling
|
|
92
|
+
# 公共 bucket(推荐)
|
|
93
|
+
scoop bucket add qling https://github.com/Zzy-min/scoop-qling
|
|
94
|
+
scoop install qling
|
|
95
|
+
|
|
96
|
+
# 或从本仓库本地路径
|
|
97
|
+
# scoop bucket add qling "$PWD\packaging\scoop-bucket"
|
|
98
|
+
# scoop install qling/qling
|
|
99
|
+
|
|
95
100
|
qling --version
|
|
96
101
|
```
|
|
97
102
|
|
|
98
|
-
|
|
103
|
+
便携包内嵌 Node 运行时,**无需系统安装 Node**。公共 bucket:https://github.com/Zzy-min/scoop-qling
|
|
104
|
+
官方 Extras PR:https://github.com/ScoopInstaller/Extras/pull/18307
|
|
99
105
|
|
|
100
106
|
### D2. 校验与同步
|
|
101
107
|
|
|
@@ -117,7 +123,7 @@ npm run build:portable-win
|
|
|
117
123
|
# → dist-portable/qling-win-x64.zip + portable-meta.json (sha256)
|
|
118
124
|
```
|
|
119
125
|
|
|
120
|
-
Release 资产示例:`https://github.com/Zzy-min/qling/releases/download/v1.2.
|
|
126
|
+
Release 资产示例:`https://github.com/Zzy-min/qling/releases/download/v1.2.2/qling-win-x64.zip`
|
|
121
127
|
清单:`packaging/winget/Zzy-min.qling.yaml`(含 InstallerSha256)。
|
|
122
128
|
|
|
123
129
|
### 本地校验
|
|
@@ -127,7 +133,7 @@ npm run validate:packaging
|
|
|
127
133
|
Select-String -Path packaging/winget/Zzy-min.qling.yaml -Pattern 'PackageVersion|InstallerUrl|InstallerSha256'
|
|
128
134
|
```
|
|
129
135
|
|
|
130
|
-
正式提交 [winget-pkgs](https://github.com/microsoft/winget-pkgs) 仍需 PR;在此之前可用 zip 解压 + `qling.cmd
|
|
136
|
+
正式提交 [winget-pkgs](https://github.com/microsoft/winget-pkgs) 仍需 PR;在此之前可用 zip 解压 + `qling.cmd`(便携 zip 已内嵌 Node 运行时(亦可使用系统 Node))。
|
|
131
137
|
|
|
132
138
|
## 配置与安全
|
|
133
139
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qlingzzy/qling",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "轻灵 Qling - 本地优先的中文 AI Agent CLI 工作台 / Local-first Chinese AI Agent CLI workbench",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/sdk.js",
|
|
@@ -92,7 +92,8 @@
|
|
|
92
92
|
"dep:layers:write-baseline": "node scripts/dep-layers.mjs --write-baseline",
|
|
93
93
|
"sync:scoop-bucket": "node scripts/sync-scoop-bucket.mjs",
|
|
94
94
|
"build:portable-win": "node scripts/build-portable-win.mjs",
|
|
95
|
-
"
|
|
95
|
+
"sync:winget-sha": "node scripts/sync-winget-sha.mjs",
|
|
96
|
+
"ci:check": "npm run build && node --experimental-test-module-mocks --test \"tests/unit/**/*.test.mjs\" && node --test \"tests/smoke/**/*.test.mjs\" && node scripts/eval-smoke.mjs && node scripts/eval-tasks.mjs && node scripts/validate-packaging.mjs && node scripts/dep-layers.mjs --strict"
|
|
96
97
|
},
|
|
97
98
|
"dependencies": {
|
|
98
99
|
"axios": "^1.7.0",
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Build a Windows portable zip
|
|
4
|
-
* Usage: node scripts/build-portable-win.mjs
|
|
3
|
+
* Build a Windows portable zip that embeds a Node.js runtime (no system Node required).
|
|
4
|
+
* Usage: node scripts/build-portable-win.mjs [--skip-node]
|
|
5
5
|
*
|
|
6
6
|
* Output: dist-portable/qling-win-x64.zip
|
|
7
7
|
* Layout:
|
|
8
8
|
* qling-win-x64/
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* runtime/node.exe # bundled Node (same major as build host by default)
|
|
10
|
+
* package/ # npm pack contents
|
|
11
|
+
* qling.exe # WinGet portable entry (Framework csc launcher)
|
|
12
|
+
* qling.cmd # Scoop / shell-friendly launcher
|
|
11
13
|
* README.txt
|
|
12
14
|
*/
|
|
13
|
-
import { spawnSync } from "node:child_process";
|
|
15
|
+
import { spawnSync, execFileSync } from "node:child_process";
|
|
14
16
|
import { createHash } from "node:crypto";
|
|
15
17
|
import {
|
|
16
18
|
mkdir,
|
|
@@ -18,31 +20,34 @@ import {
|
|
|
18
20
|
readFile,
|
|
19
21
|
writeFile,
|
|
20
22
|
copyFile,
|
|
21
|
-
|
|
22
|
-
stat,
|
|
23
|
+
access,
|
|
23
24
|
} from "node:fs/promises";
|
|
24
|
-
import { createWriteStream, existsSync
|
|
25
|
+
import { createWriteStream, existsSync } from "node:fs";
|
|
25
26
|
import { join, dirname, resolve } from "node:path";
|
|
26
27
|
import { fileURLToPath } from "node:url";
|
|
27
28
|
import { pipeline } from "node:stream/promises";
|
|
28
|
-
import {
|
|
29
|
-
import { execFileSync } from "node:child_process";
|
|
29
|
+
import { get } from "node:https";
|
|
30
30
|
|
|
31
31
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
32
32
|
const ROOT = resolve(__dirname, "..");
|
|
33
33
|
const OUT_DIR = join(ROOT, "dist-portable");
|
|
34
34
|
const STAGE = join(OUT_DIR, "qling-win-x64");
|
|
35
35
|
const ZIP_PATH = join(OUT_DIR, "qling-win-x64.zip");
|
|
36
|
+
const skipNode = process.argv.includes("--skip-node");
|
|
36
37
|
|
|
37
38
|
const pkg = JSON.parse(await readFile(join(ROOT, "package.json"), "utf8"));
|
|
38
39
|
const version = pkg.version;
|
|
40
|
+
const nodeVersion = process.version; // e.g. v22.22.1
|
|
41
|
+
const nodeDistName = `node-${nodeVersion}-win-x64`;
|
|
42
|
+
const nodeZipUrl = `https://nodejs.org/dist/${nodeVersion}/${nodeDistName}.zip`;
|
|
39
43
|
|
|
40
44
|
console.log(`[portable] building qling ${version} for win-x64…`);
|
|
45
|
+
console.log(`[portable] host node ${nodeVersion} ${process.arch}`);
|
|
41
46
|
|
|
42
47
|
await rm(OUT_DIR, { recursive: true, force: true });
|
|
43
48
|
await mkdir(STAGE, { recursive: true });
|
|
44
49
|
|
|
45
|
-
//
|
|
50
|
+
// 1) npm pack current tree
|
|
46
51
|
const pack = spawnSync(
|
|
47
52
|
process.platform === "win32" ? "npm.cmd" : "npm",
|
|
48
53
|
["pack", "--pack-destination", OUT_DIR],
|
|
@@ -64,61 +69,165 @@ if (!tgzName) {
|
|
|
64
69
|
const tgzPath = join(OUT_DIR, tgzName);
|
|
65
70
|
console.log(`[portable] packed ${tgzName}`);
|
|
66
71
|
|
|
67
|
-
// Extract with tar (Node 22+ / Windows 10+ tar)
|
|
68
|
-
const pkgDir = join(STAGE, "package");
|
|
69
|
-
await mkdir(pkgDir, { recursive: true });
|
|
70
72
|
try {
|
|
71
73
|
execFileSync("tar", ["-xzf", tgzPath, "-C", STAGE], { stdio: "inherit" });
|
|
72
|
-
// npm pack extracts to package/
|
|
73
74
|
} catch (err) {
|
|
74
75
|
console.error("tar extract failed:", err instanceof Error ? err.message : err);
|
|
75
76
|
process.exit(1);
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
// npm pack intentionally excludes node_modules. Install only runtime dependencies
|
|
80
|
+
// into the staged package, then rebuild the native dependency with lifecycle
|
|
81
|
+
// scripts enabled only for that dependency.
|
|
82
|
+
const stagedPackage = join(STAGE, "package");
|
|
83
|
+
await copyFile(join(ROOT, "package-lock.json"), join(stagedPackage, "package-lock.json"));
|
|
84
|
+
runChecked(
|
|
85
|
+
process.platform === "win32" ? "npm.cmd" : "npm",
|
|
86
|
+
["ci", "--omit=dev", "--ignore-scripts", "--no-audit", "--no-fund"],
|
|
87
|
+
stagedPackage,
|
|
88
|
+
"install staged production dependencies"
|
|
89
|
+
);
|
|
90
|
+
runChecked(
|
|
91
|
+
process.platform === "win32" ? "npm.cmd" : "npm",
|
|
92
|
+
["rebuild", "better-sqlite3"],
|
|
93
|
+
stagedPackage,
|
|
94
|
+
"rebuild staged better-sqlite3"
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// 2) Bundle Node runtime
|
|
98
|
+
const runtimeDir = join(STAGE, "runtime");
|
|
99
|
+
await mkdir(runtimeDir, { recursive: true });
|
|
100
|
+
let bundledNode = false;
|
|
101
|
+
let nodeNote = "System Node.js >= 18 on PATH";
|
|
102
|
+
|
|
103
|
+
if (!skipNode && process.platform === "win32") {
|
|
104
|
+
const cacheDir = join(OUT_DIR, ".node-cache");
|
|
105
|
+
await mkdir(cacheDir, { recursive: true });
|
|
106
|
+
const nodeZipPath = join(cacheDir, `${nodeDistName}.zip`);
|
|
107
|
+
if (!existsSync(nodeZipPath)) {
|
|
108
|
+
console.log(`[portable] downloading ${nodeZipUrl}`);
|
|
109
|
+
await downloadFile(nodeZipUrl, nodeZipPath);
|
|
110
|
+
} else {
|
|
111
|
+
console.log(`[portable] using cached ${nodeZipPath}`);
|
|
112
|
+
}
|
|
113
|
+
const extractDir = join(cacheDir, nodeDistName);
|
|
114
|
+
await rm(extractDir, { recursive: true, force: true });
|
|
115
|
+
execFileSync("tar", ["-xf", nodeZipPath, "-C", cacheDir], { stdio: "inherit" });
|
|
116
|
+
const nodeExeSrc = join(extractDir, "node.exe");
|
|
117
|
+
await access(nodeExeSrc);
|
|
118
|
+
await copyFile(nodeExeSrc, join(runtimeDir, "node.exe"));
|
|
119
|
+
// LICENSE if present
|
|
120
|
+
const license = join(extractDir, "LICENSE");
|
|
121
|
+
if (existsSync(license)) {
|
|
122
|
+
await copyFile(license, join(runtimeDir, "NODE-LICENSE"));
|
|
123
|
+
}
|
|
124
|
+
bundledNode = true;
|
|
125
|
+
nodeNote = `Bundled Node ${nodeVersion} (runtime\\node.exe)`;
|
|
126
|
+
console.log(`[portable] bundled node.exe from ${nodeDistName}`);
|
|
127
|
+
} else if (!skipNode) {
|
|
128
|
+
console.warn("[portable] non-Windows host: skip embedding node.exe (use --skip-node to silence)");
|
|
129
|
+
}
|
|
130
|
+
|
|
78
131
|
const launcher = `@echo off
|
|
79
132
|
setlocal
|
|
80
133
|
set "ROOT=%~dp0"
|
|
81
|
-
set "
|
|
82
|
-
|
|
83
|
-
if not defined
|
|
84
|
-
|
|
134
|
+
set "NODE_EXE="
|
|
135
|
+
if exist "%ROOT%runtime\\node.exe" set "NODE_EXE=%ROOT%runtime\\node.exe"
|
|
136
|
+
if not defined NODE_EXE (
|
|
137
|
+
where node >nul 2>nul && set "NODE_EXE=node"
|
|
138
|
+
)
|
|
139
|
+
if not defined NODE_EXE (
|
|
140
|
+
echo [qling] No bundled runtime and no system Node on PATH.
|
|
141
|
+
echo [qling] Re-download the portable zip or install Node.js ^>= 18.
|
|
85
142
|
exit /b 1
|
|
86
143
|
)
|
|
87
|
-
"%
|
|
144
|
+
"%NODE_EXE%" "%ROOT%package\\dist\\index.js" %*
|
|
88
145
|
`;
|
|
89
146
|
await writeFile(join(STAGE, "qling.cmd"), launcher, "utf8");
|
|
90
147
|
|
|
148
|
+
// Native .exe launcher for WinGet portable (scripted .cmd/.bat not allowed)
|
|
149
|
+
const launcherCs = join(ROOT, "packaging", "win-launcher", "qling-launcher.cs");
|
|
150
|
+
const launcherExe = join(STAGE, "qling.exe");
|
|
151
|
+
if (!existsSync(launcherCs)) {
|
|
152
|
+
console.error(`[portable] missing launcher source: ${launcherCs}`);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
const cscCandidates = [
|
|
156
|
+
process.env.QLING_CSC,
|
|
157
|
+
join(
|
|
158
|
+
process.env.WINDIR || "C:\\Windows",
|
|
159
|
+
"Microsoft.NET",
|
|
160
|
+
"Framework64",
|
|
161
|
+
"v4.0.30319",
|
|
162
|
+
"csc.exe"
|
|
163
|
+
),
|
|
164
|
+
join(
|
|
165
|
+
process.env.WINDIR || "C:\\Windows",
|
|
166
|
+
"Microsoft.NET",
|
|
167
|
+
"Framework",
|
|
168
|
+
"v4.0.30319",
|
|
169
|
+
"csc.exe"
|
|
170
|
+
),
|
|
171
|
+
].filter(Boolean);
|
|
172
|
+
const csc = cscCandidates.find((p) => existsSync(p));
|
|
173
|
+
if (!csc) {
|
|
174
|
+
console.error(
|
|
175
|
+
"[portable] csc.exe not found (need .NET Framework 4.x). Set QLING_CSC to override."
|
|
176
|
+
);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
const cscResult = spawnSync(
|
|
180
|
+
csc,
|
|
181
|
+
[
|
|
182
|
+
"/nologo",
|
|
183
|
+
"/target:exe",
|
|
184
|
+
"/platform:anycpu",
|
|
185
|
+
"/optimize+",
|
|
186
|
+
`/out:${launcherExe}`,
|
|
187
|
+
launcherCs,
|
|
188
|
+
],
|
|
189
|
+
{ encoding: "utf8" }
|
|
190
|
+
);
|
|
191
|
+
if (cscResult.status !== 0 || !existsSync(launcherExe)) {
|
|
192
|
+
console.error("[portable] csc failed:", cscResult.stdout, cscResult.stderr);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
console.log(`[portable] compiled qling.exe via ${csc}`);
|
|
196
|
+
|
|
197
|
+
runChecked(launcherExe, ["--version"], STAGE, "verify portable launcher version");
|
|
198
|
+
runChecked(launcherExe, ["doctor"], STAGE, "verify portable launcher doctor");
|
|
199
|
+
|
|
91
200
|
const readme = `Qling ${version} — Windows portable layout
|
|
92
201
|
========================================
|
|
93
202
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
203
|
+
${bundledNode ? "This build embeds a Node.js runtime — no system Node install required." : "This build expects system Node.js >= 18 on PATH."}
|
|
204
|
+
|
|
205
|
+
Runtime:
|
|
206
|
+
${nodeNote}
|
|
97
207
|
|
|
98
208
|
Run:
|
|
209
|
+
.\\qling.exe --version
|
|
99
210
|
.\\qling.cmd --version
|
|
100
|
-
.\\qling.
|
|
101
|
-
.\\qling.
|
|
211
|
+
.\\qling.exe doctor
|
|
212
|
+
.\\qling.exe setup
|
|
213
|
+
|
|
214
|
+
Notes:
|
|
215
|
+
- qling.exe is the WinGet portable entry point (native launcher).
|
|
216
|
+
- qling.cmd remains for Scoop and interactive shells.
|
|
217
|
+
- better-sqlite3 native binary is compiled for the Node ABI used at pack time.
|
|
218
|
+
- Prefer matching the bundled Node major version if you replace runtime\\node.exe.
|
|
102
219
|
|
|
103
|
-
|
|
220
|
+
npm alternative:
|
|
104
221
|
npm install -g @qlingzzy/qling@${version} --registry https://registry.npmjs.org/
|
|
105
222
|
|
|
106
223
|
Source: https://github.com/Zzy-min/qling
|
|
107
224
|
`;
|
|
108
225
|
await writeFile(join(STAGE, "README.txt"), readme, "utf8");
|
|
109
226
|
|
|
110
|
-
// Zip via tar (bsdtar on Windows supports zip)
|
|
111
227
|
await rm(ZIP_PATH, { force: true });
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
["-a", "-cf", ZIP_PATH, "-C", OUT_DIR, "qling-win-x64"],
|
|
116
|
-
{ stdio: "inherit" }
|
|
117
|
-
);
|
|
118
|
-
} catch (err) {
|
|
119
|
-
console.error("zip failed:", err instanceof Error ? err.message : err);
|
|
120
|
-
process.exit(1);
|
|
121
|
-
}
|
|
228
|
+
execFileSync("tar", ["-a", "-cf", ZIP_PATH, "-C", OUT_DIR, "qling-win-x64"], {
|
|
229
|
+
stdio: "inherit",
|
|
230
|
+
});
|
|
122
231
|
|
|
123
232
|
const buf = await readFile(ZIP_PATH);
|
|
124
233
|
const sha256 = createHash("sha256").update(buf).digest("hex");
|
|
@@ -128,10 +237,47 @@ const meta = {
|
|
|
128
237
|
path: ZIP_PATH,
|
|
129
238
|
size: buf.length,
|
|
130
239
|
sha256,
|
|
240
|
+
bundledNode,
|
|
241
|
+
nodeVersion: bundledNode ? nodeVersion : null,
|
|
131
242
|
createdAt: new Date().toISOString(),
|
|
132
243
|
};
|
|
133
244
|
await writeFile(join(OUT_DIR, "portable-meta.json"), JSON.stringify(meta, null, 2) + "\n");
|
|
134
245
|
|
|
135
246
|
console.log(`[portable] wrote ${ZIP_PATH}`);
|
|
136
247
|
console.log(`[portable] size=${buf.length} sha256=${sha256}`);
|
|
248
|
+
console.log(`[portable] bundledNode=${bundledNode}`);
|
|
137
249
|
console.log(`[portable] meta: dist-portable/portable-meta.json`);
|
|
250
|
+
|
|
251
|
+
function runChecked(command, args, cwd, label) {
|
|
252
|
+
console.log(`[portable] ${label}…`);
|
|
253
|
+
const result = spawnSync(command, args, {
|
|
254
|
+
cwd,
|
|
255
|
+
encoding: "utf8",
|
|
256
|
+
shell: process.platform === "win32" && /\.cmd$/i.test(command),
|
|
257
|
+
});
|
|
258
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
259
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
260
|
+
if (result.status !== 0) {
|
|
261
|
+
if (result.error) console.error(`[portable] ${result.error.message}`);
|
|
262
|
+
console.error(`[portable] ${label} failed with exit code ${result.status}`);
|
|
263
|
+
process.exit(result.status ?? 1);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function downloadFile(url, dest) {
|
|
268
|
+
return new Promise((resolvePromise, reject) => {
|
|
269
|
+
const file = createWriteStream(dest);
|
|
270
|
+
get(url, (res) => {
|
|
271
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
272
|
+
file.close();
|
|
273
|
+
downloadFile(res.headers.location, dest).then(resolvePromise, reject);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (res.statusCode !== 200) {
|
|
277
|
+
reject(new Error(`download failed: HTTP ${res.statusCode}`));
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
pipeline(res, file).then(resolvePromise, reject);
|
|
281
|
+
}).on("error", reject);
|
|
282
|
+
});
|
|
283
|
+
}
|
package/scripts/dep-layers.mjs
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Fill winget InstallerSha256 from dist-portable/portable-meta.json
|
|
4
|
+
*/
|
|
5
|
+
import { readFile, writeFile, readdir } from "node:fs/promises";
|
|
6
|
+
import { join, dirname } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
|
|
10
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
|
+
const metaPath = join(root, "dist-portable", "portable-meta.json");
|
|
12
|
+
if (!existsSync(metaPath)) {
|
|
13
|
+
console.error("missing dist-portable/portable-meta.json — run npm run build:portable-win first");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const meta = JSON.parse(await readFile(metaPath, "utf8"));
|
|
17
|
+
const sha = String(meta.sha256 || "").toLowerCase();
|
|
18
|
+
if (!/^[a-f0-9]{64}$/.test(sha)) {
|
|
19
|
+
console.error("invalid sha256 in portable-meta.json");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const version = meta.version || JSON.parse(await readFile(join(root, "package.json"), "utf8")).version;
|
|
24
|
+
const targets = [
|
|
25
|
+
join(root, "packaging", "winget", "Zzy-min.qling.yaml"),
|
|
26
|
+
join(root, "packaging", "winget", "manifests", "Zzy-min", "qling", version, "Zzy-min.qling.installer.yaml"),
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
for (const file of targets) {
|
|
30
|
+
if (!existsSync(file)) {
|
|
31
|
+
console.warn("skip missing", file);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
let text = await readFile(file, "utf8");
|
|
35
|
+
text = text.replace(/InstallerSha256:\s*[A-Za-z0-9_]+/g, `InstallerSha256: ${sha}`);
|
|
36
|
+
text = text.replace(/PackageVersion:\s*[\d.]+/g, `PackageVersion: ${version}`);
|
|
37
|
+
text = text.replace(/v[\d.]+\//g, `v${version}/`);
|
|
38
|
+
await writeFile(file, text, "utf8");
|
|
39
|
+
console.log("updated", file);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Scoop: rewrite Extras-compatible 64bit manifest
|
|
43
|
+
const { spawnSync } = await import("node:child_process");
|
|
44
|
+
const scoopScript = join(root, "scripts", "write-scoop-manifest.mjs");
|
|
45
|
+
const r = spawnSync(process.execPath, [scoopScript], { cwd: root, encoding: "utf8" });
|
|
46
|
+
process.stdout.write(r.stdout || "");
|
|
47
|
+
process.stderr.write(r.stderr || "");
|
|
48
|
+
if (r.status !== 0) process.exit(r.status || 1);
|
|
49
|
+
|
|
50
|
+
// Multi-file winget for this version dir (create if missing)
|
|
51
|
+
const multiDir = join(root, "packaging", "winget", "manifests", "Zzy-min", "qling", version);
|
|
52
|
+
if (!existsSync(multiDir)) {
|
|
53
|
+
console.warn("note: multi-file winget dir missing for", version, "- create under packaging/winget/manifests");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log(`sync-winget-sha OK version=${version} sha=${sha.slice(0, 16)}… bundledNode=${meta.bundledNode}`);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Validate Scoop / winget
|
|
3
|
+
* Validate Scoop / winget manifests align with package.json version.
|
|
4
4
|
* Usage: node scripts/validate-packaging.mjs
|
|
5
5
|
*/
|
|
6
6
|
import { readFile } from "node:fs/promises";
|
|
7
7
|
import { join, dirname } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
9
10
|
|
|
10
11
|
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
12
|
const errors = [];
|
|
@@ -18,17 +19,26 @@ const scoop = JSON.parse(scoopRaw);
|
|
|
18
19
|
if (scoop.version !== version) {
|
|
19
20
|
errors.push(`scoop version ${scoop.version} != package ${version}`);
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
const scoopUrl =
|
|
23
|
+
scoop.architecture?.["64bit"]?.url || scoop.url || "";
|
|
24
|
+
const scoopHash =
|
|
25
|
+
scoop.architecture?.["64bit"]?.hash || scoop.hash || "";
|
|
26
|
+
if (!String(scoopUrl).includes(version)) {
|
|
27
|
+
errors.push(`scoop url does not pin ${version}: ${scoopUrl}`);
|
|
23
28
|
}
|
|
24
|
-
if (!
|
|
25
|
-
errors.push("scoop
|
|
29
|
+
if (!scoop.architecture?.["64bit"]) {
|
|
30
|
+
errors.push("scoop should nest url/hash under architecture.64bit (Extras style)");
|
|
26
31
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
if (!scoopHash || /TODO|REPLACE|PLACEHOLDER/i.test(String(scoopHash))) {
|
|
33
|
+
errors.push("scoop hash still placeholder — run build:portable-win + sync-winget-sha");
|
|
34
|
+
} else if (!/^(sha256:)?[a-f0-9]{64}$/i.test(String(scoopHash))) {
|
|
35
|
+
errors.push(`scoop hash looks invalid: ${scoopHash}`);
|
|
36
|
+
}
|
|
37
|
+
if (/sha256:/i.test(String(scoopHash))) {
|
|
38
|
+
errors.push("scoop hash should be plain hex (no sha256: prefix) for Extras");
|
|
39
|
+
}
|
|
40
|
+
if (!String(scoop.bin || "").includes("qling")) {
|
|
41
|
+
errors.push("scoop bin should expose qling");
|
|
32
42
|
}
|
|
33
43
|
|
|
34
44
|
const winget = await readFile(
|
|
@@ -41,8 +51,36 @@ if (!winget.includes(`PackageVersion: ${version}`)) {
|
|
|
41
51
|
if (!winget.includes("PackageIdentifier: Zzy-min.qling")) {
|
|
42
52
|
errors.push("winget PackageIdentifier missing");
|
|
43
53
|
}
|
|
44
|
-
if (
|
|
45
|
-
errors.push("winget
|
|
54
|
+
if (/InstallerSha256:\s*(0{64}|PLACEHOLDER)/i.test(winget)) {
|
|
55
|
+
errors.push("winget InstallerSha256 is still placeholder");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const multiInstaller = join(
|
|
59
|
+
root,
|
|
60
|
+
"packaging",
|
|
61
|
+
"winget",
|
|
62
|
+
"manifests",
|
|
63
|
+
"Zzy-min",
|
|
64
|
+
"qling",
|
|
65
|
+
version,
|
|
66
|
+
"Zzy-min.qling.installer.yaml"
|
|
67
|
+
);
|
|
68
|
+
if (existsSync(multiInstaller)) {
|
|
69
|
+
const mi = await readFile(multiInstaller, "utf8");
|
|
70
|
+
if (/InstallerSha256:\s*(0{64}|PLACEHOLDER)/i.test(mi)) {
|
|
71
|
+
errors.push("multi-file winget installer sha still placeholder");
|
|
72
|
+
}
|
|
73
|
+
if (!mi.includes(`PackageVersion: ${version}`)) {
|
|
74
|
+
errors.push("multi-file winget installer version mismatch");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const bucket = join(root, "packaging", "scoop-bucket", "qling.json");
|
|
79
|
+
if (existsSync(bucket)) {
|
|
80
|
+
const b = JSON.parse(await readFile(bucket, "utf8"));
|
|
81
|
+
if (b.version !== version) errors.push("scoop-bucket version mismatch");
|
|
82
|
+
const bHash = b.architecture?.["64bit"]?.hash || b.hash;
|
|
83
|
+
if (bHash !== scoopHash) errors.push("scoop-bucket hash out of sync with scoop/qling.json");
|
|
46
84
|
}
|
|
47
85
|
|
|
48
86
|
if (errors.length) {
|
|
@@ -52,6 +90,6 @@ if (errors.length) {
|
|
|
52
90
|
}
|
|
53
91
|
|
|
54
92
|
console.log(`validate-packaging OK (version ${version})`);
|
|
55
|
-
console.log(" scoop:",
|
|
93
|
+
console.log(" scoop:", scoopUrl);
|
|
94
|
+
console.log(" scoop.hash:", String(scoopHash).slice(0, 20) + "…");
|
|
56
95
|
console.log(" winget: Zzy-min.qling @", version);
|
|
57
|
-
console.log(" note: still drafts — not published to official catalogs");
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Write Scoop-compatible manifest (Extras style):
|
|
4
|
+
* - 4-space indent
|
|
5
|
+
* - plain SHA256 (no sha256: prefix)
|
|
6
|
+
* - architecture.64bit for url/hash/extract_dir
|
|
7
|
+
* - autoupdate.architecture.64bit
|
|
8
|
+
*
|
|
9
|
+
* Reads dist-portable/portable-meta.json when present.
|
|
10
|
+
*/
|
|
11
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
12
|
+
import { join, dirname } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { existsSync } from "node:fs";
|
|
15
|
+
|
|
16
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
17
|
+
const pkg = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
|
|
18
|
+
const version = pkg.version;
|
|
19
|
+
|
|
20
|
+
let hash = "PLACEHOLDER";
|
|
21
|
+
const metaPath = join(root, "dist-portable", "portable-meta.json");
|
|
22
|
+
if (existsSync(metaPath)) {
|
|
23
|
+
const meta = JSON.parse(await readFile(metaPath, "utf8"));
|
|
24
|
+
hash = String(meta.sha256 || "").toLowerCase();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const manifest = {
|
|
28
|
+
version,
|
|
29
|
+
description: "Local-first Chinese AI Agent CLI workbench (Qling / 轻灵).",
|
|
30
|
+
homepage: "https://github.com/Zzy-min/qling",
|
|
31
|
+
license: "MIT",
|
|
32
|
+
notes: [
|
|
33
|
+
"Portable zip embeds a Node.js runtime (no system Node required).",
|
|
34
|
+
"Set API keys as user environment variables (never commit secrets).",
|
|
35
|
+
"After install: run qling doctor, then qling setup.",
|
|
36
|
+
],
|
|
37
|
+
architecture: {
|
|
38
|
+
"64bit": {
|
|
39
|
+
url: `https://github.com/Zzy-min/qling/releases/download/v${version}/qling-win-x64.zip`,
|
|
40
|
+
hash,
|
|
41
|
+
extract_dir: "qling-win-x64",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
bin: "qling.cmd",
|
|
45
|
+
post_install: [
|
|
46
|
+
"Write-Host 'Run qling doctor, then qling setup.' -ForegroundColor Cyan",
|
|
47
|
+
],
|
|
48
|
+
checkver: {
|
|
49
|
+
github: "https://github.com/Zzy-min/qling",
|
|
50
|
+
},
|
|
51
|
+
autoupdate: {
|
|
52
|
+
architecture: {
|
|
53
|
+
"64bit": {
|
|
54
|
+
url: "https://github.com/Zzy-min/qling/releases/download/v$version/qling-win-x64.zip",
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const json = JSON.stringify(manifest, null, 4) + "\n";
|
|
61
|
+
const targets = [
|
|
62
|
+
join(root, "packaging", "scoop", "qling.json"),
|
|
63
|
+
join(root, "packaging", "scoop-bucket", "qling.json"),
|
|
64
|
+
];
|
|
65
|
+
for (const t of targets) {
|
|
66
|
+
await writeFile(t, json, "utf8");
|
|
67
|
+
console.log("wrote", t);
|
|
68
|
+
}
|
|
69
|
+
console.log(`version=${version} hash=${hash.slice(0, 16)}…`);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"durable-session-supervisor.d.ts","sourceRoot":"","sources":["../../src/session/durable-session-supervisor.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED,qBAAa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA4B;IAChD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqB;IACrD,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,+BAA+B;IAMpD,KAAK,IAAI,IAAI;IAQP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAOb,IAAI;YAyBJ,iBAAiB;YAcjB,iBAAiB;YAiBjB,sBAAsB;YAiCtB,cAAc;YAoCd,iBAAiB;YAyBjB,WAAW;CA8B1B"}
|
|
File without changes
|