larkin 0.5.4 → 0.5.5
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 +6 -0
- package/README.md +7 -1
- package/artifacts/release/THIRD_PARTY_NOTICES.txt +1 -100
- package/dist/agent/context-prompt.cjs +16 -17
- package/dist/agent/context-prompt.mjs +15 -16
- package/dist/runtime/pi-state-dir.cjs +105 -0
- package/dist/runtime/pi-state-dir.mjs +94 -0
- package/dist/runtime/pi-tmux-extension.mjs +137 -0
- package/dist/runtime/pi-tmux-injection.mjs +49 -0
- package/dist/runtime/pi-tmux.bundle.js +600 -0
- package/dist/runtime/pi-tmux.mjs +408 -0
- package/dist/runtime/runtime-adapters.mjs +26 -113
- package/dist/runtime/runtime-host.cjs +3 -413
- package/dist/runtime/runtime-host.mjs +2 -412
- package/package.json +2 -8
- package/dist/runtime/pi-bash-timeout-extension.mjs +0 -60
- package/dist/runtime/pi-bash-timeout-injection.mjs +0 -54
- package/dist/runtime/pi-bash-timeout.bundle.js +0 -43
- package/dist/runtime/pi-extension-api.mjs +0 -1
- package/dist/runtime/pi-subagent-injection.mjs +0 -290
- package/dist/runtime/pi-subagent-ledger.cjs +0 -632
- package/dist/runtime/pi-subagent-ledger.mjs +0 -594
- package/dist/runtime/pi-subagent-record-watchdog-injection.mjs +0 -43
- package/dist/runtime/pi-subagent-record-watchdog.bundle.js +0 -183
- package/dist/runtime/pi-subagent-record-watchdog.mjs +0 -46
- package/dist/runtime/pi-subagents-notification.mjs +0 -175
- package/dist/runtime/pi-subagents.bundle.js +0 -9110
- package/dist/runtime/pi-supervised-command-extension.mjs +0 -72
- package/dist/runtime/pi-supervised-command.bundle.js +0 -4735
- package/dist/runtime/pi-supervised-command.mjs +0 -420
- package/patches/@tintinweb%2Fpi-subagents@0.14.3.patch +0 -945
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.5
|
|
4
|
+
|
|
5
|
+
Pi uses a small Larkin-owned tmux Bash extension when tmux is available on macOS/Linux. Commands preserve their working directory, including non-Git paths with spaces; a foreground wait can return while the same command continues. The returned task ID supports inspection and cancellation, and background completion resumes the Agent. Native Windows and environments without tmux retain Pi's native Bash.
|
|
6
|
+
|
|
7
|
+
Removed the injected subagent plugin, its ledger/watchdog and supervised-command infrastructure, the 60-second Bash hard-kill rule, and mandatory delegation guidance. Pi-owned turns now occupy Runtime busy state without a second host prompt. Historical subagent files are left untouched. Background commands survive Pi shutdown in tmux; notification recovery across Pi restarts is not provided. No third-party tmux plugin is installed or redistributed.
|
|
8
|
+
|
|
3
9
|
## 0.5.4
|
|
4
10
|
|
|
5
11
|
Inbox Audit is now optional and off by default. The Dashboard and CLI support global and per-Agent switches and inspection gaps. Saving a gap updates scheduling without replacing Runtime sessions; editing a gap alone does not enable auditing. Only originally wake-eligible human group/topic work is audited.
|
package/README.md
CHANGED
|
@@ -91,7 +91,13 @@ Feishu (https://open.feishu.cn) and Lark (https://open.larksuite.com) are differ
|
|
|
91
91
|
|
|
92
92
|
During setup, choose one of the three externally installed runtimes: Pi (`pi`), Codex (`codex`), or Claude Code (`claude`). Larkin does not ship a runtime and does not store provider credentials. Install the runtime yourself and complete its own login (`pi` login flow, `codex login`, or `claude login`) before setup. Interactive setup lists each runtime as installed or not installed and refuses a missing binary; non-interactive setup requires `--runtime` and exits non-zero with the same missing-install message.
|
|
93
93
|
|
|
94
|
-
`larkin setup --model <id>` optionally stores a catalog model for that runtime. After setup, use `larkin model` and `larkin runtime` to inspect or switch. For Pi, Larkin talks to your installed `pi --mode rpc
|
|
94
|
+
`larkin setup --model <id>` optionally stores a catalog model for that runtime. After setup, use `larkin model` and `larkin runtime` to inspect or switch. For Pi, Larkin talks to your installed `pi --mode rpc` and verifies the RPC handshake and compaction capability contract.
|
|
95
|
+
|
|
96
|
+
### Optional Pi tmux-backed bash
|
|
97
|
+
|
|
98
|
+
On macOS and Linux with `tmux` 3.2 or newer installed, Larkin provides a small tmux-backed Bash tool. Commands run in their requested directory, including non-Git directories and paths with spaces. By default the tool waits up to 30 seconds, then returns a `taskId` while the same command continues; `background: true` returns immediately. The `tmux` tool lists, inspects and stops tasks owned by that Pi instance. Completed background commands notify the Agent. There is no Larkin-imposed 60-second kill or ten-minute command limit.
|
|
99
|
+
|
|
100
|
+
Without a supported tmux version, or on native Windows, Pi keeps native bash. After Pi shuts down, background commands remain in tmux, but automatic completion notifications are not restored; use `tmux list-sessions` to find the session containing the task ID and attach manually. Larkin does not require a third-party tmux plugin.
|
|
95
101
|
|
|
96
102
|
### Optional Inbox Audit
|
|
97
103
|
|
|
@@ -4,7 +4,7 @@ Generated at release time from Larkin's installed runtime dependency closure.
|
|
|
4
4
|
Development-only dependencies are intentionally excluded. Packages without a bundled license file
|
|
5
5
|
use an explicitly audited, version- and hash-pinned license source and remain identified by locked package name and version.
|
|
6
6
|
|
|
7
|
-
Runtime package versions:
|
|
7
|
+
Runtime package versions: 97
|
|
8
8
|
|
|
9
9
|
| Package | Version | Relationship | Declared license | License texts (SHA-256) | Lock integrity |
|
|
10
10
|
| --- | ---: | --- | --- | --- | --- |
|
|
@@ -43,8 +43,6 @@ Runtime package versions: 101
|
|
|
43
43
|
| @radix-ui/react-use-controllable-state | 1.2.6 | runtime transitive | MIT | LICENSE (0e80a2d229d2fd4fc7e8636142ec5d0ff0bc031f14c15b682e2ac01dfd5b5138) | sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ== |
|
|
44
44
|
| @radix-ui/react-use-effect-event | 0.0.5 | runtime transitive | MIT | LICENSE (0e80a2d229d2fd4fc7e8636142ec5d0ff0bc031f14c15b682e2ac01dfd5b5138) | sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg== |
|
|
45
45
|
| @radix-ui/react-use-layout-effect | 1.1.4 | runtime transitive | MIT | LICENSE (0e80a2d229d2fd4fc7e8636142ec5d0ff0bc031f14c15b682e2ac01dfd5b5138) | sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw== |
|
|
46
|
-
| @sinclair/typebox | 0.34.52 | runtime transitive | MIT | license (411f1c560755e27b3dec9622c13b8b7a3ec1c83f4ffb8c56a8847528aa96911a) | sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw== |
|
|
47
|
-
| @tintinweb/pi-subagents | 0.14.3 | runtime direct | MIT | LICENSE (e7d9d11c28cce34f09dc7d2b0a3a6609ccba5fbc8e926e6d1bfdf060930e9f05) | sha512-iDqeadh6114AZvw8HYe1PEq8M0MZ9czJKTAIsklCPUbV9vUMS+g/LAV0vW3O9PiBXKNJh8hkrY8L6iIr8XNEqA== |
|
|
48
46
|
| agent-base | 6.0.2 | runtime transitive | MIT | AUDITED-agent-base@9.0.0-LICENSE (71368fd0f5b4129191e9afcd1e1ef2dc89a9090d3e4d80bbab92dafd032b3bef) | sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== |
|
|
49
47
|
| agent-base | 9.0.0 | runtime transitive | MIT | LICENSE (71368fd0f5b4129191e9afcd1e1ef2dc89a9090d3e4d80bbab92dafd032b3bef) | sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA== |
|
|
50
48
|
| aria-hidden | 1.2.6 | runtime transitive | MIT | LICENSE (30f0cfddf483d1128e3610205020f2041a6c5e837aa999e0aa82e5576187d4a9) | sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA== |
|
|
@@ -53,7 +51,6 @@ Runtime package versions: 101
|
|
|
53
51
|
| call-bind-apply-helpers | 1.0.2 | runtime transitive | MIT | LICENSE (5e325595b4ea8cfec3802f545b1def5d7b73e4a5b8e9ba63e32a320f67732292) | sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== |
|
|
54
52
|
| call-bound | 1.0.4 | runtime transitive | MIT | LICENSE (5e325595b4ea8cfec3802f545b1def5d7b73e4a5b8e9ba63e32a320f67732292) | sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== |
|
|
55
53
|
| combined-stream | 1.0.8 | runtime transitive | MIT | License (47eb8ca82c798246774946d1be0f9aa08f025fa8325ced0947aeeb4c05fe5547) | sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== |
|
|
56
|
-
| croner | 10.0.1 | runtime transitive | MIT | LICENSE (ad9aa40aaab4bee08121a0180096cc6a72abf29e19e606328b66c0178f41d267) | sha512-ixNtAJndqh173VQ4KodSdJEI6nuioBWI0V1ITNKhZZsO0pEMoDxz539T4FTTbSZ/xIOSuDnzxLVRqBVSvPNE2g== |
|
|
57
54
|
| debug | 4.4.3 | runtime transitive | MIT | LICENSE (23eb8602cd5902bcfca56b23aa08e8672a809f5a12eaf8bb56abfa9d44e1a4fd) | sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== |
|
|
58
55
|
| delayed-stream | 1.0.0 | runtime transitive | MIT | License (47eb8ca82c798246774946d1be0f9aa08f025fa8325ced0947aeeb4c05fe5547) | sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== |
|
|
59
56
|
| detect-node-es | 1.1.0 | runtime transitive | MIT | LICENSE (54b32293ea560d22cd9b62af9b2cda1a030d910a48c6601d227c5ff2338d95f8) | sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== |
|
|
@@ -84,7 +81,6 @@ Runtime package versions: 101
|
|
|
84
81
|
| mime-db | 1.52.0 | runtime transitive | MIT | LICENSE (cc1dfd4dafa27271e8212cd3b274eeb3f262e40a6fdab36ddc3f9696f706f58b) | sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== |
|
|
85
82
|
| mime-types | 2.1.35 | runtime transitive | MIT | LICENSE (71f83c4c0621102a56d9853812777b85751bce7e9726f686f5b056c1f8a4b0e6) | sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== |
|
|
86
83
|
| ms | 2.1.3 | runtime transitive | MIT | license.md (1662fae9b5314d11cf51284e2dcd1f006a354f7343f08712a730fcff9a359801) | sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== |
|
|
87
|
-
| nanoid | 5.1.16 | runtime transitive | MIT | LICENSE (4383cb2c3608397ce7a4159502614ed66890f8999c2a9c056dd3b1024d6721f0) | sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ== |
|
|
88
84
|
| object-inspect | 1.13.4 | runtime transitive | MIT | LICENSE (bd40cc437e28a3ad7bef2ad34e6b72e757b182e67bda1acadbab4ef0476f8232) | sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== |
|
|
89
85
|
| proper-lockfile | 4.1.2 | runtime direct | MIT | LICENSE (aaa78451b6fecd1b9c4594c796c133c0e90cad100372ff8bc6de615e9ef9adf1) | sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== |
|
|
90
86
|
| protobufjs | 7.6.5 | runtime transitive | BSD-3-Clause | LICENSE (49d6a1c9a623784c61c6cb70f773f3457faceb1914a13c8560a9823b7631950c) | sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw== |
|
|
@@ -414,55 +410,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
414
410
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
415
411
|
SOFTWARE.
|
|
416
412
|
|
|
417
|
-
--- SHA-256 411f1c560755e27b3dec9622c13b8b7a3ec1c83f4ffb8c56a8847528aa96911a ---
|
|
418
|
-
TypeBox
|
|
419
|
-
|
|
420
|
-
Json Schema Type Builder with Static Type Resolution for TypeScript
|
|
421
|
-
|
|
422
|
-
The MIT License (MIT)
|
|
423
|
-
|
|
424
|
-
Copyright (c) 2017-2026 Haydn Paterson
|
|
425
|
-
|
|
426
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
427
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
428
|
-
in the Software without restriction, including without limitation the rights
|
|
429
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
430
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
431
|
-
furnished to do so, subject to the following conditions:
|
|
432
|
-
|
|
433
|
-
The above copyright notice and this permission notice shall be included in
|
|
434
|
-
all copies or substantial portions of the Software.
|
|
435
|
-
|
|
436
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
437
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
438
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
439
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
440
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
441
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
442
|
-
THE SOFTWARE.
|
|
443
|
-
|
|
444
|
-
--- SHA-256 4383cb2c3608397ce7a4159502614ed66890f8999c2a9c056dd3b1024d6721f0 ---
|
|
445
|
-
The MIT License (MIT)
|
|
446
|
-
|
|
447
|
-
Copyright 2017 Andrey Sitnik <andrey@sitnik.es>
|
|
448
|
-
|
|
449
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
450
|
-
this software and associated documentation files (the "Software"), to deal in
|
|
451
|
-
the Software without restriction, including without limitation the rights to
|
|
452
|
-
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
453
|
-
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
454
|
-
subject to the following conditions:
|
|
455
|
-
|
|
456
|
-
The above copyright notice and this permission notice shall be included in all
|
|
457
|
-
copies or substantial portions of the Software.
|
|
458
|
-
|
|
459
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
460
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
461
|
-
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
462
|
-
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
463
|
-
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
464
|
-
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
465
|
-
|
|
466
413
|
--- SHA-256 47eb8ca82c798246774946d1be0f9aa08f025fa8325ced0947aeeb4c05fe5547 ---
|
|
467
414
|
Copyright (c) 2011 Debuggable Limited <felix@debuggable.com>
|
|
468
415
|
|
|
@@ -968,29 +915,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
968
915
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
969
916
|
SOFTWARE.
|
|
970
917
|
|
|
971
|
-
--- SHA-256 ad9aa40aaab4bee08121a0180096cc6a72abf29e19e606328b66c0178f41d267 ---
|
|
972
|
-
The MIT License (MIT)
|
|
973
|
-
|
|
974
|
-
Copyright (c) 2015-2021 Hexagon <github.com/Hexagon>
|
|
975
|
-
|
|
976
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
977
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
978
|
-
in the Software without restriction, including without limitation the rights
|
|
979
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
980
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
981
|
-
furnished to do so, subject to the following conditions:
|
|
982
|
-
|
|
983
|
-
The above copyright notice and this permission notice shall be included in all
|
|
984
|
-
copies or substantial portions of the Software.
|
|
985
|
-
|
|
986
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
987
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
988
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
989
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
990
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
991
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
992
|
-
SOFTWARE.
|
|
993
|
-
|
|
994
918
|
--- SHA-256 b495047bd93a9b06913511076f504daba17d5bbeb3e0650f3bb53a4220329c57 ---
|
|
995
919
|
ISC License
|
|
996
920
|
|
|
@@ -1711,29 +1635,6 @@ Copyright (c) 2012 Felix Geisendörfer (felix@debuggable.com) and contributors
|
|
|
1711
1635
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
1712
1636
|
THE SOFTWARE.
|
|
1713
1637
|
|
|
1714
|
-
--- SHA-256 e7d9d11c28cce34f09dc7d2b0a3a6609ccba5fbc8e926e6d1bfdf060930e9f05 ---
|
|
1715
|
-
MIT License
|
|
1716
|
-
|
|
1717
|
-
Copyright (c) 2026 tintinweb
|
|
1718
|
-
|
|
1719
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
1720
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
1721
|
-
in the Software without restriction, including without limitation the rights
|
|
1722
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
1723
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
1724
|
-
furnished to do so, subject to the following conditions:
|
|
1725
|
-
|
|
1726
|
-
The above copyright notice and this permission notice shall be included in all
|
|
1727
|
-
copies or substantial portions of the Software.
|
|
1728
|
-
|
|
1729
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
1730
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
1731
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
1732
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
1733
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
1734
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
1735
|
-
SOFTWARE.
|
|
1736
|
-
|
|
1737
1638
|
--- SHA-256 e7dc37bf662d7f786efcb46c545615e70c1daf458a38385521c63cf6607cdfe1 ---
|
|
1738
1639
|
BSD 3-Clause License
|
|
1739
1640
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ContextPromptBuilder = exports.AGENT_MENTION_GUIDANCE = exports.LARKIN_STANDING_PROMPT_VERSION = void 0;
|
|
3
|
+
exports.ContextPromptBuilder = exports.AGENT_MENTION_GUIDANCE = exports.PI_TMUX_BASH_GUIDANCE = exports.LARKIN_STANDING_PROMPT_VERSION = void 0;
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
5
|
const agent_cli_capabilities_js_1 = require("./agent-cli-capabilities.cjs");
|
|
6
6
|
/**
|
|
@@ -16,7 +16,18 @@ const agent_cli_capabilities_js_1 = require("./agent-cli-capabilities.cjs");
|
|
|
16
16
|
* 5. 版本化:每次实质改动 bump LARKIN_STANDING_PROMPT_VERSION,并同步 eval 数据集/graders/断言。
|
|
17
17
|
* 6. 用 eval 验证:行为变化必须配套固定场景 + rubric(evals/*、test/support/*-grader.mjs、live 测试)。
|
|
18
18
|
*/
|
|
19
|
-
exports.LARKIN_STANDING_PROMPT_VERSION = "larkin-standing-
|
|
19
|
+
exports.LARKIN_STANDING_PROMPT_VERSION = "larkin-standing-v30";
|
|
20
|
+
/**
|
|
21
|
+
* Pi 长命令引导的唯一来源。仅在当前工具列表出现 tmux-backed bash 时适用;
|
|
22
|
+
* 不声明 tmux 一定可用,也不复述扩展的工具细节。
|
|
23
|
+
* 工具拒绝当前 workspace 时只要求如实报告,不因此禁止其它已授权工具。
|
|
24
|
+
*/
|
|
25
|
+
exports.PI_TMUX_BASH_GUIDANCE = [
|
|
26
|
+
"If the current tools include a tmux-backed bash, long-running work may continue after a wait timeout. That timeout is not process failure.",
|
|
27
|
+
"Use the identifiers those tools return to inspect or stop the same process. When a command finishes, handle the completion notification in this originating conversation.",
|
|
28
|
+
"The user-installed extension owns tool names, arguments, and other details. Do not assume tmux or extra inspect/stop tools exist unless they appear in the current tool list.",
|
|
29
|
+
"If an available tool refuses the current workspace, report that limitation. Other authorized tools in the current list remain available. Do not assume the refusing tool works in every workspace.",
|
|
30
|
+
];
|
|
20
31
|
/**
|
|
21
32
|
* Agent 间协作唤醒引导(issue #75):纯文本 @ 不会产生飞书 mention 事件,
|
|
22
33
|
* 必须由模型直接构造真实的 mention 元素;`--mention` 扩展参数已移除。
|
|
@@ -123,21 +134,9 @@ class ContextPromptBuilder {
|
|
|
123
134
|
"Larkin Runtime Host is the only production runtime path. Do not start a second runtime or a legacy daemon.",
|
|
124
135
|
...(input.runtime === "pi" ? [
|
|
125
136
|
"",
|
|
126
|
-
"##
|
|
127
|
-
|
|
128
|
-
"
|
|
129
|
-
"Correct pattern:",
|
|
130
|
-
"1. Call Agent with arguments like {\"prompt\": \"<task>\", \"description\": \"<short label>\", \"run_in_background\": true}.",
|
|
131
|
-
"2. The tool returns an agent id immediately. Report it to the user and end the turn.",
|
|
132
|
-
"3. Do NOT poll or sleep; a completion notification arrives automatically.",
|
|
133
|
-
"4. On the notification, check the Inbox, then publish exactly one final summary.",
|
|
134
|
-
"If you explicitly use get_subagent_result with wait: true, make at most one bounded wait call per turn. If it returns timedOut: true, do not loop or call wait again in the same turn; yield and let the completion notification wake you.",
|
|
135
|
-
"Forbidden pattern (never acceptable): `nohup sh -c '...' > /tmp/x.out 2>&1 &`, `sleep N; cat ...`, disown, or any shell background substitute. These bypass subagent isolation.",
|
|
136
|
-
"Keep corrections, approvals, short commands, and Feishu writes in the foreground; do not delegate them.",
|
|
137
|
-
"Parallel independent tasks: when the user clearly asks for two or more independent tasks with no dependencies between them, delegate EACH task to its own background subagent in a single message with multiple Agent tool calls (one per task, all with run_in_background: true), report every job id, and end the turn. Do not run them one-by-one in the foreground and do not merge them into one subagent.",
|
|
138
|
-
"Sequential dependent tasks: when tasks depend on each other, sending the user an order message is a REQUIRED first step: before executing any follow-up work, send a message stating the execution order (for example: first I will do A, then B; I will report back after each step). Then execute in order and report as promised; never stay silent while chaining long work.",
|
|
139
|
-
"Reporting location: always report job ids and final summaries in the same conversation and thread where the user's message arrived (reply-in-thread when the request arrived in a thread). Never start a new conversation or DM for subagent status reports.",
|
|
140
|
-
""
|
|
137
|
+
"## Long-running commands (pi)",
|
|
138
|
+
...exports.PI_TMUX_BASH_GUIDANCE,
|
|
139
|
+
"",
|
|
141
140
|
] : []),
|
|
142
141
|
"",
|
|
143
142
|
"## Available Larkin agent commands",
|
|
@@ -13,7 +13,18 @@ import { agentCliPromptCapabilities } from "./agent-cli-capabilities.mjs";
|
|
|
13
13
|
* 5. 版本化:每次实质改动 bump LARKIN_STANDING_PROMPT_VERSION,并同步 eval 数据集/graders/断言。
|
|
14
14
|
* 6. 用 eval 验证:行为变化必须配套固定场景 + rubric(evals/*、test/support/*-grader.mjs、live 测试)。
|
|
15
15
|
*/
|
|
16
|
-
export const LARKIN_STANDING_PROMPT_VERSION = "larkin-standing-
|
|
16
|
+
export const LARKIN_STANDING_PROMPT_VERSION = "larkin-standing-v30";
|
|
17
|
+
/**
|
|
18
|
+
* Pi 长命令引导的唯一来源。仅在当前工具列表出现 tmux-backed bash 时适用;
|
|
19
|
+
* 不声明 tmux 一定可用,也不复述扩展的工具细节。
|
|
20
|
+
* 工具拒绝当前 workspace 时只要求如实报告,不因此禁止其它已授权工具。
|
|
21
|
+
*/
|
|
22
|
+
export const PI_TMUX_BASH_GUIDANCE = [
|
|
23
|
+
"If the current tools include a tmux-backed bash, long-running work may continue after a wait timeout. That timeout is not process failure.",
|
|
24
|
+
"Use the identifiers those tools return to inspect or stop the same process. When a command finishes, handle the completion notification in this originating conversation.",
|
|
25
|
+
"The user-installed extension owns tool names, arguments, and other details. Do not assume tmux or extra inspect/stop tools exist unless they appear in the current tool list.",
|
|
26
|
+
"If an available tool refuses the current workspace, report that limitation. Other authorized tools in the current list remain available. Do not assume the refusing tool works in every workspace.",
|
|
27
|
+
];
|
|
17
28
|
/**
|
|
18
29
|
* Agent 间协作唤醒引导(issue #75):纯文本 @ 不会产生飞书 mention 事件,
|
|
19
30
|
* 必须由模型直接构造真实的 mention 元素;`--mention` 扩展参数已移除。
|
|
@@ -120,21 +131,9 @@ export class ContextPromptBuilder {
|
|
|
120
131
|
"Larkin Runtime Host is the only production runtime path. Do not start a second runtime or a legacy daemon.",
|
|
121
132
|
...(input.runtime === "pi" ? [
|
|
122
133
|
"",
|
|
123
|
-
"##
|
|
124
|
-
|
|
125
|
-
"
|
|
126
|
-
"Correct pattern:",
|
|
127
|
-
"1. Call Agent with arguments like {\"prompt\": \"<task>\", \"description\": \"<short label>\", \"run_in_background\": true}.",
|
|
128
|
-
"2. The tool returns an agent id immediately. Report it to the user and end the turn.",
|
|
129
|
-
"3. Do NOT poll or sleep; a completion notification arrives automatically.",
|
|
130
|
-
"4. On the notification, check the Inbox, then publish exactly one final summary.",
|
|
131
|
-
"If you explicitly use get_subagent_result with wait: true, make at most one bounded wait call per turn. If it returns timedOut: true, do not loop or call wait again in the same turn; yield and let the completion notification wake you.",
|
|
132
|
-
"Forbidden pattern (never acceptable): `nohup sh -c '...' > /tmp/x.out 2>&1 &`, `sleep N; cat ...`, disown, or any shell background substitute. These bypass subagent isolation.",
|
|
133
|
-
"Keep corrections, approvals, short commands, and Feishu writes in the foreground; do not delegate them.",
|
|
134
|
-
"Parallel independent tasks: when the user clearly asks for two or more independent tasks with no dependencies between them, delegate EACH task to its own background subagent in a single message with multiple Agent tool calls (one per task, all with run_in_background: true), report every job id, and end the turn. Do not run them one-by-one in the foreground and do not merge them into one subagent.",
|
|
135
|
-
"Sequential dependent tasks: when tasks depend on each other, sending the user an order message is a REQUIRED first step: before executing any follow-up work, send a message stating the execution order (for example: first I will do A, then B; I will report back after each step). Then execute in order and report as promised; never stay silent while chaining long work.",
|
|
136
|
-
"Reporting location: always report job ids and final summaries in the same conversation and thread where the user's message arrived (reply-in-thread when the request arrived in a thread). Never start a new conversation or DM for subagent status reports.",
|
|
137
|
-
""
|
|
134
|
+
"## Long-running commands (pi)",
|
|
135
|
+
...PI_TMUX_BASH_GUIDANCE,
|
|
136
|
+
"",
|
|
138
137
|
] : []),
|
|
139
138
|
"",
|
|
140
139
|
"## Available Larkin agent commands",
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.effectivePiStateDir = effectivePiStateDir;
|
|
7
|
+
exports.ensureOwnedTree = ensureOwnedTree;
|
|
8
|
+
exports.ensurePrivateDirectory = ensurePrivateDirectory;
|
|
9
|
+
exports.mkdirPrivateExclusive = mkdirPrivateExclusive;
|
|
10
|
+
exports.writePrivateExclusive = writePrivateExclusive;
|
|
11
|
+
exports.writePrivateAtomic = writePrivateAtomic;
|
|
12
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
13
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
14
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
15
|
+
/** Same implicit root the Pi adapter uses when `stateDir` is omitted. */
|
|
16
|
+
function effectivePiStateDir(input) {
|
|
17
|
+
return input.stateDir ?? node_path_1.default.join(input.workspaceDir, ".larkin");
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 从 root 起逐层建 0700 子树。root 可以位于 /var、/tmp 等合法祖先 link 之后;
|
|
21
|
+
* 任一层 owned 组件自身是 symlink 则拒绝,不顺着 link 往外写。
|
|
22
|
+
*/
|
|
23
|
+
function ensureOwnedTree(root, parts = [], create = true) {
|
|
24
|
+
const resolved = node_path_1.default.resolve(root);
|
|
25
|
+
if (node_fs_1.default.existsSync(resolved)) {
|
|
26
|
+
const stat = node_fs_1.default.lstatSync(resolved);
|
|
27
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
28
|
+
throw new Error(`unsafe private directory: ${resolved}`);
|
|
29
|
+
}
|
|
30
|
+
else if (create) {
|
|
31
|
+
node_fs_1.default.mkdirSync(resolved, { recursive: true, mode: 0o700 });
|
|
32
|
+
if (node_fs_1.default.lstatSync(resolved).isSymbolicLink() || !node_fs_1.default.lstatSync(resolved).isDirectory()) {
|
|
33
|
+
throw new Error(`unsafe private directory: ${resolved}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
throw new Error(`unsafe private directory: ${resolved}`);
|
|
38
|
+
}
|
|
39
|
+
let dir = resolved;
|
|
40
|
+
for (const part of parts) {
|
|
41
|
+
if (!part || part === "." || part === ".." || /[\\/]/.test(part))
|
|
42
|
+
throw new Error(`unsafe owned path component: ${part}`);
|
|
43
|
+
dir = node_path_1.default.join(dir, part);
|
|
44
|
+
try {
|
|
45
|
+
const existing = node_fs_1.default.lstatSync(dir);
|
|
46
|
+
if (existing.isSymbolicLink() || !existing.isDirectory())
|
|
47
|
+
throw new Error(`unsafe private directory: ${dir}`);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (error.code !== "ENOENT")
|
|
51
|
+
throw error;
|
|
52
|
+
if (!create)
|
|
53
|
+
throw new Error(`unsafe private directory: ${dir}`);
|
|
54
|
+
node_fs_1.default.mkdirSync(dir, { mode: 0o700 });
|
|
55
|
+
if (node_fs_1.default.lstatSync(dir).isSymbolicLink() || !node_fs_1.default.lstatSync(dir).isDirectory())
|
|
56
|
+
throw new Error(`unsafe private directory: ${dir}`);
|
|
57
|
+
}
|
|
58
|
+
if (process.platform !== "win32")
|
|
59
|
+
node_fs_1.default.chmodSync(dir, 0o700);
|
|
60
|
+
}
|
|
61
|
+
return dir;
|
|
62
|
+
}
|
|
63
|
+
function ensurePrivateDirectory(directory) {
|
|
64
|
+
ensureOwnedTree(directory);
|
|
65
|
+
}
|
|
66
|
+
/** 独占新建 0700 目录;已存在或变成 symlink 都拒绝。 */
|
|
67
|
+
function mkdirPrivateExclusive(directory) {
|
|
68
|
+
node_fs_1.default.mkdirSync(directory, { mode: 0o700 });
|
|
69
|
+
const created = node_fs_1.default.lstatSync(directory);
|
|
70
|
+
if (created.isSymbolicLink() || !created.isDirectory())
|
|
71
|
+
throw new Error(`unsafe private directory: ${directory}`);
|
|
72
|
+
if (process.platform !== "win32")
|
|
73
|
+
node_fs_1.default.chmodSync(directory, 0o700);
|
|
74
|
+
}
|
|
75
|
+
/** 独占新建 0600 文件;已存在(含 symlink)时失败,不跟随写出。 */
|
|
76
|
+
function writePrivateExclusive(file, content) {
|
|
77
|
+
node_fs_1.default.writeFileSync(file, content, { mode: 0o600, flag: "wx" });
|
|
78
|
+
if (process.platform !== "win32")
|
|
79
|
+
node_fs_1.default.chmodSync(file, 0o600);
|
|
80
|
+
}
|
|
81
|
+
/** 原子写 0600 文件;目标或父目录已是 symlink 时拒绝,避免落到目录外。 */
|
|
82
|
+
function writePrivateAtomic(file, content) {
|
|
83
|
+
try {
|
|
84
|
+
if (node_fs_1.default.lstatSync(node_path_1.default.dirname(file)).isSymbolicLink())
|
|
85
|
+
throw new Error(`unsafe private directory: ${node_path_1.default.dirname(file)}`);
|
|
86
|
+
if (node_fs_1.default.lstatSync(file).isSymbolicLink())
|
|
87
|
+
throw new Error(`${node_path_1.default.basename(file)} must not be a symlink`);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (error.code !== "ENOENT")
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
const temporary = node_path_1.default.join(node_path_1.default.dirname(file), `.${node_path_1.default.basename(file)}.${process.pid}.${node_crypto_1.default.randomUUID()}.tmp`);
|
|
94
|
+
try {
|
|
95
|
+
node_fs_1.default.writeFileSync(temporary, content, { mode: 0o600, flag: "wx" });
|
|
96
|
+
node_fs_1.default.renameSync(temporary, file);
|
|
97
|
+
node_fs_1.default.chmodSync(file, 0o600);
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
try {
|
|
101
|
+
node_fs_1.default.unlinkSync(temporary);
|
|
102
|
+
}
|
|
103
|
+
catch { /* renamed or absent */ }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
/** Same implicit root the Pi adapter uses when `stateDir` is omitted. */
|
|
5
|
+
export function effectivePiStateDir(input) {
|
|
6
|
+
return input.stateDir ?? path.join(input.workspaceDir, ".larkin");
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* 从 root 起逐层建 0700 子树。root 可以位于 /var、/tmp 等合法祖先 link 之后;
|
|
10
|
+
* 任一层 owned 组件自身是 symlink 则拒绝,不顺着 link 往外写。
|
|
11
|
+
*/
|
|
12
|
+
export function ensureOwnedTree(root, parts = [], create = true) {
|
|
13
|
+
const resolved = path.resolve(root);
|
|
14
|
+
if (fs.existsSync(resolved)) {
|
|
15
|
+
const stat = fs.lstatSync(resolved);
|
|
16
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
17
|
+
throw new Error(`unsafe private directory: ${resolved}`);
|
|
18
|
+
}
|
|
19
|
+
else if (create) {
|
|
20
|
+
fs.mkdirSync(resolved, { recursive: true, mode: 0o700 });
|
|
21
|
+
if (fs.lstatSync(resolved).isSymbolicLink() || !fs.lstatSync(resolved).isDirectory()) {
|
|
22
|
+
throw new Error(`unsafe private directory: ${resolved}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
throw new Error(`unsafe private directory: ${resolved}`);
|
|
27
|
+
}
|
|
28
|
+
let dir = resolved;
|
|
29
|
+
for (const part of parts) {
|
|
30
|
+
if (!part || part === "." || part === ".." || /[\\/]/.test(part))
|
|
31
|
+
throw new Error(`unsafe owned path component: ${part}`);
|
|
32
|
+
dir = path.join(dir, part);
|
|
33
|
+
try {
|
|
34
|
+
const existing = fs.lstatSync(dir);
|
|
35
|
+
if (existing.isSymbolicLink() || !existing.isDirectory())
|
|
36
|
+
throw new Error(`unsafe private directory: ${dir}`);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error.code !== "ENOENT")
|
|
40
|
+
throw error;
|
|
41
|
+
if (!create)
|
|
42
|
+
throw new Error(`unsafe private directory: ${dir}`);
|
|
43
|
+
fs.mkdirSync(dir, { mode: 0o700 });
|
|
44
|
+
if (fs.lstatSync(dir).isSymbolicLink() || !fs.lstatSync(dir).isDirectory())
|
|
45
|
+
throw new Error(`unsafe private directory: ${dir}`);
|
|
46
|
+
}
|
|
47
|
+
if (process.platform !== "win32")
|
|
48
|
+
fs.chmodSync(dir, 0o700);
|
|
49
|
+
}
|
|
50
|
+
return dir;
|
|
51
|
+
}
|
|
52
|
+
export function ensurePrivateDirectory(directory) {
|
|
53
|
+
ensureOwnedTree(directory);
|
|
54
|
+
}
|
|
55
|
+
/** 独占新建 0700 目录;已存在或变成 symlink 都拒绝。 */
|
|
56
|
+
export function mkdirPrivateExclusive(directory) {
|
|
57
|
+
fs.mkdirSync(directory, { mode: 0o700 });
|
|
58
|
+
const created = fs.lstatSync(directory);
|
|
59
|
+
if (created.isSymbolicLink() || !created.isDirectory())
|
|
60
|
+
throw new Error(`unsafe private directory: ${directory}`);
|
|
61
|
+
if (process.platform !== "win32")
|
|
62
|
+
fs.chmodSync(directory, 0o700);
|
|
63
|
+
}
|
|
64
|
+
/** 独占新建 0600 文件;已存在(含 symlink)时失败,不跟随写出。 */
|
|
65
|
+
export function writePrivateExclusive(file, content) {
|
|
66
|
+
fs.writeFileSync(file, content, { mode: 0o600, flag: "wx" });
|
|
67
|
+
if (process.platform !== "win32")
|
|
68
|
+
fs.chmodSync(file, 0o600);
|
|
69
|
+
}
|
|
70
|
+
/** 原子写 0600 文件;目标或父目录已是 symlink 时拒绝,避免落到目录外。 */
|
|
71
|
+
export function writePrivateAtomic(file, content) {
|
|
72
|
+
try {
|
|
73
|
+
if (fs.lstatSync(path.dirname(file)).isSymbolicLink())
|
|
74
|
+
throw new Error(`unsafe private directory: ${path.dirname(file)}`);
|
|
75
|
+
if (fs.lstatSync(file).isSymbolicLink())
|
|
76
|
+
throw new Error(`${path.basename(file)} must not be a symlink`);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error.code !== "ENOENT")
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
83
|
+
try {
|
|
84
|
+
fs.writeFileSync(temporary, content, { mode: 0o600, flag: "wx" });
|
|
85
|
+
fs.renameSync(temporary, file);
|
|
86
|
+
fs.chmodSync(file, 0o600);
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
try {
|
|
90
|
+
fs.unlinkSync(temporary);
|
|
91
|
+
}
|
|
92
|
+
catch { /* renamed or absent */ }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { createLarkinTmux, DEFAULT_BASH_WAIT_SECONDS, ForeignTmuxTaskError, formatTmuxTaskText, LARKIN_TMUX_COMPLETION_TYPE, tmuxAvailable, } from "./pi-tmux.mjs";
|
|
4
|
+
function resultOf(snapshot) {
|
|
5
|
+
return {
|
|
6
|
+
content: [{ type: "text", text: formatTmuxTaskText(snapshot) }],
|
|
7
|
+
details: {
|
|
8
|
+
taskId: snapshot.taskId,
|
|
9
|
+
status: snapshot.status,
|
|
10
|
+
exitCode: snapshot.exitCode,
|
|
11
|
+
output: snapshot.output,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function errorResult(message) {
|
|
16
|
+
return {
|
|
17
|
+
content: [{ type: "text", text: message }],
|
|
18
|
+
details: { taskId: "", status: "failed", exitCode: null, output: message },
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function registerLarkinTmuxExtension(pi) {
|
|
22
|
+
if (!tmuxAvailable())
|
|
23
|
+
return false;
|
|
24
|
+
const stateDir = process.env.LARKIN_STATE_DIR || `${process.cwd()}/.larkin`;
|
|
25
|
+
const agentId = process.env.LARKIN_AGENT_ID || "agent";
|
|
26
|
+
const instanceId = process.env.LARKIN_TMUX_INSTANCE_ID
|
|
27
|
+
|| process.env.LARKIN_RUNTIME_OBSERVATION_GENERATION
|
|
28
|
+
|| crypto.randomUUID();
|
|
29
|
+
const tmux = createLarkinTmux({ stateDir, agentId, instanceId, env: process.env });
|
|
30
|
+
const watchers = new Map();
|
|
31
|
+
const notified = new Set();
|
|
32
|
+
const stopWatchers = () => {
|
|
33
|
+
for (const timer of watchers.values())
|
|
34
|
+
clearInterval(timer);
|
|
35
|
+
watchers.clear();
|
|
36
|
+
};
|
|
37
|
+
const notifyOnce = (snapshot) => {
|
|
38
|
+
if (snapshot.status === "running" || notified.has(snapshot.taskId))
|
|
39
|
+
return;
|
|
40
|
+
notified.add(snapshot.taskId);
|
|
41
|
+
pi.sendMessage({
|
|
42
|
+
customType: LARKIN_TMUX_COMPLETION_TYPE,
|
|
43
|
+
content: formatTmuxTaskText(snapshot),
|
|
44
|
+
display: true,
|
|
45
|
+
details: {
|
|
46
|
+
taskId: snapshot.taskId,
|
|
47
|
+
exitCode: snapshot.exitCode,
|
|
48
|
+
output: snapshot.output,
|
|
49
|
+
},
|
|
50
|
+
}, { triggerTurn: true, deliverAs: "followUp" });
|
|
51
|
+
};
|
|
52
|
+
const watch = (taskId) => {
|
|
53
|
+
if (watchers.has(taskId) || notified.has(taskId))
|
|
54
|
+
return;
|
|
55
|
+
const timer = setInterval(() => {
|
|
56
|
+
try {
|
|
57
|
+
const snapshot = tmux.peek(taskId);
|
|
58
|
+
if (snapshot.status === "running")
|
|
59
|
+
return;
|
|
60
|
+
clearInterval(timer);
|
|
61
|
+
watchers.delete(taskId);
|
|
62
|
+
notifyOnce(snapshot);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
clearInterval(timer);
|
|
66
|
+
watchers.delete(taskId);
|
|
67
|
+
}
|
|
68
|
+
}, 250);
|
|
69
|
+
watchers.set(taskId, timer);
|
|
70
|
+
};
|
|
71
|
+
pi.on("session_shutdown", () => {
|
|
72
|
+
stopWatchers();
|
|
73
|
+
});
|
|
74
|
+
pi.registerTool({
|
|
75
|
+
name: "bash",
|
|
76
|
+
label: "bash",
|
|
77
|
+
description: "Run a command in a detached tmux window. timeout is a wait window only and never hard-kills. background:true returns immediately.",
|
|
78
|
+
parameters: Type.Object({
|
|
79
|
+
command: Type.String(),
|
|
80
|
+
timeout: Type.Optional(Type.Number()),
|
|
81
|
+
background: Type.Optional(Type.Boolean()),
|
|
82
|
+
}),
|
|
83
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
84
|
+
const snapshot = tmux.start(params.command, ctx.cwd);
|
|
85
|
+
if (params.background === true) {
|
|
86
|
+
watch(snapshot.taskId);
|
|
87
|
+
return resultOf(tmux.peek(snapshot.taskId));
|
|
88
|
+
}
|
|
89
|
+
const timeout = typeof params.timeout === "number" && params.timeout >= 0 ? params.timeout : DEFAULT_BASH_WAIT_SECONDS;
|
|
90
|
+
const waited = await tmux.wait(snapshot.taskId, timeout, signal);
|
|
91
|
+
if (waited.status === "running")
|
|
92
|
+
watch(waited.taskId);
|
|
93
|
+
return resultOf(waited);
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
pi.registerTool({
|
|
97
|
+
name: "tmux",
|
|
98
|
+
label: "tmux",
|
|
99
|
+
description: "List, peek, or kill Larkin-owned tmux tasks for this Agent. Foreign task IDs are rejected.",
|
|
100
|
+
parameters: Type.Object({
|
|
101
|
+
action: Type.Union([Type.Literal("list"), Type.Literal("peek"), Type.Literal("kill")]),
|
|
102
|
+
taskId: Type.Optional(Type.String()),
|
|
103
|
+
}),
|
|
104
|
+
async execute(_toolCallId, params) {
|
|
105
|
+
try {
|
|
106
|
+
if (params.action === "list") {
|
|
107
|
+
const tasks = tmux.list();
|
|
108
|
+
return {
|
|
109
|
+
content: [{ type: "text", text: tasks.map(formatTmuxTaskText).join("\n---\n") || "no owned tmux tasks" }],
|
|
110
|
+
details: { taskId: "", status: "completed", exitCode: 0, output: "" },
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (!params.taskId)
|
|
114
|
+
return errorResult("taskId is required for peek and kill");
|
|
115
|
+
if (params.action === "peek")
|
|
116
|
+
return resultOf(tmux.peek(params.taskId));
|
|
117
|
+
const killed = tmux.kill(params.taskId);
|
|
118
|
+
notified.add(params.taskId);
|
|
119
|
+
const timer = watchers.get(params.taskId);
|
|
120
|
+
if (timer) {
|
|
121
|
+
clearInterval(timer);
|
|
122
|
+
watchers.delete(params.taskId);
|
|
123
|
+
}
|
|
124
|
+
return resultOf(killed);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
if (error instanceof ForeignTmuxTaskError)
|
|
128
|
+
return errorResult(error.message);
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
export default function larkinTmuxExtension(pi) {
|
|
136
|
+
registerLarkinTmuxExtension(pi);
|
|
137
|
+
}
|