@qilin-agent/working-directory 0.0.0-stage → 3.1.4
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/LICENSE +21 -0
- package/README.i18n.yaml +46 -0
- package/README.md +77 -2
- package/README.zh.md +78 -0
- package/lib/index.js +167 -0
- package/lib/types/index.d.ts +90 -0
- package/package.json +55 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kkutysllb
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
|
|
2
|
+
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
|
3
|
+
# After editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/session/working-directory/README.md
|
|
5
|
+
/:
|
|
6
|
+
en: 06d920cc3955e86b
|
|
7
|
+
zh: 1bc4afef77054e66
|
|
8
|
+
/qilin-agent-working-directory:
|
|
9
|
+
en: 62e2be090bdb40b8
|
|
10
|
+
zh: 61fc03fdaad53ba9
|
|
11
|
+
/qilin-agent-working-directory/summary:
|
|
12
|
+
en: 594fc1c3e0a4e9e3
|
|
13
|
+
zh: c84b91ff6fa06972
|
|
14
|
+
/qilin-agent-working-directory/table-of-contents:
|
|
15
|
+
en: d152484eb41ac6b4
|
|
16
|
+
zh: 9f0336825ccdaacd
|
|
17
|
+
/qilin-agent-working-directory/use-this-package:
|
|
18
|
+
en: c7ca8dd85f3346de
|
|
19
|
+
zh: 4a0a9fe72921fa34
|
|
20
|
+
/qilin-agent-working-directory/understand-the-implementation:
|
|
21
|
+
en: 975818cfd8c8350d
|
|
22
|
+
zh: 257fab27b8ca363a
|
|
23
|
+
/qilin-agent-working-directory/further-exploration:
|
|
24
|
+
en: 7889612b1e9c9549
|
|
25
|
+
zh: 65b74a3d8471ade7
|
|
26
|
+
/qilin-agent-working-directory/model-experience:
|
|
27
|
+
en: 215e7ba838619b7b
|
|
28
|
+
zh: a311da3843709f90
|
|
29
|
+
/qilin-agent-working-directory/model-experience/working-directory-context:
|
|
30
|
+
en: 6fcebaf8006123ef
|
|
31
|
+
zh: 950162891001da33
|
|
32
|
+
/qilin-agent-working-directory/model-experience/working-directory-context/what-the-model-sees:
|
|
33
|
+
en: 5b8df05c442e0a86
|
|
34
|
+
zh: a268d162778bf05b
|
|
35
|
+
/qilin-agent-working-directory/model-experience/working-directory-context/token-effect:
|
|
36
|
+
en: a02a00deaddbccea
|
|
37
|
+
zh: 5cb96e31c6dd2d14
|
|
38
|
+
/qilin-agent-working-directory/model-experience/working-directory-context/kv-cache-effect:
|
|
39
|
+
en: f5a095acef81df3b
|
|
40
|
+
zh: fdd47f253769ad8f
|
|
41
|
+
/qilin-agent-working-directory/known-limitations-and-deferred-work:
|
|
42
|
+
en: bd51f0d87df5f9c9
|
|
43
|
+
zh: 9ef187758e47c003
|
|
44
|
+
/qilin-agent-working-directory/known-limitations-and-deferred-work/dev-note:
|
|
45
|
+
en: 0a1253c6f7b5e8b9
|
|
46
|
+
zh: 9f56528562c7c914
|
package/README.md
CHANGED
|
@@ -1,3 +1,78 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
description: "Change one Session’s working directory while existing processes retain theirs. The selected directory survives replay and appears in user context. A missing directory restores the original project when it remains available."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
# @qilin-agent/working-directory
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
Change one Session’s working directory while existing processes retain theirs. The selected directory survives replay and appears in user context. A missing directory restores the original project when it remains available.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Use this package](#use-this-package)
|
|
17
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
18
|
+
- [Further Exploration](#further-exploration)
|
|
19
|
+
- [Model Experience](#model-experience)
|
|
20
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
21
|
+
- [Dev Note](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## Use this package
|
|
27
|
+
|
|
28
|
+
Mount alongside `fs`, `sessionProjections`, and `systemPrompt`. `defaultDirectory` selects an absolute fallback for Sessions without an original directory; omission uses the launch directory. See the [configuration catalog](../../../docs/config-catalog.md).
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
- name: '@qilin-agent/working-directory'
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
-----
|
|
35
|
+
|
|
36
|
+
<a id="understand-the-implementation"></a>
|
|
37
|
+
## Understand the implementation
|
|
38
|
+
|
|
39
|
+
The Session projection owns the effective directory and exposes it in Session observations, so cold readers can use it without activating an Agent; the header continues to identify the original project. Changes serialize per Session, validate through the filesystem provider, check cancellation and Agent lifetime, commit an event, and queue a user-context notice. If notice queuing fails after commit, the operation returns the committed directory and logs a warning; the next request or resume still receives the required directory context. Prompt assembly validates the current directory before publishing its context contribution. Existing processes and sandbox grants keep their independent ownership. The [service implementation](src/index.ts) defines the API.
|
|
40
|
+
|
|
41
|
+
-----
|
|
42
|
+
|
|
43
|
+
<a id="further-exploration"></a>
|
|
44
|
+
## Further Exploration
|
|
45
|
+
|
|
46
|
+
- [Session group](../README.md) — durable Session services.
|
|
47
|
+
- [Tool catalog](../../../docs/tool-catalog.md) — the model-facing tool schemas.
|
|
48
|
+
- [Testing](../../../docs/testing.md) — composition and replay verification.
|
|
49
|
+
|
|
50
|
+
-----
|
|
51
|
+
|
|
52
|
+
<a id="model-experience"></a>
|
|
53
|
+
## Model Experience
|
|
54
|
+
|
|
55
|
+
### Working directory context
|
|
56
|
+
|
|
57
|
+
#### What the model sees
|
|
58
|
+
|
|
59
|
+
The current value is `Current working directory: <JSON-quoted absolute path>.` The contribution is registered as required and literal: it survives disabled or suppressed optional runtime context, its text is never interpolated as prompt variables, and `refreshContext()` re-resolves it at request admission so a change made after assembly still reaches the same request. Each committed change, including automatic recovery, attempts to queue a user-context notice through the ordinary Agent inbox. Cancellation or disposal may discard unadmitted notices; the durable directory remains available to the next request.
|
|
60
|
+
|
|
61
|
+
#### Token effect
|
|
62
|
+
|
|
63
|
+
The initial snapshot and changed values add user-context tokens; unchanged snapshots are not repeated.
|
|
64
|
+
|
|
65
|
+
#### KV Cache effect
|
|
66
|
+
|
|
67
|
+
Directory transitions append context without replacing the stable system-prompt prefix. A changed directory or recovery creates new context after retained history.
|
|
68
|
+
|
|
69
|
+
## Known Limitations and Deferred Work
|
|
70
|
+
|
|
71
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
72
|
+
|
|
73
|
+
- **Missing original project** — when the current directory and the original project are both unavailable, prompt assembly rejects every model turn, including chat-only turns. The service does not choose or recreate a repository. Restore the original directory, or use SDK `setWorkingDirectory` (or `ctx.workingDirectory.set`) to select an existing absolute directory before retrying. Existing shells retain their process-local directories.
|
|
74
|
+
|
|
75
|
+
<a id="dev-note"></a>
|
|
76
|
+
### Dev Note
|
|
77
|
+
|
|
78
|
+
None.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "在既有进程保持各自目录的前提下变更单个 Session 的工作目录。所选目录在回放后依然保留,并出现在用户上下文中;目录缺失时,若原始项目仍可用则恢复它。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @qilin-agent/working-directory
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
在既有进程保持各自目录的前提下变更单个 Session 的工作目录。所选目录在回放后依然保留,并出现在用户上下文中;目录缺失时,若原始项目仍可用则恢复它。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [使用本包](#use-this-package)
|
|
17
|
+
- [理解实现](#understand-the-implementation)
|
|
18
|
+
- [延伸阅读](#further-exploration)
|
|
19
|
+
- [模型体验](#model-experience)
|
|
20
|
+
- [已知限制与延后工作](#known-limitations-and-deferred-work)
|
|
21
|
+
- [开发备注](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## 使用本包
|
|
27
|
+
|
|
28
|
+
与 `fs`、`sessionProjections`、`systemPrompt` 一同挂载。`defaultDirectory` 为没有原始目录的 Session 指定绝对回退路径,省略则使用启动目录。参见[配置目录](../../../docs/config-catalog.zh.md)。
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
- name: '@qilin-agent/working-directory'
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
-----
|
|
35
|
+
|
|
36
|
+
<a id="understand-the-implementation"></a>
|
|
37
|
+
## 理解实现
|
|
38
|
+
|
|
39
|
+
Session 投影持有生效目录并在 Session 观察中暴露它,因此冷读无需激活 Agent 即可使用;header 继续标识原始项目。变更按 Session 串行:经文件系统提供者校验、检查取消与 Agent 生命周期、提交事件、并入队一条用户上下文通知。若提交后入队失败,操作仍返回已提交的目录并记录警告;下一次请求或恢复仍会收到所需的目录上下文。提示词组装会在发布其上下文条目之前校验当前目录。既有进程与沙箱授权保持各自归属。[服务实现](src/index.ts)定义了 API。
|
|
40
|
+
|
|
41
|
+
-----
|
|
42
|
+
|
|
43
|
+
<a id="further-exploration"></a>
|
|
44
|
+
## 延伸阅读
|
|
45
|
+
|
|
46
|
+
- [Session 组](../README.zh.md) — 持久 Session 服务。
|
|
47
|
+
- [工具目录](../../../docs/tool-catalog.zh.md) — 面向模型的工具 schema。
|
|
48
|
+
- [测试](../../../docs/testing.zh.md) — 组合与回放验收。
|
|
49
|
+
|
|
50
|
+
-----
|
|
51
|
+
|
|
52
|
+
<a id="model-experience"></a>
|
|
53
|
+
## 模型体验
|
|
54
|
+
|
|
55
|
+
### 工作目录上下文
|
|
56
|
+
|
|
57
|
+
#### 模型看到什么
|
|
58
|
+
|
|
59
|
+
当前值形如 `Current working directory: <JSON 引号包裹的绝对路径>.`。该贡献以 required 且 literal 方式注册:即使可选运行时上下文被关闭或抑制也仍然保留,其文本不会被当作提示词变量插值,且 `refreshContext()` 会在请求准入时重新解析它,因此装配之后发生的变更同样能在同一次请求内到达模型。每次提交的变更(含自动恢复)都会尝试通过常规 Agent inbox 排入一条用户上下文通知;取消或销毁可能丢弃尚未接纳的通知,但持久目录对下一次请求仍然可用。
|
|
60
|
+
|
|
61
|
+
#### token 影响
|
|
62
|
+
|
|
63
|
+
首次快照与变更值会新增用户上下文 token;未变化的快照不会重复。
|
|
64
|
+
|
|
65
|
+
#### KV Cache 影响
|
|
66
|
+
|
|
67
|
+
目录切换以追加方式进入上下文,不替换稳定的系统提示前缀;目录变化或恢复会在保留历史之后产生新上下文。
|
|
68
|
+
|
|
69
|
+
## 已知限制与延后工作
|
|
70
|
+
|
|
71
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
72
|
+
|
|
73
|
+
- **原始项目缺失** —— 当前目录与原始项目都不可用时,提示词组装会拒绝每一次模型回合(包括纯聊天回合)。本服务不会挑选或重建仓库。请先恢复原目录,或通过 SDK `setWorkingDirectory`(或 `ctx.workingDirectory.set`)指定一个存在的绝对目录后重试。既有 shell 仍保有各自的进程内目录。
|
|
74
|
+
|
|
75
|
+
<a id="dev-note"></a>
|
|
76
|
+
### 开发备注
|
|
77
|
+
|
|
78
|
+
无。
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { isAbsolute } from "node:path";
|
|
2
|
+
import { Service } from "@qilin-agent/kylin";
|
|
3
|
+
import z from "@qilin-agent/schemastery";
|
|
4
|
+
import { z as z$1 } from "zod";
|
|
5
|
+
import { createUserMessage } from "@qilin-agent/llm";
|
|
6
|
+
//#region lib/types/index.js
|
|
7
|
+
/**
|
|
8
|
+
* Session working directories, durable changes, and model context.
|
|
9
|
+
* @module @qilin-agent/working-directory
|
|
10
|
+
*/
|
|
11
|
+
/** One owner for each Session's effective directory and its model-visible changes. */
|
|
12
|
+
var WorkingDirectoryService = class extends Service {
|
|
13
|
+
static inject = [
|
|
14
|
+
"fs",
|
|
15
|
+
"sessionProjections",
|
|
16
|
+
"systemPrompt"
|
|
17
|
+
];
|
|
18
|
+
static Config = z.object({ defaultDirectory: z.string() });
|
|
19
|
+
/** Absolute deployment fallback for Sessions with no recorded original directory. */
|
|
20
|
+
defaultDirectory;
|
|
21
|
+
pending = /* @__PURE__ */ new WeakMap();
|
|
22
|
+
operations = /* @__PURE__ */ new Set();
|
|
23
|
+
lifecycle = new AbortController();
|
|
24
|
+
constructor(ctx, config) {
|
|
25
|
+
super(ctx, "workingDirectory");
|
|
26
|
+
const directory = config.defaultDirectory ?? process.cwd();
|
|
27
|
+
if (!isAbsolute(directory)) throw new Error("working-directory: defaultDirectory must be absolute");
|
|
28
|
+
this.defaultDirectory = directory;
|
|
29
|
+
const directorySchema = z$1.string().min(1).refine(isAbsolute).nullable();
|
|
30
|
+
ctx.sessionProjections.register({
|
|
31
|
+
key: "workingDirectory",
|
|
32
|
+
stateVersion: 1,
|
|
33
|
+
stateSchema: directorySchema,
|
|
34
|
+
init: (header) => header.cwd ?? null,
|
|
35
|
+
apply: (state, event) => event.type === "working-directory/change" ? event.data.cwd : state,
|
|
36
|
+
wire: {
|
|
37
|
+
viewSchema: directorySchema,
|
|
38
|
+
view: (state) => state
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
ctx.systemPrompt.context({
|
|
42
|
+
name: "working-directory:current",
|
|
43
|
+
order: ctx.systemPrompt.getContextOrder("WORKING_DIRECTORY"),
|
|
44
|
+
required: true,
|
|
45
|
+
interpolate: false,
|
|
46
|
+
text: (context) => context.agent === void 0 ? "" : this.contextText(context.agent.session)
|
|
47
|
+
});
|
|
48
|
+
ctx.on("system-prompt/assemble", async (_assembly, context, next) => {
|
|
49
|
+
if (context.agent === void 0) return next();
|
|
50
|
+
await this.ensure(context.agent, context.signal);
|
|
51
|
+
const assembly = await next();
|
|
52
|
+
this.lifecycle.signal.throwIfAborted();
|
|
53
|
+
const text = this.contextText(context.agent.session);
|
|
54
|
+
const contribution = assembly.contexts.find((entry) => entry.name === "working-directory:current");
|
|
55
|
+
if (contribution === void 0) assembly.contexts.unshift({
|
|
56
|
+
name: "working-directory:current",
|
|
57
|
+
text,
|
|
58
|
+
interpolate: false
|
|
59
|
+
});
|
|
60
|
+
else {
|
|
61
|
+
contribution.text = text;
|
|
62
|
+
contribution.interpolate = false;
|
|
63
|
+
}
|
|
64
|
+
return assembly;
|
|
65
|
+
}, { prepend: true });
|
|
66
|
+
ctx.effect(() => async () => {
|
|
67
|
+
this.lifecycle.abort(/* @__PURE__ */ new Error("working-directory service disposed"));
|
|
68
|
+
await Promise.allSettled(this.operations);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Read the committed directory without filesystem I/O.
|
|
73
|
+
* @param session - Session whose directory is requested.
|
|
74
|
+
* @returns its effective absolute directory.
|
|
75
|
+
*/
|
|
76
|
+
get(session) {
|
|
77
|
+
return this.ctx.sessionProjections.stateOf(session, "workingDirectory") ?? this.defaultDirectory;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Validate the current directory and restore the original project when it disappeared.
|
|
81
|
+
* @param agent - live or unpublished Agent owning the Session.
|
|
82
|
+
* @param signal - cancellation for filesystem inspection.
|
|
83
|
+
* @returns the existing directory; recovery is committed before fulfillment.
|
|
84
|
+
* A notice failure is warned without reverting the committed state.
|
|
85
|
+
* @throws when the original project is also unavailable.
|
|
86
|
+
*/
|
|
87
|
+
ensure(agent, signal) {
|
|
88
|
+
return this.serialize(agent, signal, async (control) => {
|
|
89
|
+
const current = this.get(agent.session);
|
|
90
|
+
const target = await this.ctx.fs.resolve(current, { signal: control });
|
|
91
|
+
const info = await this.ctx.fs.stat(target, control);
|
|
92
|
+
control.throwIfAborted();
|
|
93
|
+
agent.ctx.fiber.assertActive();
|
|
94
|
+
if (info?.type === "directory") {
|
|
95
|
+
if (this.ctx.sessionProjections.stateOf(agent.session, "workingDirectory") === null) agent.session.append("working-directory/change", { cwd: current });
|
|
96
|
+
return current;
|
|
97
|
+
}
|
|
98
|
+
const original = agent.session.header.cwd ?? this.defaultDirectory;
|
|
99
|
+
const restored = await this.directory(original, original, control);
|
|
100
|
+
this.commit(agent, restored, true, control);
|
|
101
|
+
return restored;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Change one Session's directory without changing existing processes or permissions.
|
|
106
|
+
* @param agent - live or unpublished Agent owning the Session.
|
|
107
|
+
* @param path - absolute path or a path relative to its current directory.
|
|
108
|
+
* @param signal - cancellation before the durable change.
|
|
109
|
+
* @returns the canonical absolute directory, committed before fulfillment.
|
|
110
|
+
* A notice failure is warned; the next request still receives the committed directory.
|
|
111
|
+
* @throws when the requested path is not an existing directory.
|
|
112
|
+
*/
|
|
113
|
+
set(agent, path, signal) {
|
|
114
|
+
return this.serialize(agent, signal, async (control) => {
|
|
115
|
+
if (path.length === 0) throw new Error("working-directory: cd must not be empty");
|
|
116
|
+
const directory = await this.directory(path, this.get(agent.session), control);
|
|
117
|
+
this.commit(agent, directory, false, control);
|
|
118
|
+
return directory;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
contextText(session) {
|
|
122
|
+
return `Current working directory: ${JSON.stringify(this.get(session))}.`;
|
|
123
|
+
}
|
|
124
|
+
async directory(path, cwd, signal) {
|
|
125
|
+
const target = await this.ctx.fs.resolve(path, {
|
|
126
|
+
cwd,
|
|
127
|
+
signal
|
|
128
|
+
});
|
|
129
|
+
const info = await this.ctx.fs.stat(target, signal);
|
|
130
|
+
signal.throwIfAborted();
|
|
131
|
+
if (info?.type !== "directory") throw new Error(`working-directory: directory does not exist: ${JSON.stringify(path)}`);
|
|
132
|
+
return this.ctx.fs.processPath(target);
|
|
133
|
+
}
|
|
134
|
+
commit(agent, directory, recovered, signal) {
|
|
135
|
+
signal.throwIfAborted();
|
|
136
|
+
agent.ctx.fiber.assertActive();
|
|
137
|
+
const previous = this.get(agent.session);
|
|
138
|
+
if (previous === directory) return;
|
|
139
|
+
agent.session.append("working-directory/change", { cwd: directory });
|
|
140
|
+
const notice = createUserMessage({
|
|
141
|
+
content: [{
|
|
142
|
+
type: "text",
|
|
143
|
+
text: recovered ? `The working directory ${JSON.stringify(previous)} is unavailable. The working directory is now ${JSON.stringify(directory)}.` : `The working directory changed from ${JSON.stringify(previous)} to ${JSON.stringify(directory)}.`
|
|
144
|
+
}],
|
|
145
|
+
source: { kind: "working-directory" }
|
|
146
|
+
});
|
|
147
|
+
try {
|
|
148
|
+
agent.inject(notice);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
this.ctx.logger.warn(`working-directory: committed ${JSON.stringify(directory)}, but could not queue its notice: ${String(error)}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
serialize(agent, signal, action) {
|
|
154
|
+
const session = agent.session;
|
|
155
|
+
const control = signal === void 0 ? this.lifecycle.signal : AbortSignal.any([this.lifecycle.signal, signal]);
|
|
156
|
+
const settled = (this.pending.get(session) ?? Promise.resolve()).then(() => {
|
|
157
|
+
control.throwIfAborted();
|
|
158
|
+
agent.ctx.fiber.assertActive();
|
|
159
|
+
return action(control);
|
|
160
|
+
}).finally(() => this.operations.delete(settled));
|
|
161
|
+
this.operations.add(settled);
|
|
162
|
+
this.pending.set(session, settled.then(() => {}, () => {}));
|
|
163
|
+
return settled;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
//#endregion
|
|
167
|
+
export { WorkingDirectoryService, WorkingDirectoryService as default };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session working directories, durable changes, and model context.
|
|
3
|
+
* @module @qilin-agent/working-directory
|
|
4
|
+
*/
|
|
5
|
+
import { Context, Service } from '@qilin-agent/kylin';
|
|
6
|
+
import z from '@qilin-agent/schemastery';
|
|
7
|
+
import type { Agent } from '@qilin-agent/agent';
|
|
8
|
+
import type { Session } from '@qilin-agent/session';
|
|
9
|
+
declare module '@qilin-agent/kylin' {
|
|
10
|
+
interface Context {
|
|
11
|
+
workingDirectory: WorkingDirectoryService;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
declare module '@qilin-agent/llm' {
|
|
15
|
+
interface MessageSourceMap {
|
|
16
|
+
/**
|
|
17
|
+
* Committed working-directory transition notices.
|
|
18
|
+
* @persistenceAttribution
|
|
19
|
+
*/
|
|
20
|
+
'working-directory': {
|
|
21
|
+
kind: 'working-directory';
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
declare module '@qilin-agent/session/types' {
|
|
26
|
+
interface SessionEventMap {
|
|
27
|
+
/** Effective working directory; the immutable Session header retains the original project. */
|
|
28
|
+
'working-directory/change': {
|
|
29
|
+
/** Absolute directory used by subsequent directory-based operations. */
|
|
30
|
+
cwd: string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
declare module '@qilin-agent/session-projection/types' {
|
|
35
|
+
interface SessionProjectionMap {
|
|
36
|
+
/** Recorded current directory for Session observations, or null when none is recorded. */
|
|
37
|
+
workingDirectory: string | null;
|
|
38
|
+
}
|
|
39
|
+
interface SessionProjectionStateMap {
|
|
40
|
+
/** Last committed working directory, or null before an explicit value exists. */
|
|
41
|
+
workingDirectory: string | null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Deployment default for Sessions without an original project directory. */
|
|
45
|
+
export interface Config {
|
|
46
|
+
/** Absolute execution-world fallback directory; omitted values use the launch directory. */
|
|
47
|
+
defaultDirectory?: string;
|
|
48
|
+
}
|
|
49
|
+
/** One owner for each Session's effective directory and its model-visible changes. */
|
|
50
|
+
export declare class WorkingDirectoryService extends Service {
|
|
51
|
+
static inject: string[];
|
|
52
|
+
static Config: z<Config>;
|
|
53
|
+
/** Absolute deployment fallback for Sessions with no recorded original directory. */
|
|
54
|
+
readonly defaultDirectory: string;
|
|
55
|
+
private readonly pending;
|
|
56
|
+
private readonly operations;
|
|
57
|
+
private readonly lifecycle;
|
|
58
|
+
constructor(ctx: Context, config: Config);
|
|
59
|
+
/**
|
|
60
|
+
* Read the committed directory without filesystem I/O.
|
|
61
|
+
* @param session - Session whose directory is requested.
|
|
62
|
+
* @returns its effective absolute directory.
|
|
63
|
+
*/
|
|
64
|
+
get(session: Session): string;
|
|
65
|
+
/**
|
|
66
|
+
* Validate the current directory and restore the original project when it disappeared.
|
|
67
|
+
* @param agent - live or unpublished Agent owning the Session.
|
|
68
|
+
* @param signal - cancellation for filesystem inspection.
|
|
69
|
+
* @returns the existing directory; recovery is committed before fulfillment.
|
|
70
|
+
* A notice failure is warned without reverting the committed state.
|
|
71
|
+
* @throws when the original project is also unavailable.
|
|
72
|
+
*/
|
|
73
|
+
ensure(agent: Agent, signal?: AbortSignal): Promise<string>;
|
|
74
|
+
/**
|
|
75
|
+
* Change one Session's directory without changing existing processes or permissions.
|
|
76
|
+
* @param agent - live or unpublished Agent owning the Session.
|
|
77
|
+
* @param path - absolute path or a path relative to its current directory.
|
|
78
|
+
* @param signal - cancellation before the durable change.
|
|
79
|
+
* @returns the canonical absolute directory, committed before fulfillment.
|
|
80
|
+
* A notice failure is warned; the next request still receives the committed directory.
|
|
81
|
+
* @throws when the requested path is not an existing directory.
|
|
82
|
+
*/
|
|
83
|
+
set(agent: Agent, path: string, signal?: AbortSignal): Promise<string>;
|
|
84
|
+
private contextText;
|
|
85
|
+
private directory;
|
|
86
|
+
private commit;
|
|
87
|
+
private serialize;
|
|
88
|
+
}
|
|
89
|
+
export default WorkingDirectoryService;
|
|
90
|
+
//# sourceMappingURL=index.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qilin-agent/working-directory",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"description": "Session working directories with durable changes and model context",
|
|
4
|
+
"version": "3.1.4",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/kkutysllb/QiLin.git",
|
|
11
|
+
"directory": "packages/session/working-directory"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "lib/index.js",
|
|
18
|
+
"types": "lib/types/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./lib/types/index.d.ts",
|
|
22
|
+
"default": "./lib/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./src/*": "./src/*",
|
|
25
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"lib/index.js",
|
|
29
|
+
"lib/types/**/*.d.ts"
|
|
30
|
+
],
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@qilin-agent/kylin": "~4.0.3",
|
|
34
|
+
"@qilin-agent/fs": "3.1.4",
|
|
35
|
+
"@qilin-agent/agent": "3.1.4",
|
|
36
|
+
"@qilin-agent/session-projection": "3.1.4",
|
|
37
|
+
"@qilin-agent/system-prompt": "3.1.4",
|
|
38
|
+
"@qilin-agent/llm": "3.1.4",
|
|
39
|
+
"@qilin-agent/session": "3.1.4"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"zod": "^4.4.3",
|
|
43
|
+
"@qilin-agent/schemastery": "~3.18.3"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@qilin-agent/kylin": "~4.0.3",
|
|
47
|
+
"@qilin-agent/fs": "3.1.4",
|
|
48
|
+
"@qilin-agent/agent-loop-testkit": "3.1.4",
|
|
49
|
+
"@qilin-agent/fs-local": "3.1.4",
|
|
50
|
+
"@qilin-agent/llm": "3.1.4",
|
|
51
|
+
"@qilin-agent/agent": "3.1.4",
|
|
52
|
+
"@qilin-agent/session": "3.1.4",
|
|
53
|
+
"@qilin-agent/system-prompt": "3.1.4",
|
|
54
|
+
"@qilin-agent/session-projection": "3.1.4",
|
|
55
|
+
"@qilin-agent/tool-working-directory": "3.1.4",
|
|
56
|
+
"@qilin-agent/tools": "3.1.4"
|
|
57
|
+
}
|
|
6
58
|
}
|