rtrt-agent 0.1.0 → 0.1.1

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.ko.md CHANGED
@@ -7,14 +7,14 @@ hook 사이에서 상위 session, agent, worktree, invocation 및 permission bro
7
7
  ## 설치
8
8
 
9
9
  ```sh
10
- npm install rtrt-agent@0.1.0
10
+ npm install rtrt-agent@0.1.1
11
11
  ```
12
12
 
13
13
  `opencode.json`의 단수형 `plugin` 배열에 패키지를 등록합니다.
14
14
 
15
15
  ```json
16
16
  {
17
- "plugin": ["rtrt-agent"]
17
+ "plugin": ["rtrt-agent@0.1.1"]
18
18
  }
19
19
  ```
20
20
 
@@ -22,18 +22,20 @@ OpenCode는 이름 있는 `RtrtProvenance` export를 로드합니다. 이 패키
22
22
  default export를 제공하지 않습니다. 선택 사항인 RTRT TUI statusline은 별도 소스
23
23
  통합으로 유지되며 이 패키지에서 export하거나 pack하지 않습니다.
24
24
 
25
- `rtrt setup --agent opencode --apply`는 다른 setup write보다 먼저 인식된 legacy RTRT
26
- plugin을 제거합니다. Setup은 전체 RTRT 통합을 관리하지만 npm 설치는 수행하지 않으며,
27
- 설정된 패키지는 OpenCode가 시작할 설치합니다. Setup은 `rtrt-agent`의 정확히 일치하는
28
- bare, pinned, ranged, tuple, object 형식만 소유하며 bare `"rtrt-agent"` string 하나로
29
- 정규화합니다. 이전의 미게시 `rtrt`와 초안 `rtrt-opencode` 패키지 spec은 외부 값이며 순서를
30
- 유지합니다. 직접 등록과 `opencode plugin rtrt-agent --global`은 계속 유효합니다.
25
+ `rtrt setup --agent opencode --apply`는 전체 RTRT 통합을 관리하지만 npm 설치는 수행하지
26
+ 않으며, 설정된 패키지는 OpenCode가 시작할 설치합니다. Setup은 먼저 정확한
27
+ `rtrt-agent@0.1.1` 등록과 모든 대체 관리 asset을 기록합니다. 기록이 성공한 뒤에만
28
+ 인식된 legacy RTRT plugin을 제거하며, 이전 단계가 실패하면 legacy runtime을 보존합니다.
29
+ Setup은 `rtrt-agent`의 정확히 일치하는 bare, pinned, ranged, tuple, object 형식만 소유하고
30
+ 정확한 `"rtrt-agent@0.1.1"` string 하나로 정규화합니다. 이전의 미게시 `rtrt`와 초안
31
+ `rtrt-opencode` 패키지 spec은 외부 값이며 순서를 유지합니다. 위 직접 등록과
32
+ `opencode plugin rtrt-agent@0.1.1 --global`은 계속 유효합니다.
31
33
 
32
34
  관리 agent 소유권 상태는 다음 순서의 첫 비어 있지 않은 root에서 읽습니다.
33
35
  `$OPENCODE_CONFIG_DIR`, `$XDG_CONFIG_HOME/opencode`, `~/.config/opencode`. 공존은 OMO
34
36
  4.19.4를 대상으로 CI에서 검사하고 OpenCode 1.18.29에서 직접 검증했으며, 어느 쪽도
35
- 미래 버전 지원을 보장하지 않습니다. RTRT CLI 릴리스가 이를 등록하기 전에
36
- `rtrt-agent@0.1.0`을 게시해야 합니다.
37
+ 미래 버전 지원을 보장하지 않습니다. 통합 RTRT 릴리스 워크플로는 정확한
38
+ `rtrt-agent@0.1.1` 패키지를 일치하는 Rust 릴리스와 함께 게시합니다.
37
39
 
38
40
  ## 라이선스
39
41
 
package/README.md CHANGED
@@ -7,14 +7,14 @@ hooks while keeping the broker loopback-only.
7
7
  ## Install
8
8
 
9
9
  ```sh
10
- npm install rtrt-agent@0.1.0
10
+ npm install rtrt-agent@0.1.1
11
11
  ```
12
12
 
13
13
  Register the package in the singular `plugin` array in `opencode.json`:
14
14
 
15
15
  ```json
16
16
  {
17
- "plugin": ["rtrt-agent"]
17
+ "plugin": ["rtrt-agent@0.1.1"]
18
18
  }
19
19
  ```
20
20
 
@@ -22,19 +22,21 @@ OpenCode loads the named `RtrtProvenance` export. The package intentionally has
22
22
  default export. The optional RTRT TUI statusline remains a separate source integration
23
23
  and is not exported or packed by this package.
24
24
 
25
- `rtrt setup --agent opencode --apply` first removes a recognized legacy RTRT plugin
26
- before any other setup write. It manages the full RTRT integration but performs no npm
27
- installation; OpenCode installs the configured package at startup. Setup owns exact bare,
28
- pinned, ranged, tuple, and object forms of `rtrt-agent` only, and normalizes them to one
29
- bare `"rtrt-agent"` string. Old unpublished `rtrt` and draft `rtrt-opencode` package specs
30
- are foreign and retain their order. The direct registration above and
31
- `opencode plugin rtrt-agent --global` remain valid.
25
+ `rtrt setup --agent opencode --apply` manages the full RTRT integration but performs no
26
+ npm installation; OpenCode installs the configured package at startup. Setup first writes
27
+ the exact `rtrt-agent@0.1.1` registration and every replacement managed asset. Only after
28
+ those writes succeed does it remove a recognized legacy RTRT plugin; an earlier failure
29
+ preserves the legacy runtime. Setup owns exact bare, pinned, ranged, tuple, and object forms
30
+ of `rtrt-agent` only and normalizes them to one exact `"rtrt-agent@0.1.1"` string. Old
31
+ unpublished `rtrt` and draft `rtrt-opencode` package specs are foreign and retain their
32
+ order. The direct registration above and `opencode plugin rtrt-agent@0.1.1 --global`
33
+ remain valid.
32
34
 
33
35
  Managed-agent ownership state is read from the first nonempty root in this order:
34
36
  `$OPENCODE_CONFIG_DIR`, `$XDG_CONFIG_HOME/opencode`, then `~/.config/opencode`.
35
37
  Coexistence is CI-gated against OMO 4.19.4 and was verified on OpenCode 1.18.29; neither
36
- is a future-version guarantee. `rtrt-agent@0.1.0` must be published before an RTRT CLI
37
- release registers it.
38
+ is a future-version guarantee. The unified RTRT release workflow publishes the exact
39
+ `rtrt-agent@0.1.1` package together with the matching Rust release.
38
40
 
39
41
  ## License
40
42
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rtrt-agent",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "RTRT provenance and permission-broker plugin for OpenCode",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -128,94 +128,19 @@ const loadManagedAgents = async (statePath) => {
128
128
  }
129
129
  }
130
130
 
131
- const realisticInside = async (root, operand) => {
132
- if (!operand || operand.includes("\0") || URL_ARGUMENT.test(operand)) return false
133
- if (!path.isAbsolute(operand) && operand.split(/[\\/]/).includes("..")) return false
134
- const context = typeof root === "string" ? { cwd: root, bounds: [root] } : root
135
- const target = path.resolve(context.cwd, operand)
136
- if (!context.bounds.some((bound) => isWithin(bound, target))) return false
137
- let probe = target
138
- while (true) {
139
- try {
140
- const resolved = await realpath(probe)
141
- return context.bounds.some((bound) => isWithin(bound, resolved))
142
- } catch (error) {
143
- if (error?.code !== "ENOENT" && error?.code !== "ENOTDIR") return false
144
- const parent = path.dirname(probe)
145
- if (parent === probe || !context.bounds.some((bound) => isWithin(bound, parent))) return false
146
- probe = parent
147
- }
148
- }
149
- }
150
-
151
- const splitOptions = (args, valueOptions = new Set()) => {
152
- const operands = []
153
- for (let index = 0; index < args.length; index += 1) {
154
- const argument = args[index]
155
- if (argument === "--") {
156
- operands.push(...args.slice(index + 1))
157
- break
158
- }
159
- if (!argument.startsWith("-") || argument === "-") {
160
- operands.push(argument)
161
- continue
162
- }
163
- const [option, attached] = argument.split(/=(.*)/s, 2)
164
- if (valueOptions.has(option)) {
165
- const value = attached ?? args[++index]
166
- if (!value) return
167
- operands.push(value)
168
- }
169
- }
170
- return operands
171
- }
172
-
173
- const allPathsInside = async (root, operands) => {
174
- if (!operands) return false
175
- for (const operand of operands) if (!(await realisticInside(root, operand))) return false
176
- return true
177
- }
178
-
179
- const READ_COMMANDS = new Set(["cat", "wc", "stat"])
180
-
131
+ // Auto-approval deliberately covers no command that names a path. This hook
132
+ // resolves operands when the permission is evaluated, but the shell resolves
133
+ // them again when it runs, so anyone who can write to the checkout can swap an
134
+ // approved file for a symlink in between and read an arbitrary file into the
135
+ // model's context. `pwd` is the only command whose meaning cannot be redirected
136
+ // that way; everything else stays at "ask".
181
137
  const safeCommand = async (command, root) => {
138
+ if (typeof command !== "string" || /[{}]/.test(command)) return false
182
139
  const tokens = tokenizeCommand(command)
183
140
  if (!tokens || !root || !BARE_COMMAND.test(tokens[0]) || tokens.some((item) => URL_ARGUMENT.test(item))) return false
184
141
  const [executable, ...args] = tokens
185
- for (const argument of args) {
186
- const value = argument.includes("=") ? argument.slice(argument.indexOf("=") + 1) : argument
187
- if ((path.isAbsolute(value) || value.includes("/") || value.includes("\\") || value.startsWith(".")) && !(await realisticInside(root, value))) return false
188
- }
189
- if (executable === "pwd") return args.every((item) => item === "-L" || item === "-P")
190
- if (executable === "ls") {
191
- if (args.some((item) => item.startsWith("-") && !/^-[AacdFfghikLlmnopqRrSstuUx1]+$/.test(item))) return false
192
- return allPathsInside(root, args.filter((item) => !item.startsWith("-")))
193
- }
194
- if (READ_COMMANDS.has(executable)) {
195
- if (args.some((item) => item.startsWith("-") && item !== "--")) return false
196
- const operands = splitOptions(args)
197
- return Boolean(operands?.length) && allPathsInside(root, operands)
198
- }
199
- if (["head", "tail"].includes(executable)) {
200
- const operands = []
201
- for (let index = 0; index < args.length; index += 1) {
202
- const argument = args[index]
203
- if (argument === "-q" || argument === "--quiet" || argument === "--silent" || argument === "-v" || argument === "--verbose") continue
204
- const attached = argument.match(/^(?:-n|--lines|-c|--bytes)=(.*)$/)
205
- if (attached) {
206
- if (!/^[-+]?\d+$/.test(attached[1])) return false
207
- continue
208
- }
209
- if (["-n", "--lines", "-c", "--bytes"].includes(argument)) {
210
- if (!/^[-+]?\d+$/.test(args[++index] ?? "")) return false
211
- continue
212
- }
213
- if (argument.startsWith("-")) return false
214
- operands.push(argument)
215
- }
216
- return operands.length > 0 && allPathsInside(root, operands)
217
- }
218
- return false
142
+ if (executable !== "pwd") return false
143
+ return args.every((item) => item === "-L" || item === "-P")
219
144
  }
220
145
 
221
146
  const defaultClientFactory = async (options) => {
@@ -226,7 +151,6 @@ const defaultClientFactory = async (options) => {
226
151
  const RTRT_AGENT_TOOLS = new Set([
227
152
  "rtrt_agent_call",
228
153
  "rtrt_agent_route",
229
- "rtrt_team_dispatch",
230
154
  ])
231
155
 
232
156
  const sanitizeSessionID = (sessionID) => {