@ikie-dev/cli 9.8.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/.envrc +3 -0
- package/.github/CODEOWNERS +16 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +85 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +24 -0
- package/.github/ISSUE_TEMPLATE/question.yml +16 -0
- package/.github/actions/setup/action.yml +49 -0
- package/.github/pull_request_template.md +18 -0
- package/.github/release.yml +17 -0
- package/.github/workflows/acp-e2e.yml +52 -0
- package/.github/workflows/arc-terminal-bench-poc.yml +157 -0
- package/.github/workflows/auto-label-pr.yml +90 -0
- package/.github/workflows/canary.yml +302 -0
- package/.github/workflows/ci.yml +114 -0
- package/.github/workflows/close-stale.yml +21 -0
- package/.github/workflows/release.yml +288 -0
- package/.github/workflows/require-linked-issue.yml +21 -0
- package/.github/workflows/stale.yml +32 -0
- package/.github/workflows/tui-e2e.yml +52 -0
- package/.github/workflows/welcome.yml +21 -0
- package/.gitmodules +3 -0
- package/.husky/pre-commit +1 -0
- package/.ikie/ferments/019f2735-088f-75e4-9a8f-7d7b037c2a59/runtime.json +11 -0
- package/.ikie/ferments/019f2735-08b3-70fe-88df-8dd1494f6a8e/runtime.json +11 -0
- package/.ikie/ferments/019f2735-08d3-7041-8d5e-a153e2937274/runtime.json +11 -0
- package/.ikie/ferments/019f2735-08f8-7591-bf1a-6c1324842331/runtime.json +12 -0
- package/.ikie/ferments/019f2735-0913-73d5-82b4-37012a5c1148/runtime.json +11 -0
- package/.ikie/ferments/019f2735-0940-76dc-b667-964b9cd9294d/runtime.json +11 -0
- package/.ikie/ferments/019f2735-0971-7094-92e3-b8ea4cdb9390/runtime.json +15 -0
- package/.ikie/ferments/019f2735-0999-72aa-9727-f6e8c3427ed6/runtime.json +15 -0
- package/.ikie/ferments/019f2735-09bf-76dd-88a2-c7e574b45636/runtime.json +15 -0
- package/.ikie/ferments/019f2735-09f1-77af-8820-89c0b3b5a182/runtime.json +13 -0
- package/.ikie/ferments/019f2735-0a49-77d9-b4c7-5a80bbbbafd9/runtime.json +15 -0
- package/.ikie/ferments/019f2735-0a79-73c3-977b-0c9164267b94/runtime.json +15 -0
- package/.ikie/ferments/019f2735-0abd-70ba-a794-fbb63fbf23de/runtime.json +11 -0
- package/.ikie/ferments/019f2735-0ad6-7079-8576-8fb651b4b698/runtime.json +15 -0
- package/.ikie/ferments/019f2735-0b03-70db-b5e5-59b1869aa2dc/runtime.json +15 -0
- package/.ikie/ferments/019f2735-0b34-7675-a4ed-39a15333e3b8/runtime.json +15 -0
- package/.ikie/ferments/019f2735-0b6c-716e-9f92-c391d6acb6f0/runtime.json +15 -0
- package/.ikie/ferments/019f2735-0b90-72be-a60d-3dd4c514d1c3/runtime.json +15 -0
- package/.ikie/ferments/019f2735-0baf-72e7-b5c5-99d8533b8343/runtime.json +15 -0
- package/.ikie/ferments/019f2735-0bd8-70f8-9d83-1a2b9152a013/runtime.json +15 -0
- package/.ikie/ferments/019f2735-0bfa-70de-ad20-7fe8c41b968e/runtime.json +15 -0
- package/.ikie/ferments/019f2735-0c26-7246-a2ab-f24f5a76fc5f/runtime.json +15 -0
- package/.ikie/ferments/019f2735-0c49-771b-934f-4afdb5664666/runtime.json +11 -0
- package/.ikie/ferments/019f2735-0c63-73f8-944e-14d19e69ba41/runtime.json +11 -0
- package/.ikie/ferments/019f2735-0c80-772b-8530-2df4d15b9d7a/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2735-0c80-772b-8530-2df4d15b9d7a/runtime.json +17 -0
- package/.ikie/ferments/019f2735-0dfc-7551-b147-aa46815027f5/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2735-0dfc-7551-b147-aa46815027f5/runtime.json +17 -0
- package/.ikie/ferments/019f2735-0f74-70f6-804c-e4d2145b32e1/runtime.json +11 -0
- package/.ikie/ferments/019f2735-1354-7041-854e-e6a577467dd0/runtime.json +11 -0
- package/.ikie/ferments/019f2735-1371-749f-8ea9-01f7f8734568/runtime.json +11 -0
- package/.ikie/ferments/019f2735-138f-72f2-ba7b-7912ad4cd7b9/runtime.json +11 -0
- package/.ikie/ferments/019f2735-13ad-710a-9c4d-38a30a0f4b37/runtime.json +11 -0
- package/.ikie/ferments/019f2735-13cb-7698-88c1-890fba497dc0/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2735-13da-77dd-a916-8d82348671d2/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2735-14d3-727a-81db-347c03cb2919/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2735-1521-736e-8b75-22e94a8775f7/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2735-1542-777c-a3a7-bc7f1edf1922/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2735-3528-75bc-b100-8deb63dfde55/runtime.json +13 -0
- package/.ikie/ferments/019f2735-3553-72d8-a60d-9357420e110a/runtime.json +11 -0
- package/.ikie/ferments/019f2735-3569-756b-bc90-01c9ab00e413/runtime.json +11 -0
- package/.ikie/ferments/019f2735-3583-71d0-83f9-fc40b292190b/runtime.json +11 -0
- package/.ikie/ferments/019f2735-3597-7571-8332-47b3443395a6/runtime.json +11 -0
- package/.ikie/ferments/019f2735-35b3-74fe-a5db-82651a97cf0d/runtime.json +11 -0
- package/.ikie/ferments/019f2735-35de-741a-94e7-7b89c4eaaa43/runtime.json +11 -0
- package/.ikie/ferments/019f2735-35f5-713c-9892-f6179e783d02/runtime.json +11 -0
- package/.ikie/ferments/019f2735-360d-7056-a5e0-43fb3eb0f98e/runtime.json +9 -0
- package/.ikie/ferments/019f2735-362e-756c-b848-34f3c3c4602b/runtime.json +13 -0
- package/.ikie/ferments/019f2735-364a-76f9-bfba-ba52ca3852fd/runtime.json +11 -0
- package/.ikie/ferments/019f2735-3662-754c-8a88-807a68e0790e/runtime.json +11 -0
- package/.ikie/ferments/019f2735-36c2-71f9-a003-2921a1792be1/runtime.json +11 -0
- package/.ikie/ferments/019f2735-36d6-751d-9776-1e1def988d2d/runtime.json +9 -0
- package/.ikie/ferments/019f2735-36ef-70bf-a6b2-f2a0944f1b7e/runtime.json +9 -0
- package/.ikie/ferments/019f2735-3712-77e3-a46e-c69470e4e850/runtime.json +11 -0
- package/.ikie/ferments/019f2735-37cf-712f-bfaa-8fc9e29da513/runtime.json +11 -0
- package/.ikie/ferments/019f2735-37ed-727a-8a74-fe716d22571d/runtime.json +12 -0
- package/.ikie/ferments/019f2735-3810-7370-b587-eef0bb9d4d81/runtime.json +11 -0
- package/.ikie/ferments/019f2735-3828-71ad-9ee9-62fa0f46cad7/runtime.json +11 -0
- package/.ikie/ferments/019f2735-383f-722b-8d96-5038b9c9aa5a/runtime.json +11 -0
- package/.ikie/ferments/019f2735-385a-72c8-b269-a87a416c5a10/runtime.json +11 -0
- package/.ikie/ferments/019f2735-386d-73fe-be0a-1bc89a0264c4/runtime.json +13 -0
- package/.ikie/ferments/019f2735-388a-7276-9bee-81dcd177953f/runtime.json +11 -0
- package/.ikie/ferments/019f2735-82ed-74e8-870d-d8678feaf7bb/reviews/phase-phase-1-1.json +41 -0
- package/.ikie/ferments/019f2735-82ed-74e8-870d-d8678feaf7bb/runtime.json +11 -0
- package/.ikie/ferments/019f2735-831e-70aa-b9d6-1214165775d0/reviews/phase-phase-1-1.json +47 -0
- package/.ikie/ferments/019f2735-831e-70aa-b9d6-1214165775d0/runtime.json +13 -0
- package/.ikie/ferments/019f2735-835e-73b3-bb48-2d0080526fc3/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2735-835e-73b3-bb48-2d0080526fc3/runtime.json +9 -0
- package/.ikie/ferments/019f2735-8379-74b6-b62f-816bb5276a85/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2735-8379-74b6-b62f-816bb5276a85/runtime.json +9 -0
- package/.ikie/ferments/019f2735-8391-7385-bab6-03362866622a/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2735-8391-7385-bab6-03362866622a/runtime.json +9 -0
- package/.ikie/ferments/019f2735-83ad-7251-8280-aa6f7f3bfc5e/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2735-83ad-7251-8280-aa6f7f3bfc5e/runtime.json +9 -0
- package/.ikie/ferments/019f2735-83d2-77be-984e-fb65263a3a5b/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2735-83d2-77be-984e-fb65263a3a5b/runtime.json +9 -0
- package/.ikie/ferments/019f2735-83ec-7743-acc5-b2bf14c3cc54/reviews/phase-phase-1-1.json +62 -0
- package/.ikie/ferments/019f2735-83ec-7743-acc5-b2bf14c3cc54/runtime.json +9 -0
- package/.ikie/ferments/019f2740-c8c5-720f-b641-214bb99d8a83/runtime.json +11 -0
- package/.ikie/ferments/019f2740-c8fb-763f-8310-88c7e558e410/runtime.json +11 -0
- package/.ikie/ferments/019f2740-c917-70d2-8be6-15f376c60a70/runtime.json +11 -0
- package/.ikie/ferments/019f2740-c984-711f-a903-4b2251918413/runtime.json +12 -0
- package/.ikie/ferments/019f2740-ca09-71cf-93a5-86efc136ecd5/runtime.json +11 -0
- package/.ikie/ferments/019f2740-ca46-77f4-ae02-88e42f2153fb/runtime.json +15 -0
- package/.ikie/ferments/019f2740-ca74-72dd-8855-3feedfa071db/runtime.json +15 -0
- package/.ikie/ferments/019f2740-ca9a-710b-9bb7-feffa42cdb36/runtime.json +15 -0
- package/.ikie/ferments/019f2740-cac9-7518-ae93-973e4cf364ce/runtime.json +13 -0
- package/.ikie/ferments/019f2740-cb04-71c1-83a7-a448fefc6764/runtime.json +15 -0
- package/.ikie/ferments/019f2740-cb35-743b-bc18-434c7c233a08/runtime.json +15 -0
- package/.ikie/ferments/019f2740-cb6e-742c-970b-1893b4bf4e87/runtime.json +11 -0
- package/.ikie/ferments/019f2740-cb92-76a7-bd20-fd4f3f17664f/runtime.json +15 -0
- package/.ikie/ferments/019f2740-cbc1-7509-8542-302d2d1d3084/runtime.json +15 -0
- package/.ikie/ferments/019f2740-cbfa-719d-94c0-9d03a864e7e1/runtime.json +15 -0
- package/.ikie/ferments/019f2740-cc31-75f8-82a0-7d8078979000/runtime.json +15 -0
- package/.ikie/ferments/019f2740-cc58-765b-9f0c-c3aa2e09f27b/runtime.json +15 -0
- package/.ikie/ferments/019f2740-cc7c-7375-99b4-e90496e819e3/runtime.json +15 -0
- package/.ikie/ferments/019f2740-cca3-704a-bc20-e8c35cff6cec/runtime.json +15 -0
- package/.ikie/ferments/019f2740-ccd1-71cf-b8e8-be3527117f9a/runtime.json +15 -0
- package/.ikie/ferments/019f2740-cd0c-771f-ba4a-9188c31f3ee4/runtime.json +15 -0
- package/.ikie/ferments/019f2740-cd39-778e-a052-cc7f522f022d/runtime.json +11 -0
- package/.ikie/ferments/019f2740-cd5f-725e-8ec7-48a624398d2f/runtime.json +11 -0
- package/.ikie/ferments/019f2740-cd7f-713e-b20f-2321c13a86a8/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2740-cd7f-713e-b20f-2321c13a86a8/runtime.json +17 -0
- package/.ikie/ferments/019f2740-cf49-72fc-94b1-a262676c1136/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2740-cf49-72fc-94b1-a262676c1136/runtime.json +17 -0
- package/.ikie/ferments/019f2740-d16a-763d-b668-7c7cebc74c16/runtime.json +11 -0
- package/.ikie/ferments/019f2740-d675-747d-aa4c-b60d1616d218/runtime.json +11 -0
- package/.ikie/ferments/019f2740-d6ad-72de-bab7-e6344d568636/runtime.json +11 -0
- package/.ikie/ferments/019f2740-d6db-751a-bf3b-09d1c7ba989b/runtime.json +11 -0
- package/.ikie/ferments/019f2740-d712-75c8-b209-efc11bb1a22a/runtime.json +11 -0
- package/.ikie/ferments/019f2740-d745-764f-b758-99b0df2d6c01/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2740-d76b-74a8-94c1-3d0cabd8cacf/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2740-d88d-722b-8265-c8b89e1e59db/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2740-d8f6-756a-b704-8a64a28290e8/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2740-d90b-730f-87b9-85bee8e00a55/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2740-f11f-76ff-9bd0-1e449804cc12/runtime.json +13 -0
- package/.ikie/ferments/019f2740-f155-767e-96db-6cf60c8b8dc1/runtime.json +11 -0
- package/.ikie/ferments/019f2740-f172-755f-a741-8e55858f5362/runtime.json +11 -0
- package/.ikie/ferments/019f2740-f18f-7328-b679-49f91b3c558c/runtime.json +11 -0
- package/.ikie/ferments/019f2740-f1b4-7289-b2f3-46f2bedf0e8f/runtime.json +11 -0
- package/.ikie/ferments/019f2740-f1dc-769b-bfe1-abcbcfd8ff2f/runtime.json +11 -0
- package/.ikie/ferments/019f2740-f1ed-760b-91dc-3bf7950fc112/runtime.json +11 -0
- package/.ikie/ferments/019f2740-f1fd-72dd-9c40-e6ab17b819ef/runtime.json +11 -0
- package/.ikie/ferments/019f2740-f23d-7588-95af-66be8cae9529/runtime.json +9 -0
- package/.ikie/ferments/019f2740-f269-7334-9c82-6554d9fc595a/runtime.json +13 -0
- package/.ikie/ferments/019f2740-f2bf-7147-9e68-cd8cb74d1604/runtime.json +11 -0
- package/.ikie/ferments/019f2740-f2f3-73db-9542-23a91c3632e5/runtime.json +11 -0
- package/.ikie/ferments/019f2740-f387-77e6-99f2-7cabb6f1ef37/runtime.json +11 -0
- package/.ikie/ferments/019f2740-f3af-722d-92a8-b49ab2e974fd/runtime.json +9 -0
- package/.ikie/ferments/019f2740-f3e6-7142-870c-ec1ee626cef4/runtime.json +9 -0
- package/.ikie/ferments/019f2740-f408-74f3-b1a2-3187af4e7bf8/runtime.json +11 -0
- package/.ikie/ferments/019f2740-f535-7419-8dc5-3c00bfeb566e/runtime.json +11 -0
- package/.ikie/ferments/019f2740-f546-7749-b0bd-cc0ec724f9c4/runtime.json +12 -0
- package/.ikie/ferments/019f2740-f576-7649-a468-cc473a719123/runtime.json +11 -0
- package/.ikie/ferments/019f2740-f599-7239-aa50-4bde87b6fe17/runtime.json +11 -0
- package/.ikie/ferments/019f2740-f5b8-7236-9b88-a0abe214d6b6/runtime.json +11 -0
- package/.ikie/ferments/019f2740-f5d1-73fc-9eea-06cf527069f2/runtime.json +11 -0
- package/.ikie/ferments/019f2740-f5fb-757e-b89e-52aeb00d53a0/runtime.json +13 -0
- package/.ikie/ferments/019f2740-f616-7770-96df-c4d02e293b96/runtime.json +11 -0
- package/.ikie/ferments/019f2741-09b2-777a-960c-544786151af2/reviews/phase-phase-1-1.json +41 -0
- package/.ikie/ferments/019f2741-09b2-777a-960c-544786151af2/runtime.json +11 -0
- package/.ikie/ferments/019f2741-0a0a-735a-b158-6a1f99b53082/reviews/phase-phase-1-1.json +47 -0
- package/.ikie/ferments/019f2741-0a0a-735a-b158-6a1f99b53082/runtime.json +13 -0
- package/.ikie/ferments/019f2741-0a87-74ec-bcb1-e88127e8739b/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2741-0a87-74ec-bcb1-e88127e8739b/runtime.json +9 -0
- package/.ikie/ferments/019f2741-0aaa-7399-905b-0c8f62000e15/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2741-0aaa-7399-905b-0c8f62000e15/runtime.json +9 -0
- package/.ikie/ferments/019f2741-0acc-7062-86e9-0d2d365446d9/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2741-0acc-7062-86e9-0d2d365446d9/runtime.json +9 -0
- package/.ikie/ferments/019f2741-0b14-709e-a5aa-80efb5a3c7d7/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2741-0b14-709e-a5aa-80efb5a3c7d7/runtime.json +9 -0
- package/.ikie/ferments/019f2741-0b3d-77ed-bdd9-322103b094e2/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2741-0b3d-77ed-bdd9-322103b094e2/runtime.json +9 -0
- package/.ikie/ferments/019f2741-0b7b-70ab-b3d6-ada61a3e0a92/reviews/phase-phase-1-1.json +62 -0
- package/.ikie/ferments/019f2741-0b7b-70ab-b3d6-ada61a3e0a92/runtime.json +9 -0
- package/.ikie/ferments/019f2742-b177-74a8-90f2-c1130a6ddae7/runtime.json +11 -0
- package/.ikie/ferments/019f2742-b193-72cf-9826-86ede671e061/runtime.json +11 -0
- package/.ikie/ferments/019f2742-b1af-76d9-972c-b67a2f5854d1/runtime.json +11 -0
- package/.ikie/ferments/019f2742-b1de-763e-8e31-a14505f9b64d/runtime.json +12 -0
- package/.ikie/ferments/019f2742-b25b-71fe-81de-89fc3b431b39/runtime.json +11 -0
- package/.ikie/ferments/019f2742-b291-7078-ba58-d4b50df3dd81/runtime.json +15 -0
- package/.ikie/ferments/019f2742-b2db-71a8-9d5d-da4bccc3e572/runtime.json +15 -0
- package/.ikie/ferments/019f2742-b305-7323-86c6-39adaa181f18/runtime.json +15 -0
- package/.ikie/ferments/019f2742-b330-77ad-9315-8ca801c973e8/runtime.json +13 -0
- package/.ikie/ferments/019f2742-b377-703f-9781-394729f9fe89/runtime.json +15 -0
- package/.ikie/ferments/019f2742-b3af-75aa-9bb7-750cb6af56e4/runtime.json +15 -0
- package/.ikie/ferments/019f2742-b3fb-7159-a969-be0073f90b6d/runtime.json +11 -0
- package/.ikie/ferments/019f2742-b41a-730e-ae6e-89421276e69d/runtime.json +15 -0
- package/.ikie/ferments/019f2742-b44e-75ec-824c-f6b77f643438/runtime.json +15 -0
- package/.ikie/ferments/019f2742-b47d-70c6-af2f-04922c1aef02/runtime.json +15 -0
- package/.ikie/ferments/019f2742-b4ad-748b-86db-3c8ae8907dbc/runtime.json +15 -0
- package/.ikie/ferments/019f2742-b4d5-72ff-a1b0-0da356ab94e6/runtime.json +15 -0
- package/.ikie/ferments/019f2742-b508-71fd-8181-7bb013178e5e/runtime.json +15 -0
- package/.ikie/ferments/019f2742-b536-7129-8b5c-2e132b38a98e/runtime.json +15 -0
- package/.ikie/ferments/019f2742-b55d-738b-8663-88a05c1e44b5/runtime.json +15 -0
- package/.ikie/ferments/019f2742-b587-714c-b043-f0a553a22404/runtime.json +15 -0
- package/.ikie/ferments/019f2742-b5b5-74ff-a850-a24ff317c1df/runtime.json +11 -0
- package/.ikie/ferments/019f2742-b5f5-72ba-b6b8-a4d62df39a2c/runtime.json +11 -0
- package/.ikie/ferments/019f2742-b621-7530-8e63-d252ae47ffd6/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2742-b621-7530-8e63-d252ae47ffd6/runtime.json +17 -0
- package/.ikie/ferments/019f2742-b7c0-756d-8014-e96fec9713ee/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2742-b7c0-756d-8014-e96fec9713ee/runtime.json +17 -0
- package/.ikie/ferments/019f2742-b95b-76bb-a4a6-e5cc31788fe3/runtime.json +11 -0
- package/.ikie/ferments/019f2742-be6b-759b-ba34-29d0245dc929/runtime.json +11 -0
- package/.ikie/ferments/019f2742-be98-73ba-aac5-07ad4f7d887d/runtime.json +11 -0
- package/.ikie/ferments/019f2742-beba-703c-aadb-32c590404bfa/runtime.json +11 -0
- package/.ikie/ferments/019f2742-bee0-76ad-9e47-81cff634877f/runtime.json +11 -0
- package/.ikie/ferments/019f2742-bf10-742f-a792-7aa3836d81ad/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2742-bf36-779c-b126-2ccb8ec97138/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2742-c098-74ee-9a07-575f50989d9f/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2742-c135-74fe-aefa-d1a5db536132/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2742-c19a-779c-a175-27330932470d/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2742-db2a-745f-b7c8-8fb383e044e7/runtime.json +13 -0
- package/.ikie/ferments/019f2742-db83-77ac-b1d2-a422571fbcf6/runtime.json +11 -0
- package/.ikie/ferments/019f2742-dba7-779e-9560-7df90ddab3c4/runtime.json +11 -0
- package/.ikie/ferments/019f2742-dbd2-740c-9f94-9f313c07ee52/runtime.json +11 -0
- package/.ikie/ferments/019f2742-dbfb-77b3-8e3f-b0d20bd29689/runtime.json +11 -0
- package/.ikie/ferments/019f2742-dc10-769d-b9a2-bab4851f5cf5/runtime.json +11 -0
- package/.ikie/ferments/019f2742-dc23-7235-a215-8b86d261566a/runtime.json +11 -0
- package/.ikie/ferments/019f2742-dc37-762d-a5da-87ff3f2206b0/runtime.json +11 -0
- package/.ikie/ferments/019f2742-dc65-73c8-aa07-f803b1066c8b/runtime.json +9 -0
- package/.ikie/ferments/019f2742-dc91-7165-bbfe-4659f754c4a6/runtime.json +13 -0
- package/.ikie/ferments/019f2742-dcd1-7252-ad9b-12c0dab633e6/runtime.json +11 -0
- package/.ikie/ferments/019f2742-dceb-75ba-b8cb-557e60aa4a9b/runtime.json +11 -0
- package/.ikie/ferments/019f2742-dd2f-77a6-b71c-a9543ac1145f/runtime.json +11 -0
- package/.ikie/ferments/019f2742-dd6a-70ab-aff3-ae678e8be52f/runtime.json +9 -0
- package/.ikie/ferments/019f2742-dd85-7529-90c0-2cb497df7711/runtime.json +9 -0
- package/.ikie/ferments/019f2742-dda0-715e-ae24-693bfa79f191/runtime.json +11 -0
- package/.ikie/ferments/019f2742-deb9-7319-9599-d96743413a8c/runtime.json +11 -0
- package/.ikie/ferments/019f2742-dee3-705e-9550-b7701bc8dfbf/runtime.json +12 -0
- package/.ikie/ferments/019f2742-df10-71bf-8ac0-8e4a8c40cfa8/runtime.json +11 -0
- package/.ikie/ferments/019f2742-df29-735f-817c-2819becd5092/runtime.json +11 -0
- package/.ikie/ferments/019f2742-df46-7592-b070-7bdf50095ce2/runtime.json +11 -0
- package/.ikie/ferments/019f2742-df66-75e4-9926-4d5dacd457f4/runtime.json +11 -0
- package/.ikie/ferments/019f2742-df96-71c4-973e-045dc4f272e0/runtime.json +13 -0
- package/.ikie/ferments/019f2742-dfca-7353-bfd3-f010ec0726e8/runtime.json +11 -0
- package/.ikie/ferments/019f2742-f0fb-77cb-9a95-25a7231702b7/reviews/phase-phase-1-1.json +41 -0
- package/.ikie/ferments/019f2742-f0fb-77cb-9a95-25a7231702b7/runtime.json +11 -0
- package/.ikie/ferments/019f2742-f145-77ec-a4c7-fafd1605d492/reviews/phase-phase-1-1.json +47 -0
- package/.ikie/ferments/019f2742-f145-77ec-a4c7-fafd1605d492/runtime.json +13 -0
- package/.ikie/ferments/019f2742-f1dc-74bd-8452-067d9c1b909d/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2742-f1dc-74bd-8452-067d9c1b909d/runtime.json +9 -0
- package/.ikie/ferments/019f2742-f1fd-745d-b989-9a2d3ec7594d/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2742-f1fd-745d-b989-9a2d3ec7594d/runtime.json +9 -0
- package/.ikie/ferments/019f2742-f21a-7398-9190-8bba01c6248e/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2742-f21a-7398-9190-8bba01c6248e/runtime.json +9 -0
- package/.ikie/ferments/019f2742-f23a-73b1-89d2-1272093e6d2d/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2742-f23a-73b1-89d2-1272093e6d2d/runtime.json +9 -0
- package/.ikie/ferments/019f2742-f25c-74d1-80b3-129a992edd3f/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2742-f25c-74d1-80b3-129a992edd3f/runtime.json +9 -0
- package/.ikie/ferments/019f2742-f285-708d-bdd2-c2875cc16158/reviews/phase-phase-1-1.json +62 -0
- package/.ikie/ferments/019f2742-f285-708d-bdd2-c2875cc16158/runtime.json +9 -0
- package/.ikie/ferments/019f2745-3a6e-7769-8da0-1994b591349d/runtime.json +11 -0
- package/.ikie/ferments/019f2745-3a8c-74db-bd6b-b4a1164b381a/runtime.json +11 -0
- package/.ikie/ferments/019f2745-3aad-7249-b742-f1e6722d5c97/runtime.json +11 -0
- package/.ikie/ferments/019f2745-3ae9-76fb-861c-49f13c1db883/runtime.json +12 -0
- package/.ikie/ferments/019f2745-3b16-721b-b523-469846e0c9e5/runtime.json +11 -0
- package/.ikie/ferments/019f2745-3b53-7119-ae88-20cf3d567e91/runtime.json +11 -0
- package/.ikie/ferments/019f2745-3ba2-71b6-9fab-faaf750f8535/runtime.json +15 -0
- package/.ikie/ferments/019f2745-3bed-764b-9b4b-79b4d7daa35d/runtime.json +15 -0
- package/.ikie/ferments/019f2745-3c3e-7792-8df7-d88bab7aa48d/runtime.json +15 -0
- package/.ikie/ferments/019f2745-3c81-770d-bcf9-6d163ffeb285/runtime.json +13 -0
- package/.ikie/ferments/019f2745-3cca-748d-9740-8c55644c9afa/runtime.json +15 -0
- package/.ikie/ferments/019f2745-3d12-71cc-923f-520142ddf544/runtime.json +15 -0
- package/.ikie/ferments/019f2745-3d79-7083-8865-8f54989080e0/runtime.json +11 -0
- package/.ikie/ferments/019f2745-3da9-74f9-a640-86b4685f40a0/runtime.json +15 -0
- package/.ikie/ferments/019f2745-3ddf-721b-8a40-3a2318b6bc0a/runtime.json +15 -0
- package/.ikie/ferments/019f2745-3e2a-76a8-8c61-ef15db41aef2/runtime.json +15 -0
- package/.ikie/ferments/019f2745-3e5d-75bb-ab71-b4aa83c491b7/runtime.json +15 -0
- package/.ikie/ferments/019f2745-3e7e-7437-848a-100463c72f90/runtime.json +15 -0
- package/.ikie/ferments/019f2745-3ea7-7729-9000-7874145f4bb5/runtime.json +15 -0
- package/.ikie/ferments/019f2745-3ecf-760c-931c-3dc5996b06b1/runtime.json +15 -0
- package/.ikie/ferments/019f2745-3f05-72d8-ad71-d1df02c5939a/runtime.json +15 -0
- package/.ikie/ferments/019f2745-3f37-74ad-9e02-6e0d3854b664/runtime.json +15 -0
- package/.ikie/ferments/019f2745-3f65-740d-8094-58f60c9cdcec/runtime.json +11 -0
- package/.ikie/ferments/019f2745-3f91-7219-8eec-f5f7873a434a/runtime.json +11 -0
- package/.ikie/ferments/019f2745-3fc5-706a-8188-5a3b4e13a796/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2745-3fc5-706a-8188-5a3b4e13a796/runtime.json +17 -0
- package/.ikie/ferments/019f2745-417c-77ad-bdaf-380b81846577/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2745-417c-77ad-bdaf-380b81846577/runtime.json +17 -0
- package/.ikie/ferments/019f2745-4372-72ff-a50c-3eaad36a726b/runtime.json +11 -0
- package/.ikie/ferments/019f2745-4859-714b-a8b7-0a82d35b87c7/runtime.json +11 -0
- package/.ikie/ferments/019f2745-488a-724f-91a9-d621e9f2b3f7/runtime.json +11 -0
- package/.ikie/ferments/019f2745-48ae-7741-8598-03ede47b4594/runtime.json +11 -0
- package/.ikie/ferments/019f2745-48e9-710f-889b-e88aa5d7e60e/runtime.json +11 -0
- package/.ikie/ferments/019f2745-4917-7268-9b76-f8bb1215ea19/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2745-493c-775d-a10d-4a2cf8305a4e/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2745-4abd-7319-9e11-4b348ab24547/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2745-4b10-726d-ad1f-c086e7d4dfc0/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2745-4b21-75a6-b151-472f4eb9b95d/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2745-5dd5-741b-85e2-1d67ce09a138/runtime.json +13 -0
- package/.ikie/ferments/019f2745-5e14-73dd-bd03-3b5765f70fac/runtime.json +11 -0
- package/.ikie/ferments/019f2745-5e2e-70b5-9348-35ebbdb8ec8e/runtime.json +11 -0
- package/.ikie/ferments/019f2745-5e4f-7626-b075-6b6d30099847/runtime.json +11 -0
- package/.ikie/ferments/019f2745-5e71-7768-b436-e739555f1038/runtime.json +11 -0
- package/.ikie/ferments/019f2745-5e8a-77c5-ba98-cb2802c55266/runtime.json +11 -0
- package/.ikie/ferments/019f2745-5ea8-707f-b034-9c98e6ca720b/runtime.json +11 -0
- package/.ikie/ferments/019f2745-5ebd-73aa-a919-150c9bb014f9/runtime.json +11 -0
- package/.ikie/ferments/019f2745-5ed6-71e9-9a20-360fa807f598/runtime.json +9 -0
- package/.ikie/ferments/019f2745-5ef8-7199-bd13-69b5c883541b/runtime.json +13 -0
- package/.ikie/ferments/019f2745-5f30-73f9-a647-9e7518599c32/runtime.json +11 -0
- package/.ikie/ferments/019f2745-5f53-7358-897a-37717d64d996/runtime.json +11 -0
- package/.ikie/ferments/019f2745-5fb6-736b-b42d-bbc3c1916321/runtime.json +11 -0
- package/.ikie/ferments/019f2745-5fea-730e-b57b-d7f1965d5934/runtime.json +9 -0
- package/.ikie/ferments/019f2745-6014-77cb-b01f-d93baf8af409/runtime.json +9 -0
- package/.ikie/ferments/019f2745-602f-74b9-8eed-bb29fa4a76d4/runtime.json +11 -0
- package/.ikie/ferments/019f2745-60e2-7562-8e7c-2ded6cf75426/runtime.json +11 -0
- package/.ikie/ferments/019f2745-6114-703e-ac05-5ba11b255b62/runtime.json +12 -0
- package/.ikie/ferments/019f2745-6160-733e-93f5-df586c6afc0a/runtime.json +11 -0
- package/.ikie/ferments/019f2745-6179-702b-84fb-09bc60f8c04c/runtime.json +11 -0
- package/.ikie/ferments/019f2745-618d-74dc-a174-28a134d353fa/runtime.json +11 -0
- package/.ikie/ferments/019f2745-619e-72a8-8377-34127bfc774f/runtime.json +11 -0
- package/.ikie/ferments/019f2745-61ae-767e-8a2e-a5ca6bc84a88/runtime.json +13 -0
- package/.ikie/ferments/019f2745-61c7-760c-9914-a9bf3c96acd8/runtime.json +11 -0
- package/.ikie/ferments/019f2745-79a2-70b8-bcd0-fbaee13849db/reviews/phase-phase-1-1.json +41 -0
- package/.ikie/ferments/019f2745-79a2-70b8-bcd0-fbaee13849db/runtime.json +11 -0
- package/.ikie/ferments/019f2745-79e7-74d5-b867-e23b027a8512/reviews/phase-phase-1-1.json +47 -0
- package/.ikie/ferments/019f2745-79e7-74d5-b867-e23b027a8512/runtime.json +13 -0
- package/.ikie/ferments/019f2745-7a5b-703e-999e-dd5cb98133eb/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2745-7a5b-703e-999e-dd5cb98133eb/runtime.json +9 -0
- package/.ikie/ferments/019f2745-7a90-732a-b09a-9aa29e2390cd/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2745-7a90-732a-b09a-9aa29e2390cd/runtime.json +9 -0
- package/.ikie/ferments/019f2745-7aad-731c-ac2a-cf71d20fb709/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2745-7aad-731c-ac2a-cf71d20fb709/runtime.json +9 -0
- package/.ikie/ferments/019f2745-7ad2-70ff-986b-83679208066a/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2745-7ad2-70ff-986b-83679208066a/runtime.json +9 -0
- package/.ikie/ferments/019f2745-7af8-735c-8200-f49b201f5b7f/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2745-7af8-735c-8200-f49b201f5b7f/runtime.json +9 -0
- package/.ikie/ferments/019f2745-7b17-7489-b4d5-b83e4feeb641/reviews/phase-phase-1-1.json +62 -0
- package/.ikie/ferments/019f2745-7b17-7489-b4d5-b83e4feeb641/runtime.json +9 -0
- package/.ikie/ferments/019f2747-700f-77dd-8fec-eec70eb204bf/runtime.json +11 -0
- package/.ikie/ferments/019f2747-702b-748d-83a8-a44d909b5d84/runtime.json +11 -0
- package/.ikie/ferments/019f2747-704a-77c6-a068-5eff8527f149/runtime.json +11 -0
- package/.ikie/ferments/019f2747-7075-7628-9088-9c79d85087be/runtime.json +12 -0
- package/.ikie/ferments/019f2747-70e4-7157-aa37-d8201f07134a/runtime.json +11 -0
- package/.ikie/ferments/019f2747-7139-74da-8f85-02b76ecfc932/runtime.json +15 -0
- package/.ikie/ferments/019f2747-7156-762f-8c31-84ae357e2edd/runtime.json +15 -0
- package/.ikie/ferments/019f2747-7175-76e7-9d21-15608a6463ca/runtime.json +15 -0
- package/.ikie/ferments/019f2747-71c8-777c-8106-df11073ad85f/runtime.json +13 -0
- package/.ikie/ferments/019f2747-7209-71f5-8f4e-ccfd46653321/runtime.json +15 -0
- package/.ikie/ferments/019f2747-723d-728f-baa8-dd3b4b239037/runtime.json +15 -0
- package/.ikie/ferments/019f2747-728d-749b-9e9a-962ea824d13d/runtime.json +11 -0
- package/.ikie/ferments/019f2747-72b0-756e-b648-c5491d46dc37/runtime.json +15 -0
- package/.ikie/ferments/019f2747-72e8-747e-9e5b-93005dd51d36/runtime.json +15 -0
- package/.ikie/ferments/019f2747-7319-702b-84bd-aed70c3f60c3/runtime.json +15 -0
- package/.ikie/ferments/019f2747-734a-732c-89d8-47fa74a13f6b/runtime.json +15 -0
- package/.ikie/ferments/019f2747-7372-72ce-99d0-ddeeb20c73e5/runtime.json +15 -0
- package/.ikie/ferments/019f2747-73a1-723d-b064-2240965b5460/runtime.json +15 -0
- package/.ikie/ferments/019f2747-73cd-71b5-9b21-481224573278/runtime.json +15 -0
- package/.ikie/ferments/019f2747-73f7-779f-a227-64ba479c2615/runtime.json +15 -0
- package/.ikie/ferments/019f2747-743e-759e-856b-283e346eec66/runtime.json +15 -0
- package/.ikie/ferments/019f2747-74ac-702b-877f-1b4447f77938/runtime.json +11 -0
- package/.ikie/ferments/019f2747-74cc-773a-ad3a-febf5e9e5a57/runtime.json +11 -0
- package/.ikie/ferments/019f2747-750b-77dc-aa24-230eef901e40/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2747-750b-77dc-aa24-230eef901e40/runtime.json +17 -0
- package/.ikie/ferments/019f2747-76f2-72c9-8764-82fdd0429215/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2747-76f2-72c9-8764-82fdd0429215/runtime.json +17 -0
- package/.ikie/ferments/019f2747-795d-7452-9810-a64e86f3d5c5/runtime.json +11 -0
- package/.ikie/ferments/019f2747-7ee1-770e-ace1-bc0f5571f69a/runtime.json +11 -0
- package/.ikie/ferments/019f2747-7f01-738d-a2a2-c7aa33365582/runtime.json +11 -0
- package/.ikie/ferments/019f2747-7f40-712b-bea9-8a20399c9dfd/runtime.json +11 -0
- package/.ikie/ferments/019f2747-7fb4-70c9-aaf6-af4ac5fbebef/runtime.json +11 -0
- package/.ikie/ferments/019f2747-7fe2-77e7-90d5-dcee3cbe73ea/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2747-7ff9-7028-ac25-77ab9c513710/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2747-81c3-7320-8a96-88880d06cc14/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2747-8236-76c0-b17c-1f13a22af974/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2747-824b-77ba-90bd-67bcb4e9d5d6/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2747-a18a-77f4-802d-18ab07f51261/runtime.json +13 -0
- package/.ikie/ferments/019f2747-a1b6-76cf-9688-94e8f41b179f/runtime.json +11 -0
- package/.ikie/ferments/019f2747-a1e2-730b-a56d-cb622d190c49/runtime.json +11 -0
- package/.ikie/ferments/019f2747-a20b-739e-a524-b5b2cdabdf49/runtime.json +11 -0
- package/.ikie/ferments/019f2747-a228-7026-9fe2-79fc90d7c622/runtime.json +11 -0
- package/.ikie/ferments/019f2747-a244-71b5-8ceb-d6094c7a5d28/runtime.json +11 -0
- package/.ikie/ferments/019f2747-a25d-723e-9962-ed1f266a9709/runtime.json +11 -0
- package/.ikie/ferments/019f2747-a275-734d-9ede-ae9644a179b6/runtime.json +11 -0
- package/.ikie/ferments/019f2747-a2a4-7280-b8c4-d8233d9e322a/runtime.json +9 -0
- package/.ikie/ferments/019f2747-a2ca-767d-8a35-9043b18e5ec7/runtime.json +13 -0
- package/.ikie/ferments/019f2747-a2f5-745d-929e-9fb2ef5d333f/runtime.json +11 -0
- package/.ikie/ferments/019f2747-a317-76b9-8954-2b7a4487a352/runtime.json +11 -0
- package/.ikie/ferments/019f2747-a380-7746-be44-cb9e1920561f/runtime.json +11 -0
- package/.ikie/ferments/019f2747-a3a2-77ac-9fe5-2a844b64872d/runtime.json +9 -0
- package/.ikie/ferments/019f2747-a3b7-70ee-ba83-b3c43d3122c8/runtime.json +9 -0
- package/.ikie/ferments/019f2747-a3e7-76e9-96f0-143c82d8005b/runtime.json +11 -0
- package/.ikie/ferments/019f2747-a4d3-737b-8d80-2dc63a0fbe1a/runtime.json +11 -0
- package/.ikie/ferments/019f2747-a506-7442-919f-50ae2b602e7a/runtime.json +12 -0
- package/.ikie/ferments/019f2747-a53e-76cb-9876-a81dd08e3fe7/runtime.json +11 -0
- package/.ikie/ferments/019f2747-a56a-7208-ad93-0fdcbc41c1ed/runtime.json +11 -0
- package/.ikie/ferments/019f2747-a581-73fc-aac5-a44f696ccbe8/runtime.json +11 -0
- package/.ikie/ferments/019f2747-a595-71d2-9c2d-d846e9803665/runtime.json +11 -0
- package/.ikie/ferments/019f2747-a5a7-73c6-b881-f9c0a08f3af3/runtime.json +13 -0
- package/.ikie/ferments/019f2747-a5be-7777-94c0-0849e693ffcb/runtime.json +11 -0
- package/.ikie/ferments/019f2747-b3e6-706a-912c-71d57080c7c1/reviews/phase-phase-1-1.json +41 -0
- package/.ikie/ferments/019f2747-b3e6-706a-912c-71d57080c7c1/runtime.json +11 -0
- package/.ikie/ferments/019f2747-b464-75bd-9c0b-c6b5e5de789e/reviews/phase-phase-1-1.json +47 -0
- package/.ikie/ferments/019f2747-b464-75bd-9c0b-c6b5e5de789e/runtime.json +13 -0
- package/.ikie/ferments/019f2747-b4f2-719c-86a6-763c494a680c/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2747-b4f2-719c-86a6-763c494a680c/runtime.json +9 -0
- package/.ikie/ferments/019f2747-b52b-75cd-b664-8c2aedd87893/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2747-b52b-75cd-b664-8c2aedd87893/runtime.json +9 -0
- package/.ikie/ferments/019f2747-b551-7188-81a3-7b7e8371aea4/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2747-b551-7188-81a3-7b7e8371aea4/runtime.json +9 -0
- package/.ikie/ferments/019f2747-b5a5-703f-98e6-5eac131044ed/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2747-b5a5-703f-98e6-5eac131044ed/runtime.json +9 -0
- package/.ikie/ferments/019f2747-b5cc-7560-80d8-509578bdae44/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2747-b5cc-7560-80d8-509578bdae44/runtime.json +9 -0
- package/.ikie/ferments/019f2747-b5f7-7079-ba17-833018701a06/reviews/phase-phase-1-1.json +62 -0
- package/.ikie/ferments/019f2747-b5f7-7079-ba17-833018701a06/runtime.json +9 -0
- package/.ikie/ferments/019f274a-bb4e-77b1-9f03-633b22b8e54c/runtime.json +11 -0
- package/.ikie/ferments/019f274a-bb6e-7715-92a0-c73da2ff97da/runtime.json +11 -0
- package/.ikie/ferments/019f274a-bb89-75b5-9daa-c57ef85c64c1/runtime.json +11 -0
- package/.ikie/ferments/019f274a-bbb7-72ad-9695-3227786b16ef/runtime.json +12 -0
- package/.ikie/ferments/019f274a-bbfb-70da-95f3-54a3e507888d/runtime.json +11 -0
- package/.ikie/ferments/019f274a-bc37-771a-8e77-b8d5476a8a1e/runtime.json +15 -0
- package/.ikie/ferments/019f274a-bc61-7024-9311-148a94a6713e/runtime.json +15 -0
- package/.ikie/ferments/019f274a-bca0-76a9-b966-59bbe3fb5bdd/runtime.json +15 -0
- package/.ikie/ferments/019f274a-bcc9-774d-adc2-aa99e82abb79/runtime.json +13 -0
- package/.ikie/ferments/019f274a-bcfb-737e-9c7d-b9db4762b624/runtime.json +15 -0
- package/.ikie/ferments/019f274a-bd3f-725b-9d39-b672191556ac/runtime.json +15 -0
- package/.ikie/ferments/019f274a-bd82-75a8-b936-350a55a7a1da/runtime.json +11 -0
- package/.ikie/ferments/019f274a-bda4-71b2-aa8b-7ba6f6103814/runtime.json +15 -0
- package/.ikie/ferments/019f274a-bddc-70ff-90f8-f1648dede718/runtime.json +15 -0
- package/.ikie/ferments/019f274a-be12-70bd-b935-0173946e60c2/runtime.json +15 -0
- package/.ikie/ferments/019f274a-be51-7719-b04b-d720a1d215f7/runtime.json +15 -0
- package/.ikie/ferments/019f274a-be8c-7084-aa78-4d139084333b/runtime.json +15 -0
- package/.ikie/ferments/019f274a-bebc-70b7-8c8d-fc7b50eda266/runtime.json +15 -0
- package/.ikie/ferments/019f274a-bee6-732f-b9ee-9fb3f1966e3d/runtime.json +15 -0
- package/.ikie/ferments/019f274a-bf19-76bc-8ab4-fe8c3fb28875/runtime.json +15 -0
- package/.ikie/ferments/019f274a-bf40-755b-bd4f-eec6d8e7a4de/runtime.json +15 -0
- package/.ikie/ferments/019f274a-bf74-7013-beb9-08aab0dc8866/runtime.json +11 -0
- package/.ikie/ferments/019f274a-bfa3-718d-ab5d-ededc4cf9e63/runtime.json +11 -0
- package/.ikie/ferments/019f274a-bfd6-7689-b29b-2c3ebfe1afd3/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f274a-bfd6-7689-b29b-2c3ebfe1afd3/runtime.json +17 -0
- package/.ikie/ferments/019f274a-c186-77ea-a92c-78fa52657710/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f274a-c186-77ea-a92c-78fa52657710/runtime.json +17 -0
- package/.ikie/ferments/019f274a-c3cf-77e8-9917-76d94992456a/runtime.json +11 -0
- package/.ikie/ferments/019f274a-c8eb-7071-9900-1668ccf13dad/runtime.json +11 -0
- package/.ikie/ferments/019f274a-c928-7276-9fd8-c8fdfbd215ff/runtime.json +11 -0
- package/.ikie/ferments/019f274a-c957-7698-8a97-54584a38f472/runtime.json +11 -0
- package/.ikie/ferments/019f274a-c97f-71b5-8fd9-727b49c9b72c/runtime.json +11 -0
- package/.ikie/ferments/019f274a-c9ad-76bb-99f8-b8f7840ad3ab/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274a-c9cf-73ad-8229-d03bd5389697/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274a-cb01-720a-b512-e49f820b9be5/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274a-cbba-764d-a8e7-da6a060d9cd1/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274a-cbdd-744f-84e9-af64527ff116/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274a-efc1-75b6-ad54-83a9fcb061af/runtime.json +13 -0
- package/.ikie/ferments/019f274a-effe-713a-9672-ca8c839b0936/runtime.json +11 -0
- package/.ikie/ferments/019f274a-f03c-7194-b9e6-a24620ef1b89/runtime.json +11 -0
- package/.ikie/ferments/019f274a-f058-758c-9ecf-f6af131a0798/runtime.json +11 -0
- package/.ikie/ferments/019f274a-f06e-712d-b720-09a6188a152a/runtime.json +11 -0
- package/.ikie/ferments/019f274a-f085-715e-8b5b-07587107bc86/runtime.json +11 -0
- package/.ikie/ferments/019f274a-f0a8-755b-b747-adaa0f997d8a/runtime.json +11 -0
- package/.ikie/ferments/019f274a-f0cd-765c-90ce-48685c67299c/runtime.json +11 -0
- package/.ikie/ferments/019f274a-f0e1-71a8-bafc-cc3eeff4f275/runtime.json +9 -0
- package/.ikie/ferments/019f274a-f104-73c0-be62-16f724e0d4c6/runtime.json +13 -0
- package/.ikie/ferments/019f274a-f129-70ea-aa16-254d0fc662bf/runtime.json +11 -0
- package/.ikie/ferments/019f274a-f14b-7742-8e88-7a3fe9d16f08/runtime.json +11 -0
- package/.ikie/ferments/019f274a-f18b-7148-afb1-b9ff1ab54bbc/runtime.json +11 -0
- package/.ikie/ferments/019f274a-f1a2-734d-bf87-6f80955a3b64/runtime.json +9 -0
- package/.ikie/ferments/019f274a-f1c0-75a7-8675-f4fcf15df9bd/runtime.json +9 -0
- package/.ikie/ferments/019f274a-f1e2-7346-b97f-9340f86e6397/runtime.json +11 -0
- package/.ikie/ferments/019f274a-f2a1-7758-a891-413d15c9c29c/runtime.json +11 -0
- package/.ikie/ferments/019f274a-f2bc-76fd-b2dc-e14feea4fb86/runtime.json +12 -0
- package/.ikie/ferments/019f274a-f2e0-7474-97b8-79e01d130940/runtime.json +11 -0
- package/.ikie/ferments/019f274a-f2fa-70fb-a2da-0fb3e9a729ef/runtime.json +11 -0
- package/.ikie/ferments/019f274a-f30f-71bc-bf7f-198ef4b68568/runtime.json +11 -0
- package/.ikie/ferments/019f274a-f326-768b-b55e-e340dd3bf7fc/runtime.json +11 -0
- package/.ikie/ferments/019f274a-f339-75a5-9d45-d9393ccf3436/runtime.json +13 -0
- package/.ikie/ferments/019f274a-f35b-7386-9eb0-8f1e45618131/runtime.json +11 -0
- package/.ikie/ferments/019f274a-f96b-7633-9b6d-6722ea8232a2/reviews/phase-phase-1-1.json +41 -0
- package/.ikie/ferments/019f274a-f96b-7633-9b6d-6722ea8232a2/runtime.json +11 -0
- package/.ikie/ferments/019f274a-f9b7-769e-94ac-8f5c7e23ee88/reviews/phase-phase-1-1.json +47 -0
- package/.ikie/ferments/019f274a-f9b7-769e-94ac-8f5c7e23ee88/runtime.json +13 -0
- package/.ikie/ferments/019f274a-fa07-70b1-bc68-1a9d3378deff/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f274a-fa07-70b1-bc68-1a9d3378deff/runtime.json +9 -0
- package/.ikie/ferments/019f274a-fa2f-7658-86ad-cc69215f1c94/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f274a-fa2f-7658-86ad-cc69215f1c94/runtime.json +9 -0
- package/.ikie/ferments/019f274a-fa46-72ca-a252-65a5b2995bc4/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f274a-fa46-72ca-a252-65a5b2995bc4/runtime.json +9 -0
- package/.ikie/ferments/019f274a-fa70-705f-9112-d48e1042f1d4/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f274a-fa70-705f-9112-d48e1042f1d4/runtime.json +9 -0
- package/.ikie/ferments/019f274a-fa94-73e5-9fc3-ea21ee5cfc59/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f274a-fa94-73e5-9fc3-ea21ee5cfc59/runtime.json +9 -0
- package/.ikie/ferments/019f274a-fab9-720d-9ce3-0041a27f83e0/reviews/phase-phase-1-1.json +62 -0
- package/.ikie/ferments/019f274a-fab9-720d-9ce3-0041a27f83e0/runtime.json +9 -0
- package/.ikie/ferments/019f274c-7608-76f8-89c7-6e3b7d9095b2/runtime.json +11 -0
- package/.ikie/ferments/019f274c-762a-717c-9ec2-530d37897051/runtime.json +11 -0
- package/.ikie/ferments/019f274c-7644-76fc-a27d-c699d368a511/runtime.json +11 -0
- package/.ikie/ferments/019f274c-7667-7260-87b3-f2ad6ff10c04/runtime.json +12 -0
- package/.ikie/ferments/019f274c-7696-753f-8634-5cb24d2aabaa/runtime.json +11 -0
- package/.ikie/ferments/019f274c-76c8-76e8-b4d8-0cabd7f32047/runtime.json +15 -0
- package/.ikie/ferments/019f274c-76f5-732e-88f3-0461f79b7733/runtime.json +15 -0
- package/.ikie/ferments/019f274c-771b-762e-abb6-1bfe6838545b/runtime.json +15 -0
- package/.ikie/ferments/019f274c-774a-769f-b15f-d36a990c8aa2/runtime.json +13 -0
- package/.ikie/ferments/019f274c-7791-7040-9c73-fe5922824edc/runtime.json +15 -0
- package/.ikie/ferments/019f274c-77c3-76f9-a5c6-e0f5030f615c/runtime.json +15 -0
- package/.ikie/ferments/019f274c-7805-70eb-ac17-888c2a0ab5cb/runtime.json +11 -0
- package/.ikie/ferments/019f274c-7820-71da-9baf-d7953b547620/runtime.json +15 -0
- package/.ikie/ferments/019f274c-7855-708f-bbe8-a9e13258cc98/runtime.json +15 -0
- package/.ikie/ferments/019f274c-7886-70fc-9572-b96090b16cf9/runtime.json +15 -0
- package/.ikie/ferments/019f274c-78b7-756e-82ef-8a8e953000bf/runtime.json +15 -0
- package/.ikie/ferments/019f274c-78df-7198-b1dd-58c3ea1e0d6e/runtime.json +15 -0
- package/.ikie/ferments/019f274c-7902-77d4-b443-b105d0a3acd5/runtime.json +15 -0
- package/.ikie/ferments/019f274c-7926-7628-82dc-e71e8f5d58f0/runtime.json +15 -0
- package/.ikie/ferments/019f274c-7948-7755-bc18-c8d9975c762f/runtime.json +15 -0
- package/.ikie/ferments/019f274c-7972-72a5-bc5e-e47579f25c32/runtime.json +15 -0
- package/.ikie/ferments/019f274c-7998-71bf-960e-caba2791ece7/runtime.json +11 -0
- package/.ikie/ferments/019f274c-79c4-71c6-97ef-0dac30d26152/runtime.json +11 -0
- package/.ikie/ferments/019f274c-79e3-7249-8bac-82b74fbd9d1d/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f274c-79e3-7249-8bac-82b74fbd9d1d/runtime.json +17 -0
- package/.ikie/ferments/019f274c-7b11-769e-b05b-354105048fb2/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f274c-7b11-769e-b05b-354105048fb2/runtime.json +17 -0
- package/.ikie/ferments/019f274c-7c7b-73ad-98d3-739e5f0187ec/runtime.json +11 -0
- package/.ikie/ferments/019f274c-8054-728f-96c1-6cae4b96c94e/runtime.json +11 -0
- package/.ikie/ferments/019f274c-8072-7209-82b0-84facff2e61a/runtime.json +11 -0
- package/.ikie/ferments/019f274c-8094-77ec-81dd-e50e61dc11db/runtime.json +11 -0
- package/.ikie/ferments/019f274c-80bc-75c9-89e8-540dba77996e/runtime.json +11 -0
- package/.ikie/ferments/019f274c-80dd-7439-a5b0-b36fcf77c8f1/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274c-80f1-71ac-bd32-fd151f38519c/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274c-81dc-70e6-a5c9-249b196c75c3/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274c-8238-772c-9da3-f27e467100a7/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274c-824a-76d9-aac3-c51171e256b4/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274c-a854-7349-b76a-4a0bc2ecc2f7/runtime.json +11 -0
- package/.ikie/ferments/019f274c-a872-701b-a311-51267fc54aa5/runtime.json +11 -0
- package/.ikie/ferments/019f274c-a8a2-756e-9051-15fcd0be1584/runtime.json +11 -0
- package/.ikie/ferments/019f274c-a8d8-767c-8190-d30a2e78815f/runtime.json +12 -0
- package/.ikie/ferments/019f274c-a914-701e-95ab-9eeca15f5ccd/runtime.json +11 -0
- package/.ikie/ferments/019f274c-a951-720f-bc99-2ea22021a624/runtime.json +15 -0
- package/.ikie/ferments/019f274c-a978-750b-aecb-a5a2aa684cdf/runtime.json +15 -0
- package/.ikie/ferments/019f274c-a9a6-71e9-82ac-66c74cf51964/runtime.json +15 -0
- package/.ikie/ferments/019f274c-a9e0-77ae-96eb-dad523b6a843/runtime.json +13 -0
- package/.ikie/ferments/019f274c-aa2c-7658-a652-ca1a27093c42/runtime.json +15 -0
- package/.ikie/ferments/019f274c-aa77-72b4-a8de-56a2217c759f/runtime.json +15 -0
- package/.ikie/ferments/019f274c-aacf-74fa-943c-26412ddd8b5d/runtime.json +11 -0
- package/.ikie/ferments/019f274c-ab08-709f-aaff-09f10230738f/runtime.json +15 -0
- package/.ikie/ferments/019f274c-ab66-730b-86a5-7571dac8802c/runtime.json +15 -0
- package/.ikie/ferments/019f274c-abb2-77d2-91b6-ea175f561d8a/runtime.json +15 -0
- package/.ikie/ferments/019f274c-ac01-74ff-a2c6-28dbadc051e7/runtime.json +15 -0
- package/.ikie/ferments/019f274c-ac4b-75ad-93a6-aa6af74ca708/runtime.json +15 -0
- package/.ikie/ferments/019f274c-ac80-77b4-bfcf-f7a7187a42f6/runtime.json +15 -0
- package/.ikie/ferments/019f274c-acd9-7239-ad37-73d57031d1bc/runtime.json +15 -0
- package/.ikie/ferments/019f274c-ad22-74bb-9cf2-ed681dd73504/runtime.json +15 -0
- package/.ikie/ferments/019f274c-ad7d-720c-89bf-5a79ceec8173/runtime.json +15 -0
- package/.ikie/ferments/019f274c-adca-762a-b177-4d76b886003b/runtime.json +11 -0
- package/.ikie/ferments/019f274c-ae26-7109-bfaa-39d692fdc1ac/runtime.json +11 -0
- package/.ikie/ferments/019f274c-ae59-76bb-80ae-f556e764f2ca/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f274c-ae59-76bb-80ae-f556e764f2ca/runtime.json +17 -0
- package/.ikie/ferments/019f274c-b059-76e5-9b73-bedc0d4b5aaa/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f274c-b059-76e5-9b73-bedc0d4b5aaa/runtime.json +17 -0
- package/.ikie/ferments/019f274c-b21f-700a-adda-4bfd9d27a0ac/runtime.json +11 -0
- package/.ikie/ferments/019f274c-b91a-7306-8825-3581ef16ab34/runtime.json +11 -0
- package/.ikie/ferments/019f274c-b966-737f-a1c7-e8e7d3af929c/runtime.json +11 -0
- package/.ikie/ferments/019f274c-b99d-74dd-b8e8-1ddc50293a9a/runtime.json +11 -0
- package/.ikie/ferments/019f274c-ba0e-72ae-ad14-983edb8d90ac/runtime.json +11 -0
- package/.ikie/ferments/019f274c-ba4f-7101-b851-12564620c50f/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274c-ba63-712c-858f-96cee1384cec/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274c-bbe3-7343-a8b0-e6f91c3ab922/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274c-bc4a-740e-a12a-9c04181702d9/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274c-bc7b-707c-90b9-d30cf92b5fb9/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274c-e044-750a-a9e1-623de3afa666/runtime.json +13 -0
- package/.ikie/ferments/019f274c-e07a-77cb-9f17-726e4afb3ee1/runtime.json +11 -0
- package/.ikie/ferments/019f274c-e0a2-754a-ac70-aac9cd3d3dc2/runtime.json +11 -0
- package/.ikie/ferments/019f274c-e0ba-72de-bf83-25a8319eddc5/runtime.json +11 -0
- package/.ikie/ferments/019f274c-e0cf-77c1-9c23-65745da294b4/runtime.json +11 -0
- package/.ikie/ferments/019f274c-e0e7-77f7-9436-0537e2095c6b/runtime.json +11 -0
- package/.ikie/ferments/019f274c-e103-70d7-abd9-01305b905259/runtime.json +11 -0
- package/.ikie/ferments/019f274c-e121-7328-9eab-3babb40f26b3/runtime.json +11 -0
- package/.ikie/ferments/019f274c-e135-76d4-9176-2033e41fa5a8/runtime.json +9 -0
- package/.ikie/ferments/019f274c-e157-7043-9b54-fd7c40cb0793/runtime.json +13 -0
- package/.ikie/ferments/019f274c-e181-718e-804d-d0edfaabd5cd/runtime.json +11 -0
- package/.ikie/ferments/019f274c-e19b-72ab-bf87-b7a53037becb/runtime.json +11 -0
- package/.ikie/ferments/019f274c-e20a-716e-8cab-f0cd93259148/runtime.json +11 -0
- package/.ikie/ferments/019f274c-e228-73a3-a655-48bc44db0325/runtime.json +9 -0
- package/.ikie/ferments/019f274c-e243-7609-bec4-905caa78d674/runtime.json +9 -0
- package/.ikie/ferments/019f274c-e258-76ba-912c-bab41dfeaac2/runtime.json +11 -0
- package/.ikie/ferments/019f274c-e35a-776b-a644-52ee82afec4d/runtime.json +11 -0
- package/.ikie/ferments/019f274c-e378-73e8-943d-f2fc2ee875ef/runtime.json +12 -0
- package/.ikie/ferments/019f274c-e3a6-757d-b33c-c6fbdd9cf8c7/runtime.json +11 -0
- package/.ikie/ferments/019f274c-e3c9-77a7-be2b-43891355a506/runtime.json +11 -0
- package/.ikie/ferments/019f274c-e3db-776b-8de6-fb1aeab60a7b/runtime.json +11 -0
- package/.ikie/ferments/019f274c-e3f4-700e-8780-2dee3c117eb8/runtime.json +11 -0
- package/.ikie/ferments/019f274c-e409-71af-a0f9-ff159811e472/runtime.json +13 -0
- package/.ikie/ferments/019f274c-e432-73cd-bd34-39f0e34a568d/runtime.json +11 -0
- package/.ikie/ferments/019f274c-eb55-73fe-bb2a-34b4a2df3f4e/reviews/phase-phase-1-1.json +41 -0
- package/.ikie/ferments/019f274c-eb55-73fe-bb2a-34b4a2df3f4e/runtime.json +11 -0
- package/.ikie/ferments/019f274c-eb9b-728e-93e2-80afb71ae50e/reviews/phase-phase-1-1.json +47 -0
- package/.ikie/ferments/019f274c-eb9b-728e-93e2-80afb71ae50e/runtime.json +13 -0
- package/.ikie/ferments/019f274c-ebf3-77bb-8f8a-6081787fbcb3/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f274c-ebf3-77bb-8f8a-6081787fbcb3/runtime.json +9 -0
- package/.ikie/ferments/019f274c-ec22-7123-8c8f-0c129c2e0719/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f274c-ec22-7123-8c8f-0c129c2e0719/runtime.json +9 -0
- package/.ikie/ferments/019f274c-ec85-757c-9600-8be68ceccb55/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f274c-ec85-757c-9600-8be68ceccb55/runtime.json +9 -0
- package/.ikie/ferments/019f274c-ecd9-704e-8c99-65c64be0a8e1/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f274c-ecd9-704e-8c99-65c64be0a8e1/runtime.json +9 -0
- package/.ikie/ferments/019f274c-ecee-70bb-9087-da3b4c57d281/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f274c-ecee-70bb-9087-da3b4c57d281/runtime.json +9 -0
- package/.ikie/ferments/019f274c-ed18-77bf-8cde-812d1e273bda/reviews/phase-phase-1-1.json +62 -0
- package/.ikie/ferments/019f274c-ed18-77bf-8cde-812d1e273bda/runtime.json +9 -0
- package/.ikie/ferments/019f274f-0303-7543-9aa9-fa7f54d5f6c6/runtime.json +11 -0
- package/.ikie/ferments/019f274f-032c-70aa-b5b5-d695429a50bc/runtime.json +11 -0
- package/.ikie/ferments/019f274f-036d-763c-8cf9-cb62245aff49/runtime.json +11 -0
- package/.ikie/ferments/019f274f-03bd-709c-85ee-698745beac66/runtime.json +12 -0
- package/.ikie/ferments/019f274f-0404-75cf-8731-f0182bd84d87/runtime.json +11 -0
- package/.ikie/ferments/019f274f-0442-75fe-8cc3-44bfe5dd84bf/runtime.json +15 -0
- package/.ikie/ferments/019f274f-048f-759f-ae18-5fe9975647bf/runtime.json +15 -0
- package/.ikie/ferments/019f274f-04f1-72fa-a2ba-0b9e066ae6eb/runtime.json +15 -0
- package/.ikie/ferments/019f274f-0541-728d-8893-0b0b45547a56/runtime.json +13 -0
- package/.ikie/ferments/019f274f-058e-7508-b67a-ebe736113e1e/runtime.json +15 -0
- package/.ikie/ferments/019f274f-0625-77d9-bf99-bcdf0ca2d5b2/runtime.json +15 -0
- package/.ikie/ferments/019f274f-0694-715c-b008-29a3cf0d6c9c/runtime.json +11 -0
- package/.ikie/ferments/019f274f-06b1-71fb-a812-3fd3cefda2ea/runtime.json +15 -0
- package/.ikie/ferments/019f274f-06e9-701b-9396-dca2be2330e5/runtime.json +15 -0
- package/.ikie/ferments/019f274f-072b-76b2-8d77-32d0ad5abb81/runtime.json +15 -0
- package/.ikie/ferments/019f274f-0770-75ce-886f-c1c12d6682eb/runtime.json +15 -0
- package/.ikie/ferments/019f274f-07ab-75fc-a71b-ca3b4b4acca5/runtime.json +15 -0
- package/.ikie/ferments/019f274f-07f0-72cd-a577-316526b45983/runtime.json +15 -0
- package/.ikie/ferments/019f274f-0837-72ed-94f2-dd68e9273549/runtime.json +15 -0
- package/.ikie/ferments/019f274f-0866-765d-a2fc-8cce9d53e417/runtime.json +15 -0
- package/.ikie/ferments/019f274f-08bb-737e-8f23-4b2e069bc7e5/runtime.json +15 -0
- package/.ikie/ferments/019f274f-08f6-73aa-968a-85f55d5e047c/runtime.json +11 -0
- package/.ikie/ferments/019f274f-0949-77b7-8d2c-46fbeb423e3a/runtime.json +11 -0
- package/.ikie/ferments/019f274f-0979-71ad-91b5-d2e52366b74d/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f274f-0979-71ad-91b5-d2e52366b74d/runtime.json +17 -0
- package/.ikie/ferments/019f274f-0b57-7329-884a-43e712cd093c/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f274f-0b57-7329-884a-43e712cd093c/runtime.json +17 -0
- package/.ikie/ferments/019f274f-0d87-772d-a6f3-b71179eec14f/runtime.json +11 -0
- package/.ikie/ferments/019f274f-12f0-73be-b630-54a329974d0b/runtime.json +11 -0
- package/.ikie/ferments/019f274f-1332-7202-90be-f7e94dd16e66/runtime.json +11 -0
- package/.ikie/ferments/019f274f-1368-778a-b044-0306086fe9cf/runtime.json +11 -0
- package/.ikie/ferments/019f274f-13a9-7287-974d-8db5c1b7dda6/runtime.json +11 -0
- package/.ikie/ferments/019f274f-13d1-7069-91d8-2e4efe8d8982/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274f-13f8-717c-92c9-9bc9b37ee2cc/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274f-1566-753b-b053-f14ccb779e36/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274f-15d3-76f8-8467-e368f0c40baf/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274f-15f0-716f-92e7-d966f4476b09/pending-proposal.json +45 -0
- package/.ikie/ferments/019f274f-2a19-762d-be24-59afdd71b303/runtime.json +13 -0
- package/.ikie/ferments/019f274f-2a74-74cb-ab51-dd779739478e/runtime.json +11 -0
- package/.ikie/ferments/019f274f-2a92-71e1-9e68-15f750efc6ec/runtime.json +11 -0
- package/.ikie/ferments/019f274f-2ab1-73eb-8930-b9d161230fcc/runtime.json +11 -0
- package/.ikie/ferments/019f274f-2ad9-70ea-83a9-f8ae1af1edcf/runtime.json +11 -0
- package/.ikie/ferments/019f274f-2b02-77ea-b187-43489c9a88f8/runtime.json +11 -0
- package/.ikie/ferments/019f274f-2b2d-728c-9b69-7a6e4956a88c/runtime.json +11 -0
- package/.ikie/ferments/019f274f-2b42-7358-b1fa-9754d2ca530d/runtime.json +11 -0
- package/.ikie/ferments/019f274f-2b74-77ee-b062-8b111368fc58/runtime.json +9 -0
- package/.ikie/ferments/019f274f-2b9b-703b-8abd-5b4ff212ce66/runtime.json +13 -0
- package/.ikie/ferments/019f274f-2bd6-7109-93ec-12f609270c84/runtime.json +11 -0
- package/.ikie/ferments/019f274f-2bea-75cf-b3d2-aedca4b0c8a4/runtime.json +11 -0
- package/.ikie/ferments/019f274f-2c1c-7085-b221-ecf2a371e6cf/runtime.json +11 -0
- package/.ikie/ferments/019f274f-2c2f-71fb-9b03-41d6f8a5d2d6/runtime.json +9 -0
- package/.ikie/ferments/019f274f-2c5d-719d-a913-5255a7b3e376/runtime.json +9 -0
- package/.ikie/ferments/019f274f-2c92-75bb-b691-4efd9b706418/runtime.json +11 -0
- package/.ikie/ferments/019f274f-2d9e-7186-b8f9-0d5863265329/runtime.json +11 -0
- package/.ikie/ferments/019f274f-2dbb-73f9-8231-b52b6e1eb863/runtime.json +12 -0
- package/.ikie/ferments/019f274f-2ddf-7477-a5a2-a20be1921fbf/runtime.json +11 -0
- package/.ikie/ferments/019f274f-2e01-710b-8175-18eeb0e792e2/runtime.json +11 -0
- package/.ikie/ferments/019f274f-2e26-7217-a41e-3bd106e0a9cf/runtime.json +11 -0
- package/.ikie/ferments/019f274f-2e3a-72bc-9945-37a576bb7ef1/runtime.json +11 -0
- package/.ikie/ferments/019f274f-2e61-77e5-85ab-dda905bded90/runtime.json +13 -0
- package/.ikie/ferments/019f274f-2e7e-73ea-a7c9-14ab5125c3d3/runtime.json +11 -0
- package/.ikie/ferments/019f274f-4262-76b9-884a-9ff2121f2c3a/reviews/phase-phase-1-1.json +41 -0
- package/.ikie/ferments/019f274f-4262-76b9-884a-9ff2121f2c3a/runtime.json +11 -0
- package/.ikie/ferments/019f274f-42ce-753f-ba6d-c1c2be5b7c71/reviews/phase-phase-1-1.json +47 -0
- package/.ikie/ferments/019f274f-42ce-753f-ba6d-c1c2be5b7c71/runtime.json +13 -0
- package/.ikie/ferments/019f274f-4353-75ed-bad2-0f74d61ceff5/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f274f-4353-75ed-bad2-0f74d61ceff5/runtime.json +9 -0
- package/.ikie/ferments/019f274f-4379-7716-af61-2381f422540e/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f274f-4379-7716-af61-2381f422540e/runtime.json +9 -0
- package/.ikie/ferments/019f274f-43a1-716c-ba31-4b705e8afb1a/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f274f-43a1-716c-ba31-4b705e8afb1a/runtime.json +9 -0
- package/.ikie/ferments/019f274f-43de-761a-b793-fca5ca243eea/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f274f-43de-761a-b793-fca5ca243eea/runtime.json +9 -0
- package/.ikie/ferments/019f274f-4401-73e9-b9d8-99631bab030b/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f274f-4401-73e9-b9d8-99631bab030b/runtime.json +9 -0
- package/.ikie/ferments/019f274f-442b-770b-b411-feeb46ae7a8d/reviews/phase-phase-1-1.json +62 -0
- package/.ikie/ferments/019f274f-442b-770b-b411-feeb46ae7a8d/runtime.json +9 -0
- package/.ikie/ferments/019f275c-3e9e-7557-b50e-0e56cc6b07f5/runtime.json +11 -0
- package/.ikie/ferments/019f275c-3ec3-73ea-b557-77cc517095f2/runtime.json +11 -0
- package/.ikie/ferments/019f275c-3ee5-774f-876e-3caacb9b1d95/runtime.json +11 -0
- package/.ikie/ferments/019f275c-3f28-73ee-8eae-568fec8011b6/runtime.json +12 -0
- package/.ikie/ferments/019f275c-3f57-70a8-ba4d-d179cceaabbf/runtime.json +11 -0
- package/.ikie/ferments/019f275c-3f88-72cb-abb5-8e0e176e1dd2/runtime.json +15 -0
- package/.ikie/ferments/019f275c-3fb4-7248-9c23-874baa6e2a9d/runtime.json +15 -0
- package/.ikie/ferments/019f275c-3fe1-759b-afd9-f5e47564de48/runtime.json +15 -0
- package/.ikie/ferments/019f275c-401f-74e8-b968-d325ae2faf56/runtime.json +13 -0
- package/.ikie/ferments/019f275c-4067-752e-a0d8-d85f670f9473/runtime.json +15 -0
- package/.ikie/ferments/019f275c-40a2-74de-b858-ef4ad52484ea/runtime.json +15 -0
- package/.ikie/ferments/019f275c-4105-738f-95d0-6e9f7f9568fb/runtime.json +11 -0
- package/.ikie/ferments/019f275c-411b-76e9-b030-df118a1b23b4/runtime.json +15 -0
- package/.ikie/ferments/019f275c-4153-74ec-8b93-e2e0e82c27b0/runtime.json +15 -0
- package/.ikie/ferments/019f275c-4199-739b-9af9-b1cfc26623c3/runtime.json +15 -0
- package/.ikie/ferments/019f275c-41cb-746b-8481-7ca9d82a8b64/runtime.json +15 -0
- package/.ikie/ferments/019f275c-4209-756f-b335-8e6dfe51bac1/runtime.json +15 -0
- package/.ikie/ferments/019f275c-424c-7512-ac17-b8c04dcf82ac/runtime.json +15 -0
- package/.ikie/ferments/019f275c-4269-74eb-a213-e221479a26e5/runtime.json +15 -0
- package/.ikie/ferments/019f275c-428b-71ea-acda-027b98d191d1/runtime.json +15 -0
- package/.ikie/ferments/019f275c-42bb-75da-b330-e9f101c36d5c/runtime.json +15 -0
- package/.ikie/ferments/019f275c-42e3-777a-8e8c-80772f6d6bcb/runtime.json +11 -0
- package/.ikie/ferments/019f275c-430b-7779-bcc7-95a0b6fc693d/runtime.json +11 -0
- package/.ikie/ferments/019f275c-432e-71f9-b65d-88d26f3b185f/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f275c-432e-71f9-b65d-88d26f3b185f/runtime.json +17 -0
- package/.ikie/ferments/019f275c-447e-756d-8ec0-091e54bcc7b4/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f275c-447e-756d-8ec0-091e54bcc7b4/runtime.json +17 -0
- package/.ikie/ferments/019f275c-4602-757e-9283-3606516f4d15/runtime.json +11 -0
- package/.ikie/ferments/019f275c-4a3b-75f0-8355-8524d1589242/runtime.json +11 -0
- package/.ikie/ferments/019f275c-4a5c-744e-9792-deb33fe1e244/runtime.json +11 -0
- package/.ikie/ferments/019f275c-4a79-73d2-beae-4e85f8906581/runtime.json +11 -0
- package/.ikie/ferments/019f275c-4a97-70b9-98d1-53d9326b4c95/runtime.json +11 -0
- package/.ikie/ferments/019f275c-4ac1-7452-ab7a-485bde641591/pending-proposal.json +45 -0
- package/.ikie/ferments/019f275c-4ad7-77ba-8a63-d8586db466e7/pending-proposal.json +45 -0
- package/.ikie/ferments/019f275c-4bee-77b9-8470-98c4aafeae43/pending-proposal.json +45 -0
- package/.ikie/ferments/019f275c-4c3d-712b-95f6-a671b7884658/pending-proposal.json +45 -0
- package/.ikie/ferments/019f275c-4c4e-73a0-accb-e76c29bc1f6d/pending-proposal.json +45 -0
- package/.ikie/ferments/019f275c-7598-775d-beed-cde3b6cb74e6/runtime.json +13 -0
- package/.ikie/ferments/019f275c-75c0-773d-9c0c-dd4943fc6092/runtime.json +11 -0
- package/.ikie/ferments/019f275c-75dc-7629-8a52-1eba66275779/runtime.json +11 -0
- package/.ikie/ferments/019f275c-75f3-742b-8494-e8a1facb02bb/runtime.json +11 -0
- package/.ikie/ferments/019f275c-760c-705e-8d98-7963d8830086/runtime.json +11 -0
- package/.ikie/ferments/019f275c-7627-763c-ae8e-20dde59c64b9/runtime.json +11 -0
- package/.ikie/ferments/019f275c-763c-74ae-b835-b52b57c52e9b/runtime.json +11 -0
- package/.ikie/ferments/019f275c-7650-7499-b744-7f38cfa776f4/runtime.json +11 -0
- package/.ikie/ferments/019f275c-766d-75b8-ba35-69e472baf630/runtime.json +9 -0
- package/.ikie/ferments/019f275c-7687-72cc-beac-339a11625f14/runtime.json +13 -0
- package/.ikie/ferments/019f275c-76a6-773d-a4c1-19a5ec313596/runtime.json +11 -0
- package/.ikie/ferments/019f275c-76c1-71b9-a49e-4b520fcef543/runtime.json +11 -0
- package/.ikie/ferments/019f275c-7706-761f-a9cf-7e412e88015d/runtime.json +11 -0
- package/.ikie/ferments/019f275c-772b-745f-a74d-de30f52a3316/runtime.json +9 -0
- package/.ikie/ferments/019f275c-7746-759c-a116-c2cd08e2f29b/runtime.json +9 -0
- package/.ikie/ferments/019f275c-7760-709d-83bc-4d6fff7ac768/runtime.json +11 -0
- package/.ikie/ferments/019f275c-780e-74db-9d7d-bcc430c1a964/runtime.json +11 -0
- package/.ikie/ferments/019f275c-7826-734e-8113-90111e82d96a/runtime.json +12 -0
- package/.ikie/ferments/019f275c-7849-72ed-8fb0-d1815d2f66d3/runtime.json +11 -0
- package/.ikie/ferments/019f275c-785f-716e-b077-eee453653ee0/runtime.json +11 -0
- package/.ikie/ferments/019f275c-7871-70fe-b543-bcdaaa10d320/runtime.json +11 -0
- package/.ikie/ferments/019f275c-7888-777d-8edf-1a5a906679d7/runtime.json +11 -0
- package/.ikie/ferments/019f275c-78b3-722e-9d1a-f354b0d0fa96/runtime.json +13 -0
- package/.ikie/ferments/019f275c-78cb-7568-ad11-191bc98cabe5/runtime.json +11 -0
- package/.ikie/ferments/019f275c-8905-76ae-beb3-2ec9138b799b/reviews/phase-phase-1-1.json +41 -0
- package/.ikie/ferments/019f275c-8905-76ae-beb3-2ec9138b799b/runtime.json +11 -0
- package/.ikie/ferments/019f275c-8970-713f-a498-af24ac57355a/reviews/phase-phase-1-1.json +47 -0
- package/.ikie/ferments/019f275c-8970-713f-a498-af24ac57355a/runtime.json +13 -0
- package/.ikie/ferments/019f275c-89c6-712f-b3c9-d9459bdaba76/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f275c-89c6-712f-b3c9-d9459bdaba76/runtime.json +9 -0
- package/.ikie/ferments/019f275c-89e8-7105-9ba9-eda02056339a/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f275c-89e8-7105-9ba9-eda02056339a/runtime.json +9 -0
- package/.ikie/ferments/019f275c-8a0d-71cb-bbb3-aa061c305aab/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f275c-8a0d-71cb-bbb3-aa061c305aab/runtime.json +9 -0
- package/.ikie/ferments/019f275c-8a31-758b-87c7-1b2ec8b0bd7a/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f275c-8a31-758b-87c7-1b2ec8b0bd7a/runtime.json +9 -0
- package/.ikie/ferments/019f275c-8a4f-7649-a250-4074bed6f208/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f275c-8a4f-7649-a250-4074bed6f208/runtime.json +9 -0
- package/.ikie/ferments/019f275c-8a6e-706d-af6d-f6636f3b229e/reviews/phase-phase-1-1.json +62 -0
- package/.ikie/ferments/019f275c-8a6e-706d-af6d-f6636f3b229e/runtime.json +9 -0
- package/.ikie/ferments/019f275e-8903-721d-98f8-0135ab6b4698/runtime.json +11 -0
- package/.ikie/ferments/019f275e-8920-7644-a529-664b63f9bab1/runtime.json +11 -0
- package/.ikie/ferments/019f275e-8938-75c2-85ab-f5a50138a4eb/runtime.json +11 -0
- package/.ikie/ferments/019f275e-895e-74a1-b263-2f73d989898e/runtime.json +12 -0
- package/.ikie/ferments/019f275e-8977-75ee-9e9a-cc069fff1cfa/runtime.json +11 -0
- package/.ikie/ferments/019f275e-899d-773b-abe3-e93c56f57cf1/runtime.json +11 -0
- package/.ikie/ferments/019f275e-89d1-72ea-9362-d4ce4079e846/runtime.json +15 -0
- package/.ikie/ferments/019f275e-89f9-7007-b382-a55e983d9520/runtime.json +15 -0
- package/.ikie/ferments/019f275e-8a1d-7687-a56e-94ead3cafbf8/runtime.json +15 -0
- package/.ikie/ferments/019f275e-8a49-760f-9639-3ce1494f37e9/runtime.json +13 -0
- package/.ikie/ferments/019f275e-8a7d-7461-b978-c3478b99b386/runtime.json +15 -0
- package/.ikie/ferments/019f275e-8aac-736d-bd96-6b442359993b/runtime.json +15 -0
- package/.ikie/ferments/019f275e-8ae6-7298-a523-d2fb7287bed3/runtime.json +11 -0
- package/.ikie/ferments/019f275e-8b01-7059-85a1-a665334ced3e/runtime.json +15 -0
- package/.ikie/ferments/019f275e-8b29-7295-be5b-0468763ab101/runtime.json +15 -0
- package/.ikie/ferments/019f275e-8b51-7204-9d30-0a67dae9c341/runtime.json +15 -0
- package/.ikie/ferments/019f275e-8b78-71eb-a8a3-1448d3373912/runtime.json +15 -0
- package/.ikie/ferments/019f275e-8b9d-76bf-9caa-0fed193b85bf/runtime.json +15 -0
- package/.ikie/ferments/019f275e-8bc0-71df-9356-19d3075c9568/runtime.json +15 -0
- package/.ikie/ferments/019f275e-8be2-75bd-be4a-ba2ede0e95f3/runtime.json +15 -0
- package/.ikie/ferments/019f275e-8c0b-714a-a389-20ffe6d0e66d/runtime.json +15 -0
- package/.ikie/ferments/019f275e-8c30-7371-86af-78e5f5ac8b23/runtime.json +15 -0
- package/.ikie/ferments/019f275e-8c5a-7383-bd79-c7fec3a71946/runtime.json +11 -0
- package/.ikie/ferments/019f275e-8c75-76b1-bcaa-81e67fc35aa8/runtime.json +11 -0
- package/.ikie/ferments/019f275e-8caa-71e9-9e66-0d30c4a03123/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f275e-8caa-71e9-9e66-0d30c4a03123/runtime.json +17 -0
- package/.ikie/ferments/019f275e-8e59-7787-84f8-fcf93ec75949/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f275e-8e59-7787-84f8-fcf93ec75949/runtime.json +17 -0
- package/.ikie/ferments/019f275e-8ffb-70c2-844e-e1f4fa4c2cc4/runtime.json +11 -0
- package/.ikie/ferments/019f275e-949b-7158-a426-07bcca442439/runtime.json +11 -0
- package/.ikie/ferments/019f275e-94c6-7479-abcb-c22fc19445a0/runtime.json +11 -0
- package/.ikie/ferments/019f275e-94eb-76e9-a148-fb5001492823/runtime.json +11 -0
- package/.ikie/ferments/019f275e-9516-765a-b8ee-e88e5ab2530f/runtime.json +11 -0
- package/.ikie/ferments/019f275e-9539-740f-bbdb-09b82f71fc81/pending-proposal.json +45 -0
- package/.ikie/ferments/019f275e-954f-73e0-9286-fdf09cb1cbc6/pending-proposal.json +45 -0
- package/.ikie/ferments/019f275e-967d-73dc-bfaf-18e44335cff8/pending-proposal.json +45 -0
- package/.ikie/ferments/019f275e-96f0-702d-b0d2-f2aeb1c7ee15/pending-proposal.json +45 -0
- package/.ikie/ferments/019f275e-9708-770c-bae2-2652e32be3cf/pending-proposal.json +45 -0
- package/.ikie/ferments/019f275e-c992-77a4-960a-d6d51983c9d6/runtime.json +13 -0
- package/.ikie/ferments/019f275e-c9cf-724a-a59f-8e34f026db5e/runtime.json +11 -0
- package/.ikie/ferments/019f275e-c9e6-7647-9517-b2b25cd1f495/runtime.json +11 -0
- package/.ikie/ferments/019f275e-ca11-716b-832b-11fc14ce1ded/runtime.json +11 -0
- package/.ikie/ferments/019f275e-ca2f-74df-90fa-2eed00b55ecf/runtime.json +11 -0
- package/.ikie/ferments/019f275e-ca49-77ed-bb62-cd9c3c1176a5/runtime.json +11 -0
- package/.ikie/ferments/019f275e-ca62-77ca-aed0-74728cab4be4/runtime.json +11 -0
- package/.ikie/ferments/019f275e-ca7a-76aa-a860-42152a699337/runtime.json +11 -0
- package/.ikie/ferments/019f275e-ca9b-766c-9ad4-2f86ff5c9dbe/runtime.json +9 -0
- package/.ikie/ferments/019f275e-cabc-77cf-a94a-9f8988667998/runtime.json +13 -0
- package/.ikie/ferments/019f275e-cae6-76d9-8682-d138cbd0e3cf/runtime.json +11 -0
- package/.ikie/ferments/019f275e-cb0a-7361-8e72-84e44afa7847/runtime.json +11 -0
- package/.ikie/ferments/019f275e-cb67-71dd-a7b7-35d98fca2379/runtime.json +11 -0
- package/.ikie/ferments/019f275e-cb8c-75c8-9a02-812c50dbcc28/runtime.json +9 -0
- package/.ikie/ferments/019f275e-cba7-74c5-8a0e-facff616ecf0/runtime.json +9 -0
- package/.ikie/ferments/019f275e-cbc6-719a-a1e9-86fad18fb0de/runtime.json +11 -0
- package/.ikie/ferments/019f275e-cc83-7608-878d-3b107b55db48/runtime.json +11 -0
- package/.ikie/ferments/019f275e-cc96-72f9-9e5d-a350ea59c5b7/runtime.json +12 -0
- package/.ikie/ferments/019f275e-ccb5-72bf-8ad6-8ea4f87f86c0/runtime.json +11 -0
- package/.ikie/ferments/019f275e-ccd9-77de-89ff-25fa626c05b4/runtime.json +11 -0
- package/.ikie/ferments/019f275e-ccfb-777a-8a40-1ccaf4c139c4/runtime.json +11 -0
- package/.ikie/ferments/019f275e-cd15-7785-be69-1f9ef80faa52/runtime.json +11 -0
- package/.ikie/ferments/019f275e-cd31-7629-9034-57bcde8cf8e1/runtime.json +13 -0
- package/.ikie/ferments/019f275e-cd56-74be-8cdd-94af39b5593b/runtime.json +11 -0
- package/.ikie/ferments/019f275e-db71-73fb-9992-6d52de708265/reviews/phase-phase-1-1.json +41 -0
- package/.ikie/ferments/019f275e-db71-73fb-9992-6d52de708265/runtime.json +11 -0
- package/.ikie/ferments/019f275e-dbc0-725b-bb55-3eca5072215b/reviews/phase-phase-1-1.json +47 -0
- package/.ikie/ferments/019f275e-dbc0-725b-bb55-3eca5072215b/runtime.json +13 -0
- package/.ikie/ferments/019f275e-dc11-7320-bc60-fbca50c8e709/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f275e-dc11-7320-bc60-fbca50c8e709/runtime.json +9 -0
- package/.ikie/ferments/019f275e-dc39-73ee-9d63-d8fbb906d877/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f275e-dc39-73ee-9d63-d8fbb906d877/runtime.json +9 -0
- package/.ikie/ferments/019f275e-dc60-7649-9918-b236d9a16cef/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f275e-dc60-7649-9918-b236d9a16cef/runtime.json +9 -0
- package/.ikie/ferments/019f275e-dc83-7030-aa35-3a78b18fadb4/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f275e-dc83-7030-aa35-3a78b18fadb4/runtime.json +9 -0
- package/.ikie/ferments/019f275e-dcb3-706f-b3b0-b44556b7d3cb/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f275e-dcb3-706f-b3b0-b44556b7d3cb/runtime.json +9 -0
- package/.ikie/ferments/019f275e-dcd6-717c-afe6-82a959a0fcbd/reviews/phase-phase-1-1.json +62 -0
- package/.ikie/ferments/019f275e-dcd6-717c-afe6-82a959a0fcbd/runtime.json +9 -0
- package/.ikie/ferments/019f2764-6cde-7610-b409-ee715ca796e8/runtime.json +11 -0
- package/.ikie/ferments/019f2764-6cfa-733d-8231-937535e2e6b8/runtime.json +11 -0
- package/.ikie/ferments/019f2764-6d11-7611-b216-dcdaa468d686/runtime.json +11 -0
- package/.ikie/ferments/019f2764-6d33-728f-901c-4fef8141050d/runtime.json +12 -0
- package/.ikie/ferments/019f2764-6d5d-7314-9e98-3851effc803f/runtime.json +11 -0
- package/.ikie/ferments/019f2764-6d87-722e-bf53-0dd6182ea7c6/runtime.json +15 -0
- package/.ikie/ferments/019f2764-6daa-7052-b766-8aef8e542b88/runtime.json +15 -0
- package/.ikie/ferments/019f2764-6dc9-73fc-8665-a3f8b51f7450/runtime.json +15 -0
- package/.ikie/ferments/019f2764-6df3-7368-94a2-5369f3276848/runtime.json +13 -0
- package/.ikie/ferments/019f2764-6e25-762c-a0e0-0ef60c6e2653/runtime.json +15 -0
- package/.ikie/ferments/019f2764-6e58-733d-ab0e-8fad4b2c665e/runtime.json +15 -0
- package/.ikie/ferments/019f2764-6ead-742a-afc5-e10a044333ec/runtime.json +11 -0
- package/.ikie/ferments/019f2764-6ec9-7566-a4f0-e0d14f80e103/runtime.json +15 -0
- package/.ikie/ferments/019f2764-6efe-727d-a81b-36fbde2904b3/runtime.json +15 -0
- package/.ikie/ferments/019f2764-6f3d-7292-bbf4-88e9ee6b077e/runtime.json +15 -0
- package/.ikie/ferments/019f2764-6f6c-758f-9427-652571b7494f/runtime.json +15 -0
- package/.ikie/ferments/019f2764-6faa-700b-8d2d-cbed35957db8/runtime.json +15 -0
- package/.ikie/ferments/019f2764-6fcb-71d8-9daf-225746805ba4/runtime.json +15 -0
- package/.ikie/ferments/019f2764-6ff0-7573-9208-e236e2969c5a/runtime.json +15 -0
- package/.ikie/ferments/019f2764-7012-77ce-97c9-6e99dd0367b3/runtime.json +15 -0
- package/.ikie/ferments/019f2764-703d-7554-8008-5104879d081d/runtime.json +15 -0
- package/.ikie/ferments/019f2764-7082-7399-80d5-659015dc6129/runtime.json +11 -0
- package/.ikie/ferments/019f2764-70b4-7581-b83e-9945010b008b/runtime.json +11 -0
- package/.ikie/ferments/019f2764-70d2-71a1-8b2f-1db7c0de5d5b/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2764-70d2-71a1-8b2f-1db7c0de5d5b/runtime.json +17 -0
- package/.ikie/ferments/019f2764-7256-714b-9804-17a6ad3c7996/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2764-7256-714b-9804-17a6ad3c7996/runtime.json +17 -0
- package/.ikie/ferments/019f2764-742b-711f-9997-ae6ce141d7d9/runtime.json +11 -0
- package/.ikie/ferments/019f2764-78e4-765c-bcf6-ae76955d98c0/runtime.json +11 -0
- package/.ikie/ferments/019f2764-7900-7573-afaf-c32d1b6a7be0/runtime.json +11 -0
- package/.ikie/ferments/019f2764-7920-7463-b2c3-132980285998/runtime.json +11 -0
- package/.ikie/ferments/019f2764-7947-721f-9c5e-6fc087b2e912/runtime.json +11 -0
- package/.ikie/ferments/019f2764-7967-735b-9066-12a93c490f17/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2764-798b-749d-b86b-90c55c838b29/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2764-7a9f-747c-81ce-f27825e1cefb/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2764-7afc-745a-8d70-a2ebf234977d/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2764-7b0f-715d-a410-e707593c0f1b/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2764-a49c-750d-8022-8d5ef7b34341/runtime.json +13 -0
- package/.ikie/ferments/019f2764-a4c0-7779-a268-1589e2c4d025/runtime.json +11 -0
- package/.ikie/ferments/019f2764-a4e2-706f-8cc3-f71f13a5334c/runtime.json +11 -0
- package/.ikie/ferments/019f2764-a4fc-765c-b248-0fd90d273737/runtime.json +11 -0
- package/.ikie/ferments/019f2764-a511-7719-b577-47d9389dc68c/runtime.json +11 -0
- package/.ikie/ferments/019f2764-a524-731c-91d5-99c2242128e7/runtime.json +11 -0
- package/.ikie/ferments/019f2764-a53c-7308-a4a2-2c4ebd0af1f2/runtime.json +11 -0
- package/.ikie/ferments/019f2764-a555-762a-b6ec-c7a1ea59914b/runtime.json +11 -0
- package/.ikie/ferments/019f2764-a56e-776a-8421-d52ce16332e6/runtime.json +9 -0
- package/.ikie/ferments/019f2764-a584-75ad-8cf4-02ad739aeac6/runtime.json +13 -0
- package/.ikie/ferments/019f2764-a5a6-716c-8803-03dfe8638639/runtime.json +11 -0
- package/.ikie/ferments/019f2764-a5cb-75a8-9184-8bc493e04858/runtime.json +11 -0
- package/.ikie/ferments/019f2764-a613-726d-9770-828b95668f3d/runtime.json +11 -0
- package/.ikie/ferments/019f2764-a62b-703f-ad34-e560da58091f/runtime.json +9 -0
- package/.ikie/ferments/019f2764-a646-75a7-a048-e1c6a57c17db/runtime.json +9 -0
- package/.ikie/ferments/019f2764-a65d-7369-8043-22e8aa877564/runtime.json +11 -0
- package/.ikie/ferments/019f2764-a714-7737-9aca-229d1f730df1/runtime.json +11 -0
- package/.ikie/ferments/019f2764-a725-71af-91db-6146483607ce/runtime.json +12 -0
- package/.ikie/ferments/019f2764-a748-721c-a9bb-9a5cd1a6390b/runtime.json +11 -0
- package/.ikie/ferments/019f2764-a761-7632-84ba-5d4da92e371d/runtime.json +11 -0
- package/.ikie/ferments/019f2764-a776-76dd-a48e-148e4d5e6dcd/runtime.json +11 -0
- package/.ikie/ferments/019f2764-a78f-77d0-9647-ffb2ed828229/runtime.json +11 -0
- package/.ikie/ferments/019f2764-a7ab-716e-958d-c3f3c4d33a34/runtime.json +13 -0
- package/.ikie/ferments/019f2764-a7cb-775b-8844-8faa27f1ac1c/runtime.json +11 -0
- package/.ikie/ferments/019f2764-bd5e-773d-9b24-9da824db3174/reviews/phase-phase-1-1.json +41 -0
- package/.ikie/ferments/019f2764-bd5e-773d-9b24-9da824db3174/runtime.json +11 -0
- package/.ikie/ferments/019f2764-bda9-7207-b421-beff2d49cd2a/reviews/phase-phase-1-1.json +47 -0
- package/.ikie/ferments/019f2764-bda9-7207-b421-beff2d49cd2a/runtime.json +13 -0
- package/.ikie/ferments/019f2764-be1e-775f-b937-81b9fd368168/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2764-be1e-775f-b937-81b9fd368168/runtime.json +9 -0
- package/.ikie/ferments/019f2764-be38-7792-b549-8583fd6c5e70/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2764-be38-7792-b549-8583fd6c5e70/runtime.json +9 -0
- package/.ikie/ferments/019f2764-be5e-73da-85c5-508167795c94/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2764-be5e-73da-85c5-508167795c94/runtime.json +9 -0
- package/.ikie/ferments/019f2764-be82-721a-aa06-3b54c6236827/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2764-be82-721a-aa06-3b54c6236827/runtime.json +9 -0
- package/.ikie/ferments/019f2764-be97-703d-8e0a-6496ed0a83ca/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2764-be97-703d-8e0a-6496ed0a83ca/runtime.json +9 -0
- package/.ikie/ferments/019f2764-bebc-756a-a6ca-0f40b9705a0f/reviews/phase-phase-1-1.json +62 -0
- package/.ikie/ferments/019f2764-bebc-756a-a6ca-0f40b9705a0f/runtime.json +9 -0
- package/.ikie/ferments/019f2766-a81a-73aa-ba58-4a0192a7b079/runtime.json +11 -0
- package/.ikie/ferments/019f2766-a839-708c-b3bf-4c3f2279558e/runtime.json +11 -0
- package/.ikie/ferments/019f2766-a852-7641-a85f-69ea0ce5d73b/runtime.json +11 -0
- package/.ikie/ferments/019f2766-a877-74b8-b1de-3ffbbcb6f14b/runtime.json +12 -0
- package/.ikie/ferments/019f2766-a892-767d-b447-2e7a666f42f0/runtime.json +11 -0
- package/.ikie/ferments/019f2766-a8c2-7178-b06c-00d7646d1966/runtime.json +11 -0
- package/.ikie/ferments/019f2766-a8f5-713b-92d4-c4c70e48c5db/runtime.json +15 -0
- package/.ikie/ferments/019f2766-a91c-76a8-9d47-7e07b7843506/runtime.json +15 -0
- package/.ikie/ferments/019f2766-a944-7458-bae5-e3c38b3eec06/runtime.json +15 -0
- package/.ikie/ferments/019f2766-a973-77da-b2c0-d4f59f145d19/runtime.json +13 -0
- package/.ikie/ferments/019f2766-a9ae-701f-b8af-2d02369bc2f1/runtime.json +15 -0
- package/.ikie/ferments/019f2766-a9e5-7319-882f-58450ee132ea/runtime.json +15 -0
- package/.ikie/ferments/019f2766-aa3a-722b-9ed0-f38465896d02/runtime.json +11 -0
- package/.ikie/ferments/019f2766-aa58-7389-b25e-688385ff4a7f/runtime.json +15 -0
- package/.ikie/ferments/019f2766-aa8c-72f1-8b4e-a5260ac64189/runtime.json +15 -0
- package/.ikie/ferments/019f2766-aab7-7310-a9ec-b3027823b960/runtime.json +15 -0
- package/.ikie/ferments/019f2766-aae0-714c-b796-491d0c0d0e5b/runtime.json +15 -0
- package/.ikie/ferments/019f2766-ab05-740a-9fe0-2882dcad789b/runtime.json +15 -0
- package/.ikie/ferments/019f2766-ab2d-7188-9d25-545da08208e3/runtime.json +15 -0
- package/.ikie/ferments/019f2766-ab55-71d1-aafa-b6c62c6d7c7c/runtime.json +15 -0
- package/.ikie/ferments/019f2766-ab7f-76a1-9cde-c84ebb38a76c/runtime.json +15 -0
- package/.ikie/ferments/019f2766-abad-71db-9b6c-c341a91e609c/runtime.json +15 -0
- package/.ikie/ferments/019f2766-abd1-7392-a14e-a62f58887e86/runtime.json +11 -0
- package/.ikie/ferments/019f2766-abee-7229-9251-77f5474df062/runtime.json +11 -0
- package/.ikie/ferments/019f2766-ac13-77ab-ad1f-179bbf19f285/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2766-ac13-77ab-ad1f-179bbf19f285/runtime.json +17 -0
- package/.ikie/ferments/019f2766-ad4e-771a-8809-da61d5e9187d/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2766-ad4e-771a-8809-da61d5e9187d/runtime.json +17 -0
- package/.ikie/ferments/019f2766-aec7-72c7-8400-65d8774d93fe/runtime.json +11 -0
- package/.ikie/ferments/019f2766-b2e5-775e-a211-97476c9184fa/runtime.json +11 -0
- package/.ikie/ferments/019f2766-b303-70c5-a66a-e39ce29d0a4a/runtime.json +11 -0
- package/.ikie/ferments/019f2766-b32e-7618-860c-3cf1a98c2562/runtime.json +11 -0
- package/.ikie/ferments/019f2766-b353-719b-9299-207b9a3b41b3/runtime.json +11 -0
- package/.ikie/ferments/019f2766-b374-72e3-a804-8f3c82dc2059/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2766-b38b-75ca-83c3-d4e5625f814b/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2766-b495-73d6-a0d3-b26c033d4769/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2766-b4e1-70f8-a38e-c478ab72c091/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2766-b4f5-7671-b2c4-76d6f9f68cd3/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2766-d341-7410-8d94-056f70b5d884/runtime.json +11 -0
- package/.ikie/ferments/019f2766-d35f-71a3-bdd3-c749aa870ff8/runtime.json +11 -0
- package/.ikie/ferments/019f2766-d382-70be-9a79-094070335d58/runtime.json +11 -0
- package/.ikie/ferments/019f2766-d3ae-75c7-b83e-4636e859cf7d/runtime.json +12 -0
- package/.ikie/ferments/019f2766-d3c7-778d-b399-f3e13fa34204/runtime.json +11 -0
- package/.ikie/ferments/019f2766-d3f0-7730-9e47-9bbc5483d5c9/runtime.json +11 -0
- package/.ikie/ferments/019f2766-d422-74ab-8e0b-6b5f139658b8/runtime.json +15 -0
- package/.ikie/ferments/019f2766-d448-7428-94ec-084521c6f60a/runtime.json +15 -0
- package/.ikie/ferments/019f2766-d46e-730e-89a6-dd0907baa5aa/runtime.json +15 -0
- package/.ikie/ferments/019f2766-d49b-71df-91f7-b4fb8e050c88/runtime.json +13 -0
- package/.ikie/ferments/019f2766-d4d6-72fd-b5ab-8aa8554c3ac8/runtime.json +15 -0
- package/.ikie/ferments/019f2766-d50b-7146-a0e2-bcc33cbf8168/runtime.json +15 -0
- package/.ikie/ferments/019f2766-d54e-7614-a157-8c6225b09552/runtime.json +11 -0
- package/.ikie/ferments/019f2766-d571-72f9-ac5f-9a58f8913af1/runtime.json +15 -0
- package/.ikie/ferments/019f2766-d5aa-7756-b523-29a10b1f0f93/runtime.json +15 -0
- package/.ikie/ferments/019f2766-d5ce-77b9-9746-ca5281e76d1e/runtime.json +15 -0
- package/.ikie/ferments/019f2766-d5f2-76ee-92f0-88e85515bb8c/runtime.json +15 -0
- package/.ikie/ferments/019f2766-d617-7669-8175-57038e3790b0/runtime.json +15 -0
- package/.ikie/ferments/019f2766-d63b-72aa-a6ca-f355b068db00/runtime.json +15 -0
- package/.ikie/ferments/019f2766-d664-754e-b827-42f9c6fc161b/runtime.json +15 -0
- package/.ikie/ferments/019f2766-d68c-710d-8769-befe67aad042/runtime.json +15 -0
- package/.ikie/ferments/019f2766-d6af-767c-9547-eee7f9c08dcf/runtime.json +15 -0
- package/.ikie/ferments/019f2766-d6d5-751e-8752-dcdc451712e2/runtime.json +11 -0
- package/.ikie/ferments/019f2766-d6f7-77b8-807d-75d914ff0302/runtime.json +11 -0
- package/.ikie/ferments/019f2766-d720-7159-97a9-820f305b3805/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2766-d720-7159-97a9-820f305b3805/runtime.json +17 -0
- package/.ikie/ferments/019f2766-d87f-729d-be05-90c4adb20ade/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2766-d87f-729d-be05-90c4adb20ade/runtime.json +17 -0
- package/.ikie/ferments/019f2766-da17-724b-ab3c-7154c7845381/runtime.json +11 -0
- package/.ikie/ferments/019f2766-ddf5-7470-a884-297c8936753a/runtime.json +11 -0
- package/.ikie/ferments/019f2766-de1a-7283-89b6-a249b9c8bdc1/runtime.json +11 -0
- package/.ikie/ferments/019f2766-de39-7771-a717-14beb4313f79/runtime.json +11 -0
- package/.ikie/ferments/019f2766-de5b-7418-bbcc-2ed38543137c/runtime.json +11 -0
- package/.ikie/ferments/019f2766-de7d-76cc-a74f-def9d1d99ac3/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2766-de92-75a2-bcae-86efd5efe066/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2766-dfb5-771c-adc0-9e948e0b8f18/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2766-dff6-71cd-a14e-ab9e88792ca4/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2766-e006-727b-83d9-af9c71158bef/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2767-0345-74ba-a163-0c909ee37df1/runtime.json +11 -0
- package/.ikie/ferments/019f2767-036c-713d-9ef9-58a1191bd341/runtime.json +11 -0
- package/.ikie/ferments/019f2767-0387-72e6-b8b6-314fedade6e0/runtime.json +11 -0
- package/.ikie/ferments/019f2767-03af-738b-acde-360fa30a55f8/runtime.json +12 -0
- package/.ikie/ferments/019f2767-03cc-76e8-ae01-5b33c85fb46b/runtime.json +11 -0
- package/.ikie/ferments/019f2767-0409-7745-8f9d-a41ce8b7b11e/runtime.json +11 -0
- package/.ikie/ferments/019f2767-0467-70a9-8a75-2688109ab9c3/runtime.json +15 -0
- package/.ikie/ferments/019f2767-0493-7047-b2c9-a7ed6f70c89f/runtime.json +15 -0
- package/.ikie/ferments/019f2767-04b7-7486-a7cf-5ad7a09060a7/runtime.json +15 -0
- package/.ikie/ferments/019f2767-050f-713f-ba94-c01b549b3dfa/runtime.json +13 -0
- package/.ikie/ferments/019f2767-0561-7481-90da-2febe6cffddd/runtime.json +15 -0
- package/.ikie/ferments/019f2767-059c-74bb-b3fb-5e762939b3f6/runtime.json +15 -0
- package/.ikie/ferments/019f2767-05f6-7778-87d7-bd17f16cbfb5/runtime.json +11 -0
- package/.ikie/ferments/019f2767-0621-765e-9af4-4fd53e8fa0ec/runtime.json +15 -0
- package/.ikie/ferments/019f2767-0652-77a4-86f8-fd32c3d03927/runtime.json +15 -0
- package/.ikie/ferments/019f2767-067b-734b-85e5-cd7fc79c73cb/runtime.json +15 -0
- package/.ikie/ferments/019f2767-06aa-71fa-a287-db9463cdae51/runtime.json +15 -0
- package/.ikie/ferments/019f2767-06d5-771d-8f7b-cb18b2f9f122/runtime.json +15 -0
- package/.ikie/ferments/019f2767-070a-7043-87fe-09012eaea4d6/runtime.json +15 -0
- package/.ikie/ferments/019f2767-0733-707e-b141-7af7e0e43e30/runtime.json +15 -0
- package/.ikie/ferments/019f2767-0751-7478-ae88-39af753f8f08/runtime.json +15 -0
- package/.ikie/ferments/019f2767-0791-73af-be60-fe5a809f66bc/runtime.json +15 -0
- package/.ikie/ferments/019f2767-07c5-720a-b9b7-1f96816c4122/runtime.json +11 -0
- package/.ikie/ferments/019f2767-07e4-74fc-bf92-5f4c9c1cefab/runtime.json +11 -0
- package/.ikie/ferments/019f2767-080c-77c8-a174-e8b97bd69510/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2767-080c-77c8-a174-e8b97bd69510/runtime.json +17 -0
- package/.ikie/ferments/019f2767-0986-7509-b1d0-7650aae65deb/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2767-0986-7509-b1d0-7650aae65deb/runtime.json +17 -0
- package/.ikie/ferments/019f2767-0b6e-7011-aec4-f728f98727ed/runtime.json +11 -0
- package/.ikie/ferments/019f2767-10a6-75cc-850a-34d704f7eac9/runtime.json +11 -0
- package/.ikie/ferments/019f2767-10cf-70aa-a00c-086ec4813201/runtime.json +11 -0
- package/.ikie/ferments/019f2767-1101-74f7-8518-da795dd7aef7/runtime.json +11 -0
- package/.ikie/ferments/019f2767-1140-738e-b9ce-35b94e415549/runtime.json +11 -0
- package/.ikie/ferments/019f2767-1172-75a9-8f24-74316bfc7d53/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2767-118b-7048-a5d7-cffbef0e44e2/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2767-12be-730b-92b2-0bba958b78a1/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2767-131f-720b-b45a-382835a523ef/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2767-1335-74eb-b84c-9af1e9e52ff0/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2767-4563-715a-8c3c-cadf61ab94cf/runtime.json +13 -0
- package/.ikie/ferments/019f2767-45b2-701b-990c-a8a30ff2c5f2/runtime.json +11 -0
- package/.ikie/ferments/019f2767-45d7-705e-a8fc-202c2c894d5f/runtime.json +11 -0
- package/.ikie/ferments/019f2767-45ee-773f-a4e7-6022c91b113c/runtime.json +11 -0
- package/.ikie/ferments/019f2767-4613-7769-b4fa-6abde054e49d/runtime.json +11 -0
- package/.ikie/ferments/019f2767-4626-704a-9d3e-6d51ff2c45c6/runtime.json +11 -0
- package/.ikie/ferments/019f2767-4637-7346-b00e-35216ec55688/runtime.json +11 -0
- package/.ikie/ferments/019f2767-4649-709c-9269-7d92ac49807a/runtime.json +11 -0
- package/.ikie/ferments/019f2767-465e-76ec-a7c0-c63d79f0807f/runtime.json +9 -0
- package/.ikie/ferments/019f2767-4679-76c1-8e62-1c105c4c6b0b/runtime.json +13 -0
- package/.ikie/ferments/019f2767-4692-762a-963b-a53456d259fc/runtime.json +11 -0
- package/.ikie/ferments/019f2767-46a7-7236-9510-fd0d5ecf273f/runtime.json +11 -0
- package/.ikie/ferments/019f2767-46ee-705c-ad1a-e028ca79062a/runtime.json +11 -0
- package/.ikie/ferments/019f2767-4702-741b-bb09-b4fd8cd9f15a/runtime.json +9 -0
- package/.ikie/ferments/019f2767-4720-7192-9e90-89cc2710255a/runtime.json +9 -0
- package/.ikie/ferments/019f2767-475f-70dc-9883-271aaa69c2b4/runtime.json +11 -0
- package/.ikie/ferments/019f2767-4852-749f-9c8b-920b2c2b25d3/runtime.json +11 -0
- package/.ikie/ferments/019f2767-486c-7309-a9ff-92579b04dc8c/runtime.json +12 -0
- package/.ikie/ferments/019f2767-4899-771f-9f50-89e448dff488/runtime.json +11 -0
- package/.ikie/ferments/019f2767-48ae-7328-a163-a959e2087743/runtime.json +11 -0
- package/.ikie/ferments/019f2767-48ce-717a-828c-6b765da0a306/runtime.json +11 -0
- package/.ikie/ferments/019f2767-48ef-707c-a17e-9db9da0eee1f/runtime.json +11 -0
- package/.ikie/ferments/019f2767-490b-704b-9369-05fcbb050e2b/runtime.json +13 -0
- package/.ikie/ferments/019f2767-4938-752e-a022-f76dadf8fcaf/runtime.json +11 -0
- package/.ikie/ferments/019f2767-5cc6-73b9-b8e4-cb3719bbdc8d/reviews/phase-phase-1-1.json +41 -0
- package/.ikie/ferments/019f2767-5cc6-73b9-b8e4-cb3719bbdc8d/runtime.json +11 -0
- package/.ikie/ferments/019f2767-5d22-7419-840b-e18d2fe5a41d/reviews/phase-phase-1-1.json +47 -0
- package/.ikie/ferments/019f2767-5d22-7419-840b-e18d2fe5a41d/runtime.json +13 -0
- package/.ikie/ferments/019f2767-5d8c-75b0-bf28-5a0781cd0686/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2767-5d8c-75b0-bf28-5a0781cd0686/runtime.json +9 -0
- package/.ikie/ferments/019f2767-5dae-76e9-b23a-366527e3a529/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2767-5dae-76e9-b23a-366527e3a529/runtime.json +9 -0
- package/.ikie/ferments/019f2767-5dd4-73bf-821e-d74e53b266e1/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2767-5dd4-73bf-821e-d74e53b266e1/runtime.json +9 -0
- package/.ikie/ferments/019f2767-5de9-7518-a677-02b4926a0196/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2767-5de9-7518-a677-02b4926a0196/runtime.json +9 -0
- package/.ikie/ferments/019f2767-5dff-772a-b855-57afbb5e9c7e/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f2767-5dff-772a-b855-57afbb5e9c7e/runtime.json +9 -0
- package/.ikie/ferments/019f2767-5e39-76a5-8dcc-6f9004f4a58e/reviews/phase-phase-1-1.json +62 -0
- package/.ikie/ferments/019f2767-5e39-76a5-8dcc-6f9004f4a58e/runtime.json +9 -0
- package/.ikie/ferments/019f2769-b766-74eb-844e-9903bf64f06d/runtime.json +11 -0
- package/.ikie/ferments/019f2769-b784-72fa-a138-7bdc2fc6e070/runtime.json +11 -0
- package/.ikie/ferments/019f2769-b79d-745b-b577-2ee15d5649b0/runtime.json +11 -0
- package/.ikie/ferments/019f2769-b7c3-75be-9db0-360b0deea44b/runtime.json +12 -0
- package/.ikie/ferments/019f2769-b7e0-758f-bd6a-734520ad3afa/runtime.json +11 -0
- package/.ikie/ferments/019f2769-b807-721e-8827-30e490c8823c/runtime.json +11 -0
- package/.ikie/ferments/019f2769-b836-727d-9c7f-b5a5f03dcc56/runtime.json +15 -0
- package/.ikie/ferments/019f2769-b858-76ae-9fe0-b56d97afdf9b/runtime.json +15 -0
- package/.ikie/ferments/019f2769-b87a-716c-9c81-14f1fa52f217/runtime.json +15 -0
- package/.ikie/ferments/019f2769-b8a2-73dc-9d6d-ac781b700816/runtime.json +13 -0
- package/.ikie/ferments/019f2769-b8d1-7689-9405-e97b063ec3a1/runtime.json +15 -0
- package/.ikie/ferments/019f2769-b904-7154-8946-12e041f89921/runtime.json +15 -0
- package/.ikie/ferments/019f2769-b941-713f-8e2b-1a608dec7ca6/runtime.json +11 -0
- package/.ikie/ferments/019f2769-b95b-7122-bb5d-0bb3a2ad220a/runtime.json +15 -0
- package/.ikie/ferments/019f2769-b97d-728d-b8fb-b1f302b1d819/runtime.json +15 -0
- package/.ikie/ferments/019f2769-b99d-747b-853b-e415c006bfd2/runtime.json +15 -0
- package/.ikie/ferments/019f2769-b9c0-76d6-a254-fba31ac6b662/runtime.json +15 -0
- package/.ikie/ferments/019f2769-b9dd-7349-b246-95853d5d3af0/runtime.json +15 -0
- package/.ikie/ferments/019f2769-b9fe-774f-80cd-06bfedd8a956/runtime.json +15 -0
- package/.ikie/ferments/019f2769-ba1e-70d2-af18-3ea245abc903/runtime.json +15 -0
- package/.ikie/ferments/019f2769-ba45-7730-aae9-9ca14bc6a81b/runtime.json +15 -0
- package/.ikie/ferments/019f2769-ba72-7273-903c-d33bcd036a6b/runtime.json +15 -0
- package/.ikie/ferments/019f2769-ba93-70f3-8884-7b067dd711a4/runtime.json +11 -0
- package/.ikie/ferments/019f2769-baad-714f-8207-a7d630f37e84/runtime.json +11 -0
- package/.ikie/ferments/019f2769-bac7-75ae-95cb-dda85f7fd6a8/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2769-bac7-75ae-95cb-dda85f7fd6a8/runtime.json +17 -0
- package/.ikie/ferments/019f2769-bbf3-745d-87d3-d594593afbf0/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2769-bbf3-745d-87d3-d594593afbf0/runtime.json +17 -0
- package/.ikie/ferments/019f2769-bd4e-735d-b931-2077f62d385a/runtime.json +11 -0
- package/.ikie/ferments/019f2769-c103-723f-aebe-ab3ae76aedd9/runtime.json +11 -0
- package/.ikie/ferments/019f2769-c11e-771a-b365-96bf54691b95/runtime.json +11 -0
- package/.ikie/ferments/019f2769-c13b-749e-840d-5136528371f3/runtime.json +11 -0
- package/.ikie/ferments/019f2769-c15a-7065-83b5-2064b699a6d9/runtime.json +11 -0
- package/.ikie/ferments/019f2769-c175-77b2-80df-918134508175/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2769-c189-7508-8a46-3ff461bf759f/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2769-c27c-71eb-910f-36daf69588f8/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2769-c2c5-71bd-b6a6-dd0f8365cc7c/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2769-c2d6-7260-bab6-3e46b6e9100c/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2769-e6d9-700b-a486-64cad0909610/runtime.json +11 -0
- package/.ikie/ferments/019f2769-e6f5-7524-955a-dd4d91813ee5/runtime.json +11 -0
- package/.ikie/ferments/019f2769-e713-715e-9450-42b5250709c8/runtime.json +11 -0
- package/.ikie/ferments/019f2769-e738-7537-b5d5-5209200233da/runtime.json +12 -0
- package/.ikie/ferments/019f2769-e769-752f-b806-14f369200356/runtime.json +11 -0
- package/.ikie/ferments/019f2769-e795-77e8-bc69-fa7496923f00/runtime.json +15 -0
- package/.ikie/ferments/019f2769-e7bb-72a8-ba2a-6d90fe6003cc/runtime.json +15 -0
- package/.ikie/ferments/019f2769-e7e1-719b-becd-d456fe580bf8/runtime.json +15 -0
- package/.ikie/ferments/019f2769-e80b-7539-ac23-4ccf01499e01/runtime.json +13 -0
- package/.ikie/ferments/019f2769-e83e-77c7-8415-7b3e76c827b1/runtime.json +15 -0
- package/.ikie/ferments/019f2769-e86c-7008-8cdb-4d2ae411fc57/runtime.json +15 -0
- package/.ikie/ferments/019f2769-e8ac-7480-b1cc-6913e20e012f/runtime.json +11 -0
- package/.ikie/ferments/019f2769-e8ca-76bd-b0be-bee2f2a97d7d/runtime.json +15 -0
- package/.ikie/ferments/019f2769-e904-7759-b2aa-dbb47427c11b/runtime.json +15 -0
- package/.ikie/ferments/019f2769-e92a-7285-bbd7-ed4220ac3f44/runtime.json +15 -0
- package/.ikie/ferments/019f2769-e95e-77eb-9e17-6b2ad4532f0c/runtime.json +15 -0
- package/.ikie/ferments/019f2769-e992-75ea-adf9-53f548b600a0/runtime.json +15 -0
- package/.ikie/ferments/019f2769-e9b9-75bc-ab7a-a4ba5e8f1124/runtime.json +15 -0
- package/.ikie/ferments/019f2769-e9ec-700b-990f-320c347ee271/runtime.json +15 -0
- package/.ikie/ferments/019f2769-ea15-72cb-96af-a45603082ee1/runtime.json +15 -0
- package/.ikie/ferments/019f2769-ea42-735c-b71e-259c75688841/runtime.json +15 -0
- package/.ikie/ferments/019f2769-ea6f-7074-acde-65385abd199a/runtime.json +11 -0
- package/.ikie/ferments/019f2769-ea98-759c-aeda-90b28cab640c/runtime.json +11 -0
- package/.ikie/ferments/019f2769-eac3-70a7-993e-435d9a1dfd91/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2769-eac3-70a7-993e-435d9a1dfd91/runtime.json +17 -0
- package/.ikie/ferments/019f2769-ec3c-7674-829a-fba66a9bc62e/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f2769-ec3c-7674-829a-fba66a9bc62e/runtime.json +17 -0
- package/.ikie/ferments/019f2769-edcb-748c-be28-51e21c608c6f/runtime.json +11 -0
- package/.ikie/ferments/019f2769-f2df-7759-811f-ef76a54375e8/runtime.json +11 -0
- package/.ikie/ferments/019f2769-f311-7641-88b0-81c54945642f/runtime.json +11 -0
- package/.ikie/ferments/019f2769-f363-723b-80f1-b52334dc6a15/runtime.json +11 -0
- package/.ikie/ferments/019f2769-f38e-72cc-a4ac-81cefc3a95f4/runtime.json +11 -0
- package/.ikie/ferments/019f2769-f3b6-763a-bf65-011ce164f5d4/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2769-f3c7-745e-9da1-37e14b1cfddf/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2769-f4e4-71b1-83f1-b16014904ec3/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2769-f556-761d-b331-acc7cc02317d/pending-proposal.json +45 -0
- package/.ikie/ferments/019f2769-f56d-77af-82f3-b9915ca80a61/pending-proposal.json +45 -0
- package/.ikie/ferments/019f276a-23f9-7653-8be0-dca748ac5e0f/runtime.json +13 -0
- package/.ikie/ferments/019f276a-2428-731f-959b-8d346551f0c4/runtime.json +11 -0
- package/.ikie/ferments/019f276a-2446-7250-be5c-bdb0ac1ef2fa/runtime.json +11 -0
- package/.ikie/ferments/019f276a-2462-764e-a7f4-d9ab3876fbd2/runtime.json +11 -0
- package/.ikie/ferments/019f276a-2482-768b-919e-d81a0709abd5/runtime.json +11 -0
- package/.ikie/ferments/019f276a-2494-77ee-8b28-72b9b0d19d56/runtime.json +11 -0
- package/.ikie/ferments/019f276a-24aa-76fd-8fba-2a1d73960010/runtime.json +11 -0
- package/.ikie/ferments/019f276a-24c3-7799-bd35-0a3374f3e469/runtime.json +11 -0
- package/.ikie/ferments/019f276a-24f1-7498-a372-975f72302a0d/runtime.json +9 -0
- package/.ikie/ferments/019f276a-251e-71ac-ba90-af2a49c22b6b/runtime.json +13 -0
- package/.ikie/ferments/019f276a-2545-746f-bdf3-ca3c11ab9968/runtime.json +11 -0
- package/.ikie/ferments/019f276a-256d-765b-9f94-90047d4ca21e/runtime.json +11 -0
- package/.ikie/ferments/019f276a-25cb-753b-9905-7250e4eea970/runtime.json +11 -0
- package/.ikie/ferments/019f276a-25f8-73a2-aede-f2c017fa80c9/runtime.json +9 -0
- package/.ikie/ferments/019f276a-2613-774b-80c0-3b206ac95435/runtime.json +9 -0
- package/.ikie/ferments/019f276a-264e-73a3-90ad-017ef8ea05b6/runtime.json +11 -0
- package/.ikie/ferments/019f276a-2760-758f-b0a6-cd04a8b0ee76/runtime.json +11 -0
- package/.ikie/ferments/019f276a-277b-73a9-8655-6bf7018262dc/runtime.json +12 -0
- package/.ikie/ferments/019f276a-27a6-709d-a90f-f1784e248fca/runtime.json +11 -0
- package/.ikie/ferments/019f276a-27b7-75bf-b0b5-013581917821/runtime.json +11 -0
- package/.ikie/ferments/019f276a-27ce-759a-93de-474988efc050/runtime.json +11 -0
- package/.ikie/ferments/019f276a-27e9-7564-999a-73beef96d35c/runtime.json +11 -0
- package/.ikie/ferments/019f276a-27fc-714b-9ddb-117b439b664e/runtime.json +13 -0
- package/.ikie/ferments/019f276a-2824-762e-8def-3c17aa4041f9/runtime.json +11 -0
- package/.ikie/ferments/019f276a-38fe-725b-9c16-f1b5174302d0/reviews/phase-phase-1-1.json +41 -0
- package/.ikie/ferments/019f276a-38fe-725b-9c16-f1b5174302d0/runtime.json +11 -0
- package/.ikie/ferments/019f276a-393c-75e2-b29f-092b82105a74/reviews/phase-phase-1-1.json +47 -0
- package/.ikie/ferments/019f276a-393c-75e2-b29f-092b82105a74/runtime.json +13 -0
- package/.ikie/ferments/019f276a-3990-75f9-9b6f-ab6d45bf8e32/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f276a-3990-75f9-9b6f-ab6d45bf8e32/runtime.json +9 -0
- package/.ikie/ferments/019f276a-39ab-76bb-be69-8658a1b6a76f/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f276a-39ab-76bb-be69-8658a1b6a76f/runtime.json +9 -0
- package/.ikie/ferments/019f276a-39c9-76ac-95a8-2d5575d26573/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f276a-39c9-76ac-95a8-2d5575d26573/runtime.json +9 -0
- package/.ikie/ferments/019f276a-39f6-76cc-9011-3f6b08068155/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f276a-39f6-76cc-9011-3f6b08068155/runtime.json +9 -0
- package/.ikie/ferments/019f276a-3a0c-76a9-80b2-2bb7952fe9d7/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f276a-3a0c-76a9-80b2-2bb7952fe9d7/runtime.json +9 -0
- package/.ikie/ferments/019f276a-3a22-7140-8a4d-0d17ada653b3/reviews/phase-phase-1-1.json +62 -0
- package/.ikie/ferments/019f276a-3a22-7140-8a4d-0d17ada653b3/runtime.json +9 -0
- package/.ikie/ferments/019f276c-2f88-74ae-8bc2-5cec42d56757/runtime.json +11 -0
- package/.ikie/ferments/019f276c-2fb8-7599-b61c-e73c85399ee0/runtime.json +11 -0
- package/.ikie/ferments/019f276c-2fd9-75db-8e13-8e9137cecedd/runtime.json +11 -0
- package/.ikie/ferments/019f276c-302f-760f-97e7-2f10f283dfe1/runtime.json +12 -0
- package/.ikie/ferments/019f276c-3060-77ca-a0e3-d49781feca6d/runtime.json +11 -0
- package/.ikie/ferments/019f276c-3091-744e-9394-a59b9f37db9e/runtime.json +11 -0
- package/.ikie/ferments/019f276c-30da-76ba-bd76-a72e053f218d/runtime.json +15 -0
- package/.ikie/ferments/019f276c-3110-7277-ab9a-820992db76da/runtime.json +15 -0
- package/.ikie/ferments/019f276c-314a-740f-b533-a31c4e9ebd52/runtime.json +15 -0
- package/.ikie/ferments/019f276c-3189-732c-a4ec-09e4f1e3a8da/runtime.json +13 -0
- package/.ikie/ferments/019f276c-31ce-75c8-a4e9-507d408920b6/runtime.json +15 -0
- package/.ikie/ferments/019f276c-320e-710b-b32a-4577244dcc6a/runtime.json +15 -0
- package/.ikie/ferments/019f276c-3262-7611-af21-ad12693acdd5/runtime.json +11 -0
- package/.ikie/ferments/019f276c-3288-7559-b7c3-299dd548decb/runtime.json +15 -0
- package/.ikie/ferments/019f276c-32c4-70a0-899d-ad17469a015c/runtime.json +15 -0
- package/.ikie/ferments/019f276c-32f0-7036-86ae-19c9f703cd9a/runtime.json +15 -0
- package/.ikie/ferments/019f276c-3324-7021-8f89-d46fbfa45e00/runtime.json +15 -0
- package/.ikie/ferments/019f276c-334b-71df-b20d-d9a7bcccdc0a/runtime.json +15 -0
- package/.ikie/ferments/019f276c-3379-7593-957a-ee9631368280/runtime.json +15 -0
- package/.ikie/ferments/019f276c-33a9-705d-a590-eff1c46bcd67/runtime.json +15 -0
- package/.ikie/ferments/019f276c-33d5-7784-b35a-01d3c06b16c0/runtime.json +15 -0
- package/.ikie/ferments/019f276c-341c-76ce-b950-7a7e7472eeb8/runtime.json +15 -0
- package/.ikie/ferments/019f276c-3462-744d-b42a-9756a33b7223/runtime.json +11 -0
- package/.ikie/ferments/019f276c-348e-706a-9791-5213dfd00593/runtime.json +11 -0
- package/.ikie/ferments/019f276c-34b6-70f9-b678-d3a7d876758c/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f276c-34b6-70f9-b678-d3a7d876758c/runtime.json +17 -0
- package/.ikie/ferments/019f276c-3640-75da-915e-b0dba80bfcda/reviews/phase-phase-1-1.json +63 -0
- package/.ikie/ferments/019f276c-3640-75da-915e-b0dba80bfcda/runtime.json +17 -0
- package/.ikie/ferments/019f276c-37df-76f9-b7a8-1285ee0ac11d/runtime.json +11 -0
- package/.ikie/ferments/019f276c-3caa-748a-a0aa-9e068f18f2ac/runtime.json +11 -0
- package/.ikie/ferments/019f276c-3cc8-7347-9764-dd4ab7f87646/runtime.json +11 -0
- package/.ikie/ferments/019f276c-3cef-75dd-be90-593fd20a850d/runtime.json +11 -0
- package/.ikie/ferments/019f276c-3d14-72af-ae89-6c0605227a05/runtime.json +11 -0
- package/.ikie/ferments/019f276c-3d36-716c-ac4e-59444fdec814/pending-proposal.json +45 -0
- package/.ikie/ferments/019f276c-3d4c-7208-9130-7f168aeba08e/pending-proposal.json +45 -0
- package/.ikie/ferments/019f276c-3e7f-7552-a37f-61442d720fb9/pending-proposal.json +45 -0
- package/.ikie/ferments/019f276c-3ed9-74f2-9332-d5a492d39130/pending-proposal.json +45 -0
- package/.ikie/ferments/019f276c-3ef4-757e-b0c6-9d7c6f0326e7/pending-proposal.json +45 -0
- package/.ikie/ferments/019f276c-651d-70ac-bfb4-5d033388e28c/runtime.json +13 -0
- package/.ikie/ferments/019f276c-6546-7569-b609-74b2258985a8/runtime.json +11 -0
- package/.ikie/ferments/019f276c-6566-71e2-b937-390aea78900a/runtime.json +11 -0
- package/.ikie/ferments/019f276c-657c-75ee-864b-5b5601e3b9d1/runtime.json +11 -0
- package/.ikie/ferments/019f276c-6590-7729-a6e7-466ace311e0d/runtime.json +11 -0
- package/.ikie/ferments/019f276c-65a5-779f-a3da-36bc91cf31f3/runtime.json +11 -0
- package/.ikie/ferments/019f276c-65b9-77f0-97fc-fcf70d7125b8/runtime.json +11 -0
- package/.ikie/ferments/019f276c-65cd-74ef-9094-db3c5a98348b/runtime.json +11 -0
- package/.ikie/ferments/019f276c-65e3-7632-9df9-58cfa083a065/runtime.json +9 -0
- package/.ikie/ferments/019f276c-65fb-763a-aec7-54ece64d7342/runtime.json +13 -0
- package/.ikie/ferments/019f276c-661a-74e9-81b1-d1ba625c04b0/runtime.json +11 -0
- package/.ikie/ferments/019f276c-6630-7748-ab90-869f75ed2de5/runtime.json +11 -0
- package/.ikie/ferments/019f276c-6670-758f-b0da-673d81bd319e/runtime.json +11 -0
- package/.ikie/ferments/019f276c-6687-71fa-8286-2d25de36c9b4/runtime.json +9 -0
- package/.ikie/ferments/019f276c-669d-7679-91d5-4df6315d3823/runtime.json +9 -0
- package/.ikie/ferments/019f276c-66b3-72a6-974d-1bc3477eb6f2/runtime.json +11 -0
- package/.ikie/ferments/019f276c-6769-776c-9868-0c929ee31f5f/runtime.json +11 -0
- package/.ikie/ferments/019f276c-678e-7588-a059-c1efd9e9c9a6/runtime.json +12 -0
- package/.ikie/ferments/019f276c-67af-729c-a5f8-79e8e78bbe9b/runtime.json +11 -0
- package/.ikie/ferments/019f276c-67c3-70b8-8606-aad72ab0cd6f/runtime.json +11 -0
- package/.ikie/ferments/019f276c-67d7-734c-83fd-132c54564004/runtime.json +11 -0
- package/.ikie/ferments/019f276c-67ec-710d-8497-eba65301d741/runtime.json +11 -0
- package/.ikie/ferments/019f276c-6805-742c-8a1c-f370733226ae/runtime.json +13 -0
- package/.ikie/ferments/019f276c-6825-7768-8ca1-f5dac5c1d374/runtime.json +11 -0
- package/.ikie/ferments/019f276c-7f49-751d-bbb8-f828debd3cb9/reviews/phase-phase-1-1.json +41 -0
- package/.ikie/ferments/019f276c-7f49-751d-bbb8-f828debd3cb9/runtime.json +11 -0
- package/.ikie/ferments/019f276c-7f7b-74d0-b46e-e8efa73fb5b5/reviews/phase-phase-1-1.json +47 -0
- package/.ikie/ferments/019f276c-7f7b-74d0-b46e-e8efa73fb5b5/runtime.json +13 -0
- package/.ikie/ferments/019f276c-8004-74fe-b37f-f9fc659f7f0c/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f276c-8004-74fe-b37f-f9fc659f7f0c/runtime.json +9 -0
- package/.ikie/ferments/019f276c-8025-7328-afef-6703215c208c/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f276c-8025-7328-afef-6703215c208c/runtime.json +9 -0
- package/.ikie/ferments/019f276c-8043-749c-8cbb-47d0df9819df/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f276c-8043-749c-8cbb-47d0df9819df/runtime.json +9 -0
- package/.ikie/ferments/019f276c-8062-7436-99eb-80e09bc6b067/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f276c-8062-7436-99eb-80e09bc6b067/runtime.json +9 -0
- package/.ikie/ferments/019f276c-8081-727c-aa11-d606a64b88ed/reviews/phase-phase-1-1.json +40 -0
- package/.ikie/ferments/019f276c-8081-727c-aa11-d606a64b88ed/runtime.json +9 -0
- package/.ikie/ferments/019f276c-80b6-7528-8687-e11cc4691930/reviews/phase-phase-1-1.json +62 -0
- package/.ikie/ferments/019f276c-80b6-7528-8687-e11cc4691930/runtime.json +9 -0
- package/AGENTS.md +120 -0
- package/CLA.md +39 -0
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +57 -0
- package/LICENSE +203 -0
- package/NOTICE +28 -0
- package/README.md +568 -0
- package/SECURITY.md +36 -0
- package/TODO.md +33 -0
- package/benchmark/audit-session/README.md +217 -0
- package/benchmark/audit-session/audit-session-prompt.md +557 -0
- package/benchmark/audit-session/audit-session.sh +323 -0
- package/benchmark/manual/README.md +166 -0
- package/benchmark/manual/__pycache__/check-session.cpython-313.pyc +0 -0
- package/benchmark/manual/analyze-session.py +290 -0
- package/benchmark/manual/benchmark.json +4 -0
- package/benchmark/manual/check-session.py +268 -0
- package/benchmark/manual/compare-sessions.py +118 -0
- package/benchmark/manual/lib.sh +284 -0
- package/benchmark/manual/new-session-claude.sh +86 -0
- package/benchmark/manual/new-session.sh +191 -0
- package/benchmark/manual/run-evaluation.sh +201 -0
- package/benchmark/manual/seeds/explore-refactor/cmd/server/main.go +37 -0
- package/benchmark/manual/seeds/explore-refactor/go.mod +3 -0
- package/benchmark/manual/seeds/explore-refactor/internal/handler/audit.go +39 -0
- package/benchmark/manual/seeds/explore-refactor/internal/handler/invitation.go +56 -0
- package/benchmark/manual/seeds/explore-refactor/internal/handler/team.go +86 -0
- package/benchmark/manual/seeds/explore-refactor/internal/handler/user.go +126 -0
- package/benchmark/manual/seeds/explore-refactor/internal/model/model.go +37 -0
- package/benchmark/manual/seeds/explore-refactor/internal/repository/audit.go +57 -0
- package/benchmark/manual/seeds/explore-refactor/internal/repository/invitation.go +58 -0
- package/benchmark/manual/seeds/explore-refactor/internal/repository/team.go +66 -0
- package/benchmark/manual/seeds/explore-refactor/internal/repository/user.go +91 -0
- package/benchmark/manual/seeds/explore-refactor/internal/service/invitation.go +45 -0
- package/benchmark/manual/seeds/explore-refactor/internal/service/team.go +62 -0
- package/benchmark/manual/seeds/explore-refactor/internal/service/user.go +94 -0
- package/benchmark/manual/self-improvement.md +203 -0
- package/benchmark/manual/session-summary.py +95 -0
- package/benchmark/manual/start-self-improvement.sh +27 -0
- package/benchmark/manual/tasks.md +154 -0
- package/benchmark/terminal-bench-2/README.md +261 -0
- package/benchmark/terminal-bench-2/config/retry.yaml +48 -0
- package/benchmark/terminal-bench-2/docs/ferment-terminal-bench-analysis.md +233 -0
- package/benchmark/terminal-bench-2/pyproject.toml +32 -0
- package/benchmark/terminal-bench-2/scripts/analyze-ferment-bench.py +1193 -0
- package/benchmark/terminal-bench-2/scripts/run-claude-code-ikie.sh +41 -0
- package/benchmark/terminal-bench-2/scripts/run-gsd-ikie.sh +29 -0
- package/benchmark/terminal-bench-2/scripts/run-local.sh +32 -0
- package/benchmark/terminal-bench-2/scripts/run-opencode-ikie.sh +30 -0
- package/benchmark/terminal-bench-2/scripts/run-release.sh +28 -0
- package/benchmark/terminal-bench-2/src/ikie_agent/__init__.py +6 -0
- package/benchmark/terminal-bench-2/src/ikie_agent/agent.py +445 -0
- package/benchmark/terminal-bench-2/src/ikie_agent/agent_test.py +233 -0
- package/benchmark/terminal-bench-2/src/ikie_agent/claude_code_ikie.py +356 -0
- package/benchmark/terminal-bench-2/src/ikie_agent/claude_code_ikie_test.py +506 -0
- package/benchmark/terminal-bench-2/src/ikie_agent/config.py +56 -0
- package/benchmark/terminal-bench-2/src/ikie_agent/gateway.py +163 -0
- package/benchmark/terminal-bench-2/src/ikie_agent/gsd_ikie.py +340 -0
- package/benchmark/terminal-bench-2/src/ikie_agent/gsd_ikie_test.py +304 -0
- package/benchmark/terminal-bench-2/src/ikie_agent/messages.py +27 -0
- package/benchmark/terminal-bench-2/src/ikie_agent/opencode_ikie.py +146 -0
- package/benchmark/terminal-bench-2/src/ikie_agent/opencode_ikie_test.py +308 -0
- package/benchmark/terminal-bench-2/src/ikie_agent/release.py +174 -0
- package/benchmark/terminal-bench-2/src/ikie_agent/retry_config_test.py +55 -0
- package/benchmark/terminal-bench-2/uv.lock +1948 -0
- package/biome.json +54 -0
- package/docs/agents.md +295 -0
- package/docs/bash-tool-guard.md +206 -0
- package/docs/expected-execution-scenarios.md +348 -0
- package/docs/ferment-storage-schema.md +199 -0
- package/docs/ferment.md +633 -0
- package/docs/hooks.md +263 -0
- package/docs/subagents/README.md +22 -0
- package/docs/subagents/code-reviewer.md +39 -0
- package/docs/subagents/research-assistant.md +81 -0
- package/docs/subagents/test-writer.md +49 -0
- package/docs/teleport.md +424 -0
- package/docs/windows/README.md +226 -0
- package/extensions/active-tools.ts +74 -0
- package/ikie.png +0 -0
- package/package.json +125 -0
- package/patches/@earendil-works__pi-ai@0.78.1.patch +68 -0
- package/patches/@earendil-works__pi-coding-agent@0.78.1.patch +535 -0
- package/patches/@earendil-works__pi-tui.patch +171 -0
- package/patches/playwright-core@1.59.1.patch +24 -0
- package/resources/oauth/error.html +185 -0
- package/resources/oauth/success.html +185 -0
- package/scripts/build-binary.js +113 -0
- package/scripts/build-proxy-helper.js +66 -0
- package/scripts/copy-resources.js +104 -0
- package/scripts/dev-overlay.sh +52 -0
- package/scripts/dev-startup.sh +82 -0
- package/scripts/install-local.js +34 -0
- package/scripts/install.ps1 +258 -0
- package/scripts/install.sh +160 -0
- package/scripts/package-release-asset.js +82 -0
- package/scripts/patch-pi-ai-oauth.js +109 -0
- package/scripts/run-tui-e2e.js +153 -0
- package/scripts/set-version.js +31 -0
- package/scripts/verify-acp-load.mjs +358 -0
- package/scripts/verify-acp.mjs +217 -0
- package/src/__mocks__/earendil-clipboard-image.js +6 -0
- package/src/agent-discovery/agents/claude-code.test.ts +156 -0
- package/src/agent-discovery/agents/claude-code.ts +85 -0
- package/src/agent-discovery/agents/cursor.test.ts +178 -0
- package/src/agent-discovery/agents/cursor.ts +74 -0
- package/src/agent-discovery/agents/opencode.test.ts +461 -0
- package/src/agent-discovery/agents/opencode.ts +156 -0
- package/src/agent-discovery/agents/skills-only.test.ts +77 -0
- package/src/agent-discovery/agents/skills-only.ts +47 -0
- package/src/agent-discovery/engine.test.ts +408 -0
- package/src/agent-discovery/engine.ts +117 -0
- package/src/agent-discovery/index.ts +75 -0
- package/src/agent-discovery/jsonc.ts +52 -0
- package/src/ansi.ts +51 -0
- package/src/api/me.test.ts +86 -0
- package/src/api/me.ts +59 -0
- package/src/auth/validator.test.ts +106 -0
- package/src/auth/validator.ts +103 -0
- package/src/auxiliary-files/resolver.test.ts +85 -0
- package/src/auxiliary-files/resolver.ts +27 -0
- package/src/auxiliary-files/validator.test.ts +68 -0
- package/src/auxiliary-files/validator.ts +31 -0
- package/src/cli-args.test.ts +232 -0
- package/src/cli-args.ts +94 -0
- package/src/cli-auth/callback-server.test.ts +169 -0
- package/src/cli-auth/callback-server.ts +165 -0
- package/src/cli-auth/index.test.ts +103 -0
- package/src/cli-auth/index.ts +117 -0
- package/src/cli-auth/oauth-page.ts +94 -0
- package/src/cli.ts +564 -0
- package/src/commands/_helpers.ts +87 -0
- package/src/commands/banner.test.ts +39 -0
- package/src/commands/banner.ts +74 -0
- package/src/commands/claude.ts +28 -0
- package/src/commands/config.test.ts +149 -0
- package/src/commands/config.ts +98 -0
- package/src/commands/cursor.ts +23 -0
- package/src/commands/dispatch.test.ts +102 -0
- package/src/commands/dispatch.ts +40 -0
- package/src/commands/gsd2.ts +23 -0
- package/src/commands/help.ts +104 -0
- package/src/commands/login.ts +46 -0
- package/src/commands/openclaw.ts +23 -0
- package/src/commands/opencode.ts +31 -0
- package/src/commands/registry.ts +44 -0
- package/src/commands/resources.ts +77 -0
- package/src/commands/setup-tools.test.ts +231 -0
- package/src/commands/setup-tools.ts +164 -0
- package/src/commands/setup.ts +28 -0
- package/src/commands/update.test.ts +312 -0
- package/src/commands/update.ts +291 -0
- package/src/commands/version.ts +9 -0
- package/src/components/editor.test.ts +182 -0
- package/src/components/editor.ts +186 -0
- package/src/components/footer.test.ts +790 -0
- package/src/components/footer.ts +640 -0
- package/src/components/logo-art.test.ts +30 -0
- package/src/components/logo-art.ts +66 -0
- package/src/components/logo.test.ts +316 -0
- package/src/components/logo.ts +127 -0
- package/src/components/tool-block.test.ts +75 -0
- package/src/components/tool-block.ts +166 -0
- package/src/config/footer-config.test.ts +191 -0
- package/src/config/footer-config.ts +130 -0
- package/src/config/json.test.ts +78 -0
- package/src/config/json.ts +112 -0
- package/src/config/scope.test.ts +49 -0
- package/src/config/scope.ts +38 -0
- package/src/config/shell-profile.test.ts +130 -0
- package/src/config/shell-profile.ts +131 -0
- package/src/config.test.ts +723 -0
- package/src/config.ts +697 -0
- package/src/entry.ts +62 -0
- package/src/env.ts +4 -0
- package/src/expand-state.ts +41 -0
- package/src/extensions/activity.test.ts +160 -0
- package/src/extensions/activity.ts +192 -0
- package/src/extensions/agent-worker-context.test.ts +31 -0
- package/src/extensions/agent-worker-context.ts +11 -0
- package/src/extensions/agents/default-agents.test.ts +24 -0
- package/src/extensions/agents/discovery-priority.test.ts +65 -0
- package/src/extensions/agents/env.ts +33 -0
- package/src/extensions/agents/index.test.ts +32 -0
- package/src/extensions/agents/index.ts +2226 -0
- package/src/extensions/agents/manager/agent-manager.test.ts +896 -0
- package/src/extensions/agents/manager/agent-manager.ts +714 -0
- package/src/extensions/agents/manager/agent-runner.test.ts +1846 -0
- package/src/extensions/agents/manager/agent-runner.ts +964 -0
- package/src/extensions/agents/manager/budget-retry-guard.test.ts +95 -0
- package/src/extensions/agents/manager/budget-retry-guard.ts +57 -0
- package/src/extensions/agents/manager/constants.ts +1 -0
- package/src/extensions/agents/manager/group-join.ts +136 -0
- package/src/extensions/agents/manager/output-file.test.ts +31 -0
- package/src/extensions/agents/manager/output-file.ts +92 -0
- package/src/extensions/agents/manager/session-file.test.ts +59 -0
- package/src/extensions/agents/manager/session-file.ts +37 -0
- package/src/extensions/agents/manager/usage.ts +52 -0
- package/src/extensions/agents/memory/memory.test.ts +54 -0
- package/src/extensions/agents/memory/memory.ts +166 -0
- package/src/extensions/agents/memory.test.ts +54 -0
- package/src/extensions/agents/package-resources.test.ts +147 -0
- package/src/extensions/agents/package-resources.ts +37 -0
- package/src/extensions/agents/personas/__snapshots__/default-agents.test.ts.snap +75 -0
- package/src/extensions/agents/personas/agent-types.test.ts +33 -0
- package/src/extensions/agents/personas/agent-types.ts +150 -0
- package/src/extensions/agents/personas/custom-agents.test.ts +171 -0
- package/src/extensions/agents/personas/custom-agents.ts +147 -0
- package/src/extensions/agents/personas/default-agents.test.ts +101 -0
- package/src/extensions/agents/personas/default-agents.ts +335 -0
- package/src/extensions/agents/personas/discovery-priority.test.ts +65 -0
- package/src/extensions/agents/personas/types.ts +229 -0
- package/src/extensions/agents/prompt/context.ts +54 -0
- package/src/extensions/agents/prompt/prompts.test.ts +314 -0
- package/src/extensions/agents/prompt/prompts.ts +164 -0
- package/src/extensions/agents/prompt/skill-loader.test.ts +41 -0
- package/src/extensions/agents/prompt/skill-loader.ts +80 -0
- package/src/extensions/agents/resolution/invocation-config.test.ts +81 -0
- package/src/extensions/agents/resolution/invocation-config.ts +68 -0
- package/src/extensions/agents/resolution/model-resolver.ts +85 -0
- package/src/extensions/agents/resume-tool.test.ts +146 -0
- package/src/extensions/agents/resume-tool.ts +90 -0
- package/src/extensions/agents/settings.ts +163 -0
- package/src/extensions/agents/skill-loader.test.ts +41 -0
- package/src/extensions/agents/tool-result.ts +4 -0
- package/src/extensions/agents/ui/agent-widget.ts +458 -0
- package/src/extensions/agents/ui/conversation-viewer.ts +261 -0
- package/src/extensions/agents/worker-budget-policy.ts +48 -0
- package/src/extensions/agents/worker-report.test.ts +69 -0
- package/src/extensions/agents/worker-report.ts +72 -0
- package/src/extensions/assistant-prefix.ts +80 -0
- package/src/extensions/bash-collapse.test.ts +21 -0
- package/src/extensions/bash-collapse.ts +3 -0
- package/src/extensions/bash-default-timeout.test.ts +189 -0
- package/src/extensions/bash-default-timeout.ts +73 -0
- package/src/extensions/bash-tool-guard-events.ts +39 -0
- package/src/extensions/bash-tool-guard.integration.test.ts +771 -0
- package/src/extensions/bash-tool-guard.test.ts +890 -0
- package/src/extensions/bash-tool-guard.ts +640 -0
- package/src/extensions/behaviours/bash-tokenize.test.ts +125 -0
- package/src/extensions/behaviours/bash-tokenize.ts +272 -0
- package/src/extensions/behaviours/bodies/bound-tool-output.md +11 -0
- package/src/extensions/behaviours/bodies/gh-cli.md +80 -0
- package/src/extensions/behaviours/bodies/git-hygiene.md +13 -0
- package/src/extensions/behaviours/bodies/glab-cli.md +95 -0
- package/src/extensions/behaviours/bodies/python-edit.md +10 -0
- package/src/extensions/behaviours/bodies/re-read-before-edit.md +10 -0
- package/src/extensions/behaviours/bodies.d.ts +7 -0
- package/src/extensions/behaviours/build.test.ts +89 -0
- package/src/extensions/behaviours/build.ts +63 -0
- package/src/extensions/behaviours/engine.test.ts +300 -0
- package/src/extensions/behaviours/engine.ts +131 -0
- package/src/extensions/behaviours/eval-engine.test.ts +165 -0
- package/src/extensions/behaviours/eval-engine.ts +104 -0
- package/src/extensions/behaviours/frontmatter.ts +22 -0
- package/src/extensions/behaviours/index.ts +35 -0
- package/src/extensions/behaviours/matchers.test.ts +153 -0
- package/src/extensions/behaviours/matchers.ts +69 -0
- package/src/extensions/behaviours/registry.ts +73 -0
- package/src/extensions/behaviours/session-context.test.ts +192 -0
- package/src/extensions/behaviours/session-context.ts +170 -0
- package/src/extensions/behaviours/stats.ts +47 -0
- package/src/extensions/behaviours/tool-args.smoke.test.ts +34 -0
- package/src/extensions/behaviours/tool-args.ts +65 -0
- package/src/extensions/behaviours/triggers.ts +164 -0
- package/src/extensions/behaviours/types.ts +49 -0
- package/src/extensions/behaviours/wiring.smoke.test.ts +488 -0
- package/src/extensions/behaviours/wiring.ts +196 -0
- package/src/extensions/branch-command.test.ts +208 -0
- package/src/extensions/branch-command.ts +89 -0
- package/src/extensions/claude-code-hook-adapter/definition.test.ts +51 -0
- package/src/extensions/claude-code-hook-adapter/definition.ts +36 -0
- package/src/extensions/claude-code-hook-adapter/index.ts +6 -0
- package/src/extensions/claude-code-skills/definition.test.ts +228 -0
- package/src/extensions/claude-code-skills/definition.ts +475 -0
- package/src/extensions/claude-code-skills/index.test.ts +227 -0
- package/src/extensions/claude-code-skills/index.ts +101 -0
- package/src/extensions/clipboard-image.test.ts +383 -0
- package/src/extensions/clipboard-image.ts +266 -0
- package/src/extensions/compaction-thresholds.test.ts +8 -0
- package/src/extensions/compaction-thresholds.ts +13 -0
- package/src/extensions/curator/index.test.ts +33 -0
- package/src/extensions/curator/index.ts +296 -0
- package/src/extensions/curator/review.test.ts +82 -0
- package/src/extensions/curator/review.ts +438 -0
- package/src/extensions/curator/state.test.ts +79 -0
- package/src/extensions/curator/state.ts +61 -0
- package/src/extensions/curator/transitions.test.ts +96 -0
- package/src/extensions/curator/transitions.ts +56 -0
- package/src/extensions/customize-footer-command.test.ts +258 -0
- package/src/extensions/customize-footer-command.ts +158 -0
- package/src/extensions/deferred-action.ts +38 -0
- package/src/extensions/disabled/tool-result-pruning.test.ts +397 -0
- package/src/extensions/disabled/tool-result-pruning.ts +181 -0
- package/src/extensions/exit-utils.ts +8 -0
- package/src/extensions/exploration-guard.test.ts +646 -0
- package/src/extensions/exploration-guard.ts +293 -0
- package/src/extensions/ferment/action-tool-names.ts +25 -0
- package/src/extensions/ferment/agent-spawn-guard.test.ts +339 -0
- package/src/extensions/ferment/agent-spawn-guard.ts +188 -0
- package/src/extensions/ferment/ask-user.test.ts +838 -0
- package/src/extensions/ferment/ask-user.ts +569 -0
- package/src/extensions/ferment/auto-compaction.test.ts +933 -0
- package/src/extensions/ferment/auto-compaction.ts +525 -0
- package/src/extensions/ferment/breadcrumb-renderer.ts +46 -0
- package/src/extensions/ferment/colors.ts +95 -0
- package/src/extensions/ferment/command-parser.test.ts +82 -0
- package/src/extensions/ferment/command-parser.ts +52 -0
- package/src/extensions/ferment/commands.test.ts +1659 -0
- package/src/extensions/ferment/commands.ts +1025 -0
- package/src/extensions/ferment/constants.ts +37 -0
- package/src/extensions/ferment/contextual-options.test.ts +70 -0
- package/src/extensions/ferment/contextual-options.ts +107 -0
- package/src/extensions/ferment/continuation.test.ts +141 -0
- package/src/extensions/ferment/continuation.ts +59 -0
- package/src/extensions/ferment/derive-state.test.ts +222 -0
- package/src/extensions/ferment/derive-state.ts +158 -0
- package/src/extensions/ferment/domain-events-emitter.ts +255 -0
- package/src/extensions/ferment/domain-events.ts +146 -0
- package/src/extensions/ferment/events.test.ts +616 -0
- package/src/extensions/ferment/events.ts +561 -0
- package/src/extensions/ferment/footer-status.test.ts +62 -0
- package/src/extensions/ferment/footer-status.ts +61 -0
- package/src/extensions/ferment/format.test.ts +55 -0
- package/src/extensions/ferment/format.ts +111 -0
- package/src/extensions/ferment/fsm-adapter.test.ts +43 -0
- package/src/extensions/ferment/fsm-adapter.ts +112 -0
- package/src/extensions/ferment/gate-registry.test.ts +176 -0
- package/src/extensions/ferment/gate-registry.ts +352 -0
- package/src/extensions/ferment/gate-validation.test.ts +319 -0
- package/src/extensions/ferment/gate-validation.ts +160 -0
- package/src/extensions/ferment/git-init.test.ts +146 -0
- package/src/extensions/ferment/git-init.ts +66 -0
- package/src/extensions/ferment/index.test.ts +1439 -0
- package/src/extensions/ferment/index.ts +337 -0
- package/src/extensions/ferment/judge.test.ts +172 -0
- package/src/extensions/ferment/judge.ts +342 -0
- package/src/extensions/ferment/nudge.test.ts +700 -0
- package/src/extensions/ferment/nudge.ts +332 -0
- package/src/extensions/ferment/oneshot.test.ts +115 -0
- package/src/extensions/ferment/oneshot.ts +52 -0
- package/src/extensions/ferment/pending-proposal-store.test.ts +272 -0
- package/src/extensions/ferment/pending-proposal-store.ts +142 -0
- package/src/extensions/ferment/phase-editor.test.ts +174 -0
- package/src/extensions/ferment/phase-editor.ts +148 -0
- package/src/extensions/ferment/phase-evidence.test.ts +154 -0
- package/src/extensions/ferment/phase-evidence.ts +179 -0
- package/src/extensions/ferment/plan-review-trigger.ts +34 -0
- package/src/extensions/ferment/plan-review.test.ts +182 -0
- package/src/extensions/ferment/plan-review.ts +205 -0
- package/src/extensions/ferment/progress-overlay.test.ts +197 -0
- package/src/extensions/ferment/progress-overlay.ts +326 -0
- package/src/extensions/ferment/project-tests.test.ts +160 -0
- package/src/extensions/ferment/project-tests.ts +246 -0
- package/src/extensions/ferment/prompt-block.test.ts +501 -0
- package/src/extensions/ferment/prompt-block.ts +203 -0
- package/src/extensions/ferment/prompt-ui.test.ts +311 -0
- package/src/extensions/ferment/prompt-ui.ts +302 -0
- package/src/extensions/ferment/resume.test.ts +218 -0
- package/src/extensions/ferment/resume.ts +180 -0
- package/src/extensions/ferment/review-evidence.ts +269 -0
- package/src/extensions/ferment/runtime-state-store.test.ts +148 -0
- package/src/extensions/ferment/runtime-state-store.ts +132 -0
- package/src/extensions/ferment/runtime.test.ts +38 -0
- package/src/extensions/ferment/runtime.ts +208 -0
- package/src/extensions/ferment/scheduler.ts +226 -0
- package/src/extensions/ferment/scoping-benchmark.test.ts +137 -0
- package/src/extensions/ferment/scoping-confirmation.test.ts +209 -0
- package/src/extensions/ferment/scoping-confirmation.ts +87 -0
- package/src/extensions/ferment/scoping-flow.integration.test.ts +203 -0
- package/src/extensions/ferment/scoping.test.ts +235 -0
- package/src/extensions/ferment/scoping.ts +208 -0
- package/src/extensions/ferment/state.test.ts +300 -0
- package/src/extensions/ferment/state.ts +696 -0
- package/src/extensions/ferment/tips.test.ts +171 -0
- package/src/extensions/ferment/tips.ts +61 -0
- package/src/extensions/ferment/todo-headless-wiring.test.ts +641 -0
- package/src/extensions/ferment/todo-sync.test.ts +805 -0
- package/src/extensions/ferment/todo-sync.ts +520 -0
- package/src/extensions/ferment/tool-helpers.test.ts +121 -0
- package/src/extensions/ferment/tool-helpers.ts +217 -0
- package/src/extensions/ferment/tool-names.test.ts +55 -0
- package/src/extensions/ferment/tool-names.ts +78 -0
- package/src/extensions/ferment/tool-schemas.test.ts +587 -0
- package/src/extensions/ferment/tool-schemas.ts +428 -0
- package/src/extensions/ferment/tool-scope.test.ts +373 -0
- package/src/extensions/ferment/tool-scope.ts +176 -0
- package/src/extensions/ferment/tools/knowledge.ts +60 -0
- package/src/extensions/ferment/tools/lifecycle.test.ts +1196 -0
- package/src/extensions/ferment/tools/lifecycle.ts +1296 -0
- package/src/extensions/ferment/tools/phases.test.ts +379 -0
- package/src/extensions/ferment/tools/phases.ts +757 -0
- package/src/extensions/ferment/tools/steps.test.ts +998 -0
- package/src/extensions/ferment/tools/steps.ts +765 -0
- package/src/extensions/ferment/tools.integration.test.ts +1975 -0
- package/src/extensions/ferment/ui.ts +10 -0
- package/src/extensions/ferment/worker-prompt.test.ts +285 -0
- package/src/extensions/ferment/worker-prompt.ts +85 -0
- package/src/extensions/ferment/worktree.ts +44 -0
- package/src/extensions/format.ts +12 -0
- package/src/extensions/help.ts +172 -0
- package/src/extensions/hide-thinking.test.ts +407 -0
- package/src/extensions/hide-thinking.ts +375 -0
- package/src/extensions/hook-adapters/adapter.integration.test.ts +134 -0
- package/src/extensions/hook-adapters/adapter.test.ts +1062 -0
- package/src/extensions/hook-adapters/adapter.ts +784 -0
- package/src/extensions/hook-adapters/discovery.test.ts +105 -0
- package/src/extensions/hook-adapters/discovery.ts +213 -0
- package/src/extensions/ide-adapter/ARCHITECTURE.md +77 -0
- package/src/extensions/ide-adapter/CONTRACT.md +129 -0
- package/src/extensions/ide-adapter/README.md +24 -0
- package/src/extensions/ide-adapter/at-mentions.test.ts +40 -0
- package/src/extensions/ide-adapter/at-mentions.ts +44 -0
- package/src/extensions/ide-adapter/index.test.ts +434 -0
- package/src/extensions/ide-adapter/index.ts +250 -0
- package/src/extensions/ide-adapter/lockfile.test.ts +137 -0
- package/src/extensions/ide-adapter/lockfile.ts +82 -0
- package/src/extensions/ide-adapter/mcp-client.test.ts +285 -0
- package/src/extensions/ide-adapter/mcp-client.ts +208 -0
- package/src/extensions/ide-adapter/types.ts +47 -0
- package/src/extensions/ikie-minimal-tints.ts +124 -0
- package/src/extensions/ikie-process.ts +49 -0
- package/src/extensions/improve/SKILL.md +51 -0
- package/src/extensions/improve/index.ts +72 -0
- package/src/extensions/input-history.test.ts +475 -0
- package/src/extensions/input-history.ts +111 -0
- package/src/extensions/llm-response-log.test.ts +200 -0
- package/src/extensions/llm-response-log.ts +87 -0
- package/src/extensions/login/flow.test.ts +47 -0
- package/src/extensions/login/flow.ts +638 -0
- package/src/extensions/login/index.ts +57 -0
- package/src/extensions/login/startup-auth.test.ts +510 -0
- package/src/extensions/login/startup-auth.ts +205 -0
- package/src/extensions/loop-guard.test.ts +993 -0
- package/src/extensions/loop-guard.ts +710 -0
- package/src/extensions/lsp/client.ts +501 -0
- package/src/extensions/lsp/edits.test.ts +69 -0
- package/src/extensions/lsp/edits.ts +78 -0
- package/src/extensions/lsp/lsp-entry.test.ts +24 -0
- package/src/extensions/lsp/servers.ts +76 -0
- package/src/extensions/lsp/types.ts +166 -0
- package/src/extensions/lsp/utils.test.ts +81 -0
- package/src/extensions/lsp/utils.ts +80 -0
- package/src/extensions/lsp.test.ts +1049 -0
- package/src/extensions/lsp.ts +470 -0
- package/src/extensions/mcp-adapter/CHANGELOG.md +264 -0
- package/src/extensions/mcp-adapter/LICENSE +21 -0
- package/src/extensions/mcp-adapter/README.md +316 -0
- package/src/extensions/mcp-adapter/app-bridge.bundle.js +10430 -0
- package/src/extensions/mcp-adapter/bm25.ts +187 -0
- package/src/extensions/mcp-adapter/cache-resolver.integration.test.ts +282 -0
- package/src/extensions/mcp-adapter/commands.ts +235 -0
- package/src/extensions/mcp-adapter/config.test.ts +189 -0
- package/src/extensions/mcp-adapter/config.ts +233 -0
- package/src/extensions/mcp-adapter/consent-manager.ts +64 -0
- package/src/extensions/mcp-adapter/context-providers.test.ts +123 -0
- package/src/extensions/mcp-adapter/context-providers.ts +55 -0
- package/src/extensions/mcp-adapter/direct-tool-visibility.test.ts +61 -0
- package/src/extensions/mcp-adapter/direct-tool-visibility.ts +47 -0
- package/src/extensions/mcp-adapter/direct-tools.ts +413 -0
- package/src/extensions/mcp-adapter/errors.ts +198 -0
- package/src/extensions/mcp-adapter/glimpse-ui.ts +78 -0
- package/src/extensions/mcp-adapter/host-html-template.ts +423 -0
- package/src/extensions/mcp-adapter/index.test.ts +323 -0
- package/src/extensions/mcp-adapter/index.ts +604 -0
- package/src/extensions/mcp-adapter/init.ts +402 -0
- package/src/extensions/mcp-adapter/lifecycle.ts +93 -0
- package/src/extensions/mcp-adapter/logger.ts +169 -0
- package/src/extensions/mcp-adapter/mcp-auth-flow.ts +394 -0
- package/src/extensions/mcp-adapter/mcp-auth.ts +267 -0
- package/src/extensions/mcp-adapter/mcp-callback-server.ts +284 -0
- package/src/extensions/mcp-adapter/mcp-oauth-provider.ts +288 -0
- package/src/extensions/mcp-adapter/mcp-panel.test.ts +321 -0
- package/src/extensions/mcp-adapter/mcp-panel.ts +835 -0
- package/src/extensions/mcp-adapter/metadata-cache.test.ts +83 -0
- package/src/extensions/mcp-adapter/metadata-cache.ts +332 -0
- package/src/extensions/mcp-adapter/npx-resolver.ts +429 -0
- package/src/extensions/mcp-adapter/oauth-handler.ts +57 -0
- package/src/extensions/mcp-adapter/proxy-modes.test.ts +147 -0
- package/src/extensions/mcp-adapter/proxy-modes.ts +1035 -0
- package/src/extensions/mcp-adapter/resource-tools.ts +17 -0
- package/src/extensions/mcp-adapter/server-manager.ts +355 -0
- package/src/extensions/mcp-adapter/state.ts +45 -0
- package/src/extensions/mcp-adapter/tool-metadata.test.ts +115 -0
- package/src/extensions/mcp-adapter/tool-metadata.ts +185 -0
- package/src/extensions/mcp-adapter/tool-registrar.ts +46 -0
- package/src/extensions/mcp-adapter/types.ts +423 -0
- package/src/extensions/mcp-adapter/ui-resource-handler.ts +143 -0
- package/src/extensions/mcp-adapter/ui-server.ts +619 -0
- package/src/extensions/mcp-adapter/ui-session.ts +378 -0
- package/src/extensions/mcp-adapter/ui-stream-types.ts +93 -0
- package/src/extensions/mcp-adapter/utils.ts +76 -0
- package/src/extensions/model-catalog/builtin-models.ts +180 -0
- package/src/extensions/model-catalog/guidelines/claude-family.ts +49 -0
- package/src/extensions/model-catalog/guidelines/default-orchestration-guidelines.ts +11 -0
- package/src/extensions/model-catalog/guidelines/default-phase-guidelines.ts +59 -0
- package/src/extensions/model-catalog/guidelines/guidelines-resolver.test.ts +191 -0
- package/src/extensions/model-catalog/guidelines/guidelines-resolver.ts +39 -0
- package/src/extensions/model-catalog/guidelines/kimi-family.ts +82 -0
- package/src/extensions/model-catalog/guidelines/minimax-family.ts +62 -0
- package/src/extensions/model-catalog/guidelines/nemotron-family.ts +53 -0
- package/src/extensions/model-catalog/index.ts +10 -0
- package/src/extensions/model-catalog/model-metadata.test.ts +688 -0
- package/src/extensions/model-catalog/model-metadata.ts +250 -0
- package/src/extensions/model-catalog/model-registry.test.ts +228 -0
- package/src/extensions/model-catalog/model-registry.ts +116 -0
- package/src/extensions/model-catalog/ref-utils.ts +22 -0
- package/src/extensions/model-catalog/types.ts +41 -0
- package/src/extensions/model-guard.test.ts +756 -0
- package/src/extensions/model-guard.ts +402 -0
- package/src/extensions/model-switch.test.ts +1105 -0
- package/src/extensions/model-switch.ts +261 -0
- package/src/extensions/onboarding/picker-editor.ts +22 -0
- package/src/extensions/onboarding/session-mode-picker.test.ts +135 -0
- package/src/extensions/onboarding/session-mode-picker.ts +136 -0
- package/src/extensions/onboarding/session-mode-startup.test.ts +237 -0
- package/src/extensions/onboarding/session-mode-startup.ts +36 -0
- package/src/extensions/onboarding/session-mode.test.ts +501 -0
- package/src/extensions/onboarding/session-mode.ts +363 -0
- package/src/extensions/orchestration/continuation-nudge.test.ts +595 -0
- package/src/extensions/orchestration/continuation-nudge.ts +270 -0
- package/src/extensions/orchestration/model-metadata.test.ts +671 -0
- package/src/extensions/orchestration/model-metadata.ts +246 -0
- package/src/extensions/orchestration/model-ref-utils.ts +5 -0
- package/src/extensions/orchestration/model-registry/builtin-models.ts +223 -0
- package/src/extensions/orchestration/model-registry/guidelines/claude-family.ts +49 -0
- package/src/extensions/orchestration/model-registry/guidelines/default-orchestration-guidelines.ts +11 -0
- package/src/extensions/orchestration/model-registry/guidelines/default-phase-guidelines.ts +67 -0
- package/src/extensions/orchestration/model-registry/guidelines/guidelines-resolver.test.ts +273 -0
- package/src/extensions/orchestration/model-registry/guidelines/guidelines-resolver.ts +39 -0
- package/src/extensions/orchestration/model-registry/guidelines/kimi-family.ts +89 -0
- package/src/extensions/orchestration/model-registry/guidelines/minimax-family.ts +68 -0
- package/src/extensions/orchestration/model-registry/guidelines/nemotron-family.ts +60 -0
- package/src/extensions/orchestration/model-registry/index.ts +4 -0
- package/src/extensions/orchestration/model-registry/model-registry.test.ts +228 -0
- package/src/extensions/orchestration/model-registry/model-registry.ts +116 -0
- package/src/extensions/orchestration/model-registry/types.ts +41 -0
- package/src/extensions/orchestration/model-roles-command.test.ts +367 -0
- package/src/extensions/orchestration/model-roles-command.ts +531 -0
- package/src/extensions/orchestration/model-roles.test.ts +448 -0
- package/src/extensions/orchestration/model-roles.ts +302 -0
- package/src/extensions/orchestration/orchestration-instructions.test.ts +459 -0
- package/src/extensions/orchestration/orchestration-instructions.ts +614 -0
- package/src/extensions/orphan-tool-result-sanitizer.test.ts +198 -0
- package/src/extensions/orphan-tool-result-sanitizer.ts +128 -0
- package/src/extensions/permissions/classifier-model.test.ts +127 -0
- package/src/extensions/permissions/classifier-model.ts +65 -0
- package/src/extensions/permissions/classifier.test.ts +334 -0
- package/src/extensions/permissions/classifier.ts +228 -0
- package/src/extensions/permissions/commands.ts +244 -0
- package/src/extensions/permissions/config.test.ts +101 -0
- package/src/extensions/permissions/config.ts +143 -0
- package/src/extensions/permissions/constants.ts +1 -0
- package/src/extensions/permissions/index.test.ts +1822 -0
- package/src/extensions/permissions/index.ts +1193 -0
- package/src/extensions/permissions/keybindings.test.ts +130 -0
- package/src/extensions/permissions/keybindings.ts +91 -0
- package/src/extensions/permissions/mode-controller-registry.ts +50 -0
- package/src/extensions/permissions/mode-controller.ts +91 -0
- package/src/extensions/permissions/mode.test.ts +51 -0
- package/src/extensions/permissions/mode.ts +29 -0
- package/src/extensions/permissions/plan-persistence.test.ts +56 -0
- package/src/extensions/permissions/plan-persistence.ts +12 -0
- package/src/extensions/permissions/plan-persona.test.ts +42 -0
- package/src/extensions/permissions/prompter.ts +21 -0
- package/src/extensions/permissions/prompts/classifier-system-prompt.ts +33 -0
- package/src/extensions/permissions/prompts/plan-mode-supplement.test.ts +59 -0
- package/src/extensions/permissions/prompts/plan-mode-supplement.ts +38 -0
- package/src/extensions/permissions/prompts.test.ts +67 -0
- package/src/extensions/permissions/prompts.ts +212 -0
- package/src/extensions/permissions/rules.test.ts +327 -0
- package/src/extensions/permissions/rules.ts +210 -0
- package/src/extensions/permissions/select-utils.ts +2 -0
- package/src/extensions/permissions/session-memory.test.ts +75 -0
- package/src/extensions/permissions/session-memory.ts +94 -0
- package/src/extensions/permissions/taxonomy.test.ts +684 -0
- package/src/extensions/permissions/taxonomy.ts +616 -0
- package/src/extensions/permissions/types.ts +70 -0
- package/src/extensions/pi-compat.test.ts +201 -0
- package/src/extensions/pi-package-lookup/index.test.ts +177 -0
- package/src/extensions/pi-package-lookup/index.ts +263 -0
- package/src/extensions/pi-package-lookup/native-compat.test.ts +230 -0
- package/src/extensions/pi-package-lookup/native-compat.ts +279 -0
- package/src/extensions/plugin-package-hook-adapter/definition.test.ts +220 -0
- package/src/extensions/plugin-package-hook-adapter/definition.ts +60 -0
- package/src/extensions/plugin-package-hook-adapter/index.ts +4 -0
- package/src/extensions/prompt-construction/context-files.test.ts +167 -0
- package/src/extensions/prompt-construction/context-files.ts +111 -0
- package/src/extensions/prompt-construction/index.ts +8 -0
- package/src/extensions/prompt-construction/prompt-enrichment.test.ts +1103 -0
- package/src/extensions/prompt-construction/prompt-enrichment.ts +588 -0
- package/src/extensions/prompt-construction/system-prompt-blocks.test.ts +323 -0
- package/src/extensions/prompt-construction/system-prompt-blocks.ts +122 -0
- package/src/extensions/prompt-construction/system-prompt.test.ts +528 -0
- package/src/extensions/prompt-construction/system-prompt.ts +316 -0
- package/src/extensions/prompt-construction/tool-visibility.test.ts +170 -0
- package/src/extensions/prompt-construction/tool-visibility.ts +121 -0
- package/src/extensions/prompt-summary.test.ts +56 -0
- package/src/extensions/prompt-summary.ts +258 -0
- package/src/extensions/questionnaire/constants.ts +6 -0
- package/src/extensions/questionnaire/index.ts +8 -0
- package/src/extensions/questionnaire/questionnaire-fallback.test.ts +948 -0
- package/src/extensions/questionnaire/questionnaire-fallback.ts +230 -0
- package/src/extensions/questionnaire/questionnaire-form.ts +314 -0
- package/src/extensions/questionnaire/questionnaire-reducer.test.ts +508 -0
- package/src/extensions/questionnaire/questionnaire-reducer.ts +510 -0
- package/src/extensions/questionnaire/questionnaire.test.ts +425 -0
- package/src/extensions/questionnaire/questionnaire.ts +280 -0
- package/src/extensions/report-bug.test.ts +301 -0
- package/src/extensions/report-bug.ts +93 -0
- package/src/extensions/review-write-guard.integration.test.ts +167 -0
- package/src/extensions/review-write-guard.test.ts +150 -0
- package/src/extensions/review-write-guard.ts +143 -0
- package/src/extensions/rtk-rewrite.test.ts +325 -0
- package/src/extensions/rtk-rewrite.ts +201 -0
- package/src/extensions/session-metadata/index.test.ts +158 -0
- package/src/extensions/session-metadata/index.ts +66 -0
- package/src/extensions/session-name.test.ts +248 -0
- package/src/extensions/session-name.ts +192 -0
- package/src/extensions/session-repair/orphan-tool-result-repair.test.ts +227 -0
- package/src/extensions/session-repair/orphan-tool-result-repair.ts +183 -0
- package/src/extensions/shared-footer.ts +24 -0
- package/src/extensions/shared-input.test.ts +27 -0
- package/src/extensions/shared-input.ts +24 -0
- package/src/extensions/shutdown-marker.test.ts +195 -0
- package/src/extensions/shutdown-marker.ts +61 -0
- package/src/extensions/skills-manager/index.test.ts +17 -0
- package/src/extensions/skills-manager/index.ts +18 -0
- package/src/extensions/skills-manager/skill-manager.test.ts +350 -0
- package/src/extensions/skills-manager/skill-manager.ts +508 -0
- package/src/extensions/skills-manager/tool.test.ts +234 -0
- package/src/extensions/skills-manager/tool.ts +241 -0
- package/src/extensions/skills-manager/usage.test.ts +119 -0
- package/src/extensions/skills-manager/usage.ts +249 -0
- package/src/extensions/slash-commands.ts +22 -0
- package/src/extensions/spinner.ts +117 -0
- package/src/extensions/stale-ctx.ts +15 -0
- package/src/extensions/startup-update.test.ts +130 -0
- package/src/extensions/startup-update.ts +33 -0
- package/src/extensions/stats/api.ts +168 -0
- package/src/extensions/stats/display.ts +210 -0
- package/src/extensions/stats/index.ts +130 -0
- package/src/extensions/stats/stats.test.ts +547 -0
- package/src/extensions/stats/types.ts +263 -0
- package/src/extensions/stats/visual.ts +439 -0
- package/src/extensions/strip-images.test.ts +118 -0
- package/src/extensions/strip-images.ts +162 -0
- package/src/extensions/superpowers/config.ts +10 -0
- package/src/extensions/superpowers.test.ts +17 -0
- package/src/extensions/superpowers.ts +5 -0
- package/src/extensions/surveys/index.test.ts +200 -0
- package/src/extensions/surveys/index.ts +107 -0
- package/src/extensions/surveys/survey.test.ts +157 -0
- package/src/extensions/surveys/survey.ts +204 -0
- package/src/extensions/tags.test.ts +275 -0
- package/src/extensions/tags.ts +662 -0
- package/src/extensions/telemetry/README.md +113 -0
- package/src/extensions/telemetry/accumulator.test.ts +260 -0
- package/src/extensions/telemetry/accumulator.ts +146 -0
- package/src/extensions/telemetry/config-snapshot.test.ts +259 -0
- package/src/extensions/telemetry/config-snapshot.ts +177 -0
- package/src/extensions/telemetry/handlers/messages.test.ts +422 -0
- package/src/extensions/telemetry/handlers/messages.ts +113 -0
- package/src/extensions/telemetry/handlers/session.test.ts +198 -0
- package/src/extensions/telemetry/handlers/session.ts +24 -0
- package/src/extensions/telemetry/handlers/tools.test.ts +416 -0
- package/src/extensions/telemetry/handlers/tools.ts +171 -0
- package/src/extensions/telemetry/handlers/transport-errors.test.ts +178 -0
- package/src/extensions/telemetry/handlers/transport-errors.ts +42 -0
- package/src/extensions/telemetry/helpers.test.ts +266 -0
- package/src/extensions/telemetry/helpers.ts +146 -0
- package/src/extensions/telemetry/index.test.ts +1170 -0
- package/src/extensions/telemetry/index.ts +609 -0
- package/src/extensions/telemetry/pre-session.test.ts +390 -0
- package/src/extensions/telemetry/pre-session.ts +135 -0
- package/src/extensions/telemetry/session-context.test.ts +487 -0
- package/src/extensions/telemetry/session-context.ts +281 -0
- package/src/extensions/telemetry/session-type.ts +5 -0
- package/src/extensions/telemetry/settings-change-emitter.test.ts +197 -0
- package/src/extensions/telemetry/settings-change-emitter.ts +127 -0
- package/src/extensions/telemetry/survey.test.ts +140 -0
- package/src/extensions/telemetry/survey.ts +70 -0
- package/src/extensions/telemetry/transport.test.ts +301 -0
- package/src/extensions/telemetry/transport.ts +217 -0
- package/src/extensions/teleport/commands/args.test.ts +166 -0
- package/src/extensions/teleport/commands/args.ts +226 -0
- package/src/extensions/teleport/commands/attach.test.ts +209 -0
- package/src/extensions/teleport/commands/attach.ts +91 -0
- package/src/extensions/teleport/commands/errors.ts +26 -0
- package/src/extensions/teleport/commands/remote-sessions.test.ts +350 -0
- package/src/extensions/teleport/commands/remote-sessions.ts +193 -0
- package/src/extensions/teleport/commands/ssh-config.ts +25 -0
- package/src/extensions/teleport/commands/sync.test.ts +104 -0
- package/src/extensions/teleport/commands/sync.ts +205 -0
- package/src/extensions/teleport/commands/teleport.test.ts +593 -0
- package/src/extensions/teleport/commands/teleport.ts +359 -0
- package/src/extensions/teleport/commands/terminal.test.ts +228 -0
- package/src/extensions/teleport/commands/terminal.ts +97 -0
- package/src/extensions/teleport/commands/workspace-ref.test.ts +269 -0
- package/src/extensions/teleport/commands/workspace-ref.ts +105 -0
- package/src/extensions/teleport/index.test.ts +72 -0
- package/src/extensions/teleport/index.ts +66 -0
- package/src/extensions/teleport/overlay/keybindings.test.ts +101 -0
- package/src/extensions/teleport/overlay/keybindings.ts +65 -0
- package/src/extensions/teleport/overlay/overlay-component.test.ts +102 -0
- package/src/extensions/teleport/overlay/overlay-component.ts +284 -0
- package/src/extensions/teleport/overlay/reconnect-banner.test.ts +63 -0
- package/src/extensions/teleport/overlay/reconnect-banner.ts +69 -0
- package/src/extensions/teleport/overlay/tab-bar.test.ts +72 -0
- package/src/extensions/teleport/overlay/tab-bar.ts +146 -0
- package/src/extensions/teleport/overlay/tab-manager.test.ts +484 -0
- package/src/extensions/teleport/overlay/tab-manager.ts +293 -0
- package/src/extensions/teleport/preflight/git.test.ts +94 -0
- package/src/extensions/teleport/preflight/git.ts +40 -0
- package/src/extensions/teleport/preflight/index.test.ts +97 -0
- package/src/extensions/teleport/preflight/index.ts +43 -0
- package/src/extensions/teleport/preflight/rsync.test.ts +39 -0
- package/src/extensions/teleport/preflight/rsync.ts +23 -0
- package/src/extensions/teleport/preflight/workspace-size.ts +2 -0
- package/src/extensions/teleport/provisioning/concurrency.ts +40 -0
- package/src/extensions/teleport/provisioning/constants.ts +3 -0
- package/src/extensions/teleport/provisioning/estimate-bytes.test.ts +107 -0
- package/src/extensions/teleport/provisioning/estimate-bytes.ts +123 -0
- package/src/extensions/teleport/provisioning/git-provision.test.ts +76 -0
- package/src/extensions/teleport/provisioning/git-provision.ts +54 -0
- package/src/extensions/teleport/provisioning/include-list.test.ts +222 -0
- package/src/extensions/teleport/provisioning/include-list.ts +203 -0
- package/src/extensions/teleport/provisioning/paths.ts +54 -0
- package/src/extensions/teleport/provisioning/proxy-command.ts +30 -0
- package/src/extensions/teleport/provisioning/rsync-runner.test.ts +544 -0
- package/src/extensions/teleport/provisioning/rsync-runner.ts +675 -0
- package/src/extensions/teleport/pty/mouse-capture.ts +7 -0
- package/src/extensions/teleport/pty/reconnect-supervisor.ts +117 -0
- package/src/extensions/teleport/pty/terminal-component.test.ts +282 -0
- package/src/extensions/teleport/pty/terminal-component.ts +331 -0
- package/src/extensions/teleport/pty/websocket-transport.test.ts +440 -0
- package/src/extensions/teleport/pty/websocket-transport.ts +228 -0
- package/src/extensions/teleport/pty/xterm-core.test.ts +79 -0
- package/src/extensions/teleport/pty/xterm-core.ts +159 -0
- package/src/extensions/teleport/session-filter.ts +10 -0
- package/src/extensions/teleport/ssh-config/paths.ts +27 -0
- package/src/extensions/teleport/ssh-config/render.test.ts +111 -0
- package/src/extensions/teleport/ssh-config/render.ts +97 -0
- package/src/extensions/teleport/ssh-config/sync.test.ts +170 -0
- package/src/extensions/teleport/ssh-config/sync.ts +92 -0
- package/src/extensions/teleport/types.ts +14 -0
- package/src/extensions/teleport/ui/format-bytes.ts +13 -0
- package/src/extensions/teleport/ui/git-token-prompt.ts +164 -0
- package/src/extensions/teleport/ui/help-modal.ts +100 -0
- package/src/extensions/teleport/ui/progress.test.ts +236 -0
- package/src/extensions/teleport/ui/progress.ts +345 -0
- package/src/extensions/teleport/ui/remote-sessions-panel.test.ts +221 -0
- package/src/extensions/teleport/ui/remote-sessions-panel.ts +309 -0
- package/src/extensions/teleport/ui/sessions-table.ts +24 -0
- package/src/extensions/teleport/ui/sync-progress-row.ts +30 -0
- package/src/extensions/teleport/ui/tty-handoff.ts +131 -0
- package/src/extensions/teleport/ui/workspaces-panel.test.ts +307 -0
- package/src/extensions/teleport/ui/workspaces-panel.ts +364 -0
- package/src/extensions/teleport/ui/workspaces-table.ts +12 -0
- package/src/extensions/terminal-colors.ts +256 -0
- package/src/extensions/terminal-compat/keyboard-capability.test.ts +69 -0
- package/src/extensions/terminal-compat/keyboard-capability.ts +103 -0
- package/src/extensions/terminal-compat/startup-warning.test.ts +273 -0
- package/src/extensions/terminal-compat/startup-warning.ts +77 -0
- package/src/extensions/theme-selector.test.ts +297 -0
- package/src/extensions/theme-selector.ts +156 -0
- package/src/extensions/thinking-steps/index.ts +429 -0
- package/src/extensions/thinking-steps/internal-patch.ts +483 -0
- package/src/extensions/thinking-steps/parse.ts +1376 -0
- package/src/extensions/thinking-steps/persistence.ts +95 -0
- package/src/extensions/thinking-steps/render.ts +496 -0
- package/src/extensions/thinking-steps/state.ts +482 -0
- package/src/extensions/thinking-steps/types.ts +55 -0
- package/src/extensions/tips/general-tips.test.ts +33 -0
- package/src/extensions/tips/general-tips.ts +81 -0
- package/src/extensions/tips/index.test.ts +198 -0
- package/src/extensions/tips/index.ts +231 -0
- package/src/extensions/tips/presenter.test.ts +160 -0
- package/src/extensions/tips/presenter.ts +170 -0
- package/src/extensions/tips/registry.test.ts +59 -0
- package/src/extensions/tips/registry.ts +66 -0
- package/src/extensions/tips/tip-row.test.ts +97 -0
- package/src/extensions/tips/tip-row.ts +48 -0
- package/src/extensions/tips/tips-panel.test.ts +222 -0
- package/src/extensions/tips/tips-panel.ts +310 -0
- package/src/extensions/tips/types.ts +21 -0
- package/src/extensions/todos/command.test.ts +103 -0
- package/src/extensions/todos/command.ts +266 -0
- package/src/extensions/todos/constants.ts +1 -0
- package/src/extensions/todos/index.test.ts +405 -0
- package/src/extensions/todos/index.ts +238 -0
- package/src/extensions/todos/prompt-block.test.ts +219 -0
- package/src/extensions/todos/prompt-block.ts +176 -0
- package/src/extensions/todos/reducer.test.ts +141 -0
- package/src/extensions/todos/reducer.ts +155 -0
- package/src/extensions/todos/scope.test.ts +42 -0
- package/src/extensions/todos/scope.ts +109 -0
- package/src/extensions/todos/store.test.ts +78 -0
- package/src/extensions/todos/store.ts +92 -0
- package/src/extensions/todos/tool.test.ts +84 -0
- package/src/extensions/todos/tool.ts +240 -0
- package/src/extensions/todos/types.ts +79 -0
- package/src/extensions/todos/widget.test.ts +348 -0
- package/src/extensions/todos/widget.ts +332 -0
- package/src/extensions/tool-grouping.test.ts +387 -0
- package/src/extensions/tool-grouping.ts +346 -0
- package/src/extensions/tool-rendering.test.ts +401 -0
- package/src/extensions/tool-rendering.ts +4511 -0
- package/src/extensions/trace-id.test.ts +230 -0
- package/src/extensions/trace-id.ts +46 -0
- package/src/extensions/ui-branch-poll.test.ts +164 -0
- package/src/extensions/ui-branch-poll.ts +57 -0
- package/src/extensions/ui.test.ts +185 -0
- package/src/extensions/ui.ts +649 -0
- package/src/extensions/web-fetch/browser-pool.test.ts +237 -0
- package/src/extensions/web-fetch/browser-pool.ts +145 -0
- package/src/extensions/web-fetch/cache.test.ts +196 -0
- package/src/extensions/web-fetch/cache.ts +114 -0
- package/src/extensions/web-fetch/content-converter.test.ts +341 -0
- package/src/extensions/web-fetch/content-converter.ts +188 -0
- package/src/extensions/web-fetch/execute-handler.test.ts +384 -0
- package/src/extensions/web-fetch/execute-handler.ts +131 -0
- package/src/extensions/web-fetch/index.ts +113 -0
- package/src/extensions/web-fetch/integration.test.ts +618 -0
- package/src/extensions/web-fetch/page-fetcher.test.ts +532 -0
- package/src/extensions/web-fetch/page-fetcher.ts +358 -0
- package/src/extensions/web-fetch/url-validator.test.ts +230 -0
- package/src/extensions/web-fetch/url-validator.ts +153 -0
- package/src/extensions/web-search/execute-handler.test.ts +216 -0
- package/src/extensions/web-search/execute-handler.ts +151 -0
- package/src/extensions/web-search/index.ts +107 -0
- package/src/ferment/engine.test.ts +279 -0
- package/src/ferment/engine.ts +320 -0
- package/src/ferment/event-mapper.ts +313 -0
- package/src/ferment/event-store.property.test.ts +240 -0
- package/src/ferment/event-store.test.ts +780 -0
- package/src/ferment/event-store.ts +1332 -0
- package/src/ferment/fsm.test.ts +630 -0
- package/src/ferment/fsm.ts +466 -0
- package/src/ferment/lifecycle.ts +35 -0
- package/src/ferment/state-machine.test.ts +1215 -0
- package/src/ferment/state-machine.ts +994 -0
- package/src/ferment/stats.test.ts +31 -0
- package/src/ferment/stats.ts +334 -0
- package/src/ferment/store.test.ts +543 -0
- package/src/ferment/store.ts +906 -0
- package/src/ferment/success-criteria.ts +51 -0
- package/src/ferment/text.ts +11 -0
- package/src/ferment/title.test.ts +35 -0
- package/src/ferment/title.ts +28 -0
- package/src/ferment/types.ts +277 -0
- package/src/fs-paths.test.ts +104 -0
- package/src/fs-paths.ts +112 -0
- package/src/integrations/__fixtures__/models.ts +72 -0
- package/src/integrations/bun-sqlite.d.ts +22 -0
- package/src/integrations/claude-code.test.ts +488 -0
- package/src/integrations/claude-code.ts +221 -0
- package/src/integrations/constants.ts +9 -0
- package/src/integrations/cursor.test.ts +91 -0
- package/src/integrations/cursor.ts +188 -0
- package/src/integrations/detect.ts +92 -0
- package/src/integrations/gsd2.test.ts +146 -0
- package/src/integrations/gsd2.ts +162 -0
- package/src/integrations/models.ts +212 -0
- package/src/integrations/openclaw.test.ts +314 -0
- package/src/integrations/openclaw.ts +276 -0
- package/src/integrations/opencode.test.ts +216 -0
- package/src/integrations/opencode.ts +226 -0
- package/src/integrations/provider/opencode.ts +37 -0
- package/src/integrations/registry.test.ts +43 -0
- package/src/integrations/registry.ts +33 -0
- package/src/integrations/spawn.ts +88 -0
- package/src/integrations/types.ts +35 -0
- package/src/login-command-patch.test.ts +609 -0
- package/src/login-command-patch.ts +299 -0
- package/src/models.test.ts +827 -0
- package/src/models.ts +379 -0
- package/src/modes/acp/acp-prompter.ts +64 -0
- package/src/modes/acp/acp-ui-context.test.ts +1001 -0
- package/src/modes/acp/acp-ui-context.ts +435 -0
- package/src/modes/acp/capabilities.ts +36 -0
- package/src/modes/acp/commands.test.ts +29 -0
- package/src/modes/acp/commands.ts +19 -0
- package/src/modes/acp/permission-prompter-registry.ts +17 -0
- package/src/modes/acp/server.test.ts +4392 -0
- package/src/modes/acp/server.ts +1569 -0
- package/src/modes/acp/utils.ts +10 -0
- package/src/oauth-page-patch.test.ts +84 -0
- package/src/ollama.test.ts +1469 -0
- package/src/ollama.ts +491 -0
- package/src/paste-interceptor.test.ts +179 -0
- package/src/paste-interceptor.ts +61 -0
- package/src/paste-to-editor-patch.test.ts +24 -0
- package/src/paste-to-editor-patch.ts +27 -0
- package/src/posthog-device.ts +31 -0
- package/src/proxy.test.ts +36 -0
- package/src/proxy.ts +36 -0
- package/src/resources/bash-hook-discovery.ts +69 -0
- package/src/resources/bash-hooks.test.ts +93 -0
- package/src/resources/bash-hooks.ts +109 -0
- package/src/resources/claude-code-hook-resources.ts +35 -0
- package/src/resources/definitions.test.ts +92 -0
- package/src/resources/definitions.ts +149 -0
- package/src/resources/extension.test.ts +168 -0
- package/src/resources/extension.ts +114 -0
- package/src/resources/filter.ts +11 -0
- package/src/resources/package-resources.test.ts +234 -0
- package/src/resources/package-resources.ts +152 -0
- package/src/resources/rtk-install.test.ts +37 -0
- package/src/resources/rtk-install.ts +289 -0
- package/src/resources/store.test.ts +118 -0
- package/src/resources/store.ts +76 -0
- package/src/resources/tool-blocker.ts +14 -0
- package/src/resources/types.ts +23 -0
- package/src/resources/ui.test.ts +74 -0
- package/src/resources/ui.ts +221 -0
- package/src/sandbox/cloud/auth.test.ts +219 -0
- package/src/sandbox/cloud/auth.ts +127 -0
- package/src/sandbox/cloud/http.test.ts +88 -0
- package/src/sandbox/cloud/http.ts +46 -0
- package/src/sandbox/cloud/index.ts +18 -0
- package/src/sandbox/cloud/keys.test.ts +44 -0
- package/src/sandbox/cloud/keys.ts +34 -0
- package/src/sandbox/cloud/readiness.test.ts +115 -0
- package/src/sandbox/cloud/readiness.ts +122 -0
- package/src/sandbox/cloud/types.ts +70 -0
- package/src/sandbox/cloud/uri.test.ts +51 -0
- package/src/sandbox/cloud/uri.ts +30 -0
- package/src/sandbox/cloud/workspaces.test.ts +249 -0
- package/src/sandbox/cloud/workspaces.ts +161 -0
- package/src/sandbox/constants.ts +2 -0
- package/src/sandbox/git-credentials.test.ts +89 -0
- package/src/sandbox/git-credentials.ts +72 -0
- package/src/sandbox/worker/client.test.ts +156 -0
- package/src/sandbox/worker/client.ts +201 -0
- package/src/sandbox/worker/git-identity.test.ts +99 -0
- package/src/sandbox/worker/git-identity.ts +69 -0
- package/src/sandbox/worker/index.ts +36 -0
- package/src/sandbox/worker/secrets.test.ts +61 -0
- package/src/sandbox/worker/secrets.ts +28 -0
- package/src/sandbox/worker/sessions.test.ts +197 -0
- package/src/sandbox/worker/sessions.ts +32 -0
- package/src/sandbox/worker/status.test.ts +57 -0
- package/src/sandbox/worker/status.ts +6 -0
- package/src/sandbox/worker/types.ts +119 -0
- package/src/set-package-dir.ts +5 -0
- package/src/settings-watcher.test.ts +116 -0
- package/src/settings-watcher.ts +79 -0
- package/src/setup-wizard/apply-tools.test.ts +131 -0
- package/src/setup-wizard/apply-tools.ts +70 -0
- package/src/setup-wizard/index.ts +107 -0
- package/src/setup-wizard/prompt.ts +126 -0
- package/src/setup-wizard/shell-profile.test.ts +154 -0
- package/src/setup-wizard/shell-profile.ts +169 -0
- package/src/setup-wizard/state.ts +45 -0
- package/src/setup-wizard/steps/auth.test.ts +145 -0
- package/src/setup-wizard/steps/auth.ts +130 -0
- package/src/setup-wizard/steps/done.test.ts +267 -0
- package/src/setup-wizard/steps/done.ts +121 -0
- package/src/setup-wizard/steps/rtk.ts +6 -0
- package/src/setup-wizard/steps/telemetry.test.ts +46 -0
- package/src/setup-wizard/steps/telemetry.ts +41 -0
- package/src/setup-wizard/steps/tools.ts +45 -0
- package/src/setup-wizard/steps/welcome.ts +42 -0
- package/src/setup-wizard.test.ts +384 -0
- package/src/setup-wizard.ts +405 -0
- package/src/shared/planning/entry-trigger-registry.test.ts +427 -0
- package/src/shared/planning/entry-trigger-registry.ts +216 -0
- package/src/shared/planning/plan-artifact-store.test.ts +338 -0
- package/src/shared/planning/plan-artifact-store.ts +244 -0
- package/src/shared/planning/plan-decomposition.test.ts +183 -0
- package/src/shared/planning/plan-decomposition.ts +338 -0
- package/src/shared/planning/planning-stop-nudge.test.ts +217 -0
- package/src/shared/planning/planning-stop-nudge.ts +133 -0
- package/src/shared/planning/prompt-supplement-registry.test.ts +264 -0
- package/src/shared/planning/prompt-supplement-registry.ts +250 -0
- package/src/shared/planning/read-only-tool-registry.test.ts +96 -0
- package/src/shared/planning/read-only-tool-registry.ts +98 -0
- package/src/shared/planning/shared-planning-process.test.ts +224 -0
- package/src/shared/planning/shared-planning-process.ts +128 -0
- package/src/shared/planning/tool-catalog.test.ts +344 -0
- package/src/shared/planning/tool-catalog.ts +258 -0
- package/src/shared/planning/tool-profile-manager.test.ts +409 -0
- package/src/shared/planning/tool-profile-manager.ts +281 -0
- package/src/skill-paths.test.ts +33 -0
- package/src/skill-paths.ts +18 -0
- package/src/skills-wizard.ts +44 -0
- package/src/ssh-proxy.test.ts +102 -0
- package/src/ssh-proxy.ts +79 -0
- package/src/startup-context.ts +23 -0
- package/src/startup-settings.test.ts +37 -0
- package/src/startup-settings.ts +26 -0
- package/src/terminal-bg-probe.test.ts +131 -0
- package/src/terminal-bg-probe.ts +238 -0
- package/src/theme-file-watcher.ts +95 -0
- package/src/types/before-provider-headers.ts +48 -0
- package/src/types/pi-coding-agent-utils.d.ts +34 -0
- package/src/update/extract.ts +75 -0
- package/src/update/github.test.ts +133 -0
- package/src/update/github.ts +190 -0
- package/src/update/install.test.ts +210 -0
- package/src/update/install.ts +205 -0
- package/src/update/paths.test.ts +97 -0
- package/src/update/paths.ts +109 -0
- package/src/update/state.test.ts +215 -0
- package/src/update/state.ts +94 -0
- package/src/update/workflow.test.ts +397 -0
- package/src/update/workflow.ts +217 -0
- package/src/upstream-retry-patch.test.ts +77 -0
- package/src/upstream-retry-patch.ts +41 -0
- package/src/utils/clipboard-image.test.ts +117 -0
- package/src/utils/clipboard-image.ts +316 -0
- package/src/utils/clipboard-linux-routing.test.ts +100 -0
- package/src/utils/clipboard-linux-subprocess.test.ts +176 -0
- package/src/utils/clipboard-linux-subprocess.ts +58 -0
- package/src/utils/clipboard-native-harness-wsl.test.ts +109 -0
- package/src/utils/clipboard-native-harness.test.ts +54 -0
- package/src/utils/clipboard-native-harness.ts +146 -0
- package/src/utils/clipboard-read.test.ts +88 -0
- package/src/utils/clipboard-read.ts +69 -0
- package/src/utils/export-post-process.test.ts +539 -0
- package/src/utils/export-post-process.ts +213 -0
- package/src/utils/http.test.ts +302 -0
- package/src/utils/http.ts +106 -0
- package/src/utils/image-registry.test.ts +88 -0
- package/src/utils/image-registry.ts +132 -0
- package/src/utils/image-utils.test.ts +58 -0
- package/src/utils/image-utils.ts +38 -0
- package/src/utils/os-metadata.ts +65 -0
- package/src/utils/sandbox.test.ts +98 -0
- package/src/utils/sandbox.ts +24 -0
- package/src/utils/session-metadata-store.test.ts +228 -0
- package/src/utils/session-metadata-store.ts +96 -0
- package/src/utils/spawn-ikie-subprocess.ts +57 -0
- package/src/utils/trace-id-export.test.ts +343 -0
- package/src/utils/trace-id-export.ts +84 -0
- package/src/utils.test.ts +38 -0
- package/src/utils.ts +65 -0
- package/test-cases/README.md +54 -0
- package/test-cases/bitcoin-price.md +52 -0
- package/test-cases/golang-question.md +29 -0
- package/test-cases/offender.md +32 -0
- package/test-cases/pi-mono-design.md +27 -0
- package/test-cases/pi-mono-tool-card-loop.txt +1381 -0
- package/test-cases/pi-mono-tool-card.md +60 -0
- package/test-cases/pokemon.md +27 -0
- package/test-cases/problem-740.md +20 -0
- package/test-cases/python-project.md +24 -0
- package/test-cases/remove-home-directory.md +53 -0
- package/test-cases/use-gh.md +25 -0
- package/tests/e2e/acp/all-capabilities.test.ts +102 -0
- package/tests/e2e/acp/elicitation-dialogs.test.ts +139 -0
- package/tests/e2e/acp/elicitation-only.test.ts +76 -0
- package/tests/e2e/acp/message-id.test.ts +139 -0
- package/tests/e2e/acp/no-capabilities.test.ts +71 -0
- package/tests/e2e/acp/permission-flow.test.ts +237 -0
- package/tests/e2e/acp/support/acp-fixture.ts +413 -0
- package/tests/e2e/acp/support/scenarios.ts +55 -0
- package/tests/e2e/acp/support/test-dialog-extension.js +22 -0
- package/tests/e2e/acp/support/test-ui-extension.js +14 -0
- package/tests/e2e/acp/vitest.config.ts +12 -0
- package/tests/e2e/tui/ask-user-form.test.ts +310 -0
- package/tests/e2e/tui/basic-session-lifecycle.test.ts +20 -0
- package/tests/e2e/tui/branch-resume.test.ts +71 -0
- package/tests/e2e/tui/cooking-animation.test.ts +213 -0
- package/tests/e2e/tui/editor-narrow-layout.test.ts +47 -0
- package/tests/e2e/tui/extension-custom-headers.test.ts +75 -0
- package/tests/e2e/tui/ferment-draft-proposal-persistence.test.ts +125 -0
- package/tests/e2e/tui/ferment-new-runs-planning.test.ts +211 -0
- package/tests/e2e/tui/ferment-phase-review.test.ts +78 -0
- package/tests/e2e/tui/ferment-plan-review-suppression.test.ts +404 -0
- package/tests/e2e/tui/multi-model-command.test.ts +624 -0
- package/tests/e2e/tui/nudge-wiring.test.ts +129 -0
- package/tests/e2e/tui/ollama-models.test.ts +143 -0
- package/tests/e2e/tui/oneshot-bypasses-dropdown.test.ts +97 -0
- package/tests/e2e/tui/plan-mode-tool-switch.test.ts +73 -0
- package/tests/e2e/tui/plan-review-dismissal.test.ts +291 -0
- package/tests/e2e/tui/plan-to-ferment-promo.test.ts +204 -0
- package/tests/e2e/tui/skip-list.js +9 -0
- package/tests/e2e/tui/support/assertions.ts +41 -0
- package/tests/e2e/tui/support/fake-ollama-server.ts +356 -0
- package/tests/e2e/tui/support/fake-openai-server.ts +361 -0
- package/tests/e2e/tui/support/ikie-fixture.ts +374 -0
- package/tests/e2e/tui/theme-selector.test.ts +99 -0
- package/tests/e2e/tui/todo-overlay.test.ts +163 -0
- package/tests/e2e/tui/todo-widget-ferment.test.ts +346 -0
- package/tests/e2e/tui/tui-test.config.ts +6 -0
- package/tests/smoke/agent-session-tracking.test.ts +203 -0
- package/tests/smoke/binary.test.ts +186 -0
- package/tests/smoke/ferment-oneshot-exit.test.ts +514 -0
- package/tests/smoke/harness.ts +236 -0
- package/tests/smoke/interactive-paste.test.ts +167 -0
- package/tests/smoke/vitest.config.ts +12 -0
- package/tests/smoke/web-fetch.test.ts +41 -0
- package/tests/smoke/web-search.test.ts +47 -0
- package/themes/catppuccin-macchiato.json +82 -0
- package/themes/dark.json +87 -0
- package/themes/dracula.json +79 -0
- package/themes/github-dark.json +80 -0
- package/themes/github-light.json +80 -0
- package/themes/ikie-light.json +92 -0
- package/themes/ikie-minimal.json +79 -0
- package/themes/ikie.json +96 -0
- package/themes/light.json +86 -0
- package/themes/lucent-orng.json +79 -0
- package/themes/monokai.json +78 -0
- package/themes/neon.json +87 -0
- package/themes/night-owl.json +87 -0
- package/themes/nord.json +82 -0
- package/themes/one-dark.json +79 -0
- package/themes/solarized-dark.json +81 -0
- package/themes/solarized-light.json +81 -0
- package/tools/proxy-helper/Makefile +11 -0
- package/tools/proxy-helper/cmd/proxy/proxy.go +211 -0
- package/tools/proxy-helper/cmd/proxy/proxy_test.go +309 -0
- package/tools/proxy-helper/go.mod +13 -0
- package/tools/proxy-helper/go.sum +12 -0
- package/tools/proxy-helper/main.go +29 -0
- package/tools/proxy-helper/pkg/cast/cast.go +287 -0
- package/tsconfig.json +17 -0
- package/vendor/superpowers/.claude-plugin/marketplace.json +20 -0
- package/vendor/superpowers/.claude-plugin/plugin.json +20 -0
- package/vendor/superpowers/.codex-plugin/plugin.json +47 -0
- package/vendor/superpowers/.cursor-plugin/plugin.json +25 -0
- package/vendor/superpowers/.gitattributes +18 -0
- package/vendor/superpowers/.github/FUNDING.yml +3 -0
- package/vendor/superpowers/.github/ISSUE_TEMPLATE/bug_report.md +52 -0
- package/vendor/superpowers/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/vendor/superpowers/.github/ISSUE_TEMPLATE/feature_request.md +34 -0
- package/vendor/superpowers/.github/ISSUE_TEMPLATE/platform_support.md +23 -0
- package/vendor/superpowers/.github/PULL_REQUEST_TEMPLATE.md +126 -0
- package/vendor/superpowers/.opencode/INSTALL.md +110 -0
- package/vendor/superpowers/.opencode/plugins/superpowers.js +135 -0
- package/vendor/superpowers/.version-bump.json +20 -0
- package/vendor/superpowers/CLAUDE.md +106 -0
- package/vendor/superpowers/CODE_OF_CONDUCT.md +128 -0
- package/vendor/superpowers/GEMINI.md +2 -0
- package/vendor/superpowers/LICENSE +21 -0
- package/vendor/superpowers/README.md +233 -0
- package/vendor/superpowers/RELEASE-NOTES.md +1180 -0
- package/vendor/superpowers/assets/app-icon.png +0 -0
- package/vendor/superpowers/assets/superpowers-small.svg +1 -0
- package/vendor/superpowers/docs/README.opencode.md +157 -0
- package/vendor/superpowers/docs/plans/2025-11-22-opencode-support-design.md +294 -0
- package/vendor/superpowers/docs/plans/2025-11-22-opencode-support-implementation.md +1095 -0
- package/vendor/superpowers/docs/plans/2025-11-28-skills-improvements-from-user-feedback.md +711 -0
- package/vendor/superpowers/docs/plans/2026-01-17-visual-brainstorming.md +571 -0
- package/vendor/superpowers/docs/superpowers/plans/2026-01-22-document-review-system.md +301 -0
- package/vendor/superpowers/docs/superpowers/plans/2026-02-19-visual-brainstorming-refactor.md +523 -0
- package/vendor/superpowers/docs/superpowers/plans/2026-03-11-zero-dep-brainstorm-server.md +479 -0
- package/vendor/superpowers/docs/superpowers/plans/2026-03-23-codex-app-compatibility.md +564 -0
- package/vendor/superpowers/docs/superpowers/plans/2026-04-06-worktree-rototill.md +879 -0
- package/vendor/superpowers/docs/superpowers/specs/2026-01-22-document-review-system-design.md +136 -0
- package/vendor/superpowers/docs/superpowers/specs/2026-02-19-visual-brainstorming-refactor-design.md +162 -0
- package/vendor/superpowers/docs/superpowers/specs/2026-03-11-zero-dep-brainstorm-server-design.md +118 -0
- package/vendor/superpowers/docs/superpowers/specs/2026-03-23-codex-app-compatibility-design.md +244 -0
- package/vendor/superpowers/docs/superpowers/specs/2026-04-06-worktree-rototill-design.md +342 -0
- package/vendor/superpowers/docs/testing.md +303 -0
- package/vendor/superpowers/docs/windows/polyglot-hooks.md +212 -0
- package/vendor/superpowers/gemini-extension.json +6 -0
- package/vendor/superpowers/hooks/hooks-cursor.json +10 -0
- package/vendor/superpowers/hooks/hooks.json +16 -0
- package/vendor/superpowers/hooks/run-hook.cmd +46 -0
- package/vendor/superpowers/hooks/session-start +57 -0
- package/vendor/superpowers/package.json +6 -0
- package/vendor/superpowers/scripts/bump-version.sh +220 -0
- package/vendor/superpowers/scripts/sync-to-codex-plugin.sh +462 -0
- package/vendor/superpowers/skills/brainstorming/SKILL.md +164 -0
- package/vendor/superpowers/skills/brainstorming/scripts/frame-template.html +214 -0
- package/vendor/superpowers/skills/brainstorming/scripts/helper.js +88 -0
- package/vendor/superpowers/skills/brainstorming/scripts/server.cjs +354 -0
- package/vendor/superpowers/skills/brainstorming/scripts/start-server.sh +148 -0
- package/vendor/superpowers/skills/brainstorming/scripts/stop-server.sh +56 -0
- package/vendor/superpowers/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/vendor/superpowers/skills/brainstorming/visual-companion.md +287 -0
- package/vendor/superpowers/skills/dispatching-parallel-agents/SKILL.md +182 -0
- package/vendor/superpowers/skills/executing-plans/SKILL.md +70 -0
- package/vendor/superpowers/skills/finishing-a-development-branch/SKILL.md +251 -0
- package/vendor/superpowers/skills/receiving-code-review/SKILL.md +213 -0
- package/vendor/superpowers/skills/requesting-code-review/SKILL.md +103 -0
- package/vendor/superpowers/skills/requesting-code-review/code-reviewer.md +168 -0
- package/vendor/superpowers/skills/subagent-driven-development/SKILL.md +279 -0
- package/vendor/superpowers/skills/subagent-driven-development/code-quality-reviewer-prompt.md +25 -0
- package/vendor/superpowers/skills/subagent-driven-development/implementer-prompt.md +113 -0
- package/vendor/superpowers/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
- package/vendor/superpowers/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/vendor/superpowers/skills/systematic-debugging/SKILL.md +296 -0
- package/vendor/superpowers/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/vendor/superpowers/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/vendor/superpowers/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/vendor/superpowers/skills/systematic-debugging/find-polluter.sh +63 -0
- package/vendor/superpowers/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/vendor/superpowers/skills/systematic-debugging/test-academic.md +14 -0
- package/vendor/superpowers/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/vendor/superpowers/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/vendor/superpowers/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/vendor/superpowers/skills/test-driven-development/SKILL.md +371 -0
- package/vendor/superpowers/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/vendor/superpowers/skills/using-git-worktrees/SKILL.md +215 -0
- package/vendor/superpowers/skills/using-superpowers/SKILL.md +117 -0
- package/vendor/superpowers/skills/using-superpowers/references/codex-tools.md +59 -0
- package/vendor/superpowers/skills/using-superpowers/references/copilot-tools.md +42 -0
- package/vendor/superpowers/skills/using-superpowers/references/gemini-tools.md +51 -0
- package/vendor/superpowers/skills/verification-before-completion/SKILL.md +139 -0
- package/vendor/superpowers/skills/writing-plans/SKILL.md +152 -0
- package/vendor/superpowers/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/vendor/superpowers/skills/writing-skills/SKILL.md +655 -0
- package/vendor/superpowers/skills/writing-skills/anthropic-best-practices.md +1150 -0
- package/vendor/superpowers/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/vendor/superpowers/skills/writing-skills/graphviz-conventions.dot +172 -0
- package/vendor/superpowers/skills/writing-skills/persuasion-principles.md +187 -0
- package/vendor/superpowers/skills/writing-skills/render-graphs.js +168 -0
- package/vendor/superpowers/skills/writing-skills/testing-skills-with-subagents.md +384 -0
- package/vendor/superpowers/tests/brainstorm-server/package-lock.json +36 -0
- package/vendor/superpowers/tests/brainstorm-server/package.json +10 -0
- package/vendor/superpowers/tests/brainstorm-server/server.test.js +427 -0
- package/vendor/superpowers/tests/brainstorm-server/windows-lifecycle.test.sh +351 -0
- package/vendor/superpowers/tests/brainstorm-server/ws-protocol.test.js +392 -0
- package/vendor/superpowers/tests/claude-code/README.md +170 -0
- package/vendor/superpowers/tests/claude-code/analyze-token-usage.py +168 -0
- package/vendor/superpowers/tests/claude-code/run-skill-tests.sh +188 -0
- package/vendor/superpowers/tests/claude-code/test-document-review-system.sh +177 -0
- package/vendor/superpowers/tests/claude-code/test-helpers.sh +202 -0
- package/vendor/superpowers/tests/claude-code/test-requesting-code-review.sh +214 -0
- package/vendor/superpowers/tests/claude-code/test-subagent-driven-development-integration.sh +322 -0
- package/vendor/superpowers/tests/claude-code/test-subagent-driven-development.sh +165 -0
- package/vendor/superpowers/tests/claude-code/test-worktree-native-preference.sh +176 -0
- package/vendor/superpowers/tests/codex-plugin-sync/test-sync-to-codex-plugin.sh +615 -0
- package/vendor/superpowers/tests/explicit-skill-requests/prompts/action-oriented.txt +3 -0
- package/vendor/superpowers/tests/explicit-skill-requests/prompts/after-planning-flow.txt +17 -0
- package/vendor/superpowers/tests/explicit-skill-requests/prompts/claude-suggested-it.txt +11 -0
- package/vendor/superpowers/tests/explicit-skill-requests/prompts/i-know-what-sdd-means.txt +8 -0
- package/vendor/superpowers/tests/explicit-skill-requests/prompts/mid-conversation-execute-plan.txt +3 -0
- package/vendor/superpowers/tests/explicit-skill-requests/prompts/please-use-brainstorming.txt +1 -0
- package/vendor/superpowers/tests/explicit-skill-requests/prompts/skip-formalities.txt +3 -0
- package/vendor/superpowers/tests/explicit-skill-requests/prompts/subagent-driven-development-please.txt +1 -0
- package/vendor/superpowers/tests/explicit-skill-requests/prompts/use-systematic-debugging.txt +1 -0
- package/vendor/superpowers/tests/explicit-skill-requests/run-all.sh +70 -0
- package/vendor/superpowers/tests/explicit-skill-requests/run-claude-describes-sdd.sh +100 -0
- package/vendor/superpowers/tests/explicit-skill-requests/run-extended-multiturn-test.sh +113 -0
- package/vendor/superpowers/tests/explicit-skill-requests/run-haiku-test.sh +144 -0
- package/vendor/superpowers/tests/explicit-skill-requests/run-multiturn-test.sh +143 -0
- package/vendor/superpowers/tests/explicit-skill-requests/run-test.sh +136 -0
- package/vendor/superpowers/tests/opencode/run-tests.sh +165 -0
- package/vendor/superpowers/tests/opencode/setup.sh +88 -0
- package/vendor/superpowers/tests/opencode/test-bootstrap-caching.mjs +124 -0
- package/vendor/superpowers/tests/opencode/test-bootstrap-caching.sh +32 -0
- package/vendor/superpowers/tests/opencode/test-plugin-loading.sh +82 -0
- package/vendor/superpowers/tests/opencode/test-priority.sh +217 -0
- package/vendor/superpowers/tests/opencode/test-tools.sh +95 -0
- package/vendor/superpowers/tests/skill-triggering/prompts/dispatching-parallel-agents.txt +8 -0
- package/vendor/superpowers/tests/skill-triggering/prompts/executing-plans.txt +1 -0
- package/vendor/superpowers/tests/skill-triggering/prompts/requesting-code-review.txt +3 -0
- package/vendor/superpowers/tests/skill-triggering/prompts/systematic-debugging.txt +11 -0
- package/vendor/superpowers/tests/skill-triggering/prompts/test-driven-development.txt +7 -0
- package/vendor/superpowers/tests/skill-triggering/prompts/writing-plans.txt +10 -0
- package/vendor/superpowers/tests/skill-triggering/run-all.sh +60 -0
- package/vendor/superpowers/tests/skill-triggering/run-test.sh +88 -0
- package/vendor/superpowers/tests/subagent-driven-dev/go-fractals/design.md +81 -0
- package/vendor/superpowers/tests/subagent-driven-dev/go-fractals/plan.md +172 -0
- package/vendor/superpowers/tests/subagent-driven-dev/go-fractals/scaffold.sh +45 -0
- package/vendor/superpowers/tests/subagent-driven-dev/run-test.sh +106 -0
- package/vendor/superpowers/tests/subagent-driven-dev/svelte-todo/design.md +70 -0
- package/vendor/superpowers/tests/subagent-driven-dev/svelte-todo/plan.md +222 -0
- package/vendor/superpowers/tests/subagent-driven-dev/svelte-todo/scaffold.sh +46 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,4392 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"
|
|
2
|
+
import { tmpdir } from "node:os"
|
|
3
|
+
import { join, resolve } from "node:path"
|
|
4
|
+
import type {
|
|
5
|
+
AgentSideConnection,
|
|
6
|
+
ListSessionsRequest,
|
|
7
|
+
RequestPermissionRequest,
|
|
8
|
+
SessionNotification,
|
|
9
|
+
TextContent,
|
|
10
|
+
} from "@agentclientprotocol/sdk"
|
|
11
|
+
import type { AssistantMessage } from "@earendil-works/pi-ai"
|
|
12
|
+
import type {
|
|
13
|
+
AgentSession,
|
|
14
|
+
AgentSessionEvent,
|
|
15
|
+
AgentSessionEventListener,
|
|
16
|
+
AuthStorage,
|
|
17
|
+
ExtensionContext,
|
|
18
|
+
ExtensionUIContext,
|
|
19
|
+
ModelRegistry,
|
|
20
|
+
SessionInfo as PiSessionInfo,
|
|
21
|
+
SessionManager,
|
|
22
|
+
Theme,
|
|
23
|
+
} from "@earendil-works/pi-coding-agent"
|
|
24
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
|
25
|
+
|
|
26
|
+
const THEME_KEY = Symbol.for("@earendil-works/pi-coding-agent:theme")
|
|
27
|
+
const THEME_KEY_OLD = Symbol.for("@mariozechner/pi-coding-agent:theme")
|
|
28
|
+
|
|
29
|
+
import { _resetState as _resetHideThinking, _setHideThinking } from "../../extensions/hide-thinking.js"
|
|
30
|
+
import { PERMISSIONS_ENV_KEY } from "../../extensions/permissions/constants.js"
|
|
31
|
+
import { getSessionPermissionFlagController } from "../../extensions/permissions/mode-controller-registry.js"
|
|
32
|
+
import { ALL_PERMISSION_MODES } from "../../extensions/permissions/types.js"
|
|
33
|
+
import { getAcpPrompter } from "./permission-prompter-registry.js"
|
|
34
|
+
import {
|
|
35
|
+
type AcpSessionFactory,
|
|
36
|
+
type AcpSessionLister,
|
|
37
|
+
type AcpSessionLoader,
|
|
38
|
+
IkieAcpAgent,
|
|
39
|
+
assertSessionHasModel,
|
|
40
|
+
buildSessionModelState,
|
|
41
|
+
describeToolCall,
|
|
42
|
+
initializeHeadlessTheme,
|
|
43
|
+
isHiddenToolCall,
|
|
44
|
+
shouldEmitThinking,
|
|
45
|
+
stripAnsi,
|
|
46
|
+
toAcpSessionInfo,
|
|
47
|
+
userMessageText,
|
|
48
|
+
} from "./server.js"
|
|
49
|
+
|
|
50
|
+
// Minimal fake of AgentSession surface used by IkieAcpAgent. The factory seam
|
|
51
|
+
// means we only need to stand in for the methods the ACP server actually calls:
|
|
52
|
+
// sessionId, subscribe, prompt, abort, dispose. loadSession also reads
|
|
53
|
+
// `sessionManager.getBranch()` for replay and `model` for the response, so the
|
|
54
|
+
// fake exposes settable fields for both.
|
|
55
|
+
class FakeAgentSession {
|
|
56
|
+
readonly sessionId: string
|
|
57
|
+
private listeners = new Set<AgentSessionEventListener>()
|
|
58
|
+
disposed = false
|
|
59
|
+
aborted = false
|
|
60
|
+
model: { provider: string; id: string; name?: string; input?: string[] } | undefined = {
|
|
61
|
+
provider: "test",
|
|
62
|
+
id: "test-model",
|
|
63
|
+
name: "Test Model",
|
|
64
|
+
input: ["text"],
|
|
65
|
+
}
|
|
66
|
+
modelRegistry = {
|
|
67
|
+
getAvailable: () =>
|
|
68
|
+
this.model
|
|
69
|
+
? [
|
|
70
|
+
{
|
|
71
|
+
provider: this.model.provider,
|
|
72
|
+
id: this.model.id,
|
|
73
|
+
name: this.model.name ?? this.model.id,
|
|
74
|
+
},
|
|
75
|
+
]
|
|
76
|
+
: [],
|
|
77
|
+
}
|
|
78
|
+
promptImpl: (text: string, opts?: { images?: unknown[] }) => Promise<void> = async () => {}
|
|
79
|
+
abortImpl: () => Promise<void> = async () => {}
|
|
80
|
+
bindExtensionsImpl: (_bindings: unknown) => Promise<void> = async () => {}
|
|
81
|
+
lastPromptImages?: unknown[]
|
|
82
|
+
promptCalls: Array<{ prompt: string; opts?: { images?: unknown[] } }> = []
|
|
83
|
+
// Branch entries returned to the replay walker. Tests fill this with the
|
|
84
|
+
// shape buildSessionContext consumers expect (type:"message" + role).
|
|
85
|
+
branch: unknown[] = []
|
|
86
|
+
sessionManager = {
|
|
87
|
+
getBranch: () => this.branch,
|
|
88
|
+
}
|
|
89
|
+
// Captures whatever setUIContext the agent installs so tests can assert
|
|
90
|
+
// on it. The real AgentSession exposes this via its extensionRunner
|
|
91
|
+
// getter; the fake keeps it on the session root for simpler test wiring.
|
|
92
|
+
setUIContextCalls: unknown[] = []
|
|
93
|
+
extensionRunner = {
|
|
94
|
+
setUIContext: (ui: unknown) => {
|
|
95
|
+
this.setUIContextCalls.push(ui)
|
|
96
|
+
},
|
|
97
|
+
emit: async (_event: unknown) => {
|
|
98
|
+
// Real runner exposes emit for session lifecycle events; the fake
|
|
99
|
+
// just resolves so disposeSessionRecord's session_shutdown hook
|
|
100
|
+
// doesn't throw.
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
constructor(sessionId: string) {
|
|
105
|
+
this.sessionId = sessionId
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
subscribe(listener: AgentSessionEventListener): () => void {
|
|
109
|
+
this.listeners.add(listener)
|
|
110
|
+
return () => {
|
|
111
|
+
this.listeners.delete(listener)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
emit(event: AgentSessionEvent): void {
|
|
116
|
+
for (const l of [...this.listeners]) l(event)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async prompt(text: string, opts?: { images?: unknown[] }): Promise<void> {
|
|
120
|
+
this.lastPromptImages = opts?.images
|
|
121
|
+
this.promptCalls.push({ prompt: text, opts })
|
|
122
|
+
await this.promptImpl(text, opts)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async setModel(model: { provider: string; id: string }): Promise<void> {
|
|
126
|
+
this.model = model
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async abort(): Promise<void> {
|
|
130
|
+
this.aborted = true
|
|
131
|
+
await this.abortImpl()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async bindExtensions(bindings: unknown): Promise<void> {
|
|
135
|
+
await this.bindExtensionsImpl(bindings)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
dispose(): void {
|
|
139
|
+
this.disposed = true
|
|
140
|
+
this.listeners.clear()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function asSession(fake: FakeAgentSession): AgentSession {
|
|
145
|
+
return fake as unknown as AgentSession
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function makeConn(): AgentSideConnection {
|
|
149
|
+
const stub = {
|
|
150
|
+
sessionUpdate: async (_p: SessionNotification) => {},
|
|
151
|
+
}
|
|
152
|
+
return stub as unknown as AgentSideConnection
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Recording variant of makeConn: captures every sessionUpdate the agent emits
|
|
156
|
+
// so tests can assert on the full notification stream (tool_call, partial
|
|
157
|
+
// tool_call_update, terminal tool_call_update, etc.).
|
|
158
|
+
function makeRecordingConn(): {
|
|
159
|
+
conn: AgentSideConnection
|
|
160
|
+
updates: SessionNotification[]
|
|
161
|
+
} {
|
|
162
|
+
const updates: SessionNotification[] = []
|
|
163
|
+
const stub = {
|
|
164
|
+
sessionUpdate: async (p: SessionNotification) => {
|
|
165
|
+
updates.push(p)
|
|
166
|
+
},
|
|
167
|
+
requestPermission: vi.fn().mockResolvedValue({ outcome: "cancelled" }),
|
|
168
|
+
}
|
|
169
|
+
return { conn: stub as unknown as AgentSideConnection, updates }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms))
|
|
173
|
+
|
|
174
|
+
function agentEnd(): AgentSessionEvent {
|
|
175
|
+
return { type: "agent_end", messages: [], willRetry: false }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Drop the incidental `available_commands_update` re-broadcast on session
|
|
179
|
+
// resume so replay tests can assert on transcript shape alone.
|
|
180
|
+
function replayOnly(updates: SessionNotification[]): SessionNotification[] {
|
|
181
|
+
return updates.filter((u) => u.update.sessionUpdate !== "available_commands_update")
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
describe("IkieAcpAgent turn lifecycle", () => {
|
|
185
|
+
let fake: FakeAgentSession
|
|
186
|
+
let agent: IkieAcpAgent
|
|
187
|
+
let sessionId: string
|
|
188
|
+
|
|
189
|
+
beforeEach(async () => {
|
|
190
|
+
fake = new FakeAgentSession("session-a")
|
|
191
|
+
const sessionFactory: AcpSessionFactory = async () => asSession(fake)
|
|
192
|
+
agent = new IkieAcpAgent(makeConn(), {
|
|
193
|
+
extensionFactories: [],
|
|
194
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
195
|
+
sessionFactory,
|
|
196
|
+
})
|
|
197
|
+
const res = await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
198
|
+
sessionId = res.sessionId
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
// initialize() should declare image support based on cached models.json
|
|
202
|
+
describe("initialize capability detection", () => {
|
|
203
|
+
const tempAgentDir = "/tmp/ikie-acp-test-agent-dir"
|
|
204
|
+
|
|
205
|
+
beforeEach(() => {
|
|
206
|
+
// Clean up and create temp agent dir
|
|
207
|
+
try {
|
|
208
|
+
rmSync(tempAgentDir, { recursive: true, force: true })
|
|
209
|
+
} catch {}
|
|
210
|
+
mkdirSync(tempAgentDir, { recursive: true })
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
afterEach(() => {
|
|
214
|
+
try {
|
|
215
|
+
rmSync(tempAgentDir, { recursive: true, force: true })
|
|
216
|
+
} catch {}
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it("declares image: true when cached models include vision models", async () => {
|
|
220
|
+
// Stub API key so models are considered "available" (configured auth)
|
|
221
|
+
const restoreEnv = (key: string, value: string | undefined) => {
|
|
222
|
+
const original = process.env[key]
|
|
223
|
+
process.env[key] = value
|
|
224
|
+
return () => {
|
|
225
|
+
process.env[key] = original
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const cleanup = restoreEnv("OPENAI_API_KEY", "fake-key-for-testing")
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
const modelsJson = {
|
|
232
|
+
providers: {
|
|
233
|
+
openai: {
|
|
234
|
+
models: [
|
|
235
|
+
{
|
|
236
|
+
id: "gpt-4o",
|
|
237
|
+
name: "GPT-4o",
|
|
238
|
+
input: ["text", "image"],
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
}
|
|
244
|
+
writeFileSync(resolve(tempAgentDir, "models.json"), JSON.stringify(modelsJson))
|
|
245
|
+
|
|
246
|
+
const testAgent = new IkieAcpAgent(makeConn(), {
|
|
247
|
+
extensionFactories: [],
|
|
248
|
+
agentDir: tempAgentDir,
|
|
249
|
+
sessionFactory: async () => asSession(fake),
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
const response = await testAgent.initialize({ protocolVersion: 1 })
|
|
253
|
+
expect(response.agentCapabilities?.promptCapabilities?.image).toBe(true)
|
|
254
|
+
} finally {
|
|
255
|
+
cleanup()
|
|
256
|
+
}
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it("declares image capability based on available models", async () => {
|
|
260
|
+
// ModelRegistry merges cached models with built-in defaults.
|
|
261
|
+
// This test verifies the initialize() method correctly queries
|
|
262
|
+
// the registry and returns a boolean for image support.
|
|
263
|
+
const testAgent = new IkieAcpAgent(makeConn(), {
|
|
264
|
+
extensionFactories: [],
|
|
265
|
+
agentDir: tempAgentDir,
|
|
266
|
+
sessionFactory: async () => asSession(fake),
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
const response = await testAgent.initialize({ protocolVersion: 1 })
|
|
270
|
+
// The result depends on merged models (cached + built-in).
|
|
271
|
+
// Just verify it's a boolean (the logic ran successfully).
|
|
272
|
+
expect(typeof response.agentCapabilities?.promptCapabilities?.image).toBe("boolean")
|
|
273
|
+
})
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
// session.prompt() is the source of truth for "turn is done". When events
|
|
277
|
+
// arrive AFTER session.prompt resolves (e.g. a slow downstream handler
|
|
278
|
+
// awaited something), the turn was already finalized on session.prompt
|
|
279
|
+
// resolve — late events must be dropped. agent_end no longer drives
|
|
280
|
+
// finalization, so it cannot be used as a barrier. If this ever regresses
|
|
281
|
+
// to "wait for late agent_end", we'd be vulnerable to the chained
|
|
282
|
+
// agent.continue() bug again.
|
|
283
|
+
it("drops late turn events that arrive after session.prompt resolves", async () => {
|
|
284
|
+
let lateEventsFired = false
|
|
285
|
+
fake.promptImpl = async () => {
|
|
286
|
+
await delay(5)
|
|
287
|
+
setTimeout(() => {
|
|
288
|
+
fake.emit({ type: "agent_start" })
|
|
289
|
+
fake.emit(agentEnd())
|
|
290
|
+
lateEventsFired = true
|
|
291
|
+
}, 30)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const result = await agent.prompt({
|
|
295
|
+
sessionId,
|
|
296
|
+
prompt: [{ type: "text", text: "hi" }],
|
|
297
|
+
})
|
|
298
|
+
expect(result.stopReason).toBe("end_turn")
|
|
299
|
+
expect(lateEventsFired).toBe(false)
|
|
300
|
+
|
|
301
|
+
await delay(40)
|
|
302
|
+
expect(lateEventsFired).toBe(true)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
// Extension-command / input-handler / no-op path: session.prompt returns
|
|
306
|
+
// without emitting any agent events. The ACP handler must synthesize
|
|
307
|
+
// end_turn itself — no agent_end is ever coming.
|
|
308
|
+
it("synthesizes end_turn when the turn short-circuits without agent_start", async () => {
|
|
309
|
+
fake.promptImpl = async () => {
|
|
310
|
+
// No events emitted — exactly like an extension-command path.
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const result = await agent.prompt({
|
|
314
|
+
sessionId,
|
|
315
|
+
prompt: [{ type: "text", text: "/help" }],
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
expect(result.stopReason).toBe("end_turn")
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
// Regression: pi-mono's _runAgentPrompt chains multiple agent.prompt /
|
|
322
|
+
// agent.continue calls when retries, queued follow-up messages, or
|
|
323
|
+
// compaction are pending. Each chained call emits its own agent_start +
|
|
324
|
+
// agent_end pair. Previously the ACP handler finalized on the FIRST
|
|
325
|
+
// agent_end, sending end_turn mid-stream — the client then tried to send
|
|
326
|
+
// a new prompt and hit pi-mono's "Agent is already processing" throw
|
|
327
|
+
// because session.prompt was still running the chained continues.
|
|
328
|
+
// Now: end_turn is sent exactly once, only after session.prompt()
|
|
329
|
+
// resolves (i.e. after ALL chained calls complete).
|
|
330
|
+
it("sends exactly one end_turn after chained agent.continue() cycles complete", async () => {
|
|
331
|
+
const { conn: recordingConn, updates } = makeRecordingConn()
|
|
332
|
+
const localAgent = new IkieAcpAgent(recordingConn, {
|
|
333
|
+
extensionFactories: [],
|
|
334
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
335
|
+
sessionFactory: async () => asSession(fake),
|
|
336
|
+
})
|
|
337
|
+
const res = await localAgent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
338
|
+
const localSessionId = res.sessionId
|
|
339
|
+
|
|
340
|
+
fake.promptImpl = async () => {
|
|
341
|
+
// Cycle 1 — first agent.prompt call.
|
|
342
|
+
fake.emit({ type: "agent_start" })
|
|
343
|
+
fake.emit({
|
|
344
|
+
type: "message_update",
|
|
345
|
+
assistantMessageEvent: {
|
|
346
|
+
type: "text_delta",
|
|
347
|
+
delta: "first",
|
|
348
|
+
contentIndex: 0,
|
|
349
|
+
partial: {} as unknown as AssistantMessage,
|
|
350
|
+
},
|
|
351
|
+
message: {} as unknown as AssistantMessage,
|
|
352
|
+
})
|
|
353
|
+
fake.emit(agentEnd())
|
|
354
|
+
await delay(5)
|
|
355
|
+
// Cycle 2 — agent.continue() (e.g. follow-up message queued,
|
|
356
|
+
// retry needed, or compaction). The user-visible bug is that the
|
|
357
|
+
// FIRST agent_end used to trigger end_turn here, dropping cycle 2.
|
|
358
|
+
fake.emit({ type: "agent_start" })
|
|
359
|
+
fake.emit({
|
|
360
|
+
type: "message_update",
|
|
361
|
+
assistantMessageEvent: {
|
|
362
|
+
type: "text_delta",
|
|
363
|
+
delta: "second",
|
|
364
|
+
contentIndex: 0,
|
|
365
|
+
partial: {} as unknown as AssistantMessage,
|
|
366
|
+
},
|
|
367
|
+
message: {} as unknown as AssistantMessage,
|
|
368
|
+
})
|
|
369
|
+
fake.emit(agentEnd())
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const result = await localAgent.prompt({
|
|
373
|
+
sessionId: localSessionId,
|
|
374
|
+
prompt: [{ type: "text", text: "go" }],
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
// Single end_turn, with stopReason "end_turn".
|
|
378
|
+
expect(result.stopReason).toBe("end_turn")
|
|
379
|
+
|
|
380
|
+
// Both cycles' chunks reach the client. Under the OLD behavior,
|
|
381
|
+
// the first agent_end would have cleared entry.turn and dropped
|
|
382
|
+
// every cycle-2 event — so this assertion is the bug-reproducer.
|
|
383
|
+
const chunks = updates.flatMap((u) =>
|
|
384
|
+
u.update.sessionUpdate === "agent_message_chunk" ? [(u.update.content as TextContent).text] : [],
|
|
385
|
+
)
|
|
386
|
+
expect(chunks).toEqual(["first", "second"])
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
// Cancel arrives between chained agent.continue() cycles. Cycle 1 completes
|
|
390
|
+
// normally (agent_start → message_update → agent_end), then the client calls
|
|
391
|
+
// cancel() before cycle 2 starts. session.prompt() resolves (abort may or may
|
|
392
|
+
// not prevent the next continue), and the .then() handler sees cancelled=true.
|
|
393
|
+
// The first cycle's chunks must still reach the client; the result must be
|
|
394
|
+
// "cancelled", not "end_turn".
|
|
395
|
+
it("resolves cancelled when cancel arrives between chained agent.continue() cycles", async () => {
|
|
396
|
+
const { conn: recordingConn, updates } = makeRecordingConn()
|
|
397
|
+
const localAgent = new IkieAcpAgent(recordingConn, {
|
|
398
|
+
extensionFactories: [],
|
|
399
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
400
|
+
sessionFactory: async () => asSession(fake),
|
|
401
|
+
})
|
|
402
|
+
const res = await localAgent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
403
|
+
const localSessionId = res.sessionId
|
|
404
|
+
|
|
405
|
+
let cancelSeen = false
|
|
406
|
+
fake.promptImpl = async () => {
|
|
407
|
+
// Cycle 1 — completes normally.
|
|
408
|
+
fake.emit({ type: "agent_start" })
|
|
409
|
+
fake.emit({
|
|
410
|
+
type: "message_update",
|
|
411
|
+
assistantMessageEvent: {
|
|
412
|
+
type: "text_delta",
|
|
413
|
+
delta: "before-cancel",
|
|
414
|
+
contentIndex: 0,
|
|
415
|
+
partial: {} as unknown as AssistantMessage,
|
|
416
|
+
},
|
|
417
|
+
message: {} as unknown as AssistantMessage,
|
|
418
|
+
})
|
|
419
|
+
fake.emit(agentEnd())
|
|
420
|
+
|
|
421
|
+
// Simulate the async boundary between chained calls where
|
|
422
|
+
// _handlePostAgentRun decides whether to continue. The client's
|
|
423
|
+
// cancel() lands here.
|
|
424
|
+
while (!cancelSeen) await delay(5)
|
|
425
|
+
|
|
426
|
+
// Cycle 2 — would have run, but abort arrived. In real pi-mono the
|
|
427
|
+
// next agent.continue() may not even start; simulate the benign case
|
|
428
|
+
// where it does start but produces nothing meaningful before abort
|
|
429
|
+
// tears it down.
|
|
430
|
+
fake.emit({ type: "agent_start" })
|
|
431
|
+
fake.emit(agentEnd())
|
|
432
|
+
}
|
|
433
|
+
fake.abortImpl = async () => {
|
|
434
|
+
cancelSeen = true
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const promptP = localAgent.prompt({
|
|
438
|
+
sessionId: localSessionId,
|
|
439
|
+
prompt: [{ type: "text", text: "go" }],
|
|
440
|
+
})
|
|
441
|
+
// Let cycle 1 complete and the promptImpl pause at the while-loop.
|
|
442
|
+
await delay(20)
|
|
443
|
+
await localAgent.cancel({ sessionId: localSessionId })
|
|
444
|
+
|
|
445
|
+
const result = await promptP
|
|
446
|
+
expect(result.stopReason).toBe("cancelled")
|
|
447
|
+
expect(fake.aborted).toBe(true)
|
|
448
|
+
|
|
449
|
+
// Cycle 1's chunk must have been delivered before cancellation.
|
|
450
|
+
const chunks = updates.flatMap((u) =>
|
|
451
|
+
u.update.sessionUpdate === "agent_message_chunk" ? [(u.update.content as TextContent).text] : [],
|
|
452
|
+
)
|
|
453
|
+
expect(chunks).toContain("before-cancel")
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
// Client cancels mid-turn: cancelled=true is set on the turn context, then
|
|
457
|
+
// agent_end fires and the subscriber finalizes with stopReason=cancelled.
|
|
458
|
+
it("resolves cancelled when cancel fires before agent_end", async () => {
|
|
459
|
+
let cancelSeen = false
|
|
460
|
+
fake.promptImpl = async () => {
|
|
461
|
+
fake.emit({ type: "agent_start" })
|
|
462
|
+
// Wait until cancel() runs.
|
|
463
|
+
while (!cancelSeen) await delay(5)
|
|
464
|
+
// pi-mono's abort path still emits agent_end on teardown.
|
|
465
|
+
fake.emit(agentEnd())
|
|
466
|
+
}
|
|
467
|
+
fake.abortImpl = async () => {
|
|
468
|
+
cancelSeen = true
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const promptP = agent.prompt({
|
|
472
|
+
sessionId,
|
|
473
|
+
prompt: [{ type: "text", text: "run forever" }],
|
|
474
|
+
})
|
|
475
|
+
// Give the prompt a moment to arm the turn context.
|
|
476
|
+
await delay(10)
|
|
477
|
+
await agent.cancel({ sessionId })
|
|
478
|
+
|
|
479
|
+
const result = await promptP
|
|
480
|
+
expect(result.stopReason).toBe("cancelled")
|
|
481
|
+
expect(fake.aborted).toBe(true)
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
// Cancel path where pi-mono surfaces abortion as a rejection instead of a
|
|
485
|
+
// final agent_end: the RPC contract still demands stopReason="cancelled",
|
|
486
|
+
// not a JSON-RPC error. The prompt() catch block must honor cancelled=true
|
|
487
|
+
// and resolve, not reject.
|
|
488
|
+
it("resolves cancelled when session.prompt rejects after cancel", async () => {
|
|
489
|
+
let cancelSeen = false
|
|
490
|
+
fake.promptImpl = async () => {
|
|
491
|
+
fake.emit({ type: "agent_start" })
|
|
492
|
+
while (!cancelSeen) await delay(5)
|
|
493
|
+
// Simulate pi-mono's "abort throws out of prompt()" variant — no
|
|
494
|
+
// agent_end is emitted before the rejection.
|
|
495
|
+
throw new Error("AbortError: operation was aborted")
|
|
496
|
+
}
|
|
497
|
+
fake.abortImpl = async () => {
|
|
498
|
+
cancelSeen = true
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const promptP = agent.prompt({
|
|
502
|
+
sessionId,
|
|
503
|
+
prompt: [{ type: "text", text: "run forever" }],
|
|
504
|
+
})
|
|
505
|
+
await delay(10)
|
|
506
|
+
await agent.cancel({ sessionId })
|
|
507
|
+
|
|
508
|
+
const result = await promptP
|
|
509
|
+
expect(result.stopReason).toBe("cancelled")
|
|
510
|
+
expect(fake.aborted).toBe(true)
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
// If session.prompt throws (pre-turn validation, config error, etc.), the
|
|
514
|
+
// outer RPC promise must reject — not hang — regardless of whether any
|
|
515
|
+
// events were emitted before the throw.
|
|
516
|
+
it("rejects the outer prompt when session.prompt throws", async () => {
|
|
517
|
+
fake.promptImpl = async () => {
|
|
518
|
+
throw new Error("no model configured")
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
await expect(agent.prompt({ sessionId, prompt: [{ type: "text", text: "x" }] })).rejects.toThrow(
|
|
522
|
+
/no model configured/,
|
|
523
|
+
)
|
|
524
|
+
})
|
|
525
|
+
|
|
526
|
+
// Chained continues: cycle 1 succeeds but cycle 2's agent.continue() throws
|
|
527
|
+
// a non-abort error (e.g. context overflow during compaction retry). The
|
|
528
|
+
// .catch() handler must propagate the error since cancelled is false — the
|
|
529
|
+
// client sees a JSON-RPC error, not a silent end_turn that hides the failure.
|
|
530
|
+
it("rejects the outer prompt when a chained agent.continue() throws a non-abort error", async () => {
|
|
531
|
+
fake.promptImpl = async () => {
|
|
532
|
+
// Cycle 1 — completes normally.
|
|
533
|
+
fake.emit({ type: "agent_start" })
|
|
534
|
+
fake.emit({
|
|
535
|
+
type: "message_update",
|
|
536
|
+
assistantMessageEvent: {
|
|
537
|
+
type: "text_delta",
|
|
538
|
+
delta: "partial",
|
|
539
|
+
contentIndex: 0,
|
|
540
|
+
partial: {} as unknown as AssistantMessage,
|
|
541
|
+
},
|
|
542
|
+
message: {} as unknown as AssistantMessage,
|
|
543
|
+
})
|
|
544
|
+
fake.emit(agentEnd())
|
|
545
|
+
await delay(5)
|
|
546
|
+
// Cycle 2 — blows up.
|
|
547
|
+
fake.emit({ type: "agent_start" })
|
|
548
|
+
throw new Error("context window overflow during compaction")
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
await expect(agent.prompt({ sessionId, prompt: [{ type: "text", text: "go" }] })).rejects.toThrow(
|
|
552
|
+
/context window overflow during compaction/,
|
|
553
|
+
)
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
// shutdown() must not leave pending PromptResponse promises dangling.
|
|
557
|
+
// When the caller awaits shutdown (e.g. runAcpMode's finally after
|
|
558
|
+
// conn.closed resolves) an in-flight turn must be rejected so the prompt
|
|
559
|
+
// caller's await settles rather than hanging until process exit.
|
|
560
|
+
it("rejects in-flight turns when shutdown() is called", async () => {
|
|
561
|
+
let resumePrompt!: () => void
|
|
562
|
+
const pending = new Promise<void>((resolve) => {
|
|
563
|
+
resumePrompt = resolve
|
|
564
|
+
})
|
|
565
|
+
fake.promptImpl = async () => {
|
|
566
|
+
fake.emit({ type: "agent_start" })
|
|
567
|
+
await pending // never resolves on its own in this test
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const promptP = agent.prompt({
|
|
571
|
+
sessionId,
|
|
572
|
+
prompt: [{ type: "text", text: "hang forever" }],
|
|
573
|
+
})
|
|
574
|
+
// Pre-attach catch handler so the rejection fires synchronously during
|
|
575
|
+
// shutdown without landing as an unhandled rejection.
|
|
576
|
+
const caught = promptP.catch((err) => err)
|
|
577
|
+
// Arm the turn.
|
|
578
|
+
await delay(10)
|
|
579
|
+
|
|
580
|
+
await agent.shutdown()
|
|
581
|
+
const err = await caught
|
|
582
|
+
expect(err).toBeInstanceOf(Error)
|
|
583
|
+
expect((err as Error).message).toMatch(/shutting down/)
|
|
584
|
+
// Cleanup the dangling promptImpl.
|
|
585
|
+
resumePrompt()
|
|
586
|
+
expect(fake.disposed).toBe(true)
|
|
587
|
+
})
|
|
588
|
+
|
|
589
|
+
// Misbehaving client sends a block type our capabilities declared as
|
|
590
|
+
// unsupported (image/audio/embeddedContext). The server drops it silently
|
|
591
|
+
// from the text payload but must warn on stderr so a dev debugging the
|
|
592
|
+
// resulting empty-turn sees what happened. Warn exactly once per type.
|
|
593
|
+
it("warns once on stderr for unsupported prompt block types", async () => {
|
|
594
|
+
const writes: string[] = []
|
|
595
|
+
const origWrite = process.stderr.write.bind(process.stderr)
|
|
596
|
+
// biome-ignore lint/suspicious/noExplicitAny: test-only stderr capture
|
|
597
|
+
;(process.stderr.write as any) = (chunk: string | Uint8Array) => {
|
|
598
|
+
writes.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"))
|
|
599
|
+
return true
|
|
600
|
+
}
|
|
601
|
+
try {
|
|
602
|
+
const r1 = await agent.prompt({
|
|
603
|
+
sessionId,
|
|
604
|
+
// biome-ignore lint/suspicious/noExplicitAny: unsupported block on purpose
|
|
605
|
+
prompt: [{ type: "audio" as any, data: "x" } as any],
|
|
606
|
+
})
|
|
607
|
+
expect(r1.stopReason).toBe("end_turn")
|
|
608
|
+
// Second call with same unsupported type: no new warning (deduped).
|
|
609
|
+
const r2 = await agent.prompt({
|
|
610
|
+
sessionId,
|
|
611
|
+
// biome-ignore lint/suspicious/noExplicitAny: unsupported block on purpose
|
|
612
|
+
prompt: [{ type: "audio" as any, data: "y" } as any],
|
|
613
|
+
})
|
|
614
|
+
expect(r2.stopReason).toBe("end_turn")
|
|
615
|
+
// New unsupported type: warns again.
|
|
616
|
+
const r3 = await agent.prompt({
|
|
617
|
+
sessionId,
|
|
618
|
+
// biome-ignore lint/suspicious/noExplicitAny: unsupported block on purpose
|
|
619
|
+
prompt: [{ type: "embeddedContext" as any, data: "z" } as any],
|
|
620
|
+
})
|
|
621
|
+
expect(r3.stopReason).toBe("end_turn")
|
|
622
|
+
} finally {
|
|
623
|
+
process.stderr.write = origWrite
|
|
624
|
+
}
|
|
625
|
+
const matches = writes.filter((w) => w.includes("acp prompt: dropping"))
|
|
626
|
+
expect(matches).toHaveLength(2)
|
|
627
|
+
expect(matches.some((w) => w.includes("audio block"))).toBe(true)
|
|
628
|
+
expect(matches.some((w) => w.includes("embeddedContext block"))).toBe(true)
|
|
629
|
+
})
|
|
630
|
+
|
|
631
|
+
// Image blocks are supported when model supports vision: they should be
|
|
632
|
+
// extracted and passed to session.prompt() without warnings.
|
|
633
|
+
it("accepts image blocks when model supports vision", async () => {
|
|
634
|
+
fake.model = {
|
|
635
|
+
provider: "test",
|
|
636
|
+
id: "vision-model",
|
|
637
|
+
input: ["text", "image"],
|
|
638
|
+
}
|
|
639
|
+
fake.promptImpl = async () => {
|
|
640
|
+
fake.emit({ type: "agent_start" })
|
|
641
|
+
await delay(5)
|
|
642
|
+
fake.emit(agentEnd())
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const result = await agent.prompt({
|
|
646
|
+
sessionId,
|
|
647
|
+
prompt: [
|
|
648
|
+
{ type: "text", text: "describe this image" },
|
|
649
|
+
{ type: "image", data: "base64data", mimeType: "image/png" },
|
|
650
|
+
],
|
|
651
|
+
})
|
|
652
|
+
expect(result.stopReason).toBe("end_turn")
|
|
653
|
+
// Verify images were passed to session.prompt
|
|
654
|
+
expect(fake.lastPromptImages).toHaveLength(1)
|
|
655
|
+
expect(fake.lastPromptImages?.[0]).toMatchObject({
|
|
656
|
+
type: "image",
|
|
657
|
+
data: "base64data",
|
|
658
|
+
mimeType: "image/png",
|
|
659
|
+
})
|
|
660
|
+
})
|
|
661
|
+
|
|
662
|
+
// Image blocks are dropped when model doesn't support vision: they should
|
|
663
|
+
// be silently discarded with a warning.
|
|
664
|
+
it("drops image blocks when model has no vision support", async () => {
|
|
665
|
+
fake.model = { provider: "test", id: "text-only-model", input: ["text"] }
|
|
666
|
+
fake.promptImpl = async () => {
|
|
667
|
+
fake.emit({ type: "agent_start" })
|
|
668
|
+
await delay(5)
|
|
669
|
+
fake.emit(agentEnd())
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const writes: string[] = []
|
|
673
|
+
const origWrite = process.stderr.write.bind(process.stderr)
|
|
674
|
+
// biome-ignore lint/suspicious/noExplicitAny: test-only stderr capture
|
|
675
|
+
;(process.stderr.write as any) = (chunk: string | Uint8Array) => {
|
|
676
|
+
writes.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"))
|
|
677
|
+
return true
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
try {
|
|
681
|
+
const result = await agent.prompt({
|
|
682
|
+
sessionId,
|
|
683
|
+
prompt: [
|
|
684
|
+
{ type: "text", text: "describe this image" },
|
|
685
|
+
{ type: "image", data: "base64data", mimeType: "image/png" },
|
|
686
|
+
],
|
|
687
|
+
})
|
|
688
|
+
expect(result.stopReason).toBe("end_turn")
|
|
689
|
+
// Images should be dropped, not passed to session.prompt (passed as empty array)
|
|
690
|
+
expect(fake.lastPromptImages).toEqual([])
|
|
691
|
+
} finally {
|
|
692
|
+
process.stderr.write = origWrite
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const matches = writes.filter((w) =>
|
|
696
|
+
w.includes("acp prompt: dropping image block (active model has no vision input)"),
|
|
697
|
+
)
|
|
698
|
+
expect(matches).toHaveLength(1)
|
|
699
|
+
})
|
|
700
|
+
|
|
701
|
+
// Defensive: once a turn is finalized (short-circuit, shutdown, cancel),
|
|
702
|
+
// stray tool_execution_{start,end} must not emit tool_call notifications
|
|
703
|
+
// to the client. Clients would otherwise see tool activity on a turn they
|
|
704
|
+
// consider complete. Checked alongside the existing agent_end drop test.
|
|
705
|
+
it("drops stray tool_execution_start/end after a short-circuited turn", async () => {
|
|
706
|
+
const localFake = new FakeAgentSession("session-tool-late")
|
|
707
|
+
const factory: AcpSessionFactory = async () => asSession(localFake)
|
|
708
|
+
const { conn, updates } = makeRecordingConn()
|
|
709
|
+
const localAgent = new IkieAcpAgent(conn, {
|
|
710
|
+
extensionFactories: [],
|
|
711
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
712
|
+
sessionFactory: factory,
|
|
713
|
+
})
|
|
714
|
+
const { sessionId: sid } = await localAgent.newSession({
|
|
715
|
+
cwd: "/tmp",
|
|
716
|
+
mcpServers: [],
|
|
717
|
+
})
|
|
718
|
+
|
|
719
|
+
localFake.promptImpl = async () => {
|
|
720
|
+
// Short-circuit: no events.
|
|
721
|
+
}
|
|
722
|
+
const result = await localAgent.prompt({
|
|
723
|
+
sessionId: sid,
|
|
724
|
+
prompt: [{ type: "text", text: "/help" }],
|
|
725
|
+
})
|
|
726
|
+
expect(result.stopReason).toBe("end_turn")
|
|
727
|
+
const updatesBefore = updates.length
|
|
728
|
+
|
|
729
|
+
// Stray tool events arrive after finalization — must be dropped.
|
|
730
|
+
localFake.emit({
|
|
731
|
+
type: "tool_execution_start",
|
|
732
|
+
toolCallId: "tc-late",
|
|
733
|
+
toolName: "bash",
|
|
734
|
+
args: { command: "late" },
|
|
735
|
+
})
|
|
736
|
+
localFake.emit({
|
|
737
|
+
type: "tool_execution_end",
|
|
738
|
+
toolCallId: "tc-late",
|
|
739
|
+
toolName: "bash",
|
|
740
|
+
result: { content: [{ type: "text", text: "late" }] },
|
|
741
|
+
isError: false,
|
|
742
|
+
})
|
|
743
|
+
expect(updates.length).toBe(updatesBefore)
|
|
744
|
+
})
|
|
745
|
+
|
|
746
|
+
// Defensive: a late agent_end arriving after the short-circuit path has
|
|
747
|
+
// already finalized must be a no-op, not a crash or double-resolve.
|
|
748
|
+
it("ignores a late agent_end after a short-circuited turn", async () => {
|
|
749
|
+
fake.promptImpl = async () => {
|
|
750
|
+
// No events.
|
|
751
|
+
}
|
|
752
|
+
const result = await agent.prompt({
|
|
753
|
+
sessionId,
|
|
754
|
+
prompt: [{ type: "text", text: "/help" }],
|
|
755
|
+
})
|
|
756
|
+
expect(result.stopReason).toBe("end_turn")
|
|
757
|
+
|
|
758
|
+
// Stray agent_end arrives later (shouldn't happen in production, but
|
|
759
|
+
// the guard in onSessionEvent must keep us safe either way).
|
|
760
|
+
expect(() => fake.emit(agentEnd())).not.toThrow()
|
|
761
|
+
})
|
|
762
|
+
|
|
763
|
+
// Resource safety on the newSession error path: if subscribe (or any step
|
|
764
|
+
// between factory-returns-session and sessions.set) throws, the live session
|
|
765
|
+
// must be disposed — nothing else will ever clean it up.
|
|
766
|
+
it("disposes the session if subscribe throws during newSession", async () => {
|
|
767
|
+
const leaky = new FakeAgentSession("session-leak")
|
|
768
|
+
leaky.subscribe = () => {
|
|
769
|
+
throw new Error("subscribe boom")
|
|
770
|
+
}
|
|
771
|
+
const factory: AcpSessionFactory = async () => asSession(leaky)
|
|
772
|
+
const localAgent = new IkieAcpAgent(makeConn(), {
|
|
773
|
+
extensionFactories: [],
|
|
774
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
775
|
+
sessionFactory: factory,
|
|
776
|
+
})
|
|
777
|
+
|
|
778
|
+
await expect(localAgent.newSession({ cwd: "/tmp", mcpServers: [] })).rejects.toThrow(/subscribe boom/)
|
|
779
|
+
expect(leaky.disposed).toBe(true)
|
|
780
|
+
})
|
|
781
|
+
|
|
782
|
+
it("unregisters the ACP permission prompter if bindExtensions throws during newSession", async () => {
|
|
783
|
+
const leaky = new FakeAgentSession("session-bind-leak")
|
|
784
|
+
leaky.bindExtensionsImpl = async () => {
|
|
785
|
+
throw new Error("bind boom")
|
|
786
|
+
}
|
|
787
|
+
const factory: AcpSessionFactory = async () => asSession(leaky)
|
|
788
|
+
const localAgent = new IkieAcpAgent(makeConn(), {
|
|
789
|
+
extensionFactories: [],
|
|
790
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
791
|
+
sessionFactory: factory,
|
|
792
|
+
})
|
|
793
|
+
|
|
794
|
+
await expect(localAgent.newSession({ cwd: "/tmp", mcpServers: [] })).rejects.toThrow(/bind boom/)
|
|
795
|
+
expect(leaky.disposed).toBe(true)
|
|
796
|
+
expect(getAcpPrompter("session-bind-leak")).toBeUndefined()
|
|
797
|
+
})
|
|
798
|
+
|
|
799
|
+
it("registers an ACP permission prompter that maps allow_once through requestPermission", async () => {
|
|
800
|
+
const requests: RequestPermissionRequest[] = []
|
|
801
|
+
const conn = {
|
|
802
|
+
sessionUpdate: async (_p: SessionNotification) => {},
|
|
803
|
+
requestPermission: async (params: RequestPermissionRequest) => {
|
|
804
|
+
requests.push(params)
|
|
805
|
+
return { outcome: { outcome: "selected", optionId: "choice-0" } }
|
|
806
|
+
},
|
|
807
|
+
} as unknown as AgentSideConnection
|
|
808
|
+
const localFake = new FakeAgentSession("session-permission")
|
|
809
|
+
const localAgent = new IkieAcpAgent(conn, {
|
|
810
|
+
extensionFactories: [],
|
|
811
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
812
|
+
sessionFactory: async () => asSession(localFake),
|
|
813
|
+
})
|
|
814
|
+
await localAgent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
815
|
+
|
|
816
|
+
const prompter = getAcpPrompter("session-permission")
|
|
817
|
+
expect(prompter).toBeDefined()
|
|
818
|
+
const result = await prompter?.request({
|
|
819
|
+
toolCallId: "tc-permission",
|
|
820
|
+
toolName: "bash",
|
|
821
|
+
input: { command: "touch allowed.txt" },
|
|
822
|
+
choices: [
|
|
823
|
+
{ kind: "allow-once", label: "Allow once" },
|
|
824
|
+
{ kind: "deny", label: "Deny" },
|
|
825
|
+
],
|
|
826
|
+
})
|
|
827
|
+
|
|
828
|
+
expect(result).toEqual({ kind: "allow-once" })
|
|
829
|
+
expect(requests).toHaveLength(1)
|
|
830
|
+
expect(requests[0]).toMatchObject({
|
|
831
|
+
sessionId: "session-permission",
|
|
832
|
+
toolCall: {
|
|
833
|
+
toolCallId: "tc-permission",
|
|
834
|
+
title: "touch allowed.txt",
|
|
835
|
+
kind: "execute",
|
|
836
|
+
status: "pending",
|
|
837
|
+
rawInput: { command: "touch allowed.txt" },
|
|
838
|
+
},
|
|
839
|
+
options: [
|
|
840
|
+
{ optionId: "choice-0", name: "Allow once", kind: "allow_once" },
|
|
841
|
+
{ optionId: "choice-1", name: "Deny", kind: "reject_once" },
|
|
842
|
+
],
|
|
843
|
+
})
|
|
844
|
+
|
|
845
|
+
await localAgent.shutdown()
|
|
846
|
+
expect(getAcpPrompter("session-permission")).toBeUndefined()
|
|
847
|
+
})
|
|
848
|
+
|
|
849
|
+
it("maps ACP permission cancellation to an aborted prompt result", async () => {
|
|
850
|
+
const requests: RequestPermissionRequest[] = []
|
|
851
|
+
const conn = {
|
|
852
|
+
sessionUpdate: async (_p: SessionNotification) => {},
|
|
853
|
+
requestPermission: async (params: RequestPermissionRequest) => {
|
|
854
|
+
requests.push(params)
|
|
855
|
+
return {
|
|
856
|
+
outcome: { outcome: "cancelled" },
|
|
857
|
+
}
|
|
858
|
+
},
|
|
859
|
+
} as unknown as AgentSideConnection
|
|
860
|
+
const localFake = new FakeAgentSession("session-permission-cancel")
|
|
861
|
+
const localAgent = new IkieAcpAgent(conn, {
|
|
862
|
+
extensionFactories: [],
|
|
863
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
864
|
+
sessionFactory: async () => asSession(localFake),
|
|
865
|
+
})
|
|
866
|
+
await localAgent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
867
|
+
|
|
868
|
+
const prompter = getAcpPrompter("session-permission-cancel")
|
|
869
|
+
const result = await prompter?.request({
|
|
870
|
+
toolCallId: "tc-cancel",
|
|
871
|
+
toolName: "write",
|
|
872
|
+
input: { path: "/tmp/out.txt", content: "x" },
|
|
873
|
+
choices: [{ kind: "allow-once", label: "Allow once" }],
|
|
874
|
+
})
|
|
875
|
+
|
|
876
|
+
expect(result).toEqual({ kind: "aborted" })
|
|
877
|
+
expect(requests).toHaveLength(1)
|
|
878
|
+
|
|
879
|
+
const abort = new AbortController()
|
|
880
|
+
abort.abort()
|
|
881
|
+
const skippedResult = await prompter?.request({
|
|
882
|
+
toolCallId: "tc-cancel-retry",
|
|
883
|
+
toolName: "write",
|
|
884
|
+
input: { path: "/tmp/out.txt", content: "x" },
|
|
885
|
+
choices: [{ kind: "allow-once", label: "Allow once" }],
|
|
886
|
+
signal: abort.signal,
|
|
887
|
+
})
|
|
888
|
+
|
|
889
|
+
expect(skippedResult).toEqual({ kind: "aborted" })
|
|
890
|
+
expect(requests).toHaveLength(1)
|
|
891
|
+
await localAgent.shutdown()
|
|
892
|
+
})
|
|
893
|
+
|
|
894
|
+
it("closes a live session and unregisters its ACP permission prompter", async () => {
|
|
895
|
+
expect(getAcpPrompter(sessionId)).toBeDefined()
|
|
896
|
+
|
|
897
|
+
await agent.unstable_closeSession({ sessionId })
|
|
898
|
+
|
|
899
|
+
expect(fake.disposed).toBe(true)
|
|
900
|
+
expect(getAcpPrompter(sessionId)).toBeUndefined()
|
|
901
|
+
await expect(
|
|
902
|
+
agent.prompt({
|
|
903
|
+
sessionId,
|
|
904
|
+
prompt: [{ type: "text", text: "hello" }],
|
|
905
|
+
}),
|
|
906
|
+
).rejects.toMatchObject({ code: -32602 })
|
|
907
|
+
})
|
|
908
|
+
|
|
909
|
+
it("cancels an in-flight turn when closing a session", async () => {
|
|
910
|
+
let releasePrompt!: () => void
|
|
911
|
+
const promptReleased = new Promise<void>((resolve) => {
|
|
912
|
+
releasePrompt = resolve
|
|
913
|
+
})
|
|
914
|
+
fake.promptImpl = async () => {
|
|
915
|
+
fake.emit({ type: "agent_start" })
|
|
916
|
+
await promptReleased
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
const result = agent.prompt({
|
|
920
|
+
sessionId,
|
|
921
|
+
prompt: [{ type: "text", text: "hello" }],
|
|
922
|
+
})
|
|
923
|
+
await delay(0)
|
|
924
|
+
await agent.unstable_closeSession({ sessionId })
|
|
925
|
+
releasePrompt()
|
|
926
|
+
|
|
927
|
+
await expect(result).resolves.toEqual({ stopReason: "cancelled" })
|
|
928
|
+
expect(fake.aborted).toBe(true)
|
|
929
|
+
expect(fake.disposed).toBe(true)
|
|
930
|
+
})
|
|
931
|
+
|
|
932
|
+
it("keeps a same-id reload isolated while close awaits abort", async () => {
|
|
933
|
+
const sid = "session-close-reload"
|
|
934
|
+
const oldFake = new FakeAgentSession(sid)
|
|
935
|
+
const newFake = new FakeAgentSession(sid)
|
|
936
|
+
const { conn, updates } = makeRecordingConn()
|
|
937
|
+
let loaderCalls = 0
|
|
938
|
+
let releaseOldPrompt!: () => void
|
|
939
|
+
const oldPromptReleased = new Promise<void>((resolve) => {
|
|
940
|
+
releaseOldPrompt = resolve
|
|
941
|
+
})
|
|
942
|
+
let releaseNewPrompt!: () => void
|
|
943
|
+
const newPromptReleased = new Promise<void>((resolve) => {
|
|
944
|
+
releaseNewPrompt = resolve
|
|
945
|
+
})
|
|
946
|
+
let markNewPromptStarted!: () => void
|
|
947
|
+
const newPromptStarted = new Promise<void>((resolve) => {
|
|
948
|
+
markNewPromptStarted = resolve
|
|
949
|
+
})
|
|
950
|
+
let newPrompt: Promise<unknown> | undefined
|
|
951
|
+
const localAgent = new IkieAcpAgent(conn, {
|
|
952
|
+
extensionFactories: [],
|
|
953
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
954
|
+
sessionFactory: async () => asSession(oldFake),
|
|
955
|
+
sessionLoader: async () => {
|
|
956
|
+
loaderCalls++
|
|
957
|
+
return asSession(newFake)
|
|
958
|
+
},
|
|
959
|
+
})
|
|
960
|
+
|
|
961
|
+
oldFake.promptImpl = async () => {
|
|
962
|
+
oldFake.emit({ type: "agent_start" })
|
|
963
|
+
await oldPromptReleased
|
|
964
|
+
}
|
|
965
|
+
newFake.promptImpl = async () => {
|
|
966
|
+
newFake.emit({ type: "agent_start" })
|
|
967
|
+
markNewPromptStarted()
|
|
968
|
+
await newPromptReleased
|
|
969
|
+
newFake.emit(agentEnd())
|
|
970
|
+
}
|
|
971
|
+
oldFake.abortImpl = async () => {
|
|
972
|
+
await localAgent.loadSession({
|
|
973
|
+
sessionId: sid,
|
|
974
|
+
cwd: "/tmp",
|
|
975
|
+
mcpServers: [],
|
|
976
|
+
})
|
|
977
|
+
newPrompt = localAgent.prompt({
|
|
978
|
+
sessionId: sid,
|
|
979
|
+
prompt: [{ type: "text", text: "new turn" }],
|
|
980
|
+
})
|
|
981
|
+
await newPromptStarted
|
|
982
|
+
oldFake.emit({
|
|
983
|
+
type: "message_update",
|
|
984
|
+
assistantMessageEvent: { type: "text_delta", delta: "stale old event" },
|
|
985
|
+
} as unknown as AgentSessionEvent)
|
|
986
|
+
releaseNewPrompt()
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
await localAgent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
990
|
+
const oldPrompt = localAgent.prompt({
|
|
991
|
+
sessionId: sid,
|
|
992
|
+
prompt: [{ type: "text", text: "old turn" }],
|
|
993
|
+
})
|
|
994
|
+
await delay(0)
|
|
995
|
+
|
|
996
|
+
await localAgent.unstable_closeSession({ sessionId: sid })
|
|
997
|
+
releaseOldPrompt()
|
|
998
|
+
|
|
999
|
+
await expect(oldPrompt).resolves.toEqual({ stopReason: "cancelled" })
|
|
1000
|
+
await expect(newPrompt).resolves.toEqual({ stopReason: "end_turn" })
|
|
1001
|
+
expect(loaderCalls).toBe(1)
|
|
1002
|
+
expect(getAcpPrompter(sid)).toBeDefined()
|
|
1003
|
+
expect(
|
|
1004
|
+
updates.some(
|
|
1005
|
+
(u) =>
|
|
1006
|
+
u.update.sessionUpdate === "agent_message_chunk" &&
|
|
1007
|
+
(u.update as { content: { text: string } }).content.text === "stale old event",
|
|
1008
|
+
),
|
|
1009
|
+
).toBe(false)
|
|
1010
|
+
|
|
1011
|
+
await localAgent.shutdown()
|
|
1012
|
+
})
|
|
1013
|
+
|
|
1014
|
+
// mcpServers is declared in the ACP request shape but ikie has no hook to
|
|
1015
|
+
// wire them into a live session — pi-coding-agent loads MCP servers from its
|
|
1016
|
+
// own config. Silently dropping them would leave the client believing those
|
|
1017
|
+
// servers are available; reject up-front with invalidParams instead.
|
|
1018
|
+
it("rejects newSession when mcpServers is non-empty", async () => {
|
|
1019
|
+
const factoryCalled = { count: 0 }
|
|
1020
|
+
const factory: AcpSessionFactory = async () => {
|
|
1021
|
+
factoryCalled.count++
|
|
1022
|
+
return asSession(new FakeAgentSession("unused"))
|
|
1023
|
+
}
|
|
1024
|
+
const localAgent = new IkieAcpAgent(makeConn(), {
|
|
1025
|
+
extensionFactories: [],
|
|
1026
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1027
|
+
sessionFactory: factory,
|
|
1028
|
+
})
|
|
1029
|
+
await expect(
|
|
1030
|
+
localAgent.newSession({
|
|
1031
|
+
cwd: "/tmp",
|
|
1032
|
+
// biome-ignore lint/suspicious/noExplicitAny: only the shape we care about
|
|
1033
|
+
mcpServers: [{ name: "x", command: "x", args: [] } as any],
|
|
1034
|
+
}),
|
|
1035
|
+
).rejects.toMatchObject({ code: -32602 })
|
|
1036
|
+
expect(factoryCalled.count).toBe(0)
|
|
1037
|
+
})
|
|
1038
|
+
|
|
1039
|
+
// Empty array is fine — equivalent to "no per-session servers requested".
|
|
1040
|
+
it("accepts newSession with empty mcpServers array", async () => {
|
|
1041
|
+
const localFake = new FakeAgentSession("empty-mcp")
|
|
1042
|
+
const factory: AcpSessionFactory = async () => asSession(localFake)
|
|
1043
|
+
const localAgent = new IkieAcpAgent(makeConn(), {
|
|
1044
|
+
extensionFactories: [],
|
|
1045
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1046
|
+
sessionFactory: factory,
|
|
1047
|
+
})
|
|
1048
|
+
const res = await localAgent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
1049
|
+
expect(res.sessionId).toBe("empty-mcp")
|
|
1050
|
+
})
|
|
1051
|
+
|
|
1052
|
+
// If the factory itself throws (e.g. bindExtensions failure in the default
|
|
1053
|
+
// factory), newSession must propagate the error — the factory owns disposal
|
|
1054
|
+
// of anything it allocated before throwing.
|
|
1055
|
+
it("propagates errors thrown by the session factory", async () => {
|
|
1056
|
+
const throwing: AcpSessionFactory = async () => {
|
|
1057
|
+
throw new Error("factory refused")
|
|
1058
|
+
}
|
|
1059
|
+
const localAgent = new IkieAcpAgent(makeConn(), {
|
|
1060
|
+
extensionFactories: [],
|
|
1061
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1062
|
+
sessionFactory: throwing,
|
|
1063
|
+
})
|
|
1064
|
+
|
|
1065
|
+
await expect(localAgent.newSession({ cwd: "/tmp", mcpServers: [] })).rejects.toThrow(/factory refused/)
|
|
1066
|
+
})
|
|
1067
|
+
|
|
1068
|
+
// Two sessions run prompts concurrently; each turn must finalize against
|
|
1069
|
+
// its own agent_end. The slower session must not block the faster one.
|
|
1070
|
+
it("isolates turn state across parallel sessions", async () => {
|
|
1071
|
+
const fakeA = new FakeAgentSession("session-a")
|
|
1072
|
+
const fakeB = new FakeAgentSession("session-b")
|
|
1073
|
+
const fakes = [fakeA, fakeB]
|
|
1074
|
+
let i = 0
|
|
1075
|
+
const rotating: AcpSessionFactory = async () => asSession(fakes[i++] ?? fakes[fakes.length - 1])
|
|
1076
|
+
const parallelAgent = new IkieAcpAgent(makeConn(), {
|
|
1077
|
+
extensionFactories: [],
|
|
1078
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1079
|
+
sessionFactory: rotating,
|
|
1080
|
+
})
|
|
1081
|
+
const a = await parallelAgent.newSession({ cwd: "/tmp/a", mcpServers: [] })
|
|
1082
|
+
const b = await parallelAgent.newSession({ cwd: "/tmp/b", mcpServers: [] })
|
|
1083
|
+
expect(a.sessionId).not.toBe(b.sessionId)
|
|
1084
|
+
|
|
1085
|
+
fakeA.promptImpl = async () => {
|
|
1086
|
+
fakeA.emit({ type: "agent_start" })
|
|
1087
|
+
await delay(5)
|
|
1088
|
+
setTimeout(() => fakeA.emit(agentEnd()), 60)
|
|
1089
|
+
}
|
|
1090
|
+
fakeB.promptImpl = async () => {
|
|
1091
|
+
fakeB.emit({ type: "agent_start" })
|
|
1092
|
+
await delay(5)
|
|
1093
|
+
setTimeout(() => fakeB.emit(agentEnd()), 10)
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
const [resA, resB] = await Promise.all([
|
|
1097
|
+
parallelAgent.prompt({
|
|
1098
|
+
sessionId: a.sessionId,
|
|
1099
|
+
prompt: [{ type: "text", text: "a" }],
|
|
1100
|
+
}),
|
|
1101
|
+
parallelAgent.prompt({
|
|
1102
|
+
sessionId: b.sessionId,
|
|
1103
|
+
prompt: [{ type: "text", text: "b" }],
|
|
1104
|
+
}),
|
|
1105
|
+
])
|
|
1106
|
+
expect(resA.stopReason).toBe("end_turn")
|
|
1107
|
+
expect(resB.stopReason).toBe("end_turn")
|
|
1108
|
+
})
|
|
1109
|
+
})
|
|
1110
|
+
|
|
1111
|
+
// ACP ContentChunk contract (src/modes/acp/server.ts onSessionEvent):
|
|
1112
|
+
// "All chunks belonging to the same message share the same messageId.
|
|
1113
|
+
// A change in messageId indicates a new message has started." pi-mono
|
|
1114
|
+
// streams one text_delta / thinking_delta event per chunk, but every delta
|
|
1115
|
+
// within a content block shares its contentIndex — so the server can collapse
|
|
1116
|
+
// them onto a single messageId without coordinating across events. The block
|
|
1117
|
+
// below exercises both branches directly so a regression that drops the field
|
|
1118
|
+
// or breaks its stability surfaces as a test failure rather than a quiet
|
|
1119
|
+
// client-side bug.
|
|
1120
|
+
describe("IkieAcpAgent messageId on streaming chunks", () => {
|
|
1121
|
+
let fake: FakeAgentSession
|
|
1122
|
+
let agent: IkieAcpAgent
|
|
1123
|
+
let sessionId: string
|
|
1124
|
+
let updates: SessionNotification[]
|
|
1125
|
+
|
|
1126
|
+
beforeEach(async () => {
|
|
1127
|
+
fake = new FakeAgentSession("session-msgid")
|
|
1128
|
+
const rec = makeRecordingConn()
|
|
1129
|
+
updates = rec.updates
|
|
1130
|
+
agent = new IkieAcpAgent(rec.conn, {
|
|
1131
|
+
extensionFactories: [],
|
|
1132
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1133
|
+
sessionFactory: async () => asSession(fake),
|
|
1134
|
+
})
|
|
1135
|
+
const res = await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
1136
|
+
sessionId = res.sessionId
|
|
1137
|
+
})
|
|
1138
|
+
|
|
1139
|
+
function emitTextDeltas(contentIndex: number, deltas: string[]): void {
|
|
1140
|
+
for (const delta of deltas) {
|
|
1141
|
+
fake.emit({
|
|
1142
|
+
type: "message_update",
|
|
1143
|
+
assistantMessageEvent: {
|
|
1144
|
+
type: "text_delta",
|
|
1145
|
+
delta,
|
|
1146
|
+
contentIndex,
|
|
1147
|
+
partial: {} as unknown as AssistantMessage,
|
|
1148
|
+
},
|
|
1149
|
+
message: {} as unknown as AssistantMessage,
|
|
1150
|
+
})
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function emitThinkingDeltas(contentIndex: number, deltas: string[]): void {
|
|
1155
|
+
for (const delta of deltas) {
|
|
1156
|
+
fake.emit({
|
|
1157
|
+
type: "message_update",
|
|
1158
|
+
assistantMessageEvent: {
|
|
1159
|
+
type: "thinking_delta",
|
|
1160
|
+
delta,
|
|
1161
|
+
contentIndex,
|
|
1162
|
+
partial: {} as unknown as AssistantMessage,
|
|
1163
|
+
},
|
|
1164
|
+
message: {} as unknown as AssistantMessage,
|
|
1165
|
+
})
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
function messageIdsFor(update: string): Array<string | null | undefined> {
|
|
1170
|
+
return updates
|
|
1171
|
+
.filter((u) => u.update.sessionUpdate === update)
|
|
1172
|
+
.map((u) => (u.update as { messageId?: string | null }).messageId)
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
it("keeps messageId stable across deltas within a block and flips it when contentIndex advances", async () => {
|
|
1176
|
+
// Two text blocks back-to-back. The first three deltas share index 0
|
|
1177
|
+
// (one messageId); the next three share index 1 (a different one).
|
|
1178
|
+
fake.promptImpl = async () => {
|
|
1179
|
+
fake.emit({ type: "agent_start" })
|
|
1180
|
+
emitTextDeltas(0, ["a", "b", "c"])
|
|
1181
|
+
emitTextDeltas(1, ["d", "e", "f"])
|
|
1182
|
+
fake.emit(agentEnd())
|
|
1183
|
+
}
|
|
1184
|
+
const result = await agent.prompt({
|
|
1185
|
+
sessionId,
|
|
1186
|
+
prompt: [{ type: "text", text: "two blocks" }],
|
|
1187
|
+
})
|
|
1188
|
+
expect(result.stopReason).toBe("end_turn")
|
|
1189
|
+
expect(messageIdsFor("agent_message_chunk")).toEqual([
|
|
1190
|
+
"ikie_msg_0",
|
|
1191
|
+
"ikie_msg_0",
|
|
1192
|
+
"ikie_msg_0",
|
|
1193
|
+
"ikie_msg_1",
|
|
1194
|
+
"ikie_msg_1",
|
|
1195
|
+
"ikie_msg_1",
|
|
1196
|
+
])
|
|
1197
|
+
})
|
|
1198
|
+
|
|
1199
|
+
it("emits messageId on agent_thought_chunk the same way (same contentIndex → same id)", async () => {
|
|
1200
|
+
// Smoke test for the thought branch: the formula is identical to the
|
|
1201
|
+
// text branch, so a regression that drops the field on either side
|
|
1202
|
+
// surfaces in at least one of the two tests.
|
|
1203
|
+
fake.promptImpl = async () => {
|
|
1204
|
+
fake.emit({ type: "agent_start" })
|
|
1205
|
+
emitThinkingDeltas(0, ["hmm", " ", "ok"])
|
|
1206
|
+
fake.emit(agentEnd())
|
|
1207
|
+
}
|
|
1208
|
+
const result = await agent.prompt({
|
|
1209
|
+
sessionId,
|
|
1210
|
+
prompt: [{ type: "text", text: "think" }],
|
|
1211
|
+
})
|
|
1212
|
+
expect(result.stopReason).toBe("end_turn")
|
|
1213
|
+
expect(messageIdsFor("agent_thought_chunk")).toEqual(["ikie_msg_0", "ikie_msg_0", "ikie_msg_0"])
|
|
1214
|
+
})
|
|
1215
|
+
|
|
1216
|
+
it("advances the counter across turns so two turns both starting at contentIndex=0 get distinct ids", async () => {
|
|
1217
|
+
// Regression guard for the ACP "change in messageId indicates a new
|
|
1218
|
+
// message" contract. pi-mono resets contentIndex to 0 on each new
|
|
1219
|
+
// assistant message; without a session-wide counter, turn 2's first
|
|
1220
|
+
// block would re-use turn 1's first block's messageId and a client
|
|
1221
|
+
// would merge two separate replies into one bubble.
|
|
1222
|
+
fake.promptImpl = async () => {
|
|
1223
|
+
// Turn 1: one text block at index 0.
|
|
1224
|
+
fake.emit({ type: "agent_start" })
|
|
1225
|
+
emitTextDeltas(0, ["hello"])
|
|
1226
|
+
fake.emit(agentEnd())
|
|
1227
|
+
// Turn 2 (chained continue): another text block at index 0 again.
|
|
1228
|
+
// contentIndex resets per assistant message, but the session-wide
|
|
1229
|
+
// counter must keep advancing.
|
|
1230
|
+
fake.emit({ type: "agent_start" })
|
|
1231
|
+
emitTextDeltas(0, ["world"])
|
|
1232
|
+
fake.emit(agentEnd())
|
|
1233
|
+
}
|
|
1234
|
+
const result = await agent.prompt({
|
|
1235
|
+
sessionId,
|
|
1236
|
+
prompt: [{ type: "text", text: "two turns" }],
|
|
1237
|
+
})
|
|
1238
|
+
expect(result.stopReason).toBe("end_turn")
|
|
1239
|
+
expect(messageIdsFor("agent_message_chunk")).toEqual(["ikie_msg_0", "ikie_msg_1"])
|
|
1240
|
+
})
|
|
1241
|
+
})
|
|
1242
|
+
|
|
1243
|
+
// Streaming tools (bash in particular) emit tool_execution_update with a
|
|
1244
|
+
// partialResult payload for every output chunk. The ACP server translates each
|
|
1245
|
+
// of these into a tool_call_update with status="in_progress" and content carrying
|
|
1246
|
+
// the partial output — distinct from the terminal completed/failed update that
|
|
1247
|
+
// accompanies tool_execution_end. The block below covers that branch directly.
|
|
1248
|
+
describe("IkieAcpAgent tool execution stream", () => {
|
|
1249
|
+
let fake: FakeAgentSession
|
|
1250
|
+
let agent: IkieAcpAgent
|
|
1251
|
+
let sessionId: string
|
|
1252
|
+
let updates: SessionNotification[]
|
|
1253
|
+
|
|
1254
|
+
beforeEach(async () => {
|
|
1255
|
+
fake = new FakeAgentSession("session-tool")
|
|
1256
|
+
const sessionFactory: AcpSessionFactory = async () => asSession(fake)
|
|
1257
|
+
const rec = makeRecordingConn()
|
|
1258
|
+
updates = rec.updates
|
|
1259
|
+
agent = new IkieAcpAgent(rec.conn, {
|
|
1260
|
+
extensionFactories: [],
|
|
1261
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1262
|
+
sessionFactory,
|
|
1263
|
+
})
|
|
1264
|
+
const res = await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
1265
|
+
sessionId = res.sessionId
|
|
1266
|
+
})
|
|
1267
|
+
|
|
1268
|
+
it("forwards partial tool_execution_update events as in_progress tool_call_update notifications with content", async () => {
|
|
1269
|
+
fake.promptImpl = async () => {
|
|
1270
|
+
fake.emit({ type: "agent_start" })
|
|
1271
|
+
fake.emit({
|
|
1272
|
+
type: "tool_execution_start",
|
|
1273
|
+
toolCallId: "tc-1",
|
|
1274
|
+
toolName: "bash",
|
|
1275
|
+
args: { command: "printf a; sleep 0; printf b" },
|
|
1276
|
+
})
|
|
1277
|
+
fake.emit({
|
|
1278
|
+
type: "tool_execution_update",
|
|
1279
|
+
toolCallId: "tc-1",
|
|
1280
|
+
toolName: "bash",
|
|
1281
|
+
args: { command: "printf a; sleep 0; printf b" },
|
|
1282
|
+
partialResult: { content: [{ type: "text", text: "a" }] },
|
|
1283
|
+
})
|
|
1284
|
+
fake.emit({
|
|
1285
|
+
type: "tool_execution_update",
|
|
1286
|
+
toolCallId: "tc-1",
|
|
1287
|
+
toolName: "bash",
|
|
1288
|
+
args: { command: "printf a; sleep 0; printf b" },
|
|
1289
|
+
partialResult: { content: [{ type: "text", text: "ab" }] },
|
|
1290
|
+
})
|
|
1291
|
+
fake.emit({
|
|
1292
|
+
type: "tool_execution_end",
|
|
1293
|
+
toolCallId: "tc-1",
|
|
1294
|
+
toolName: "bash",
|
|
1295
|
+
result: { content: [{ type: "text", text: "ab" }] },
|
|
1296
|
+
isError: false,
|
|
1297
|
+
})
|
|
1298
|
+
fake.emit(agentEnd())
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
const res = await agent.prompt({
|
|
1302
|
+
sessionId,
|
|
1303
|
+
prompt: [{ type: "text", text: "run" }],
|
|
1304
|
+
})
|
|
1305
|
+
expect(res.stopReason).toBe("end_turn")
|
|
1306
|
+
|
|
1307
|
+
const toolCallUpdates = updates.filter((u) => u.update.sessionUpdate === "tool_call_update")
|
|
1308
|
+
const partials = toolCallUpdates.filter((u) => {
|
|
1309
|
+
const up = u.update as { status?: string; content?: unknown[] }
|
|
1310
|
+
return up.status === "in_progress" && Array.isArray(up.content) && up.content.length > 0
|
|
1311
|
+
})
|
|
1312
|
+
expect(partials).toHaveLength(2)
|
|
1313
|
+
// Each partial must carry the agent_session partialResult content verbatim
|
|
1314
|
+
// as ACP tool_call content blocks — proving the partialResult -> content
|
|
1315
|
+
// translation (toolResultContent) ran on the stream path, not only at end.
|
|
1316
|
+
const firstContent = (partials[0].update as { content: Array<{ content: { text: string } }> }).content
|
|
1317
|
+
expect(firstContent[0].content.text).toBe("a")
|
|
1318
|
+
const secondContent = (partials[1].update as { content: Array<{ content: { text: string } }> }).content
|
|
1319
|
+
expect(secondContent[0].content.text).toBe("ab")
|
|
1320
|
+
|
|
1321
|
+
// Terminal completed update still fires after the partials.
|
|
1322
|
+
const terminal = toolCallUpdates.find((u) => (u.update as { status?: string }).status === "completed")
|
|
1323
|
+
expect(terminal).toBeDefined()
|
|
1324
|
+
})
|
|
1325
|
+
|
|
1326
|
+
// web_fetch (and MCP image tools whose blocks survive transformMcpContent)
|
|
1327
|
+
// can return image blocks in a tool result. toolResultContent must forward
|
|
1328
|
+
// them as ACP image content; before this they were dropped and the client
|
|
1329
|
+
// saw a completed tool call with empty content.
|
|
1330
|
+
it("forwards an image block on tool_execution_end as ACP image content", async () => {
|
|
1331
|
+
fake.promptImpl = async () => {
|
|
1332
|
+
fake.emit({ type: "agent_start" })
|
|
1333
|
+
fake.emit({ type: "tool_execution_start", toolCallId: "tc-img", toolName: "web_fetch", args: { url: "x" } })
|
|
1334
|
+
fake.emit({
|
|
1335
|
+
type: "tool_execution_end",
|
|
1336
|
+
toolCallId: "tc-img",
|
|
1337
|
+
toolName: "web_fetch",
|
|
1338
|
+
result: { content: [{ type: "image", data: "aGVsbG8=", mimeType: "image/png" }] },
|
|
1339
|
+
isError: false,
|
|
1340
|
+
})
|
|
1341
|
+
fake.emit(agentEnd())
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
const res = await agent.prompt({ sessionId, prompt: [{ type: "text", text: "run" }] })
|
|
1345
|
+
expect(res.stopReason).toBe("end_turn")
|
|
1346
|
+
|
|
1347
|
+
const completed = updates.find(
|
|
1348
|
+
(u) => u.update.sessionUpdate === "tool_call_update" && (u.update as { status?: string }).status === "completed",
|
|
1349
|
+
)
|
|
1350
|
+
expect(completed).toBeDefined()
|
|
1351
|
+
const content = (completed?.update as { content: unknown[] }).content
|
|
1352
|
+
expect(content).toEqual([{ type: "content", content: { type: "image", data: "aGVsbG8=", mimeType: "image/png" } }])
|
|
1353
|
+
})
|
|
1354
|
+
|
|
1355
|
+
// The image path also runs on the streaming branch: an image-only partial
|
|
1356
|
+
// must produce an in_progress update rather than being swallowed as empty.
|
|
1357
|
+
it("forwards image blocks from a streaming partialResult", async () => {
|
|
1358
|
+
fake.promptImpl = async () => {
|
|
1359
|
+
fake.emit({ type: "agent_start" })
|
|
1360
|
+
fake.emit({ type: "tool_execution_start", toolCallId: "tc-img2", toolName: "web_fetch", args: { url: "x" } })
|
|
1361
|
+
fake.emit({
|
|
1362
|
+
type: "tool_execution_update",
|
|
1363
|
+
toolCallId: "tc-img2",
|
|
1364
|
+
toolName: "web_fetch",
|
|
1365
|
+
args: { url: "x" },
|
|
1366
|
+
partialResult: { content: [{ type: "image", data: "Zm9v", mimeType: "image/jpeg" }] },
|
|
1367
|
+
})
|
|
1368
|
+
fake.emit({
|
|
1369
|
+
type: "tool_execution_end",
|
|
1370
|
+
toolCallId: "tc-img2",
|
|
1371
|
+
toolName: "web_fetch",
|
|
1372
|
+
result: { content: [{ type: "image", data: "Zm9v", mimeType: "image/jpeg" }] },
|
|
1373
|
+
isError: false,
|
|
1374
|
+
})
|
|
1375
|
+
fake.emit(agentEnd())
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
const res = await agent.prompt({ sessionId, prompt: [{ type: "text", text: "run" }] })
|
|
1379
|
+
expect(res.stopReason).toBe("end_turn")
|
|
1380
|
+
|
|
1381
|
+
const partial = updates.find(
|
|
1382
|
+
(u) =>
|
|
1383
|
+
u.update.sessionUpdate === "tool_call_update" && (u.update as { status?: string }).status === "in_progress",
|
|
1384
|
+
)
|
|
1385
|
+
expect(partial).toBeDefined()
|
|
1386
|
+
const content = (partial?.update as { content: unknown[] }).content
|
|
1387
|
+
expect(content).toEqual([{ type: "content", content: { type: "image", data: "Zm9v", mimeType: "image/jpeg" } }])
|
|
1388
|
+
})
|
|
1389
|
+
|
|
1390
|
+
// A result mixing text and image blocks forwards every block, in order.
|
|
1391
|
+
it("forwards text and image blocks together, preserving order", async () => {
|
|
1392
|
+
fake.promptImpl = async () => {
|
|
1393
|
+
fake.emit({ type: "agent_start" })
|
|
1394
|
+
fake.emit({ type: "tool_execution_start", toolCallId: "tc-mix", toolName: "web_fetch", args: { url: "x" } })
|
|
1395
|
+
fake.emit({
|
|
1396
|
+
type: "tool_execution_end",
|
|
1397
|
+
toolCallId: "tc-mix",
|
|
1398
|
+
toolName: "web_fetch",
|
|
1399
|
+
result: {
|
|
1400
|
+
content: [
|
|
1401
|
+
{ type: "text", text: "before" },
|
|
1402
|
+
{ type: "image", data: "YmFy", mimeType: "image/png" },
|
|
1403
|
+
],
|
|
1404
|
+
},
|
|
1405
|
+
isError: false,
|
|
1406
|
+
})
|
|
1407
|
+
fake.emit(agentEnd())
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
const res = await agent.prompt({ sessionId, prompt: [{ type: "text", text: "run" }] })
|
|
1411
|
+
expect(res.stopReason).toBe("end_turn")
|
|
1412
|
+
|
|
1413
|
+
const completed = updates.find(
|
|
1414
|
+
(u) => u.update.sessionUpdate === "tool_call_update" && (u.update as { status?: string }).status === "completed",
|
|
1415
|
+
)
|
|
1416
|
+
const content = (completed?.update as { content: unknown[] }).content
|
|
1417
|
+
expect(content).toEqual([
|
|
1418
|
+
{ type: "content", content: { type: "text", text: "before" } },
|
|
1419
|
+
{ type: "content", content: { type: "image", data: "YmFy", mimeType: "image/png" } },
|
|
1420
|
+
])
|
|
1421
|
+
})
|
|
1422
|
+
|
|
1423
|
+
// Guard on server.ts:213-214: an empty partialResult must NOT produce a
|
|
1424
|
+
// tool_call_update — an in_progress update with empty content is noise for
|
|
1425
|
+
// clients that render the stream as it arrives.
|
|
1426
|
+
it("skips tool_execution_update events whose partialResult carries no content", async () => {
|
|
1427
|
+
fake.promptImpl = async () => {
|
|
1428
|
+
fake.emit({ type: "agent_start" })
|
|
1429
|
+
fake.emit({
|
|
1430
|
+
type: "tool_execution_start",
|
|
1431
|
+
toolCallId: "tc-2",
|
|
1432
|
+
toolName: "bash",
|
|
1433
|
+
args: { command: "true" },
|
|
1434
|
+
})
|
|
1435
|
+
// Empty partial shapes we can plausibly see: null, undefined, missing content, empty array.
|
|
1436
|
+
fake.emit({
|
|
1437
|
+
type: "tool_execution_update",
|
|
1438
|
+
toolCallId: "tc-2",
|
|
1439
|
+
toolName: "bash",
|
|
1440
|
+
args: { command: "true" },
|
|
1441
|
+
partialResult: null,
|
|
1442
|
+
})
|
|
1443
|
+
fake.emit({
|
|
1444
|
+
type: "tool_execution_update",
|
|
1445
|
+
toolCallId: "tc-2",
|
|
1446
|
+
toolName: "bash",
|
|
1447
|
+
args: { command: "true" },
|
|
1448
|
+
partialResult: { content: [] },
|
|
1449
|
+
})
|
|
1450
|
+
fake.emit({
|
|
1451
|
+
type: "tool_execution_end",
|
|
1452
|
+
toolCallId: "tc-2",
|
|
1453
|
+
toolName: "bash",
|
|
1454
|
+
result: { content: [{ type: "text", text: "" }] },
|
|
1455
|
+
isError: false,
|
|
1456
|
+
})
|
|
1457
|
+
fake.emit(agentEnd())
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
const res = await agent.prompt({
|
|
1461
|
+
sessionId,
|
|
1462
|
+
prompt: [{ type: "text", text: "run" }],
|
|
1463
|
+
})
|
|
1464
|
+
expect(res.stopReason).toBe("end_turn")
|
|
1465
|
+
|
|
1466
|
+
const toolCallUpdates = updates.filter((u) => u.update.sessionUpdate === "tool_call_update")
|
|
1467
|
+
const partials = toolCallUpdates.filter((u) => (u.update as { status?: string }).status === "in_progress")
|
|
1468
|
+
expect(partials).toHaveLength(0)
|
|
1469
|
+
// Terminal completed update still present.
|
|
1470
|
+
const terminal = toolCallUpdates.find((u) => (u.update as { status?: string }).status === "completed")
|
|
1471
|
+
expect(terminal).toBeDefined()
|
|
1472
|
+
})
|
|
1473
|
+
|
|
1474
|
+
it("suppresses ACP tool notifications for system Agent tool calls", async () => {
|
|
1475
|
+
fake.promptImpl = async () => {
|
|
1476
|
+
fake.emit({ type: "agent_start" })
|
|
1477
|
+
fake.emit({
|
|
1478
|
+
type: "tool_execution_start",
|
|
1479
|
+
toolCallId: "tc-system-agent",
|
|
1480
|
+
toolName: "Agent",
|
|
1481
|
+
args: {
|
|
1482
|
+
prompt: "classify",
|
|
1483
|
+
description: "permission classifier",
|
|
1484
|
+
visibility: "system",
|
|
1485
|
+
},
|
|
1486
|
+
})
|
|
1487
|
+
fake.emit({
|
|
1488
|
+
type: "tool_execution_update",
|
|
1489
|
+
toolCallId: "tc-system-agent",
|
|
1490
|
+
toolName: "Agent",
|
|
1491
|
+
args: {
|
|
1492
|
+
prompt: "classify",
|
|
1493
|
+
description: "permission classifier",
|
|
1494
|
+
visibility: "system",
|
|
1495
|
+
},
|
|
1496
|
+
partialResult: {
|
|
1497
|
+
content: [{ type: "text", text: "System agent started." }],
|
|
1498
|
+
},
|
|
1499
|
+
})
|
|
1500
|
+
fake.emit({
|
|
1501
|
+
type: "tool_execution_end",
|
|
1502
|
+
toolCallId: "tc-system-agent",
|
|
1503
|
+
toolName: "Agent",
|
|
1504
|
+
result: { content: [{ type: "text", text: "System agent started." }] },
|
|
1505
|
+
isError: false,
|
|
1506
|
+
})
|
|
1507
|
+
fake.emit(agentEnd())
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
const res = await agent.prompt({
|
|
1511
|
+
sessionId,
|
|
1512
|
+
prompt: [{ type: "text", text: "run" }],
|
|
1513
|
+
})
|
|
1514
|
+
expect(res.stopReason).toBe("end_turn")
|
|
1515
|
+
expect(updates.some((u) => u.update.sessionUpdate === "tool_call")).toBe(false)
|
|
1516
|
+
expect(updates.some((u) => u.update.sessionUpdate === "tool_call_update")).toBe(false)
|
|
1517
|
+
})
|
|
1518
|
+
|
|
1519
|
+
it("suppresses system Agent updates even if an update arrives before the start event", async () => {
|
|
1520
|
+
fake.promptImpl = async () => {
|
|
1521
|
+
fake.emit({ type: "agent_start" })
|
|
1522
|
+
fake.emit({
|
|
1523
|
+
type: "tool_execution_update",
|
|
1524
|
+
toolCallId: "tc-system-agent-update-first",
|
|
1525
|
+
toolName: "Agent",
|
|
1526
|
+
args: {
|
|
1527
|
+
prompt: "classify",
|
|
1528
|
+
description: "permission classifier",
|
|
1529
|
+
visibility: "system",
|
|
1530
|
+
},
|
|
1531
|
+
partialResult: {
|
|
1532
|
+
content: [{ type: "text", text: "System agent started." }],
|
|
1533
|
+
},
|
|
1534
|
+
})
|
|
1535
|
+
fake.emit({
|
|
1536
|
+
type: "tool_execution_end",
|
|
1537
|
+
toolCallId: "tc-system-agent-update-first",
|
|
1538
|
+
toolName: "Agent",
|
|
1539
|
+
result: { content: [{ type: "text", text: "System agent started." }] },
|
|
1540
|
+
isError: false,
|
|
1541
|
+
})
|
|
1542
|
+
fake.emit(agentEnd())
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
const res = await agent.prompt({
|
|
1546
|
+
sessionId,
|
|
1547
|
+
prompt: [{ type: "text", text: "run" }],
|
|
1548
|
+
})
|
|
1549
|
+
expect(res.stopReason).toBe("end_turn")
|
|
1550
|
+
expect(updates.some((u) => u.update.sessionUpdate === "tool_call")).toBe(false)
|
|
1551
|
+
expect(updates.some((u) => u.update.sessionUpdate === "tool_call_update")).toBe(false)
|
|
1552
|
+
})
|
|
1553
|
+
})
|
|
1554
|
+
|
|
1555
|
+
describe("isHiddenToolCall", () => {
|
|
1556
|
+
it("returns false for non-Agent tool names", () => {
|
|
1557
|
+
expect(isHiddenToolCall("bash", {})).toBe(false)
|
|
1558
|
+
expect(isHiddenToolCall("read", { visibility: "system" })).toBe(false)
|
|
1559
|
+
})
|
|
1560
|
+
|
|
1561
|
+
it("returns false when visibility is missing", () => {
|
|
1562
|
+
expect(isHiddenToolCall("Agent", {})).toBe(false)
|
|
1563
|
+
expect(isHiddenToolCall("Agent", { prompt: "hello" })).toBe(false)
|
|
1564
|
+
})
|
|
1565
|
+
|
|
1566
|
+
it("returns false when visibility is not 'system' (any casing)", () => {
|
|
1567
|
+
expect(isHiddenToolCall("Agent", { visibility: "public" })).toBe(false)
|
|
1568
|
+
expect(isHiddenToolCall("Agent", { visibility: "private" })).toBe(false)
|
|
1569
|
+
})
|
|
1570
|
+
|
|
1571
|
+
it("returns true when visibility is 'system' (case-insensitive)", () => {
|
|
1572
|
+
expect(isHiddenToolCall("Agent", { visibility: "system" })).toBe(true)
|
|
1573
|
+
expect(isHiddenToolCall("Agent", { visibility: "System" })).toBe(true)
|
|
1574
|
+
expect(isHiddenToolCall("Agent", { visibility: "SYSTEM" })).toBe(true)
|
|
1575
|
+
})
|
|
1576
|
+
|
|
1577
|
+
it("returns true for Agent with mixed-case 'System' visibility", () => {
|
|
1578
|
+
expect(isHiddenToolCall("Agent", { visibility: "SyStEm" })).toBe(true)
|
|
1579
|
+
})
|
|
1580
|
+
})
|
|
1581
|
+
|
|
1582
|
+
// Coverage for assertSessionHasModel: ACP clients (Zed) should see authRequired
|
|
1583
|
+
// (-32000), not a generic internal error, when the model is unavailable — that
|
|
1584
|
+
// error code routes to the client's auth UI instead of an opaque failure toast.
|
|
1585
|
+
describe("assertSessionHasModel", () => {
|
|
1586
|
+
it("throws RequestError with code -32000 when model is missing", () => {
|
|
1587
|
+
try {
|
|
1588
|
+
assertSessionHasModel({ model: undefined } as Parameters<typeof assertSessionHasModel>[0])
|
|
1589
|
+
throw new Error("expected throw")
|
|
1590
|
+
} catch (err) {
|
|
1591
|
+
expect((err as { code?: number }).code).toBe(-32000)
|
|
1592
|
+
expect((err as Error).message).toMatch(/No model available/)
|
|
1593
|
+
}
|
|
1594
|
+
})
|
|
1595
|
+
|
|
1596
|
+
it("is a no-op when model is present", () => {
|
|
1597
|
+
expect(() =>
|
|
1598
|
+
assertSessionHasModel({
|
|
1599
|
+
model: {} as NonNullable<Parameters<typeof assertSessionHasModel>[0]["model"]>,
|
|
1600
|
+
}),
|
|
1601
|
+
).not.toThrow()
|
|
1602
|
+
})
|
|
1603
|
+
})
|
|
1604
|
+
|
|
1605
|
+
describe("initializeHeadlessTheme", () => {
|
|
1606
|
+
beforeEach(() => {
|
|
1607
|
+
vi.stubGlobal(THEME_KEY, undefined)
|
|
1608
|
+
vi.stubGlobal(THEME_KEY_OLD, undefined)
|
|
1609
|
+
})
|
|
1610
|
+
|
|
1611
|
+
afterEach(() => {
|
|
1612
|
+
vi.unstubAllGlobals()
|
|
1613
|
+
})
|
|
1614
|
+
|
|
1615
|
+
it("initializes pi's global theme proxy for headless sessions", () => {
|
|
1616
|
+
const globals = globalThis as Record<symbol, unknown>
|
|
1617
|
+
|
|
1618
|
+
expect(globals[THEME_KEY]).toBeUndefined()
|
|
1619
|
+
expect(globals[THEME_KEY_OLD]).toBeUndefined()
|
|
1620
|
+
|
|
1621
|
+
initializeHeadlessTheme({ getTheme: () => "default" })
|
|
1622
|
+
|
|
1623
|
+
expect(globals[THEME_KEY]).toBeDefined()
|
|
1624
|
+
expect(globals[THEME_KEY_OLD]).toBeDefined()
|
|
1625
|
+
const initializedTheme = globals[THEME_KEY] as {
|
|
1626
|
+
getFgAnsi(color: string): string
|
|
1627
|
+
}
|
|
1628
|
+
expect(() => initializedTheme.getFgAnsi("accent")).not.toThrow()
|
|
1629
|
+
})
|
|
1630
|
+
})
|
|
1631
|
+
|
|
1632
|
+
describe("buildSessionModelState", () => {
|
|
1633
|
+
it("returns null when model is missing", () => {
|
|
1634
|
+
const fake = new FakeAgentSession("s1")
|
|
1635
|
+
fake.model = undefined
|
|
1636
|
+
const result = buildSessionModelState(fake as unknown as Parameters<typeof buildSessionModelState>[0])
|
|
1637
|
+
expect(result).toBeNull()
|
|
1638
|
+
})
|
|
1639
|
+
|
|
1640
|
+
it("returns currentModelId and availableModels when model is present", () => {
|
|
1641
|
+
const fake = new FakeAgentSession("s1")
|
|
1642
|
+
fake.model = { provider: "openai", id: "gpt-4" }
|
|
1643
|
+
fake.modelRegistry = {
|
|
1644
|
+
getAvailable: () => [
|
|
1645
|
+
{ provider: "openai", id: "gpt-4", name: "GPT-4" },
|
|
1646
|
+
{ provider: "anthropic", id: "claude-3", name: "Claude 3" },
|
|
1647
|
+
],
|
|
1648
|
+
}
|
|
1649
|
+
const result = buildSessionModelState(fake as unknown as Parameters<typeof buildSessionModelState>[0])
|
|
1650
|
+
expect(result).toEqual({
|
|
1651
|
+
currentModelId: "openai/gpt-4",
|
|
1652
|
+
availableModels: [
|
|
1653
|
+
{ modelId: "openai/gpt-4", name: "GPT-4" },
|
|
1654
|
+
{ modelId: "anthropic/claude-3", name: "Claude 3" },
|
|
1655
|
+
],
|
|
1656
|
+
})
|
|
1657
|
+
})
|
|
1658
|
+
|
|
1659
|
+
it("returns empty availableModels when registry has no models", () => {
|
|
1660
|
+
const fake = new FakeAgentSession("s1")
|
|
1661
|
+
fake.model = { provider: "openai", id: "gpt-4" }
|
|
1662
|
+
fake.modelRegistry = { getAvailable: () => [] }
|
|
1663
|
+
const result = buildSessionModelState(fake as unknown as Parameters<typeof buildSessionModelState>[0])
|
|
1664
|
+
expect(result).toEqual({
|
|
1665
|
+
currentModelId: "openai/gpt-4",
|
|
1666
|
+
availableModels: [],
|
|
1667
|
+
})
|
|
1668
|
+
})
|
|
1669
|
+
})
|
|
1670
|
+
|
|
1671
|
+
describe("newSession model state", () => {
|
|
1672
|
+
it("returns model state in the response when a model is available", async () => {
|
|
1673
|
+
const fake = new FakeAgentSession("session-model")
|
|
1674
|
+
fake.model = { provider: "openai", id: "gpt-4" }
|
|
1675
|
+
fake.modelRegistry = {
|
|
1676
|
+
getAvailable: () => [
|
|
1677
|
+
{ provider: "openai", id: "gpt-4", name: "GPT-4" },
|
|
1678
|
+
{ provider: "anthropic", id: "claude-3", name: "Claude 3" },
|
|
1679
|
+
],
|
|
1680
|
+
}
|
|
1681
|
+
const factory: AcpSessionFactory = async () => asSession(fake)
|
|
1682
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
1683
|
+
extensionFactories: [],
|
|
1684
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1685
|
+
sessionFactory: factory,
|
|
1686
|
+
})
|
|
1687
|
+
const res = await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
1688
|
+
expect(res.sessionId).toBe("session-model")
|
|
1689
|
+
expect(res.models).toBeDefined()
|
|
1690
|
+
expect(res.models?.currentModelId).toBe("openai/gpt-4")
|
|
1691
|
+
expect(res.models?.availableModels).toHaveLength(2)
|
|
1692
|
+
expect(res.models?.availableModels[0]).toEqual({
|
|
1693
|
+
modelId: "openai/gpt-4",
|
|
1694
|
+
name: "GPT-4",
|
|
1695
|
+
})
|
|
1696
|
+
expect(res.models?.availableModels[1]).toEqual({
|
|
1697
|
+
modelId: "anthropic/claude-3",
|
|
1698
|
+
name: "Claude 3",
|
|
1699
|
+
})
|
|
1700
|
+
})
|
|
1701
|
+
|
|
1702
|
+
it("rejects with authRequired when no model is active", async () => {
|
|
1703
|
+
const fake = new FakeAgentSession("session-empty")
|
|
1704
|
+
fake.model = undefined
|
|
1705
|
+
const factory: AcpSessionFactory = async () => asSession(fake)
|
|
1706
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
1707
|
+
extensionFactories: [],
|
|
1708
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1709
|
+
sessionFactory: factory,
|
|
1710
|
+
})
|
|
1711
|
+
await expect(agent.newSession({ cwd: "/tmp", mcpServers: [] })).rejects.toMatchObject({ code: -32000 })
|
|
1712
|
+
expect(fake.disposed).toBe(true)
|
|
1713
|
+
})
|
|
1714
|
+
|
|
1715
|
+
it("returns configOptions in newSession response", async () => {
|
|
1716
|
+
const fake = new FakeAgentSession("test-session-config")
|
|
1717
|
+
const sessionFactory: AcpSessionFactory = async () => asSession(fake)
|
|
1718
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
1719
|
+
extensionFactories: [],
|
|
1720
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1721
|
+
sessionFactory,
|
|
1722
|
+
})
|
|
1723
|
+
|
|
1724
|
+
const res = await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
1725
|
+
|
|
1726
|
+
expect(res.configOptions).toBeDefined()
|
|
1727
|
+
expect(res.configOptions).toHaveLength(1)
|
|
1728
|
+
expect(res.configOptions?.[0].id).toBe("permissions-mode")
|
|
1729
|
+
expect(res.configOptions?.[0].type).toBe("select")
|
|
1730
|
+
expect(res.configOptions?.[0].currentValue).toBeDefined()
|
|
1731
|
+
})
|
|
1732
|
+
})
|
|
1733
|
+
|
|
1734
|
+
describe("newSession available commands", () => {
|
|
1735
|
+
it("sends available_commands_update with /bug command on session init", async () => {
|
|
1736
|
+
const fake = new FakeAgentSession("session-commands")
|
|
1737
|
+
const factory: AcpSessionFactory = async () => asSession(fake)
|
|
1738
|
+
const { conn, updates } = makeRecordingConn()
|
|
1739
|
+
const agent = new IkieAcpAgent(conn, {
|
|
1740
|
+
extensionFactories: [],
|
|
1741
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1742
|
+
sessionFactory: factory,
|
|
1743
|
+
})
|
|
1744
|
+
await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
1745
|
+
|
|
1746
|
+
// Find the available_commands_update notification
|
|
1747
|
+
const update = updates.find((u) => u.update.sessionUpdate === "available_commands_update")
|
|
1748
|
+
expect(update).toBeDefined()
|
|
1749
|
+
expect(update?.sessionId).toBe("session-commands")
|
|
1750
|
+
|
|
1751
|
+
const updatePayload = update?.update as { availableCommands: Array<Record<string, unknown>> }
|
|
1752
|
+
expect(updatePayload.availableCommands).toHaveLength(1)
|
|
1753
|
+
|
|
1754
|
+
const cmd = updatePayload.availableCommands[0]
|
|
1755
|
+
expect(cmd).toMatchObject({
|
|
1756
|
+
name: "bug",
|
|
1757
|
+
description: expect.any(String),
|
|
1758
|
+
input: { hint: expect.any(String) },
|
|
1759
|
+
})
|
|
1760
|
+
})
|
|
1761
|
+
})
|
|
1762
|
+
|
|
1763
|
+
describe("loadSession available commands", () => {
|
|
1764
|
+
it("sends available_commands_update alongside the transcript replay", async () => {
|
|
1765
|
+
const fake = new FakeAgentSession("session-load-test")
|
|
1766
|
+
fake.branch = [userTextEntry("previous message", "u1", null)]
|
|
1767
|
+
const loader: AcpSessionLoader = async () => asSession(fake)
|
|
1768
|
+
const { conn, updates } = makeRecordingConn()
|
|
1769
|
+
const agent = new IkieAcpAgent(conn, {
|
|
1770
|
+
extensionFactories: [],
|
|
1771
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1772
|
+
sessionFactory: async () => asSession(new FakeAgentSession("unused")),
|
|
1773
|
+
sessionLoader: loader,
|
|
1774
|
+
})
|
|
1775
|
+
|
|
1776
|
+
await agent.loadSession({ sessionId: "session-load-test", cwd: "/tmp", mcpServers: [] })
|
|
1777
|
+
|
|
1778
|
+
// loadSessionFresh re-broadcasts the command palette on resume.
|
|
1779
|
+
const cmdUpdate = updates.find((u) => u.update.sessionUpdate === "available_commands_update")
|
|
1780
|
+
expect(cmdUpdate).toBeDefined()
|
|
1781
|
+
expect(updates.find((u) => u.update.sessionUpdate === "user_message_chunk")).toBeDefined()
|
|
1782
|
+
})
|
|
1783
|
+
})
|
|
1784
|
+
|
|
1785
|
+
describe("unstable_setSessionModel", () => {
|
|
1786
|
+
it("switches to a valid model", async () => {
|
|
1787
|
+
const fake = new FakeAgentSession("switch-session")
|
|
1788
|
+
fake.model = { provider: "provider-a", id: "model-a" }
|
|
1789
|
+
fake.modelRegistry = {
|
|
1790
|
+
getAvailable: () => [
|
|
1791
|
+
{ provider: "provider-a", id: "model-a", name: "Model A" },
|
|
1792
|
+
{ provider: "provider-b", id: "model-b", name: "Model B" },
|
|
1793
|
+
],
|
|
1794
|
+
}
|
|
1795
|
+
const factory: AcpSessionFactory = async () => asSession(fake)
|
|
1796
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
1797
|
+
extensionFactories: [],
|
|
1798
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1799
|
+
sessionFactory: factory,
|
|
1800
|
+
})
|
|
1801
|
+
await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
1802
|
+
const res = await agent.unstable_setSessionModel({
|
|
1803
|
+
sessionId: "switch-session",
|
|
1804
|
+
modelId: "provider-b/model-b",
|
|
1805
|
+
})
|
|
1806
|
+
expect(res).toEqual({})
|
|
1807
|
+
expect(fake.model?.provider).toBe("provider-b")
|
|
1808
|
+
expect(fake.model?.id).toBe("model-b")
|
|
1809
|
+
})
|
|
1810
|
+
|
|
1811
|
+
it("throws invalidParams for unknown modelId", async () => {
|
|
1812
|
+
const fake = new FakeAgentSession("switch-session")
|
|
1813
|
+
fake.model = { provider: "provider-a", id: "model-a" }
|
|
1814
|
+
fake.modelRegistry = {
|
|
1815
|
+
getAvailable: () => [{ provider: "provider-a", id: "model-a", name: "Model A" }],
|
|
1816
|
+
}
|
|
1817
|
+
const factory: AcpSessionFactory = async () => asSession(fake)
|
|
1818
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
1819
|
+
extensionFactories: [],
|
|
1820
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1821
|
+
sessionFactory: factory,
|
|
1822
|
+
})
|
|
1823
|
+
await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
1824
|
+
await expect(
|
|
1825
|
+
agent.unstable_setSessionModel({
|
|
1826
|
+
sessionId: "switch-session",
|
|
1827
|
+
modelId: "unknown",
|
|
1828
|
+
}),
|
|
1829
|
+
).rejects.toThrow()
|
|
1830
|
+
})
|
|
1831
|
+
|
|
1832
|
+
it("prompt still works after switching model", async () => {
|
|
1833
|
+
const fake = new FakeAgentSession("switch-session")
|
|
1834
|
+
fake.model = { provider: "provider-a", id: "model-a" }
|
|
1835
|
+
fake.modelRegistry = {
|
|
1836
|
+
getAvailable: () => [
|
|
1837
|
+
{ provider: "provider-a", id: "model-a", name: "Model A" },
|
|
1838
|
+
{ provider: "provider-b", id: "model-b", name: "Model B" },
|
|
1839
|
+
],
|
|
1840
|
+
}
|
|
1841
|
+
const factory: AcpSessionFactory = async () => asSession(fake)
|
|
1842
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
1843
|
+
extensionFactories: [],
|
|
1844
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1845
|
+
sessionFactory: factory,
|
|
1846
|
+
})
|
|
1847
|
+
await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
1848
|
+
await agent.unstable_setSessionModel({
|
|
1849
|
+
sessionId: "switch-session",
|
|
1850
|
+
modelId: "provider-b/model-b",
|
|
1851
|
+
})
|
|
1852
|
+
const result = await agent.prompt({
|
|
1853
|
+
sessionId: "switch-session",
|
|
1854
|
+
prompt: [{ type: "text", text: "hello" }],
|
|
1855
|
+
})
|
|
1856
|
+
expect(result).toBeDefined()
|
|
1857
|
+
expect(fake.model?.provider).toBe("provider-b")
|
|
1858
|
+
expect(fake.model?.id).toBe("model-b")
|
|
1859
|
+
})
|
|
1860
|
+
|
|
1861
|
+
it("throws invalidParams for unknown sessionId", async () => {
|
|
1862
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
1863
|
+
extensionFactories: [],
|
|
1864
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1865
|
+
})
|
|
1866
|
+
await expect(
|
|
1867
|
+
agent.unstable_setSessionModel({
|
|
1868
|
+
sessionId: "no-such-session",
|
|
1869
|
+
modelId: "model-a",
|
|
1870
|
+
}),
|
|
1871
|
+
).rejects.toThrow()
|
|
1872
|
+
})
|
|
1873
|
+
})
|
|
1874
|
+
|
|
1875
|
+
describe("setSessionConfigOption", () => {
|
|
1876
|
+
it("sets permission mode via setSessionConfigOption and returns configOptions", async () => {
|
|
1877
|
+
const fake = new FakeAgentSession("test-session")
|
|
1878
|
+
const sessionFactory: AcpSessionFactory = async () => asSession(fake)
|
|
1879
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
1880
|
+
extensionFactories: [],
|
|
1881
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1882
|
+
sessionFactory,
|
|
1883
|
+
})
|
|
1884
|
+
|
|
1885
|
+
const { sessionId } = await agent.newSession({
|
|
1886
|
+
cwd: "/tmp",
|
|
1887
|
+
mcpServers: [],
|
|
1888
|
+
})
|
|
1889
|
+
|
|
1890
|
+
const res = await agent.setSessionConfigOption({
|
|
1891
|
+
sessionId,
|
|
1892
|
+
configId: "permissions-mode",
|
|
1893
|
+
value: "plan",
|
|
1894
|
+
})
|
|
1895
|
+
|
|
1896
|
+
expect(res.configOptions).toHaveLength(1)
|
|
1897
|
+
expect(res.configOptions[0].id).toBe("permissions-mode")
|
|
1898
|
+
expect(res.configOptions[0].currentValue).toBe("plan")
|
|
1899
|
+
})
|
|
1900
|
+
|
|
1901
|
+
it("sets all valid permission modes", async () => {
|
|
1902
|
+
const fake = new FakeAgentSession("test-session-modes")
|
|
1903
|
+
const sessionFactory: AcpSessionFactory = async () => asSession(fake)
|
|
1904
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
1905
|
+
extensionFactories: [],
|
|
1906
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1907
|
+
sessionFactory,
|
|
1908
|
+
})
|
|
1909
|
+
|
|
1910
|
+
await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
1911
|
+
|
|
1912
|
+
for (const mode of ALL_PERMISSION_MODES) {
|
|
1913
|
+
const res = await agent.setSessionConfigOption({
|
|
1914
|
+
sessionId: "test-session-modes",
|
|
1915
|
+
configId: "permissions-mode",
|
|
1916
|
+
value: mode,
|
|
1917
|
+
})
|
|
1918
|
+
expect(res.configOptions).toHaveLength(1)
|
|
1919
|
+
expect(res.configOptions[0].currentValue).toBe(mode)
|
|
1920
|
+
}
|
|
1921
|
+
})
|
|
1922
|
+
|
|
1923
|
+
it("rejects invalid permission mode value", async () => {
|
|
1924
|
+
const fake = new FakeAgentSession("test-session-invalid")
|
|
1925
|
+
const sessionFactory: AcpSessionFactory = async () => asSession(fake)
|
|
1926
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
1927
|
+
extensionFactories: [],
|
|
1928
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1929
|
+
sessionFactory,
|
|
1930
|
+
})
|
|
1931
|
+
|
|
1932
|
+
await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
1933
|
+
|
|
1934
|
+
await expect(
|
|
1935
|
+
agent.setSessionConfigOption({
|
|
1936
|
+
sessionId: "test-session-invalid",
|
|
1937
|
+
configId: "permissions-mode",
|
|
1938
|
+
value: "invalid-mode",
|
|
1939
|
+
}),
|
|
1940
|
+
).rejects.toMatchObject({ code: -32602 })
|
|
1941
|
+
})
|
|
1942
|
+
|
|
1943
|
+
it("rejects unknown configId", async () => {
|
|
1944
|
+
const fake = new FakeAgentSession("test-session-unknown-config")
|
|
1945
|
+
const sessionFactory: AcpSessionFactory = async () => asSession(fake)
|
|
1946
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
1947
|
+
extensionFactories: [],
|
|
1948
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1949
|
+
sessionFactory,
|
|
1950
|
+
})
|
|
1951
|
+
|
|
1952
|
+
await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
1953
|
+
|
|
1954
|
+
await expect(
|
|
1955
|
+
agent.setSessionConfigOption({
|
|
1956
|
+
sessionId: "test-session-unknown-config",
|
|
1957
|
+
configId: "unknown-config",
|
|
1958
|
+
value: "plan",
|
|
1959
|
+
}),
|
|
1960
|
+
).rejects.toMatchObject({ code: -32602 })
|
|
1961
|
+
})
|
|
1962
|
+
|
|
1963
|
+
it("rejects unknown sessionId", async () => {
|
|
1964
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
1965
|
+
extensionFactories: [],
|
|
1966
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1967
|
+
})
|
|
1968
|
+
|
|
1969
|
+
await expect(
|
|
1970
|
+
agent.setSessionConfigOption({
|
|
1971
|
+
sessionId: "no-such-session",
|
|
1972
|
+
configId: "permissions-mode",
|
|
1973
|
+
value: "plan",
|
|
1974
|
+
}),
|
|
1975
|
+
).rejects.toMatchObject({ code: -32602 })
|
|
1976
|
+
})
|
|
1977
|
+
|
|
1978
|
+
it("returns configOptions with correct structure", async () => {
|
|
1979
|
+
const fake = new FakeAgentSession("test-session-structure")
|
|
1980
|
+
const sessionFactory: AcpSessionFactory = async () => asSession(fake)
|
|
1981
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
1982
|
+
extensionFactories: [],
|
|
1983
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
1984
|
+
sessionFactory,
|
|
1985
|
+
})
|
|
1986
|
+
|
|
1987
|
+
await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
1988
|
+
|
|
1989
|
+
const res = await agent.setSessionConfigOption({
|
|
1990
|
+
sessionId: "test-session-structure",
|
|
1991
|
+
configId: "permissions-mode",
|
|
1992
|
+
value: "auto",
|
|
1993
|
+
})
|
|
1994
|
+
|
|
1995
|
+
expect(res.configOptions[0]).toMatchObject({
|
|
1996
|
+
id: "permissions-mode",
|
|
1997
|
+
name: "Permissions Mode",
|
|
1998
|
+
type: "select",
|
|
1999
|
+
category: "mode",
|
|
2000
|
+
currentValue: "auto",
|
|
2001
|
+
})
|
|
2002
|
+
// Verify options array is present with all valid modes
|
|
2003
|
+
// biome-ignore lint/suspicious/noExplicitAny: union type requires assertion
|
|
2004
|
+
const selectOption = res.configOptions[0] as any
|
|
2005
|
+
expect(selectOption.options).toHaveLength(4)
|
|
2006
|
+
expect(selectOption.options.map((o: { value: string }) => o.value)).toEqual(ALL_PERMISSION_MODES)
|
|
2007
|
+
})
|
|
2008
|
+
|
|
2009
|
+
it("emits config_option_update when permission mode changes", async () => {
|
|
2010
|
+
const { conn, updates } = makeRecordingConn()
|
|
2011
|
+
const fake = new FakeAgentSession("test-session-notify")
|
|
2012
|
+
const sessionFactory: AcpSessionFactory = async () => asSession(fake)
|
|
2013
|
+
const agent = new IkieAcpAgent(conn, {
|
|
2014
|
+
extensionFactories: [],
|
|
2015
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
2016
|
+
sessionFactory,
|
|
2017
|
+
})
|
|
2018
|
+
|
|
2019
|
+
// Create a session (this subscribes to mode changes)
|
|
2020
|
+
const { sessionId } = await agent.newSession({
|
|
2021
|
+
cwd: "/tmp",
|
|
2022
|
+
mcpServers: [],
|
|
2023
|
+
})
|
|
2024
|
+
|
|
2025
|
+
// Change the mode using setSessionConfigOption (this emits the notification)
|
|
2026
|
+
await agent.setSessionConfigOption({
|
|
2027
|
+
sessionId,
|
|
2028
|
+
configId: "permissions-mode",
|
|
2029
|
+
value: "plan",
|
|
2030
|
+
})
|
|
2031
|
+
|
|
2032
|
+
// Check that config_option_update was emitted
|
|
2033
|
+
const configUpdates = updates.filter((u) => u.update.sessionUpdate === "config_option_update")
|
|
2034
|
+
expect(configUpdates).toHaveLength(1)
|
|
2035
|
+
expect(configUpdates[0].sessionId).toBe(sessionId)
|
|
2036
|
+
// Type assertion needed because SessionUpdate is a union type
|
|
2037
|
+
const configUpdate = configUpdates[0].update as {
|
|
2038
|
+
sessionUpdate: "config_option_update"
|
|
2039
|
+
configOptions: Array<{ id: string; currentValue: string }>
|
|
2040
|
+
}
|
|
2041
|
+
expect(configUpdate.configOptions[0].id).toBe("permissions-mode")
|
|
2042
|
+
expect(configUpdate.configOptions[0].currentValue).toBe("plan")
|
|
2043
|
+
})
|
|
2044
|
+
|
|
2045
|
+
it("session-scoped mode is used by permissions extension for tool gating", async () => {
|
|
2046
|
+
const fake = new FakeAgentSession("test-session-enforcement")
|
|
2047
|
+
const sessionFactory: AcpSessionFactory = async () => asSession(fake)
|
|
2048
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
2049
|
+
extensionFactories: [],
|
|
2050
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
2051
|
+
sessionFactory,
|
|
2052
|
+
})
|
|
2053
|
+
|
|
2054
|
+
// Create a session
|
|
2055
|
+
const { sessionId } = await agent.newSession({
|
|
2056
|
+
cwd: "/tmp",
|
|
2057
|
+
mcpServers: [],
|
|
2058
|
+
})
|
|
2059
|
+
|
|
2060
|
+
// Verify initial mode is default
|
|
2061
|
+
const initialRes = await agent.setSessionConfigOption({
|
|
2062
|
+
sessionId,
|
|
2063
|
+
configId: "permissions-mode",
|
|
2064
|
+
value: "default",
|
|
2065
|
+
})
|
|
2066
|
+
expect(initialRes.configOptions[0].currentValue).toBe("default")
|
|
2067
|
+
|
|
2068
|
+
// Switch to plan mode via ACP
|
|
2069
|
+
await agent.setSessionConfigOption({
|
|
2070
|
+
sessionId,
|
|
2071
|
+
configId: "permissions-mode",
|
|
2072
|
+
value: "plan",
|
|
2073
|
+
})
|
|
2074
|
+
|
|
2075
|
+
// Verify the mode was updated
|
|
2076
|
+
const controller = getSessionPermissionFlagController(sessionId)
|
|
2077
|
+
expect(controller).toBeDefined()
|
|
2078
|
+
expect(controller?.getMode()).toEqual({ mode: "plan", source: "user" })
|
|
2079
|
+
|
|
2080
|
+
// Verify mode can be switched back
|
|
2081
|
+
await agent.setSessionConfigOption({
|
|
2082
|
+
sessionId,
|
|
2083
|
+
configId: "permissions-mode",
|
|
2084
|
+
value: "yolo",
|
|
2085
|
+
})
|
|
2086
|
+
expect(controller?.getMode()).toEqual({ mode: "yolo", source: "user" })
|
|
2087
|
+
|
|
2088
|
+
// Cleanup
|
|
2089
|
+
await agent.unstable_closeSession({ sessionId })
|
|
2090
|
+
const afterClose = getSessionPermissionFlagController(sessionId)
|
|
2091
|
+
expect(afterClose).toBeUndefined()
|
|
2092
|
+
})
|
|
2093
|
+
|
|
2094
|
+
it("mode changes via setSessionConfigOption are visible to permissions extension currentMode", async () => {
|
|
2095
|
+
const fake = new FakeAgentSession("test-session-permissions-integration")
|
|
2096
|
+
// Ensure clean env state - previous tests may have set this
|
|
2097
|
+
vi.stubEnv(PERMISSIONS_ENV_KEY, "")
|
|
2098
|
+
Reflect.deleteProperty(process.env, PERMISSIONS_ENV_KEY)
|
|
2099
|
+
|
|
2100
|
+
const sessionFactory: AcpSessionFactory = async () => asSession(fake)
|
|
2101
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
2102
|
+
extensionFactories: [],
|
|
2103
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
2104
|
+
sessionFactory,
|
|
2105
|
+
})
|
|
2106
|
+
|
|
2107
|
+
// Create a session
|
|
2108
|
+
const { sessionId } = await agent.newSession({
|
|
2109
|
+
cwd: "/tmp",
|
|
2110
|
+
mcpServers: [],
|
|
2111
|
+
})
|
|
2112
|
+
|
|
2113
|
+
// Verify the session controller is registered
|
|
2114
|
+
const controller = getSessionPermissionFlagController(sessionId)
|
|
2115
|
+
expect(controller).toBeDefined()
|
|
2116
|
+
expect(controller?.getMode()).toEqual({ mode: "default", source: "user" })
|
|
2117
|
+
|
|
2118
|
+
// Change to plan mode via ACP
|
|
2119
|
+
await agent.setSessionConfigOption({
|
|
2120
|
+
sessionId,
|
|
2121
|
+
configId: "permissions-mode",
|
|
2122
|
+
value: "plan",
|
|
2123
|
+
})
|
|
2124
|
+
|
|
2125
|
+
// Verify the controller reflects the new mode
|
|
2126
|
+
|
|
2127
|
+
expect(controller?.getMode()).toEqual({ mode: "plan", source: "user" })
|
|
2128
|
+
|
|
2129
|
+
await agent.unstable_closeSession({ sessionId })
|
|
2130
|
+
})
|
|
2131
|
+
|
|
2132
|
+
it("delivers config_option_update to multiple concurrent sessions independently", async () => {
|
|
2133
|
+
const updates1: SessionNotification[] = []
|
|
2134
|
+
const updates2: SessionNotification[] = []
|
|
2135
|
+
|
|
2136
|
+
// Create a connection that splits notifications by sessionId
|
|
2137
|
+
const conn = {
|
|
2138
|
+
sessionUpdate: async (msg: SessionNotification) => {
|
|
2139
|
+
if (msg.sessionId === "multi-session-1") {
|
|
2140
|
+
updates1.push(msg)
|
|
2141
|
+
} else if (msg.sessionId === "multi-session-2") {
|
|
2142
|
+
updates2.push(msg)
|
|
2143
|
+
}
|
|
2144
|
+
},
|
|
2145
|
+
} as unknown as AgentSideConnection
|
|
2146
|
+
|
|
2147
|
+
let callCount = 0
|
|
2148
|
+
const fake1 = new FakeAgentSession("multi-session-1")
|
|
2149
|
+
const fake2 = new FakeAgentSession("multi-session-2")
|
|
2150
|
+
|
|
2151
|
+
// One agent with a session factory that returns different sessions
|
|
2152
|
+
const agent = new IkieAcpAgent(conn, {
|
|
2153
|
+
extensionFactories: [],
|
|
2154
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
2155
|
+
sessionFactory: async () => {
|
|
2156
|
+
callCount++
|
|
2157
|
+
return asSession(callCount === 1 ? fake1 : fake2)
|
|
2158
|
+
},
|
|
2159
|
+
})
|
|
2160
|
+
|
|
2161
|
+
const { sessionId: sid1 } = await agent.newSession({
|
|
2162
|
+
cwd: "/tmp",
|
|
2163
|
+
mcpServers: [],
|
|
2164
|
+
})
|
|
2165
|
+
const { sessionId: sid2 } = await agent.newSession({
|
|
2166
|
+
cwd: "/tmp",
|
|
2167
|
+
mcpServers: [],
|
|
2168
|
+
})
|
|
2169
|
+
|
|
2170
|
+
// Verify sessions are under the same agent (same entries map)
|
|
2171
|
+
expect(sid1).toBe("multi-session-1")
|
|
2172
|
+
expect(sid2).toBe("multi-session-2")
|
|
2173
|
+
|
|
2174
|
+
const filter = (u: SessionNotification) => u.update.sessionUpdate === "config_option_update"
|
|
2175
|
+
|
|
2176
|
+
// Change mode via session 1 — only session 1 is notified
|
|
2177
|
+
await agent.setSessionConfigOption({
|
|
2178
|
+
sessionId: sid1,
|
|
2179
|
+
configId: "permissions-mode",
|
|
2180
|
+
value: "yolo",
|
|
2181
|
+
})
|
|
2182
|
+
|
|
2183
|
+
// Session 1 should have exactly 1 notification (its own)
|
|
2184
|
+
expect(updates1.filter(filter)).toHaveLength(1)
|
|
2185
|
+
// Session 2 should have no notification yet
|
|
2186
|
+
expect(updates2.filter(filter)).toHaveLength(0)
|
|
2187
|
+
|
|
2188
|
+
// Change mode via session 2 — only session 2 is notified
|
|
2189
|
+
await agent.setSessionConfigOption({
|
|
2190
|
+
sessionId: sid2,
|
|
2191
|
+
configId: "permissions-mode",
|
|
2192
|
+
value: "auto",
|
|
2193
|
+
})
|
|
2194
|
+
|
|
2195
|
+
// Session 1 should still have only 1 notification
|
|
2196
|
+
expect(updates1.filter(filter)).toHaveLength(1)
|
|
2197
|
+
// Session 2 should now have exactly 1 notification
|
|
2198
|
+
expect(updates2.filter(filter)).toHaveLength(1)
|
|
2199
|
+
})
|
|
2200
|
+
})
|
|
2201
|
+
|
|
2202
|
+
describe("ACP mode controller integration with permissions extension", () => {
|
|
2203
|
+
// Import permissions extension test utilities
|
|
2204
|
+
async function createPermissionsHarness(tools: string[], flags: Record<string, unknown> = {}) {
|
|
2205
|
+
const { default: permissionsExtension } = await import("../../extensions/permissions/index.js")
|
|
2206
|
+
const handlers = new Map<string, Array<(...args: unknown[]) => unknown>>()
|
|
2207
|
+
const commands = new Map<
|
|
2208
|
+
string,
|
|
2209
|
+
{
|
|
2210
|
+
description: string
|
|
2211
|
+
handler: (args: string, ctx: unknown) => Promise<void> | void
|
|
2212
|
+
}
|
|
2213
|
+
>()
|
|
2214
|
+
let activeTools: string[] = []
|
|
2215
|
+
|
|
2216
|
+
const pi = {
|
|
2217
|
+
on: (event: string, handler: (...args: unknown[]) => unknown) => {
|
|
2218
|
+
const list = handlers.get(event) ?? []
|
|
2219
|
+
list.push(handler)
|
|
2220
|
+
handlers.set(event, list)
|
|
2221
|
+
},
|
|
2222
|
+
registerCommand: (
|
|
2223
|
+
name: string,
|
|
2224
|
+
command: {
|
|
2225
|
+
description: string
|
|
2226
|
+
handler: (args: string, ctx: unknown) => Promise<void> | void
|
|
2227
|
+
},
|
|
2228
|
+
) => {
|
|
2229
|
+
commands.set(name, command)
|
|
2230
|
+
},
|
|
2231
|
+
getAllTools: () => tools,
|
|
2232
|
+
getActiveTools: () => activeTools,
|
|
2233
|
+
setActiveTools: (names: string[]) => {
|
|
2234
|
+
activeTools = names.filter((n) => tools.includes(n))
|
|
2235
|
+
},
|
|
2236
|
+
getFlag: (name: string) => flags[name],
|
|
2237
|
+
registerFlag: () => {},
|
|
2238
|
+
sendMessage: () => {},
|
|
2239
|
+
getEnvironment: () => ({
|
|
2240
|
+
environmentInfo: {
|
|
2241
|
+
permittedTools: new Set(tools),
|
|
2242
|
+
},
|
|
2243
|
+
}),
|
|
2244
|
+
setActiveToolIdsByServer: () => {},
|
|
2245
|
+
} as unknown as import("@earendil-works/pi-coding-agent").ExtensionAPI
|
|
2246
|
+
|
|
2247
|
+
permissionsExtension(pi)
|
|
2248
|
+
|
|
2249
|
+
return {
|
|
2250
|
+
async fireSessionStart(ctx: unknown) {
|
|
2251
|
+
for (const handler of handlers.get("session_start") ?? []) {
|
|
2252
|
+
await handler({}, ctx)
|
|
2253
|
+
}
|
|
2254
|
+
},
|
|
2255
|
+
async fireToolCall(event: { toolName: string; input: unknown }, ctx: unknown) {
|
|
2256
|
+
const toolHandlers = handlers.get("tool_call") ?? []
|
|
2257
|
+
for (const handler of toolHandlers) {
|
|
2258
|
+
const result = await handler(event, ctx)
|
|
2259
|
+
if (result) return result
|
|
2260
|
+
}
|
|
2261
|
+
return undefined
|
|
2262
|
+
},
|
|
2263
|
+
commands,
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
function createMockContext(sessionId: string, cwd: string): ExtensionContext {
|
|
2268
|
+
return {
|
|
2269
|
+
sessionManager: { getSessionId: vi.fn().mockReturnValue(sessionId) } as unknown as SessionManager,
|
|
2270
|
+
cwd,
|
|
2271
|
+
mode: "rpc",
|
|
2272
|
+
hasUI: true,
|
|
2273
|
+
ui: {
|
|
2274
|
+
notify: vi.fn(),
|
|
2275
|
+
setStatus: vi.fn(),
|
|
2276
|
+
onTerminalInput: vi.fn(),
|
|
2277
|
+
theme: { fg: vi.fn(), bg: vi.fn(), getFgAnsi: vi.fn() } as unknown as Theme,
|
|
2278
|
+
} as unknown as ExtensionUIContext,
|
|
2279
|
+
modelRegistry: {
|
|
2280
|
+
authStorage: {} as AuthStorage,
|
|
2281
|
+
getApiKeyAndHeaders: vi.fn().mockReturnValue({ ok: true, apiKey: "test" }),
|
|
2282
|
+
getAvailable: vi.fn().mockReturnValue([
|
|
2283
|
+
{
|
|
2284
|
+
id: "kimi",
|
|
2285
|
+
name: "kimi",
|
|
2286
|
+
},
|
|
2287
|
+
]),
|
|
2288
|
+
find: vi.fn().mockReturnValue({ id: "kimi", name: "kimi" }),
|
|
2289
|
+
} as unknown as ModelRegistry,
|
|
2290
|
+
} as unknown as ExtensionContext
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
afterEach(() => {
|
|
2294
|
+
vi.unstubAllEnvs()
|
|
2295
|
+
})
|
|
2296
|
+
|
|
2297
|
+
it("plan mode via ACP blocks write operations through permissions extension", async () => {
|
|
2298
|
+
// Clean env state
|
|
2299
|
+
vi.stubEnv(PERMISSIONS_ENV_KEY, "")
|
|
2300
|
+
Reflect.deleteProperty(process.env, PERMISSIONS_ENV_KEY)
|
|
2301
|
+
|
|
2302
|
+
const sessionId = "test-plan-mode-session"
|
|
2303
|
+
const cwd = "/tmp"
|
|
2304
|
+
|
|
2305
|
+
// Set up permissions extension with write tool
|
|
2306
|
+
const harness = await createPermissionsHarness(["write", "read"])
|
|
2307
|
+
|
|
2308
|
+
// Create an ACP session (registers the controller)
|
|
2309
|
+
const fake = new FakeAgentSession(sessionId)
|
|
2310
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
2311
|
+
extensionFactories: [],
|
|
2312
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
2313
|
+
sessionFactory: async () => asSession(fake),
|
|
2314
|
+
})
|
|
2315
|
+
|
|
2316
|
+
await agent.newSession({ cwd, mcpServers: [] })
|
|
2317
|
+
|
|
2318
|
+
// Fire session_start so the permissions extension subscribes to the controller
|
|
2319
|
+
const mockCtx = createMockContext(sessionId, cwd)
|
|
2320
|
+
await harness.fireSessionStart(mockCtx)
|
|
2321
|
+
|
|
2322
|
+
// Verify controller is registered
|
|
2323
|
+
const controller = getSessionPermissionFlagController(sessionId)
|
|
2324
|
+
expect(controller).toBeDefined()
|
|
2325
|
+
|
|
2326
|
+
// Switch to plan mode via ACP
|
|
2327
|
+
await agent.setSessionConfigOption({
|
|
2328
|
+
sessionId,
|
|
2329
|
+
configId: "permissions-mode",
|
|
2330
|
+
value: "plan",
|
|
2331
|
+
})
|
|
2332
|
+
|
|
2333
|
+
// Verify controller mode is plan
|
|
2334
|
+
expect(controller?.getMode()).toEqual({ mode: "plan", source: "user" })
|
|
2335
|
+
const writeToolEvent = {
|
|
2336
|
+
toolName: "write",
|
|
2337
|
+
input: { path: "/tmp/test.txt", content: "hello" },
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
const result = (await harness.fireToolCall(writeToolEvent, mockCtx)) as
|
|
2341
|
+
| { block: boolean; reason: string }
|
|
2342
|
+
| undefined
|
|
2343
|
+
|
|
2344
|
+
// Should be blocked in plan mode
|
|
2345
|
+
expect(result).toBeDefined()
|
|
2346
|
+
expect(result?.block).toBe(true)
|
|
2347
|
+
expect(result?.reason).toContain("Plan")
|
|
2348
|
+
|
|
2349
|
+
// Cleanup
|
|
2350
|
+
await agent.unstable_closeSession({ sessionId })
|
|
2351
|
+
})
|
|
2352
|
+
|
|
2353
|
+
it("yolo mode via ACP allows write operations through permissions extension", async () => {
|
|
2354
|
+
// Clean env state
|
|
2355
|
+
vi.stubEnv(PERMISSIONS_ENV_KEY, "")
|
|
2356
|
+
Reflect.deleteProperty(process.env, PERMISSIONS_ENV_KEY)
|
|
2357
|
+
|
|
2358
|
+
const sessionId = "test-yolo-mode-session"
|
|
2359
|
+
const cwd = "/tmp"
|
|
2360
|
+
|
|
2361
|
+
// Set up permissions extension
|
|
2362
|
+
const harness = await createPermissionsHarness(["write", "read", "bash"])
|
|
2363
|
+
|
|
2364
|
+
// Create an ACP session
|
|
2365
|
+
const fake = new FakeAgentSession(sessionId)
|
|
2366
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
2367
|
+
extensionFactories: [],
|
|
2368
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
2369
|
+
sessionFactory: async () => asSession(fake),
|
|
2370
|
+
})
|
|
2371
|
+
|
|
2372
|
+
await agent.newSession({ cwd, mcpServers: [] })
|
|
2373
|
+
|
|
2374
|
+
// Fire session_start so the permissions extension subscribes to the controller
|
|
2375
|
+
const mockCtx = createMockContext(sessionId, cwd)
|
|
2376
|
+
await harness.fireSessionStart(mockCtx)
|
|
2377
|
+
|
|
2378
|
+
// Switch to yolo mode via ACP
|
|
2379
|
+
await agent.setSessionConfigOption({
|
|
2380
|
+
sessionId,
|
|
2381
|
+
configId: "permissions-mode",
|
|
2382
|
+
value: "yolo",
|
|
2383
|
+
})
|
|
2384
|
+
|
|
2385
|
+
// Verify controller mode is yolo
|
|
2386
|
+
const controller = getSessionPermissionFlagController(sessionId)
|
|
2387
|
+
expect(controller?.getMode()).toEqual({ mode: "yolo", source: "user" })
|
|
2388
|
+
const writeToolEvent = {
|
|
2389
|
+
toolName: "write",
|
|
2390
|
+
input: { path: "/tmp/test.txt", content: "hello" },
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
const result = await harness.fireToolCall(writeToolEvent, mockCtx)
|
|
2394
|
+
|
|
2395
|
+
// Should NOT be blocked in yolo mode
|
|
2396
|
+
expect(result).toBeUndefined()
|
|
2397
|
+
|
|
2398
|
+
// Cleanup
|
|
2399
|
+
await agent.unstable_closeSession({ sessionId })
|
|
2400
|
+
})
|
|
2401
|
+
|
|
2402
|
+
it("setSessionConfigOption emits exactly one config_option_update when permissions extension is active", async () => {
|
|
2403
|
+
// Regression test for the double-notification bug:
|
|
2404
|
+
// setSessionConfigOption -> controller.setMode fires the ACP notification subscriber.
|
|
2405
|
+
// The permissions extension's session_start subscriber also fires changeMode ->
|
|
2406
|
+
// setRuntimePermissionMode, which must NOT re-enter controller.setMode (which would
|
|
2407
|
+
// fire a second notification). The insideControllerCallback guard prevents this.
|
|
2408
|
+
vi.stubEnv(PERMISSIONS_ENV_KEY, "")
|
|
2409
|
+
Reflect.deleteProperty(process.env, PERMISSIONS_ENV_KEY)
|
|
2410
|
+
|
|
2411
|
+
const sessionId = "test-no-double-notify"
|
|
2412
|
+
const cwd = "/tmp"
|
|
2413
|
+
|
|
2414
|
+
const harness = await createPermissionsHarness(["write", "read"])
|
|
2415
|
+
|
|
2416
|
+
const { conn, updates } = makeRecordingConn()
|
|
2417
|
+
const fake = new FakeAgentSession(sessionId)
|
|
2418
|
+
const agent = new IkieAcpAgent(conn, {
|
|
2419
|
+
extensionFactories: [],
|
|
2420
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
2421
|
+
sessionFactory: async () => asSession(fake),
|
|
2422
|
+
})
|
|
2423
|
+
|
|
2424
|
+
await agent.newSession({ cwd, mcpServers: [] })
|
|
2425
|
+
|
|
2426
|
+
// Fire session_start so the permissions extension subscribes to the controller.
|
|
2427
|
+
const mockCtx = createMockContext(sessionId, cwd)
|
|
2428
|
+
await harness.fireSessionStart(mockCtx)
|
|
2429
|
+
|
|
2430
|
+
// Drain any notifications emitted during session setup.
|
|
2431
|
+
updates.length = 0
|
|
2432
|
+
|
|
2433
|
+
// Change mode — this is the operation that previously emitted two notifications.
|
|
2434
|
+
await agent.setSessionConfigOption({
|
|
2435
|
+
sessionId,
|
|
2436
|
+
configId: "permissions-mode",
|
|
2437
|
+
value: "plan",
|
|
2438
|
+
})
|
|
2439
|
+
|
|
2440
|
+
const configUpdates = updates.filter((u) => u.update.sessionUpdate === "config_option_update")
|
|
2441
|
+
expect(configUpdates).toHaveLength(1)
|
|
2442
|
+
// biome-ignore lint/suspicious/noExplicitAny: union type requires assertion
|
|
2443
|
+
const update = configUpdates[0].update as any
|
|
2444
|
+
expect(update.configOptions[0].currentValue).toBe("plan")
|
|
2445
|
+
|
|
2446
|
+
await agent.unstable_closeSession({ sessionId })
|
|
2447
|
+
})
|
|
2448
|
+
|
|
2449
|
+
it("leaving plan mode via ACP restores write/edit tools and emits exactly one config update", async () => {
|
|
2450
|
+
// Changing permissions-mode through ACP must run the full changeMode transition,
|
|
2451
|
+
// including restoring tool visibility and aborting stale permission prompts.
|
|
2452
|
+
// The skipNotify flag should prevent duplicate ACP config updates.
|
|
2453
|
+
vi.stubEnv(PERMISSIONS_ENV_KEY, "")
|
|
2454
|
+
Reflect.deleteProperty(process.env, PERMISSIONS_ENV_KEY)
|
|
2455
|
+
|
|
2456
|
+
const sessionId = "test-leave-plan-mode"
|
|
2457
|
+
const cwd = "/tmp"
|
|
2458
|
+
|
|
2459
|
+
// Set up harness with write and edit tools that would be hidden in plan mode
|
|
2460
|
+
const harness = await createPermissionsHarness(["write", "edit", "read", "bash"])
|
|
2461
|
+
|
|
2462
|
+
const { conn, updates } = makeRecordingConn()
|
|
2463
|
+
const fake = new FakeAgentSession(sessionId)
|
|
2464
|
+
const agent = new IkieAcpAgent(conn, {
|
|
2465
|
+
extensionFactories: [],
|
|
2466
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
2467
|
+
sessionFactory: async () => asSession(fake),
|
|
2468
|
+
})
|
|
2469
|
+
|
|
2470
|
+
await agent.newSession({ cwd, mcpServers: [] })
|
|
2471
|
+
|
|
2472
|
+
// Fire session_start so the permissions extension subscribes to the controller
|
|
2473
|
+
const mockCtx = createMockContext(sessionId, cwd)
|
|
2474
|
+
await harness.fireSessionStart(mockCtx)
|
|
2475
|
+
|
|
2476
|
+
// Clear any notifications from setup
|
|
2477
|
+
updates.length = 0
|
|
2478
|
+
|
|
2479
|
+
// Start in plan mode via ACP
|
|
2480
|
+
await agent.setSessionConfigOption({
|
|
2481
|
+
sessionId,
|
|
2482
|
+
configId: "permissions-mode",
|
|
2483
|
+
value: "plan",
|
|
2484
|
+
})
|
|
2485
|
+
|
|
2486
|
+
// Verify we're in plan mode - write/edit should be blocked by permissions extension
|
|
2487
|
+
const writeBlockedResult = (await harness.fireToolCall(
|
|
2488
|
+
{ toolName: "write", input: { path: "/tmp/test.txt", content: "hello" } },
|
|
2489
|
+
mockCtx,
|
|
2490
|
+
)) as { block: boolean; reason: string }
|
|
2491
|
+
expect(writeBlockedResult?.block).toBe(true)
|
|
2492
|
+
expect(writeBlockedResult?.reason).toContain("Plan mode")
|
|
2493
|
+
|
|
2494
|
+
// Clear notifications from entering plan mode
|
|
2495
|
+
updates.length = 0
|
|
2496
|
+
|
|
2497
|
+
// Test leaving plan mode to yolo
|
|
2498
|
+
await agent.setSessionConfigOption({
|
|
2499
|
+
sessionId,
|
|
2500
|
+
configId: "permissions-mode",
|
|
2501
|
+
value: "yolo",
|
|
2502
|
+
})
|
|
2503
|
+
|
|
2504
|
+
// Verify exactly one config_option_update was emitted
|
|
2505
|
+
const yoloConfigUpdates = updates.filter((u) => u.update.sessionUpdate === "config_option_update")
|
|
2506
|
+
expect(yoloConfigUpdates).toHaveLength(1)
|
|
2507
|
+
// biome-ignore lint/suspicious/noExplicitAny: union type requires assertion
|
|
2508
|
+
const yoloUpdate = yoloConfigUpdates[0].update as any
|
|
2509
|
+
expect(yoloUpdate.configOptions[0].currentValue).toBe("yolo")
|
|
2510
|
+
|
|
2511
|
+
// Verify write operations are now allowed (permissions extension allows them in yolo)
|
|
2512
|
+
const writeAllowedResult = await harness.fireToolCall(
|
|
2513
|
+
{ toolName: "write", input: { path: "/tmp/test.txt", content: "hello" } },
|
|
2514
|
+
mockCtx,
|
|
2515
|
+
)
|
|
2516
|
+
expect(writeAllowedResult).toBeUndefined() // Not blocked
|
|
2517
|
+
|
|
2518
|
+
// Reset and test leaving plan mode to default
|
|
2519
|
+
updates.length = 0
|
|
2520
|
+
|
|
2521
|
+
// Go back to plan mode
|
|
2522
|
+
await agent.setSessionConfigOption({
|
|
2523
|
+
sessionId,
|
|
2524
|
+
configId: "permissions-mode",
|
|
2525
|
+
value: "plan",
|
|
2526
|
+
})
|
|
2527
|
+
updates.length = 0 // Clear notifications
|
|
2528
|
+
|
|
2529
|
+
// Verify tools are blocked again in plan mode
|
|
2530
|
+
const writeBlockedAgain = (await harness.fireToolCall(
|
|
2531
|
+
{ toolName: "write", input: { path: "/tmp/test.txt", content: "hello" } },
|
|
2532
|
+
mockCtx,
|
|
2533
|
+
)) as { block: boolean; reason: string }
|
|
2534
|
+
expect(writeBlockedAgain?.block).toBe(true)
|
|
2535
|
+
|
|
2536
|
+
// Leave plan mode to default
|
|
2537
|
+
await agent.setSessionConfigOption({
|
|
2538
|
+
sessionId,
|
|
2539
|
+
configId: "permissions-mode",
|
|
2540
|
+
value: "default",
|
|
2541
|
+
})
|
|
2542
|
+
|
|
2543
|
+
// Verify exactly one config_option_update was emitted
|
|
2544
|
+
const defaultConfigUpdates = updates.filter((u) => u.update.sessionUpdate === "config_option_update")
|
|
2545
|
+
expect(defaultConfigUpdates).toHaveLength(1)
|
|
2546
|
+
// biome-ignore lint/suspicious/noExplicitAny: union type requires assertion
|
|
2547
|
+
const defaultUpdate = defaultConfigUpdates[0].update as any
|
|
2548
|
+
expect(defaultUpdate.configOptions[0].currentValue).toBe("default")
|
|
2549
|
+
|
|
2550
|
+
// Verify write operations are now gated behind explicit user approval
|
|
2551
|
+
const writeDefaultResult = (await harness.fireToolCall(
|
|
2552
|
+
{ toolName: "write", input: { path: "/tmp/test.txt", content: "hello" } },
|
|
2553
|
+
mockCtx,
|
|
2554
|
+
)) as { block: boolean; reason: string }
|
|
2555
|
+
expect(writeDefaultResult.block).toBe(true)
|
|
2556
|
+
expect(writeDefaultResult.reason).toContain("Declined by user")
|
|
2557
|
+
|
|
2558
|
+
await agent.unstable_closeSession({ sessionId })
|
|
2559
|
+
})
|
|
2560
|
+
})
|
|
2561
|
+
|
|
2562
|
+
describe("session mode controller lifecycle", () => {
|
|
2563
|
+
afterEach(() => {
|
|
2564
|
+
vi.unstubAllEnvs()
|
|
2565
|
+
})
|
|
2566
|
+
|
|
2567
|
+
it("unregisters mode controller on closeSession", async () => {
|
|
2568
|
+
const fake = new FakeAgentSession("close-mode-ctrl")
|
|
2569
|
+
const sessionFactory: AcpSessionFactory = async () => asSession(fake)
|
|
2570
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
2571
|
+
extensionFactories: [],
|
|
2572
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
2573
|
+
sessionFactory,
|
|
2574
|
+
})
|
|
2575
|
+
|
|
2576
|
+
const { sessionId } = await agent.newSession({
|
|
2577
|
+
cwd: "/tmp",
|
|
2578
|
+
mcpServers: [],
|
|
2579
|
+
})
|
|
2580
|
+
expect(getSessionPermissionFlagController(sessionId)).toBeDefined()
|
|
2581
|
+
|
|
2582
|
+
await agent.unstable_closeSession({ sessionId })
|
|
2583
|
+
expect(getSessionPermissionFlagController(sessionId)).toBeUndefined()
|
|
2584
|
+
})
|
|
2585
|
+
|
|
2586
|
+
it("unregisters mode controllers on shutdown", async () => {
|
|
2587
|
+
const fake1 = new FakeAgentSession("shutdown-mode-1")
|
|
2588
|
+
const fake2 = new FakeAgentSession("shutdown-mode-2")
|
|
2589
|
+
let callCount = 0
|
|
2590
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
2591
|
+
extensionFactories: [],
|
|
2592
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
2593
|
+
sessionFactory: async () => asSession(++callCount === 1 ? fake1 : fake2),
|
|
2594
|
+
})
|
|
2595
|
+
|
|
2596
|
+
const r1 = await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
2597
|
+
const r2 = await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
2598
|
+
expect(getSessionPermissionFlagController(r1.sessionId)).toBeDefined()
|
|
2599
|
+
expect(getSessionPermissionFlagController(r2.sessionId)).toBeDefined()
|
|
2600
|
+
|
|
2601
|
+
await agent.shutdown()
|
|
2602
|
+
expect(getSessionPermissionFlagController(r1.sessionId)).toBeUndefined()
|
|
2603
|
+
expect(getSessionPermissionFlagController(r2.sessionId)).toBeUndefined()
|
|
2604
|
+
})
|
|
2605
|
+
|
|
2606
|
+
it("seeds initial mode from env baseline, not from live env mutations", async () => {
|
|
2607
|
+
vi.stubEnv(PERMISSIONS_ENV_KEY, "auto")
|
|
2608
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
2609
|
+
extensionFactories: [],
|
|
2610
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
2611
|
+
sessionFactory: async () => asSession(new FakeAgentSession("env-baseline-1")),
|
|
2612
|
+
})
|
|
2613
|
+
|
|
2614
|
+
// Mutate env after construction (simulates another session writing to env)
|
|
2615
|
+
process.env[PERMISSIONS_ENV_KEY] = "yolo"
|
|
2616
|
+
|
|
2617
|
+
const res = await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
2618
|
+
// Should use the snapshotted baseline (auto), not the live env (yolo)
|
|
2619
|
+
expect(res.configOptions?.[0].currentValue).toBe("auto")
|
|
2620
|
+
})
|
|
2621
|
+
|
|
2622
|
+
it("defaults to 'default' mode when IKIE_PERMISSIONS env is unset", async () => {
|
|
2623
|
+
vi.stubEnv(PERMISSIONS_ENV_KEY, "")
|
|
2624
|
+
Reflect.deleteProperty(process.env, PERMISSIONS_ENV_KEY)
|
|
2625
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
2626
|
+
extensionFactories: [],
|
|
2627
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
2628
|
+
sessionFactory: async () => asSession(new FakeAgentSession("env-unset")),
|
|
2629
|
+
})
|
|
2630
|
+
|
|
2631
|
+
const res = await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
2632
|
+
expect(res.configOptions?.[0].currentValue).toBe("default")
|
|
2633
|
+
})
|
|
2634
|
+
|
|
2635
|
+
it("uses defaultMode from permissions config file when env is not set", async () => {
|
|
2636
|
+
// Create a temp directory with a .ikie/permissions.json
|
|
2637
|
+
const tmpDir = mkdtempSync(join(tmpdir(), "acp-mode-config-test-"))
|
|
2638
|
+
const ikieDir = join(tmpDir, ".ikie")
|
|
2639
|
+
mkdirSync(ikieDir, { recursive: true })
|
|
2640
|
+
writeFileSync(join(ikieDir, "permissions.json"), JSON.stringify({ defaultMode: "plan" }))
|
|
2641
|
+
|
|
2642
|
+
vi.stubEnv(PERMISSIONS_ENV_KEY, "")
|
|
2643
|
+
Reflect.deleteProperty(process.env, PERMISSIONS_ENV_KEY)
|
|
2644
|
+
|
|
2645
|
+
try {
|
|
2646
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
2647
|
+
extensionFactories: [],
|
|
2648
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
2649
|
+
sessionFactory: async () => asSession(new FakeAgentSession("config-mode")),
|
|
2650
|
+
})
|
|
2651
|
+
|
|
2652
|
+
const res = await agent.newSession({ cwd: tmpDir, mcpServers: [] })
|
|
2653
|
+
expect(res.configOptions?.[0].currentValue).toBe("plan")
|
|
2654
|
+
} finally {
|
|
2655
|
+
rmSync(tmpDir, { recursive: true, force: true })
|
|
2656
|
+
}
|
|
2657
|
+
})
|
|
2658
|
+
|
|
2659
|
+
it("env IKIE_PERMISSIONS takes precedence over config defaultMode", async () => {
|
|
2660
|
+
// Create a temp directory with a .ikie/permissions.json
|
|
2661
|
+
const tmpDir = mkdtempSync(join(tmpdir(), "acp-mode-env-precedence-test-"))
|
|
2662
|
+
const ikieDir = join(tmpDir, ".ikie")
|
|
2663
|
+
mkdirSync(ikieDir, { recursive: true })
|
|
2664
|
+
writeFileSync(join(ikieDir, "permissions.json"), JSON.stringify({ defaultMode: "plan" }))
|
|
2665
|
+
|
|
2666
|
+
// Set env to a different mode
|
|
2667
|
+
vi.stubEnv(PERMISSIONS_ENV_KEY, "auto")
|
|
2668
|
+
|
|
2669
|
+
try {
|
|
2670
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
2671
|
+
extensionFactories: [],
|
|
2672
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
2673
|
+
sessionFactory: async () => asSession(new FakeAgentSession("env-precedence")),
|
|
2674
|
+
})
|
|
2675
|
+
|
|
2676
|
+
const res = await agent.newSession({ cwd: tmpDir, mcpServers: [] })
|
|
2677
|
+
// Env should take precedence over config
|
|
2678
|
+
expect(res.configOptions?.[0].currentValue).toBe("auto")
|
|
2679
|
+
} finally {
|
|
2680
|
+
rmSync(tmpDir, { recursive: true, force: true })
|
|
2681
|
+
}
|
|
2682
|
+
})
|
|
2683
|
+
|
|
2684
|
+
it("per-session mode changes do not leak across sessions", async () => {
|
|
2685
|
+
vi.stubEnv(PERMISSIONS_ENV_KEY, "")
|
|
2686
|
+
Reflect.deleteProperty(process.env, PERMISSIONS_ENV_KEY)
|
|
2687
|
+
const fake1 = new FakeAgentSession("isolate-1")
|
|
2688
|
+
const fake2 = new FakeAgentSession("isolate-2")
|
|
2689
|
+
let callCount = 0
|
|
2690
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
2691
|
+
extensionFactories: [],
|
|
2692
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
2693
|
+
sessionFactory: async () => asSession(++callCount === 1 ? fake1 : fake2),
|
|
2694
|
+
})
|
|
2695
|
+
|
|
2696
|
+
const r1 = await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
2697
|
+
const r2 = await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
2698
|
+
|
|
2699
|
+
// Both sessions start at "default"
|
|
2700
|
+
expect(getSessionPermissionFlagController(r1.sessionId)?.getMode()).toEqual({ mode: "default", source: "user" })
|
|
2701
|
+
expect(getSessionPermissionFlagController(r2.sessionId)?.getMode()).toEqual({ mode: "default", source: "user" })
|
|
2702
|
+
|
|
2703
|
+
// Change session 1 to yolo
|
|
2704
|
+
await agent.setSessionConfigOption({
|
|
2705
|
+
sessionId: r1.sessionId,
|
|
2706
|
+
configId: "permissions-mode",
|
|
2707
|
+
value: "yolo",
|
|
2708
|
+
})
|
|
2709
|
+
|
|
2710
|
+
// Session 1 is yolo, session 2 is still default
|
|
2711
|
+
expect(getSessionPermissionFlagController(r1.sessionId)?.getMode()).toEqual({ mode: "yolo", source: "user" })
|
|
2712
|
+
expect(getSessionPermissionFlagController(r2.sessionId)?.getMode()).toEqual({ mode: "default", source: "user" })
|
|
2713
|
+
})
|
|
2714
|
+
|
|
2715
|
+
it("closeSession deletes the IKIE_PERMISSIONS_<sessionId> env key", async () => {
|
|
2716
|
+
vi.stubEnv(PERMISSIONS_ENV_KEY, "")
|
|
2717
|
+
Reflect.deleteProperty(process.env, PERMISSIONS_ENV_KEY)
|
|
2718
|
+
|
|
2719
|
+
const fake = new FakeAgentSession("close-env-key")
|
|
2720
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
2721
|
+
extensionFactories: [],
|
|
2722
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
2723
|
+
sessionFactory: async () => asSession(fake),
|
|
2724
|
+
})
|
|
2725
|
+
|
|
2726
|
+
const { sessionId } = await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
2727
|
+
|
|
2728
|
+
// Set a mode so the namespaced env key is definitely written.
|
|
2729
|
+
await agent.setSessionConfigOption({ sessionId, configId: "permissions-mode", value: "yolo" })
|
|
2730
|
+
const envKey = `${PERMISSIONS_ENV_KEY}_${sessionId}`
|
|
2731
|
+
expect(process.env[envKey]).toBe("yolo")
|
|
2732
|
+
|
|
2733
|
+
await agent.unstable_closeSession({ sessionId })
|
|
2734
|
+
|
|
2735
|
+
expect(process.env[envKey]).toBeUndefined()
|
|
2736
|
+
})
|
|
2737
|
+
|
|
2738
|
+
it("shutdown deletes IKIE_PERMISSIONS_<sessionId> env keys for all sessions", async () => {
|
|
2739
|
+
vi.stubEnv(PERMISSIONS_ENV_KEY, "")
|
|
2740
|
+
Reflect.deleteProperty(process.env, PERMISSIONS_ENV_KEY)
|
|
2741
|
+
|
|
2742
|
+
const fake1 = new FakeAgentSession("shutdown-env-1")
|
|
2743
|
+
const fake2 = new FakeAgentSession("shutdown-env-2")
|
|
2744
|
+
let callCount = 0
|
|
2745
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
2746
|
+
extensionFactories: [],
|
|
2747
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
2748
|
+
sessionFactory: async () => asSession(++callCount === 1 ? fake1 : fake2),
|
|
2749
|
+
})
|
|
2750
|
+
|
|
2751
|
+
const r1 = await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
2752
|
+
const r2 = await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
2753
|
+
|
|
2754
|
+
// Write a namespaced env key for each session.
|
|
2755
|
+
await agent.setSessionConfigOption({ sessionId: r1.sessionId, configId: "permissions-mode", value: "plan" })
|
|
2756
|
+
await agent.setSessionConfigOption({ sessionId: r2.sessionId, configId: "permissions-mode", value: "auto" })
|
|
2757
|
+
|
|
2758
|
+
const key1 = `${PERMISSIONS_ENV_KEY}_${r1.sessionId}`
|
|
2759
|
+
const key2 = `${PERMISSIONS_ENV_KEY}_${r2.sessionId}`
|
|
2760
|
+
expect(process.env[key1]).toBe("plan")
|
|
2761
|
+
expect(process.env[key2]).toBe("auto")
|
|
2762
|
+
|
|
2763
|
+
await agent.shutdown()
|
|
2764
|
+
|
|
2765
|
+
expect(process.env[key1]).toBeUndefined()
|
|
2766
|
+
expect(process.env[key2]).toBeUndefined()
|
|
2767
|
+
})
|
|
2768
|
+
})
|
|
2769
|
+
|
|
2770
|
+
// Direct coverage for describeToolCall. The function drives the tool_call
|
|
2771
|
+
// notification's title, kind, and locations — ACP clients key UI affordances
|
|
2772
|
+
// off these. Two recent fixes (064ff92, 00f58f3) landed on it; table-driven
|
|
2773
|
+
// cases here keep the title/kind matrix from silently drifting.
|
|
2774
|
+
describe("describeToolCall", () => {
|
|
2775
|
+
it("detects hidden system Agent calls", () => {
|
|
2776
|
+
expect(isHiddenToolCall("Agent", { visibility: "system" })).toBe(true)
|
|
2777
|
+
expect(isHiddenToolCall("Agent", { visibility: "user" })).toBe(false)
|
|
2778
|
+
expect(isHiddenToolCall("bash", { visibility: "system" })).toBe(false)
|
|
2779
|
+
})
|
|
2780
|
+
|
|
2781
|
+
const longCommand = "a".repeat(120)
|
|
2782
|
+
const longPath = `/tmp/${"x".repeat(120)}`
|
|
2783
|
+
const longPattern = "p".repeat(120)
|
|
2784
|
+
const cases: Array<{
|
|
2785
|
+
name: string
|
|
2786
|
+
toolName: string
|
|
2787
|
+
args: unknown
|
|
2788
|
+
expect: { title: string; kind: string; locations: Array<{ path: string }> }
|
|
2789
|
+
}> = [
|
|
2790
|
+
{
|
|
2791
|
+
name: "bash with command uses command as title and execute kind",
|
|
2792
|
+
toolName: "bash",
|
|
2793
|
+
args: { command: "ls -la" },
|
|
2794
|
+
expect: { title: "ls -la", kind: "execute", locations: [] },
|
|
2795
|
+
},
|
|
2796
|
+
{
|
|
2797
|
+
name: "bash without command falls back to tool name",
|
|
2798
|
+
toolName: "bash",
|
|
2799
|
+
args: {},
|
|
2800
|
+
expect: { title: "bash", kind: "execute", locations: [] },
|
|
2801
|
+
},
|
|
2802
|
+
{
|
|
2803
|
+
name: "bash command is truncated at TITLE_MAX",
|
|
2804
|
+
toolName: "bash",
|
|
2805
|
+
args: { command: longCommand },
|
|
2806
|
+
expect: { title: `${"a".repeat(80)}…`, kind: "execute", locations: [] },
|
|
2807
|
+
},
|
|
2808
|
+
{
|
|
2809
|
+
name: "read with file_path uses path and populates locations",
|
|
2810
|
+
toolName: "read",
|
|
2811
|
+
args: { file_path: "/etc/hosts" },
|
|
2812
|
+
expect: {
|
|
2813
|
+
title: "/etc/hosts",
|
|
2814
|
+
kind: "read",
|
|
2815
|
+
locations: [{ path: "/etc/hosts" }],
|
|
2816
|
+
},
|
|
2817
|
+
},
|
|
2818
|
+
{
|
|
2819
|
+
name: "edit with file_path uses path and edit kind",
|
|
2820
|
+
toolName: "edit",
|
|
2821
|
+
args: { file_path: "/tmp/a.ts" },
|
|
2822
|
+
expect: {
|
|
2823
|
+
title: "/tmp/a.ts",
|
|
2824
|
+
kind: "edit",
|
|
2825
|
+
locations: [{ path: "/tmp/a.ts" }],
|
|
2826
|
+
},
|
|
2827
|
+
},
|
|
2828
|
+
{
|
|
2829
|
+
name: "write with path (not file_path) still populates locations",
|
|
2830
|
+
toolName: "write",
|
|
2831
|
+
args: { path: "/tmp/b.ts" },
|
|
2832
|
+
expect: {
|
|
2833
|
+
title: "/tmp/b.ts",
|
|
2834
|
+
kind: "edit",
|
|
2835
|
+
locations: [{ path: "/tmp/b.ts" }],
|
|
2836
|
+
},
|
|
2837
|
+
},
|
|
2838
|
+
{
|
|
2839
|
+
name: "grep with pattern uses pattern as title and search kind",
|
|
2840
|
+
toolName: "grep",
|
|
2841
|
+
args: { pattern: "foo.*bar" },
|
|
2842
|
+
expect: { title: "foo.*bar", kind: "search", locations: [] },
|
|
2843
|
+
},
|
|
2844
|
+
{
|
|
2845
|
+
name: "ls maps to read kind",
|
|
2846
|
+
toolName: "ls",
|
|
2847
|
+
args: { path: "/tmp" },
|
|
2848
|
+
expect: { title: "/tmp", kind: "read", locations: [{ path: "/tmp" }] },
|
|
2849
|
+
},
|
|
2850
|
+
{
|
|
2851
|
+
name: "find maps to search kind",
|
|
2852
|
+
toolName: "find",
|
|
2853
|
+
args: { pattern: "*.ts" },
|
|
2854
|
+
expect: { title: "*.ts", kind: "search", locations: [] },
|
|
2855
|
+
},
|
|
2856
|
+
{
|
|
2857
|
+
name: "web_fetch maps to fetch kind",
|
|
2858
|
+
toolName: "web_fetch",
|
|
2859
|
+
args: { url: "https://example.com" },
|
|
2860
|
+
expect: { title: "web_fetch", kind: "fetch", locations: [] },
|
|
2861
|
+
},
|
|
2862
|
+
{
|
|
2863
|
+
name: "web_search maps to search kind",
|
|
2864
|
+
toolName: "web_search",
|
|
2865
|
+
args: { query: "ikie" },
|
|
2866
|
+
expect: { title: "web_search", kind: "search", locations: [] },
|
|
2867
|
+
},
|
|
2868
|
+
{
|
|
2869
|
+
name: "Agent maps to think kind",
|
|
2870
|
+
toolName: "Agent",
|
|
2871
|
+
args: { prompt: "go", visibility: "user" },
|
|
2872
|
+
expect: { title: "Agent", kind: "think", locations: [] },
|
|
2873
|
+
},
|
|
2874
|
+
{
|
|
2875
|
+
name: "unknown tool falls back to other kind",
|
|
2876
|
+
toolName: "mcp__foo__bar",
|
|
2877
|
+
args: { arg: 1 },
|
|
2878
|
+
expect: { title: "mcp__foo__bar", kind: "other", locations: [] },
|
|
2879
|
+
},
|
|
2880
|
+
{
|
|
2881
|
+
name: "null args is tolerated",
|
|
2882
|
+
toolName: "bash",
|
|
2883
|
+
args: null,
|
|
2884
|
+
expect: { title: "bash", kind: "execute", locations: [] },
|
|
2885
|
+
},
|
|
2886
|
+
{
|
|
2887
|
+
name: "long path title is truncated (locations keep full path)",
|
|
2888
|
+
toolName: "read",
|
|
2889
|
+
args: { file_path: longPath },
|
|
2890
|
+
expect: {
|
|
2891
|
+
title: `${longPath.slice(0, 80)}…`,
|
|
2892
|
+
kind: "read",
|
|
2893
|
+
locations: [{ path: longPath }],
|
|
2894
|
+
},
|
|
2895
|
+
},
|
|
2896
|
+
{
|
|
2897
|
+
name: "long pattern title is truncated",
|
|
2898
|
+
toolName: "grep",
|
|
2899
|
+
args: { pattern: longPattern },
|
|
2900
|
+
expect: {
|
|
2901
|
+
title: `${longPattern.slice(0, 80)}…`,
|
|
2902
|
+
kind: "search",
|
|
2903
|
+
locations: [],
|
|
2904
|
+
},
|
|
2905
|
+
},
|
|
2906
|
+
]
|
|
2907
|
+
|
|
2908
|
+
for (const c of cases) {
|
|
2909
|
+
it(c.name, () => {
|
|
2910
|
+
const result = describeToolCall(c.toolName, c.args)
|
|
2911
|
+
expect(result.title).toBe(c.expect.title)
|
|
2912
|
+
expect(result.kind).toBe(c.expect.kind)
|
|
2913
|
+
expect(result.locations).toEqual(c.expect.locations)
|
|
2914
|
+
})
|
|
2915
|
+
}
|
|
2916
|
+
})
|
|
2917
|
+
|
|
2918
|
+
// Helper for the listSessions tests: builds a pi SessionInfo with sensible
|
|
2919
|
+
// defaults so each test only spells out the fields it cares about.
|
|
2920
|
+
function makePiSession(overrides: Partial<PiSessionInfo> = {}): PiSessionInfo {
|
|
2921
|
+
return {
|
|
2922
|
+
path: "/tmp/sessions/x.jsonl",
|
|
2923
|
+
id: "id-x",
|
|
2924
|
+
cwd: "/tmp/proj",
|
|
2925
|
+
name: undefined,
|
|
2926
|
+
parentSessionPath: undefined,
|
|
2927
|
+
created: new Date("2026-01-01T00:00:00Z"),
|
|
2928
|
+
modified: new Date("2026-01-01T00:00:00Z"),
|
|
2929
|
+
messageCount: 0,
|
|
2930
|
+
firstMessage: "",
|
|
2931
|
+
allMessagesText: "",
|
|
2932
|
+
...overrides,
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
// Direct coverage for the pi → ACP SessionInfo mapping. Title fallback and
|
|
2937
|
+
// updatedAt formatting are both load-bearing for Zed's thread-picker UI.
|
|
2938
|
+
describe("toAcpSessionInfo", () => {
|
|
2939
|
+
it("uses the user-defined name when present", () => {
|
|
2940
|
+
const out = toAcpSessionInfo(
|
|
2941
|
+
makePiSession({
|
|
2942
|
+
id: "s1",
|
|
2943
|
+
cwd: "/p",
|
|
2944
|
+
name: "named",
|
|
2945
|
+
firstMessage: "ignored",
|
|
2946
|
+
}),
|
|
2947
|
+
)
|
|
2948
|
+
expect(out).toEqual({
|
|
2949
|
+
sessionId: "s1",
|
|
2950
|
+
cwd: "/p",
|
|
2951
|
+
title: "named",
|
|
2952
|
+
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
2953
|
+
})
|
|
2954
|
+
})
|
|
2955
|
+
|
|
2956
|
+
it("falls back to truncated firstMessage when name is absent", () => {
|
|
2957
|
+
const long = "q".repeat(120)
|
|
2958
|
+
const out = toAcpSessionInfo(makePiSession({ firstMessage: long }))
|
|
2959
|
+
expect(out.title).toBe(`${"q".repeat(80)}…`)
|
|
2960
|
+
})
|
|
2961
|
+
|
|
2962
|
+
it("returns null title when both name and firstMessage are empty", () => {
|
|
2963
|
+
const out = toAcpSessionInfo(makePiSession({ name: undefined, firstMessage: "" }))
|
|
2964
|
+
expect(out.title).toBeNull()
|
|
2965
|
+
})
|
|
2966
|
+
|
|
2967
|
+
it("falls back to firstMessage when name is the empty string (not just undefined)", () => {
|
|
2968
|
+
// Pi types `name?: string`, but a hand-edited / migrated session info
|
|
2969
|
+
// entry can land with name === "". A previous version used `??` which
|
|
2970
|
+
// only short-circuits on null/undefined, so the fallback was skipped
|
|
2971
|
+
// and the title became null even though firstMessage was populated.
|
|
2972
|
+
const out = toAcpSessionInfo(makePiSession({ name: "", firstMessage: "hello world" }))
|
|
2973
|
+
expect(out.title).toBe("hello world")
|
|
2974
|
+
})
|
|
2975
|
+
|
|
2976
|
+
it("formats updatedAt as ISO 8601 from the modified Date", () => {
|
|
2977
|
+
const out = toAcpSessionInfo(makePiSession({ modified: new Date("2026-05-09T12:34:56.789Z") }))
|
|
2978
|
+
expect(out.updatedAt).toBe("2026-05-09T12:34:56.789Z")
|
|
2979
|
+
})
|
|
2980
|
+
})
|
|
2981
|
+
|
|
2982
|
+
describe("IkieAcpAgent listSessions", () => {
|
|
2983
|
+
function makeAgent(lister: AcpSessionLister): IkieAcpAgent {
|
|
2984
|
+
return new IkieAcpAgent(makeConn(), {
|
|
2985
|
+
extensionFactories: [],
|
|
2986
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
2987
|
+
sessionFactory: async () => asSession(new FakeAgentSession("unused")),
|
|
2988
|
+
sessionLister: lister,
|
|
2989
|
+
})
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
it("advertises sessionCapabilities.list in initialize", async () => {
|
|
2993
|
+
const agent = makeAgent(async () => [])
|
|
2994
|
+
const init = await agent.initialize({
|
|
2995
|
+
protocolVersion: 1,
|
|
2996
|
+
clientCapabilities: {} as never,
|
|
2997
|
+
} as never)
|
|
2998
|
+
expect(init.agentCapabilities?.sessionCapabilities?.list).toEqual({})
|
|
2999
|
+
})
|
|
3000
|
+
|
|
3001
|
+
it("dispatches to SessionManager.list when cwd is provided", async () => {
|
|
3002
|
+
let received: { cwd?: string | null } = {}
|
|
3003
|
+
const agent = makeAgent(async (params) => {
|
|
3004
|
+
received = { cwd: params.cwd }
|
|
3005
|
+
return []
|
|
3006
|
+
})
|
|
3007
|
+
await agent.listSessions({ cwd: "/repo/foo", mcpServers: [] } as never)
|
|
3008
|
+
expect(received.cwd).toBe("/repo/foo")
|
|
3009
|
+
})
|
|
3010
|
+
|
|
3011
|
+
it("falls back to listAll when cwd is omitted", async () => {
|
|
3012
|
+
let cwdSeen: string | null | undefined = "sentinel"
|
|
3013
|
+
const agent = makeAgent(async (params) => {
|
|
3014
|
+
cwdSeen = params.cwd
|
|
3015
|
+
return []
|
|
3016
|
+
})
|
|
3017
|
+
await agent.listSessions({} as never)
|
|
3018
|
+
// Lister receives the original (undefined) cwd; the default lister maps
|
|
3019
|
+
// that to listAll(). Tests of the default lister itself live in pi.
|
|
3020
|
+
expect(cwdSeen).toBeUndefined()
|
|
3021
|
+
})
|
|
3022
|
+
|
|
3023
|
+
it("forwards additionalDirectories to the session lister", async () => {
|
|
3024
|
+
let received: ListSessionsRequest | undefined
|
|
3025
|
+
const agent = makeAgent(async (params) => {
|
|
3026
|
+
received = params
|
|
3027
|
+
return []
|
|
3028
|
+
})
|
|
3029
|
+
await agent.listSessions({
|
|
3030
|
+
cwd: "/repo/foo",
|
|
3031
|
+
additionalDirectories: ["/repo/bar", "/repo/baz"],
|
|
3032
|
+
} as never)
|
|
3033
|
+
expect(received?.additionalDirectories).toEqual(["/repo/bar", "/repo/baz"])
|
|
3034
|
+
})
|
|
3035
|
+
|
|
3036
|
+
it("dedupes sessions returned across multiple roots by id", async () => {
|
|
3037
|
+
// Custom lister stand-in for the multi-root default lister: returns the
|
|
3038
|
+
// same id from two roots; handler must surface it once.
|
|
3039
|
+
const dup = makePiSession({
|
|
3040
|
+
id: "shared",
|
|
3041
|
+
modified: new Date("2026-04-01T00:00:00Z"),
|
|
3042
|
+
name: "shared",
|
|
3043
|
+
})
|
|
3044
|
+
const uniq = makePiSession({
|
|
3045
|
+
id: "uniq",
|
|
3046
|
+
modified: new Date("2026-03-01T00:00:00Z"),
|
|
3047
|
+
name: "uniq",
|
|
3048
|
+
})
|
|
3049
|
+
const agent = makeAgent(async () => [dup, uniq, dup])
|
|
3050
|
+
const res = await agent.listSessions({ cwd: "/p" } as never)
|
|
3051
|
+
expect(res.sessions.map((s) => s.sessionId)).toEqual(["shared", "uniq"])
|
|
3052
|
+
})
|
|
3053
|
+
|
|
3054
|
+
it("returns empty array for a directory with no sessions", async () => {
|
|
3055
|
+
const agent = makeAgent(async () => [])
|
|
3056
|
+
const res = await agent.listSessions({ cwd: "/empty" } as never)
|
|
3057
|
+
expect(res.sessions).toEqual([])
|
|
3058
|
+
})
|
|
3059
|
+
|
|
3060
|
+
it("returns nextCursor: null to signal end-of-pagination", async () => {
|
|
3061
|
+
const agent = makeAgent(async () => [makePiSession({ id: "s" })])
|
|
3062
|
+
const res = await agent.listSessions({ cwd: "/p" } as never)
|
|
3063
|
+
expect(res.nextCursor).toBeNull()
|
|
3064
|
+
})
|
|
3065
|
+
|
|
3066
|
+
it("sorts sessions newest-first by updatedAt", async () => {
|
|
3067
|
+
const piSessions: PiSessionInfo[] = [
|
|
3068
|
+
makePiSession({
|
|
3069
|
+
id: "old",
|
|
3070
|
+
modified: new Date("2026-01-01T00:00:00Z"),
|
|
3071
|
+
name: "old",
|
|
3072
|
+
}),
|
|
3073
|
+
makePiSession({
|
|
3074
|
+
id: "new",
|
|
3075
|
+
modified: new Date("2026-05-09T00:00:00Z"),
|
|
3076
|
+
name: "new",
|
|
3077
|
+
}),
|
|
3078
|
+
makePiSession({
|
|
3079
|
+
id: "mid",
|
|
3080
|
+
modified: new Date("2026-03-01T00:00:00Z"),
|
|
3081
|
+
name: "mid",
|
|
3082
|
+
}),
|
|
3083
|
+
]
|
|
3084
|
+
const agent = makeAgent(async () => piSessions)
|
|
3085
|
+
const res = await agent.listSessions({ cwd: "/p" } as never)
|
|
3086
|
+
expect(res.sessions.map((s) => s.sessionId)).toEqual(["new", "mid", "old"])
|
|
3087
|
+
})
|
|
3088
|
+
|
|
3089
|
+
it("uses truncated firstMessage as title when name is absent", async () => {
|
|
3090
|
+
const long = "z".repeat(120)
|
|
3091
|
+
const agent = makeAgent(async () => [makePiSession({ id: "s", firstMessage: long, name: undefined })])
|
|
3092
|
+
const res = await agent.listSessions({ cwd: "/p" } as never)
|
|
3093
|
+
expect(res.sessions[0].title).toBe(`${"z".repeat(80)}…`)
|
|
3094
|
+
})
|
|
3095
|
+
})
|
|
3096
|
+
|
|
3097
|
+
// Helpers for replay tests: build the SessionMessageEntry shape that
|
|
3098
|
+
// SessionManager.getBranch() returns. `userText` / `assistantText` produce the
|
|
3099
|
+
// minimal valid envelope — id/parentId/timestamp are not consulted by the
|
|
3100
|
+
// replay walker, so any non-empty placeholder is fine.
|
|
3101
|
+
function userTextEntry(text: string, id = "u1", parentId: string | null = null): unknown {
|
|
3102
|
+
return {
|
|
3103
|
+
type: "message",
|
|
3104
|
+
id,
|
|
3105
|
+
parentId,
|
|
3106
|
+
timestamp: "2026-05-09T00:00:00Z",
|
|
3107
|
+
message: { role: "user", content: text, timestamp: 0 },
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
function assistantTextEntry(text: string, id = "a1", parentId: string | null = null): unknown {
|
|
3111
|
+
return assistantBlocksEntry([{ type: "text", text }], id, parentId)
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3114
|
+
// Variant that builds an assistant entry from arbitrary content blocks (text,
|
|
3115
|
+
// thinking, toolCall). Used by replay tests to drop in mixed-block fixtures
|
|
3116
|
+
// without re-spelling the message envelope each time.
|
|
3117
|
+
function assistantBlocksEntry(content: unknown[], id = "a1", parentId: string | null = null): unknown {
|
|
3118
|
+
return {
|
|
3119
|
+
type: "message",
|
|
3120
|
+
id,
|
|
3121
|
+
parentId,
|
|
3122
|
+
timestamp: "2026-05-09T00:00:01Z",
|
|
3123
|
+
message: {
|
|
3124
|
+
role: "assistant",
|
|
3125
|
+
content,
|
|
3126
|
+
api: "anthropic-messages",
|
|
3127
|
+
provider: "anthropic",
|
|
3128
|
+
model: "claude",
|
|
3129
|
+
usage: {
|
|
3130
|
+
input: 0,
|
|
3131
|
+
output: 0,
|
|
3132
|
+
cacheRead: 0,
|
|
3133
|
+
cacheWrite: 0,
|
|
3134
|
+
totalTokens: 0,
|
|
3135
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
3136
|
+
},
|
|
3137
|
+
stopReason: "stop",
|
|
3138
|
+
timestamp: 0,
|
|
3139
|
+
},
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
function toolResultEntry(
|
|
3144
|
+
toolCallId: string,
|
|
3145
|
+
toolName: string,
|
|
3146
|
+
text: string,
|
|
3147
|
+
isError = false,
|
|
3148
|
+
id = `tr-${toolCallId}`,
|
|
3149
|
+
parentId: string | null = null,
|
|
3150
|
+
): unknown {
|
|
3151
|
+
return {
|
|
3152
|
+
type: "message",
|
|
3153
|
+
id,
|
|
3154
|
+
parentId,
|
|
3155
|
+
timestamp: "2026-05-09T00:00:02Z",
|
|
3156
|
+
message: {
|
|
3157
|
+
role: "toolResult",
|
|
3158
|
+
toolCallId,
|
|
3159
|
+
toolName,
|
|
3160
|
+
content: [{ type: "text", text }],
|
|
3161
|
+
isError,
|
|
3162
|
+
timestamp: 0,
|
|
3163
|
+
},
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
function testEncodeCwdDir(cwd: string): string {
|
|
3168
|
+
return `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`
|
|
3169
|
+
}
|
|
3170
|
+
|
|
3171
|
+
describe("IkieAcpAgent loadSession", () => {
|
|
3172
|
+
function makeAgent(loader: AcpSessionLoader, opts?: { conn?: AgentSideConnection }): IkieAcpAgent {
|
|
3173
|
+
return new IkieAcpAgent(opts?.conn ?? makeConn(), {
|
|
3174
|
+
extensionFactories: [],
|
|
3175
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
3176
|
+
sessionFactory: async () => asSession(new FakeAgentSession("unused")),
|
|
3177
|
+
sessionLoader: loader,
|
|
3178
|
+
})
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
it("advertises loadSession capability in initialize", async () => {
|
|
3182
|
+
const agent = makeAgent(async () => asSession(new FakeAgentSession("unused")))
|
|
3183
|
+
const init = await agent.initialize({
|
|
3184
|
+
protocolVersion: 1,
|
|
3185
|
+
clientCapabilities: {} as never,
|
|
3186
|
+
} as never)
|
|
3187
|
+
expect(init.agentCapabilities?.loadSession).toBe(true)
|
|
3188
|
+
expect(init.agentCapabilities?.sessionCapabilities?.close).toEqual({})
|
|
3189
|
+
})
|
|
3190
|
+
|
|
3191
|
+
it("rejects loadSession when mcpServers is non-empty (does not invoke loader)", async () => {
|
|
3192
|
+
const loaderCalls = { count: 0 }
|
|
3193
|
+
const loader: AcpSessionLoader = async () => {
|
|
3194
|
+
loaderCalls.count++
|
|
3195
|
+
return asSession(new FakeAgentSession("unused"))
|
|
3196
|
+
}
|
|
3197
|
+
const agent = makeAgent(loader)
|
|
3198
|
+
await expect(
|
|
3199
|
+
agent.loadSession({
|
|
3200
|
+
sessionId: "s1",
|
|
3201
|
+
cwd: "/tmp",
|
|
3202
|
+
// biome-ignore lint/suspicious/noExplicitAny: only the shape we care about
|
|
3203
|
+
mcpServers: [{ name: "x", command: "x", args: [] } as any],
|
|
3204
|
+
}),
|
|
3205
|
+
).rejects.toMatchObject({ code: -32602 })
|
|
3206
|
+
expect(loaderCalls.count).toBe(0)
|
|
3207
|
+
})
|
|
3208
|
+
|
|
3209
|
+
it("replays and returns an already loaded session without reopening it", async () => {
|
|
3210
|
+
const loaderCalls = { count: 0 }
|
|
3211
|
+
const live = new FakeAgentSession("live-1")
|
|
3212
|
+
live.branch = [userTextEntry("already here", "u1", null)]
|
|
3213
|
+
const factory: AcpSessionFactory = async () => asSession(live)
|
|
3214
|
+
const loader: AcpSessionLoader = async () => {
|
|
3215
|
+
loaderCalls.count++
|
|
3216
|
+
return asSession(new FakeAgentSession("unused"))
|
|
3217
|
+
}
|
|
3218
|
+
const { conn, updates } = makeRecordingConn()
|
|
3219
|
+
const agent = new IkieAcpAgent(conn, {
|
|
3220
|
+
extensionFactories: [],
|
|
3221
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
3222
|
+
sessionFactory: factory,
|
|
3223
|
+
sessionLoader: loader,
|
|
3224
|
+
})
|
|
3225
|
+
await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
3226
|
+
|
|
3227
|
+
const res = await agent.loadSession({
|
|
3228
|
+
sessionId: "live-1",
|
|
3229
|
+
cwd: "/tmp",
|
|
3230
|
+
mcpServers: [],
|
|
3231
|
+
})
|
|
3232
|
+
|
|
3233
|
+
expect(res.models).toMatchObject({
|
|
3234
|
+
currentModelId: "test/test-model",
|
|
3235
|
+
})
|
|
3236
|
+
expect(loaderCalls.count).toBe(0)
|
|
3237
|
+
|
|
3238
|
+
const replayUpdates = replayOnly(updates)
|
|
3239
|
+
// loadSession replay sends user_message_chunk
|
|
3240
|
+
expect(replayUpdates).toHaveLength(1)
|
|
3241
|
+
expect(replayUpdates[0].update).toMatchObject({
|
|
3242
|
+
sessionUpdate: "user_message_chunk",
|
|
3243
|
+
content: { type: "text", text: "already here" },
|
|
3244
|
+
})
|
|
3245
|
+
})
|
|
3246
|
+
|
|
3247
|
+
it("returns configOptions in loadSession response", async () => {
|
|
3248
|
+
const tmpDir = mkdtempSync(join(tmpdir(), "ikie-acp-load-config-"))
|
|
3249
|
+
try {
|
|
3250
|
+
const fake = new FakeAgentSession("test-load-session-config")
|
|
3251
|
+
const sessionFactory: AcpSessionFactory = async () => asSession(fake)
|
|
3252
|
+
const lister: AcpSessionLister = async () => [
|
|
3253
|
+
makePiSession({
|
|
3254
|
+
id: "test-load-session-config",
|
|
3255
|
+
cwd: tmpDir,
|
|
3256
|
+
path: join(tmpDir, "test-load-session-config.jsonl"),
|
|
3257
|
+
}),
|
|
3258
|
+
]
|
|
3259
|
+
const loader: AcpSessionLoader = async () => {
|
|
3260
|
+
return asSession(fake)
|
|
3261
|
+
}
|
|
3262
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
3263
|
+
extensionFactories: [],
|
|
3264
|
+
agentDir: tmpDir,
|
|
3265
|
+
sessionFactory,
|
|
3266
|
+
sessionLister: lister,
|
|
3267
|
+
sessionLoader: loader,
|
|
3268
|
+
})
|
|
3269
|
+
|
|
3270
|
+
// Create the session first
|
|
3271
|
+
await agent.newSession({ cwd: tmpDir, mcpServers: [] })
|
|
3272
|
+
|
|
3273
|
+
// Close it
|
|
3274
|
+
await agent.unstable_closeSession({
|
|
3275
|
+
sessionId: "test-load-session-config",
|
|
3276
|
+
})
|
|
3277
|
+
|
|
3278
|
+
// Load it back
|
|
3279
|
+
const res = await agent.loadSession({
|
|
3280
|
+
sessionId: "test-load-session-config",
|
|
3281
|
+
cwd: tmpDir,
|
|
3282
|
+
mcpServers: [],
|
|
3283
|
+
})
|
|
3284
|
+
|
|
3285
|
+
expect(res.configOptions).toBeDefined()
|
|
3286
|
+
expect(res.configOptions).toHaveLength(1)
|
|
3287
|
+
expect(res.configOptions?.[0].id).toBe("permissions-mode")
|
|
3288
|
+
} finally {
|
|
3289
|
+
rmSync(tmpDir, { recursive: true, force: true })
|
|
3290
|
+
}
|
|
3291
|
+
})
|
|
3292
|
+
|
|
3293
|
+
it("coalesces concurrent loadSession requests for the same sessionId", async () => {
|
|
3294
|
+
const fake = new FakeAgentSession("coalesce-1")
|
|
3295
|
+
let loaderCalls = 0
|
|
3296
|
+
let markLoaderStarted!: () => void
|
|
3297
|
+
let releaseLoader!: () => void
|
|
3298
|
+
const loaderStarted = new Promise<void>((resolve) => {
|
|
3299
|
+
markLoaderStarted = resolve
|
|
3300
|
+
})
|
|
3301
|
+
const release = new Promise<void>((resolve) => {
|
|
3302
|
+
releaseLoader = resolve
|
|
3303
|
+
})
|
|
3304
|
+
const loader: AcpSessionLoader = async () => {
|
|
3305
|
+
loaderCalls++
|
|
3306
|
+
markLoaderStarted()
|
|
3307
|
+
await release
|
|
3308
|
+
return asSession(fake)
|
|
3309
|
+
}
|
|
3310
|
+
const agent = makeAgent(loader)
|
|
3311
|
+
const first = agent.loadSession({
|
|
3312
|
+
sessionId: "coalesce-1",
|
|
3313
|
+
cwd: "/tmp",
|
|
3314
|
+
mcpServers: [],
|
|
3315
|
+
})
|
|
3316
|
+
await loaderStarted
|
|
3317
|
+
const second = agent.loadSession({
|
|
3318
|
+
sessionId: "coalesce-1",
|
|
3319
|
+
cwd: "/tmp",
|
|
3320
|
+
mcpServers: [],
|
|
3321
|
+
})
|
|
3322
|
+
releaseLoader()
|
|
3323
|
+
|
|
3324
|
+
await expect(Promise.all([first, second])).resolves.toHaveLength(2)
|
|
3325
|
+
expect(loaderCalls).toBe(1)
|
|
3326
|
+
})
|
|
3327
|
+
|
|
3328
|
+
it("propagates loader errors (e.g. missing file → invalidParams)", async () => {
|
|
3329
|
+
const agent = makeAgent(async () => {
|
|
3330
|
+
throw Object.assign(new Error("session not found"), { code: -32602 })
|
|
3331
|
+
})
|
|
3332
|
+
await expect(agent.loadSession({ sessionId: "missing", cwd: "/tmp", mcpServers: [] })).rejects.toThrow(
|
|
3333
|
+
/session not found/,
|
|
3334
|
+
)
|
|
3335
|
+
})
|
|
3336
|
+
|
|
3337
|
+
it("rejects default-loaded sessions whose header cwd disagrees before opening", async () => {
|
|
3338
|
+
const agentDir = mkdtempSync(join(tmpdir(), "ikie-acp-load-"))
|
|
3339
|
+
try {
|
|
3340
|
+
const sessionId = "cwd-mismatch"
|
|
3341
|
+
const requestedCwd = "/tmp/requested"
|
|
3342
|
+
const sessionDir = join(agentDir, "sessions", testEncodeCwdDir(requestedCwd))
|
|
3343
|
+
mkdirSync(sessionDir, { recursive: true })
|
|
3344
|
+
writeFileSync(
|
|
3345
|
+
join(sessionDir, `2026-05-09T00-00-00.000Z_${sessionId}.jsonl`),
|
|
3346
|
+
`${JSON.stringify({
|
|
3347
|
+
type: "session",
|
|
3348
|
+
version: 3,
|
|
3349
|
+
id: sessionId,
|
|
3350
|
+
timestamp: "2026-05-09T00:00:00Z",
|
|
3351
|
+
cwd: "/tmp/other",
|
|
3352
|
+
})}\n`,
|
|
3353
|
+
)
|
|
3354
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
3355
|
+
extensionFactories: [],
|
|
3356
|
+
agentDir,
|
|
3357
|
+
sessionFactory: async () => asSession(new FakeAgentSession("unused")),
|
|
3358
|
+
})
|
|
3359
|
+
|
|
3360
|
+
await expect(agent.loadSession({ sessionId, cwd: requestedCwd, mcpServers: [] })).rejects.toThrow(
|
|
3361
|
+
/session cwd \/tmp\/other does not match requested cwd \/tmp\/requested/,
|
|
3362
|
+
)
|
|
3363
|
+
} finally {
|
|
3364
|
+
rmSync(agentDir, { recursive: true, force: true })
|
|
3365
|
+
}
|
|
3366
|
+
})
|
|
3367
|
+
|
|
3368
|
+
it("disposes the session if subscribe throws during loadSession", async () => {
|
|
3369
|
+
const leaky = new FakeAgentSession("leak-1")
|
|
3370
|
+
leaky.subscribe = () => {
|
|
3371
|
+
throw new Error("subscribe boom")
|
|
3372
|
+
}
|
|
3373
|
+
const agent = makeAgent(async () => asSession(leaky))
|
|
3374
|
+
await expect(agent.loadSession({ sessionId: "leak-1", cwd: "/tmp", mcpServers: [] })).rejects.toThrow(
|
|
3375
|
+
/subscribe boom/,
|
|
3376
|
+
)
|
|
3377
|
+
expect(leaky.disposed).toBe(true)
|
|
3378
|
+
})
|
|
3379
|
+
|
|
3380
|
+
it("unwinds registration and disposes the session if replay throws", async () => {
|
|
3381
|
+
// Pin the atomic-ownership invariant: a throw during replay must remove
|
|
3382
|
+
// the session from the registry AND dispose it, otherwise the id stays
|
|
3383
|
+
// "live" while loadSession rejects — blocking re-load with invalidRequest.
|
|
3384
|
+
const fake = new FakeAgentSession("replay-boom")
|
|
3385
|
+
fake.sessionManager = {
|
|
3386
|
+
getBranch: () => {
|
|
3387
|
+
throw new Error("branch read failed")
|
|
3388
|
+
},
|
|
3389
|
+
}
|
|
3390
|
+
const agent = makeAgent(async () => asSession(fake))
|
|
3391
|
+
await expect(
|
|
3392
|
+
agent.loadSession({
|
|
3393
|
+
sessionId: "replay-boom",
|
|
3394
|
+
cwd: "/tmp",
|
|
3395
|
+
mcpServers: [],
|
|
3396
|
+
}),
|
|
3397
|
+
).rejects.toThrow(/branch read failed/)
|
|
3398
|
+
expect(fake.disposed).toBe(true)
|
|
3399
|
+
// Re-load must not see the failed session as already-live.
|
|
3400
|
+
fake.sessionManager = { getBranch: () => [] }
|
|
3401
|
+
fake.disposed = false
|
|
3402
|
+
await expect(
|
|
3403
|
+
agent.loadSession({
|
|
3404
|
+
sessionId: "replay-boom",
|
|
3405
|
+
cwd: "/tmp",
|
|
3406
|
+
mcpServers: [],
|
|
3407
|
+
}),
|
|
3408
|
+
).resolves.toBeDefined()
|
|
3409
|
+
})
|
|
3410
|
+
|
|
3411
|
+
it("replays user/assistant text turns as session/update notifications before the response resolves", async () => {
|
|
3412
|
+
const fake = new FakeAgentSession("loaded-1")
|
|
3413
|
+
fake.model = { provider: "test", id: "test-model", name: "Test Model" }
|
|
3414
|
+
fake.modelRegistry = {
|
|
3415
|
+
getAvailable: () => [{ provider: "test", id: "test-model", name: "Test Model" }],
|
|
3416
|
+
}
|
|
3417
|
+
fake.branch = [
|
|
3418
|
+
userTextEntry("hello", "u1", null),
|
|
3419
|
+
assistantTextEntry("hi there", "a1", "u1"),
|
|
3420
|
+
userTextEntry("how are you?", "u2", "a1"),
|
|
3421
|
+
assistantTextEntry("doing well", "a2", "u2"),
|
|
3422
|
+
]
|
|
3423
|
+
const { conn, updates } = makeRecordingConn()
|
|
3424
|
+
const agent = makeAgent(async () => asSession(fake), { conn })
|
|
3425
|
+
|
|
3426
|
+
// Capture the order: replay notifications must land BEFORE the response
|
|
3427
|
+
// resolves so Zed sees a coherent transcript on the load promise.
|
|
3428
|
+
const updatesAtResolve: (typeof updates)[number][] = []
|
|
3429
|
+
const original = (
|
|
3430
|
+
conn as unknown as {
|
|
3431
|
+
sessionUpdate: (p: SessionNotification) => Promise<void>
|
|
3432
|
+
}
|
|
3433
|
+
).sessionUpdate
|
|
3434
|
+
;(
|
|
3435
|
+
conn as unknown as {
|
|
3436
|
+
sessionUpdate: (p: SessionNotification) => Promise<void>
|
|
3437
|
+
}
|
|
3438
|
+
).sessionUpdate = async (p: SessionNotification) => {
|
|
3439
|
+
await original(p)
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
const res = await agent.loadSession({
|
|
3443
|
+
sessionId: "loaded-1",
|
|
3444
|
+
cwd: "/tmp",
|
|
3445
|
+
mcpServers: [],
|
|
3446
|
+
})
|
|
3447
|
+
// Snapshot updates seen at this point (ie. before any further awaits).
|
|
3448
|
+
// Drop the incidental available_commands_update so we can assert on
|
|
3449
|
+
// transcript shape without coupling to the command palette.
|
|
3450
|
+
updatesAtResolve.push(...replayOnly(updates))
|
|
3451
|
+
|
|
3452
|
+
expect(updatesAtResolve).toHaveLength(4)
|
|
3453
|
+
expect(updatesAtResolve[0].update).toMatchObject({
|
|
3454
|
+
sessionUpdate: "user_message_chunk",
|
|
3455
|
+
content: { type: "text", text: "hello" },
|
|
3456
|
+
})
|
|
3457
|
+
expect(updatesAtResolve[1].update).toMatchObject({
|
|
3458
|
+
sessionUpdate: "agent_message_chunk",
|
|
3459
|
+
content: { type: "text", text: "hi there" },
|
|
3460
|
+
})
|
|
3461
|
+
expect(updatesAtResolve[2].update).toMatchObject({
|
|
3462
|
+
sessionUpdate: "user_message_chunk",
|
|
3463
|
+
content: { type: "text", text: "how are you?" },
|
|
3464
|
+
})
|
|
3465
|
+
expect(updatesAtResolve[3].update).toMatchObject({
|
|
3466
|
+
sessionUpdate: "agent_message_chunk",
|
|
3467
|
+
content: { type: "text", text: "doing well" },
|
|
3468
|
+
})
|
|
3469
|
+
expect(res.models).toMatchObject({
|
|
3470
|
+
currentModelId: "test/test-model",
|
|
3471
|
+
availableModels: [{ modelId: "test/test-model", name: "Test Model" }],
|
|
3472
|
+
})
|
|
3473
|
+
})
|
|
3474
|
+
|
|
3475
|
+
// Replay walker: text + thinking + tool calls all surface as their own
|
|
3476
|
+
// session/update notifications; non-message entries (compaction,
|
|
3477
|
+
// branch_summary, model_change, custom) emit nothing. The block of tests
|
|
3478
|
+
// below exercises one fixture per kind plus a combined fixture.
|
|
3479
|
+
|
|
3480
|
+
it("rejects a load whose session-header id disagrees with the requested sessionId", async () => {
|
|
3481
|
+
// Defensive: pi reads sessionId from the JSONL header, not the file
|
|
3482
|
+
// name. A corrupt / hand-edited session whose header drifted from the
|
|
3483
|
+
// filename would otherwise land in `sessions` under a different key
|
|
3484
|
+
// than the client knows, and subsequent prompts would 404 even though
|
|
3485
|
+
// the file is held open.
|
|
3486
|
+
const fake = new FakeAgentSession("header-id")
|
|
3487
|
+
const loader: AcpSessionLoader = async () => asSession(fake)
|
|
3488
|
+
const agent = makeAgent(loader)
|
|
3489
|
+
await expect(
|
|
3490
|
+
agent.loadSession({
|
|
3491
|
+
sessionId: "requested-id",
|
|
3492
|
+
cwd: "/tmp",
|
|
3493
|
+
mcpServers: [],
|
|
3494
|
+
}),
|
|
3495
|
+
).rejects.toMatchObject({
|
|
3496
|
+
code: -32602,
|
|
3497
|
+
})
|
|
3498
|
+
expect(fake.disposed).toBe(true)
|
|
3499
|
+
})
|
|
3500
|
+
|
|
3501
|
+
it("coalesces contiguous text blocks within an assistant message into one chunk", async () => {
|
|
3502
|
+
// Replay contract: emit the full message as a single chunk. Adjacent
|
|
3503
|
+
// text blocks (rare but legal) must merge into one agent_message_chunk;
|
|
3504
|
+
// structural blocks (thinking / toolCall) flush the buffer so ordering
|
|
3505
|
+
// stays faithful.
|
|
3506
|
+
const fake = new FakeAgentSession("loaded-coalesce")
|
|
3507
|
+
fake.branch = [
|
|
3508
|
+
userTextEntry("go", "u1", null),
|
|
3509
|
+
assistantBlocksEntry(
|
|
3510
|
+
[
|
|
3511
|
+
{ type: "text", text: "first " },
|
|
3512
|
+
{ type: "text", text: "second" },
|
|
3513
|
+
{ type: "thinking", thinking: "pondering" },
|
|
3514
|
+
{ type: "text", text: "third" },
|
|
3515
|
+
],
|
|
3516
|
+
"a1",
|
|
3517
|
+
"u1",
|
|
3518
|
+
),
|
|
3519
|
+
]
|
|
3520
|
+
const { conn, updates } = makeRecordingConn()
|
|
3521
|
+
const agent = makeAgent(async () => asSession(fake), { conn })
|
|
3522
|
+
await agent.loadSession({
|
|
3523
|
+
sessionId: "loaded-coalesce",
|
|
3524
|
+
cwd: "/tmp",
|
|
3525
|
+
mcpServers: [],
|
|
3526
|
+
})
|
|
3527
|
+
const replay = replayOnly(updates)
|
|
3528
|
+
expect(replay.map((u) => u.update.sessionUpdate)).toEqual([
|
|
3529
|
+
"user_message_chunk",
|
|
3530
|
+
"agent_message_chunk",
|
|
3531
|
+
"agent_thought_chunk",
|
|
3532
|
+
"agent_message_chunk",
|
|
3533
|
+
])
|
|
3534
|
+
expect((replay[1].update as { content: { text: string } }).content.text).toBe("first second")
|
|
3535
|
+
expect((replay[3].update as { content: { text: string } }).content.text).toBe("third")
|
|
3536
|
+
})
|
|
3537
|
+
|
|
3538
|
+
it("routes ANSI-dimmed replay text to thought chunks and strips remaining ANSI", async () => {
|
|
3539
|
+
// hide-thinking-aware models (DeepSeek, QwQ) plus hideThinkingBlock=false
|
|
3540
|
+
// persist text with ANSI dim escapes around inner <think> content. The
|
|
3541
|
+
// live TUI renders them as reasoning; ACP's text content type is
|
|
3542
|
+
// plaintext, so replay must preserve that semantic split explicitly.
|
|
3543
|
+
const dimmed = "before \x1b[2minner\x1b[22m after"
|
|
3544
|
+
const fake = new FakeAgentSession("loaded-ansi")
|
|
3545
|
+
fake.branch = [
|
|
3546
|
+
userTextEntry("go", "u1", null),
|
|
3547
|
+
assistantBlocksEntry(
|
|
3548
|
+
[
|
|
3549
|
+
{ type: "text", text: dimmed },
|
|
3550
|
+
{ type: "thinking", thinking: "raw \x1b[2mthought\x1b[22m" },
|
|
3551
|
+
],
|
|
3552
|
+
"a1",
|
|
3553
|
+
"u1",
|
|
3554
|
+
),
|
|
3555
|
+
]
|
|
3556
|
+
const { conn, updates } = makeRecordingConn()
|
|
3557
|
+
const agent = makeAgent(async () => asSession(fake), { conn })
|
|
3558
|
+
await agent.loadSession({
|
|
3559
|
+
sessionId: "loaded-ansi",
|
|
3560
|
+
cwd: "/tmp",
|
|
3561
|
+
mcpServers: [],
|
|
3562
|
+
})
|
|
3563
|
+
const messageTexts = updates
|
|
3564
|
+
.filter((u) => u.update.sessionUpdate === "agent_message_chunk")
|
|
3565
|
+
.map((u) => (u.update as { content: { text: string } }).content.text)
|
|
3566
|
+
const thoughtTexts = updates
|
|
3567
|
+
.filter((u) => u.update.sessionUpdate === "agent_thought_chunk")
|
|
3568
|
+
.map((u) => (u.update as { content: { text: string } }).content.text)
|
|
3569
|
+
expect(messageTexts).toEqual(["before ", " after"])
|
|
3570
|
+
expect(thoughtTexts).toEqual(["inner", "raw thought"])
|
|
3571
|
+
})
|
|
3572
|
+
|
|
3573
|
+
it("drops ANSI-dimmed replay thinking when hideThinkingBlock is enabled", async () => {
|
|
3574
|
+
_setHideThinking(true)
|
|
3575
|
+
try {
|
|
3576
|
+
const fake = new FakeAgentSession("loaded-ansi-hidden")
|
|
3577
|
+
fake.branch = [
|
|
3578
|
+
userTextEntry("go", "u1", null),
|
|
3579
|
+
assistantBlocksEntry([{ type: "text", text: "before \x1b[2minner\x1b[22m after" }], "a1", "u1"),
|
|
3580
|
+
]
|
|
3581
|
+
const { conn, updates } = makeRecordingConn()
|
|
3582
|
+
const agent = makeAgent(async () => asSession(fake), { conn })
|
|
3583
|
+
await agent.loadSession({
|
|
3584
|
+
sessionId: "loaded-ansi-hidden",
|
|
3585
|
+
cwd: "/tmp",
|
|
3586
|
+
mcpServers: [],
|
|
3587
|
+
})
|
|
3588
|
+
const messageTexts = updates
|
|
3589
|
+
.filter((u) => u.update.sessionUpdate === "agent_message_chunk")
|
|
3590
|
+
.map((u) => (u.update as { content: { text: string } }).content.text)
|
|
3591
|
+
expect(updates.find((u) => u.update.sessionUpdate === "agent_thought_chunk")).toBeUndefined()
|
|
3592
|
+
expect(messageTexts).toEqual(["before after"])
|
|
3593
|
+
} finally {
|
|
3594
|
+
_resetHideThinking()
|
|
3595
|
+
}
|
|
3596
|
+
})
|
|
3597
|
+
|
|
3598
|
+
it("treats a concurrent session/cancel during replay as a no-op (no turn active)", async () => {
|
|
3599
|
+
const fake = new FakeAgentSession("loaded-cancel")
|
|
3600
|
+
fake.branch = [userTextEntry("hi", "u1", null)]
|
|
3601
|
+
const agent = makeAgent(async () => asSession(fake))
|
|
3602
|
+
await agent.loadSession({
|
|
3603
|
+
sessionId: "loaded-cancel",
|
|
3604
|
+
cwd: "/tmp",
|
|
3605
|
+
mcpServers: [],
|
|
3606
|
+
})
|
|
3607
|
+
// No turn was created during loadSession, so cancel must not throw and
|
|
3608
|
+
// must not invoke abort with side effects beyond the no-op session.abort.
|
|
3609
|
+
await expect(agent.cancel({ sessionId: "loaded-cancel" })).resolves.toBeUndefined()
|
|
3610
|
+
})
|
|
3611
|
+
|
|
3612
|
+
it("rejects loaded sessions with no model", async () => {
|
|
3613
|
+
const fake = new FakeAgentSession("loaded-no-model")
|
|
3614
|
+
fake.model = undefined
|
|
3615
|
+
fake.branch = []
|
|
3616
|
+
const agent = makeAgent(async () => asSession(fake))
|
|
3617
|
+
await expect(
|
|
3618
|
+
agent.loadSession({
|
|
3619
|
+
sessionId: "loaded-no-model",
|
|
3620
|
+
cwd: "/tmp",
|
|
3621
|
+
mcpServers: [],
|
|
3622
|
+
}),
|
|
3623
|
+
).rejects.toMatchObject({ code: -32000 })
|
|
3624
|
+
expect(fake.disposed).toBe(true)
|
|
3625
|
+
})
|
|
3626
|
+
|
|
3627
|
+
it("registers the loaded session so a follow-up prompt is accepted", async () => {
|
|
3628
|
+
const fake = new FakeAgentSession("loaded-prompt")
|
|
3629
|
+
fake.branch = [userTextEntry("prior", "u1", null)]
|
|
3630
|
+
const agent = makeAgent(async () => asSession(fake))
|
|
3631
|
+
await agent.loadSession({
|
|
3632
|
+
sessionId: "loaded-prompt",
|
|
3633
|
+
cwd: "/tmp",
|
|
3634
|
+
mcpServers: [],
|
|
3635
|
+
})
|
|
3636
|
+
// Drive a turn through the loaded session — short-circuit path is fine,
|
|
3637
|
+
// we just need to confirm prompt() doesn't reject with "unknown
|
|
3638
|
+
// sessionId" (which would mean the load registration failed).
|
|
3639
|
+
fake.promptImpl = async () => {}
|
|
3640
|
+
const res = await agent.prompt({
|
|
3641
|
+
sessionId: "loaded-prompt",
|
|
3642
|
+
prompt: [{ type: "text", text: "follow up" }],
|
|
3643
|
+
})
|
|
3644
|
+
expect(res.stopReason).toBe("end_turn")
|
|
3645
|
+
})
|
|
3646
|
+
|
|
3647
|
+
// Per ToolKind: replayed tool calls should produce the same shape as live
|
|
3648
|
+
// turns — an initial tool_call carrying the kind/title/locations, followed
|
|
3649
|
+
// by a single terminal tool_call_update that pins status + content. This
|
|
3650
|
+
// matrix locks in describeToolCall coverage on the replay path so a future
|
|
3651
|
+
// kind addition doesn't silently bypass replay.
|
|
3652
|
+
const toolKindMatrix: Array<{
|
|
3653
|
+
name: string
|
|
3654
|
+
toolName: string
|
|
3655
|
+
args: Record<string, unknown>
|
|
3656
|
+
result: { text: string; isError?: boolean }
|
|
3657
|
+
expect: {
|
|
3658
|
+
kind: string
|
|
3659
|
+
title: string
|
|
3660
|
+
status: string
|
|
3661
|
+
locations: Array<{ path: string }>
|
|
3662
|
+
}
|
|
3663
|
+
}> = [
|
|
3664
|
+
{
|
|
3665
|
+
name: "bash → execute",
|
|
3666
|
+
toolName: "bash",
|
|
3667
|
+
args: { command: "ls -la" },
|
|
3668
|
+
result: { text: "ok" },
|
|
3669
|
+
expect: {
|
|
3670
|
+
kind: "execute",
|
|
3671
|
+
title: "ls -la",
|
|
3672
|
+
status: "completed",
|
|
3673
|
+
locations: [],
|
|
3674
|
+
},
|
|
3675
|
+
},
|
|
3676
|
+
{
|
|
3677
|
+
name: "read → read with location",
|
|
3678
|
+
toolName: "read",
|
|
3679
|
+
args: { file_path: "/tmp/a.ts" },
|
|
3680
|
+
result: { text: "contents" },
|
|
3681
|
+
expect: {
|
|
3682
|
+
kind: "read",
|
|
3683
|
+
title: "/tmp/a.ts",
|
|
3684
|
+
status: "completed",
|
|
3685
|
+
locations: [{ path: "/tmp/a.ts" }],
|
|
3686
|
+
},
|
|
3687
|
+
},
|
|
3688
|
+
{
|
|
3689
|
+
name: "edit → edit with location",
|
|
3690
|
+
toolName: "edit",
|
|
3691
|
+
args: { file_path: "/tmp/b.ts" },
|
|
3692
|
+
result: { text: "edited" },
|
|
3693
|
+
expect: {
|
|
3694
|
+
kind: "edit",
|
|
3695
|
+
title: "/tmp/b.ts",
|
|
3696
|
+
status: "completed",
|
|
3697
|
+
locations: [{ path: "/tmp/b.ts" }],
|
|
3698
|
+
},
|
|
3699
|
+
},
|
|
3700
|
+
{
|
|
3701
|
+
name: "grep → search",
|
|
3702
|
+
toolName: "grep",
|
|
3703
|
+
args: { pattern: "foo" },
|
|
3704
|
+
result: { text: "match" },
|
|
3705
|
+
expect: {
|
|
3706
|
+
kind: "search",
|
|
3707
|
+
title: "foo",
|
|
3708
|
+
status: "completed",
|
|
3709
|
+
locations: [],
|
|
3710
|
+
},
|
|
3711
|
+
},
|
|
3712
|
+
{
|
|
3713
|
+
name: "ls → read",
|
|
3714
|
+
toolName: "ls",
|
|
3715
|
+
args: { path: "/tmp" },
|
|
3716
|
+
result: { text: "listing" },
|
|
3717
|
+
expect: {
|
|
3718
|
+
kind: "read",
|
|
3719
|
+
title: "/tmp",
|
|
3720
|
+
status: "completed",
|
|
3721
|
+
locations: [{ path: "/tmp" }],
|
|
3722
|
+
},
|
|
3723
|
+
},
|
|
3724
|
+
{
|
|
3725
|
+
name: "find → search",
|
|
3726
|
+
toolName: "find",
|
|
3727
|
+
args: { pattern: "*.ts" },
|
|
3728
|
+
result: { text: "found" },
|
|
3729
|
+
expect: {
|
|
3730
|
+
kind: "search",
|
|
3731
|
+
title: "*.ts",
|
|
3732
|
+
status: "completed",
|
|
3733
|
+
locations: [],
|
|
3734
|
+
},
|
|
3735
|
+
},
|
|
3736
|
+
{
|
|
3737
|
+
name: "web_fetch → fetch",
|
|
3738
|
+
toolName: "web_fetch",
|
|
3739
|
+
args: { url: "https://example.com" },
|
|
3740
|
+
result: { text: "html" },
|
|
3741
|
+
expect: {
|
|
3742
|
+
kind: "fetch",
|
|
3743
|
+
title: "web_fetch",
|
|
3744
|
+
status: "completed",
|
|
3745
|
+
locations: [],
|
|
3746
|
+
},
|
|
3747
|
+
},
|
|
3748
|
+
{
|
|
3749
|
+
name: "Agent → think",
|
|
3750
|
+
toolName: "Agent",
|
|
3751
|
+
args: { prompt: "go" },
|
|
3752
|
+
result: { text: "done" },
|
|
3753
|
+
expect: {
|
|
3754
|
+
kind: "think",
|
|
3755
|
+
title: "Agent",
|
|
3756
|
+
status: "completed",
|
|
3757
|
+
locations: [],
|
|
3758
|
+
},
|
|
3759
|
+
},
|
|
3760
|
+
{
|
|
3761
|
+
name: "unknown tool → other",
|
|
3762
|
+
toolName: "mcp__foo__bar",
|
|
3763
|
+
args: { arg: 1 },
|
|
3764
|
+
result: { text: "ok" },
|
|
3765
|
+
expect: {
|
|
3766
|
+
kind: "other",
|
|
3767
|
+
title: "mcp__foo__bar",
|
|
3768
|
+
status: "completed",
|
|
3769
|
+
locations: [],
|
|
3770
|
+
},
|
|
3771
|
+
},
|
|
3772
|
+
]
|
|
3773
|
+
for (const c of toolKindMatrix) {
|
|
3774
|
+
it(`replays tool call (${c.name}) as tool_call + terminal tool_call_update`, async () => {
|
|
3775
|
+
const fake = new FakeAgentSession(`loaded-tool-${c.toolName}`)
|
|
3776
|
+
fake.branch = [
|
|
3777
|
+
userTextEntry("go", "u1", null),
|
|
3778
|
+
assistantBlocksEntry(
|
|
3779
|
+
[
|
|
3780
|
+
{
|
|
3781
|
+
type: "toolCall",
|
|
3782
|
+
id: "tc-1",
|
|
3783
|
+
name: c.toolName,
|
|
3784
|
+
arguments: c.args,
|
|
3785
|
+
},
|
|
3786
|
+
],
|
|
3787
|
+
"a1",
|
|
3788
|
+
"u1",
|
|
3789
|
+
),
|
|
3790
|
+
toolResultEntry("tc-1", c.toolName, c.result.text, c.result.isError ?? false, "tr1", "a1"),
|
|
3791
|
+
]
|
|
3792
|
+
const { conn, updates } = makeRecordingConn()
|
|
3793
|
+
const agent = makeAgent(async () => asSession(fake), { conn })
|
|
3794
|
+
await agent.loadSession({
|
|
3795
|
+
sessionId: fake.sessionId,
|
|
3796
|
+
cwd: "/tmp",
|
|
3797
|
+
mcpServers: [],
|
|
3798
|
+
})
|
|
3799
|
+
const seq = replayOnly(updates).map((u) => u.update.sessionUpdate)
|
|
3800
|
+
expect(seq).toEqual(["user_message_chunk", "tool_call", "tool_call_update"])
|
|
3801
|
+
|
|
3802
|
+
const replay = replayOnly(updates)
|
|
3803
|
+
const toolCall = replay[1].update as Record<string, unknown>
|
|
3804
|
+
expect(toolCall).toMatchObject({
|
|
3805
|
+
sessionUpdate: "tool_call",
|
|
3806
|
+
toolCallId: "tc-1",
|
|
3807
|
+
kind: c.expect.kind,
|
|
3808
|
+
title: c.expect.title,
|
|
3809
|
+
status: c.expect.status,
|
|
3810
|
+
locations: c.expect.locations,
|
|
3811
|
+
rawInput: c.args,
|
|
3812
|
+
})
|
|
3813
|
+
const update = replay[2].update as Record<string, unknown>
|
|
3814
|
+
expect(update).toMatchObject({
|
|
3815
|
+
sessionUpdate: "tool_call_update",
|
|
3816
|
+
toolCallId: "tc-1",
|
|
3817
|
+
status: c.expect.status,
|
|
3818
|
+
})
|
|
3819
|
+
const content = (update as { content: Array<{ content: { text: string } }> }).content
|
|
3820
|
+
expect(content[0].content.text).toBe(c.result.text)
|
|
3821
|
+
})
|
|
3822
|
+
}
|
|
3823
|
+
|
|
3824
|
+
it("replays a denied/failed tool call with status failed and the persisted error content", async () => {
|
|
3825
|
+
const fake = new FakeAgentSession("loaded-tool-fail")
|
|
3826
|
+
fake.branch = [
|
|
3827
|
+
userTextEntry("go", "u1", null),
|
|
3828
|
+
assistantBlocksEntry(
|
|
3829
|
+
[
|
|
3830
|
+
{
|
|
3831
|
+
type: "toolCall",
|
|
3832
|
+
id: "tc-deny",
|
|
3833
|
+
name: "bash",
|
|
3834
|
+
arguments: { command: "rm -rf /" },
|
|
3835
|
+
},
|
|
3836
|
+
],
|
|
3837
|
+
"a1",
|
|
3838
|
+
"u1",
|
|
3839
|
+
),
|
|
3840
|
+
toolResultEntry("tc-deny", "bash", "permission denied", true, "tr1", "a1"),
|
|
3841
|
+
]
|
|
3842
|
+
const { conn, updates } = makeRecordingConn()
|
|
3843
|
+
const agent = makeAgent(async () => asSession(fake), { conn })
|
|
3844
|
+
await agent.loadSession({
|
|
3845
|
+
sessionId: "loaded-tool-fail",
|
|
3846
|
+
cwd: "/tmp",
|
|
3847
|
+
mcpServers: [],
|
|
3848
|
+
})
|
|
3849
|
+
const replay = replayOnly(updates)
|
|
3850
|
+
const toolCall = replay[1].update as { status?: string }
|
|
3851
|
+
const update = replay[2].update as {
|
|
3852
|
+
status?: string
|
|
3853
|
+
content: Array<{ content: { text: string } }>
|
|
3854
|
+
}
|
|
3855
|
+
expect(toolCall.status).toBe("failed")
|
|
3856
|
+
expect(update.status).toBe("failed")
|
|
3857
|
+
expect(update.content[0].content.text).toBe("permission denied")
|
|
3858
|
+
})
|
|
3859
|
+
|
|
3860
|
+
it("replays an interrupted tool call (no persisted result) as failed with empty content", async () => {
|
|
3861
|
+
// Session was killed mid tool execution; the tool result never landed in
|
|
3862
|
+
// the JSONL. "failed" is the only honest terminal status we can synthesize
|
|
3863
|
+
// — leaving it in_progress would hang the client's spinner forever.
|
|
3864
|
+
const fake = new FakeAgentSession("loaded-tool-orphan")
|
|
3865
|
+
fake.branch = [
|
|
3866
|
+
userTextEntry("go", "u1", null),
|
|
3867
|
+
assistantBlocksEntry(
|
|
3868
|
+
[
|
|
3869
|
+
{
|
|
3870
|
+
type: "toolCall",
|
|
3871
|
+
id: "tc-orphan",
|
|
3872
|
+
name: "bash",
|
|
3873
|
+
arguments: { command: "sleep 100" },
|
|
3874
|
+
},
|
|
3875
|
+
],
|
|
3876
|
+
"a1",
|
|
3877
|
+
"u1",
|
|
3878
|
+
),
|
|
3879
|
+
]
|
|
3880
|
+
const { conn, updates } = makeRecordingConn()
|
|
3881
|
+
const agent = makeAgent(async () => asSession(fake), { conn })
|
|
3882
|
+
await agent.loadSession({
|
|
3883
|
+
sessionId: "loaded-tool-orphan",
|
|
3884
|
+
cwd: "/tmp",
|
|
3885
|
+
mcpServers: [],
|
|
3886
|
+
})
|
|
3887
|
+
expect(replayOnly(updates).map((u) => u.update.sessionUpdate)).toEqual([
|
|
3888
|
+
"user_message_chunk",
|
|
3889
|
+
"tool_call",
|
|
3890
|
+
"tool_call_update",
|
|
3891
|
+
])
|
|
3892
|
+
const replay = replayOnly(updates)
|
|
3893
|
+
const toolCall = replay[1].update as { status?: string }
|
|
3894
|
+
const update = replay[2].update as { status?: string; content: unknown[] }
|
|
3895
|
+
expect(toolCall.status).toBe("failed")
|
|
3896
|
+
expect(update.status).toBe("failed")
|
|
3897
|
+
expect(update.content).toEqual([])
|
|
3898
|
+
})
|
|
3899
|
+
|
|
3900
|
+
it("replays thinking blocks as agent_thought_chunk with raw text (no ANSI)", async () => {
|
|
3901
|
+
const fake = new FakeAgentSession("loaded-thinking")
|
|
3902
|
+
fake.branch = [
|
|
3903
|
+
userTextEntry("go", "u1", null),
|
|
3904
|
+
assistantBlocksEntry([{ type: "thinking", thinking: "considering options" }], "a1", "u1"),
|
|
3905
|
+
]
|
|
3906
|
+
const { conn, updates } = makeRecordingConn()
|
|
3907
|
+
const agent = makeAgent(async () => asSession(fake), { conn })
|
|
3908
|
+
await agent.loadSession({
|
|
3909
|
+
sessionId: "loaded-thinking",
|
|
3910
|
+
cwd: "/tmp",
|
|
3911
|
+
mcpServers: [],
|
|
3912
|
+
})
|
|
3913
|
+
const thought = updates.find((u) => u.update.sessionUpdate === "agent_thought_chunk")
|
|
3914
|
+
expect(thought).toBeDefined()
|
|
3915
|
+
const content = (thought?.update as { content: { type: string; text: string } }).content
|
|
3916
|
+
expect(content.text).toBe("considering options")
|
|
3917
|
+
// No ANSI escape codes — filterThinkingForDisplay returns dimmed text
|
|
3918
|
+
// for live TUI rendering, but ACP clients can't render escapes. Replay
|
|
3919
|
+
// uses the helper as a yes/no predicate and emits the raw thinking.
|
|
3920
|
+
expect(content.text.includes("\x1b[")).toBe(false)
|
|
3921
|
+
})
|
|
3922
|
+
|
|
3923
|
+
it("skips thinking blocks when hideThinkingBlock is enabled (shared with hide-thinking extension)", async () => {
|
|
3924
|
+
// Same redaction rule the live TUI uses — verified by sharing
|
|
3925
|
+
// filterThinkingForDisplay across both code paths so a setting flip
|
|
3926
|
+
// doesn't drift between live and replay.
|
|
3927
|
+
_setHideThinking(true)
|
|
3928
|
+
try {
|
|
3929
|
+
const fake = new FakeAgentSession("loaded-thinking-hidden")
|
|
3930
|
+
fake.branch = [
|
|
3931
|
+
userTextEntry("go", "u1", null),
|
|
3932
|
+
assistantBlocksEntry([{ type: "thinking", thinking: "considering options" }], "a1", "u1"),
|
|
3933
|
+
]
|
|
3934
|
+
const { conn, updates } = makeRecordingConn()
|
|
3935
|
+
const agent = makeAgent(async () => asSession(fake), { conn })
|
|
3936
|
+
await agent.loadSession({
|
|
3937
|
+
sessionId: "loaded-thinking-hidden",
|
|
3938
|
+
cwd: "/tmp",
|
|
3939
|
+
mcpServers: [],
|
|
3940
|
+
})
|
|
3941
|
+
expect(updates.find((u) => u.update.sessionUpdate === "agent_thought_chunk")).toBeUndefined()
|
|
3942
|
+
} finally {
|
|
3943
|
+
_resetHideThinking()
|
|
3944
|
+
}
|
|
3945
|
+
})
|
|
3946
|
+
|
|
3947
|
+
it("skips redacted thinking blocks even when hideThinkingBlock is off", async () => {
|
|
3948
|
+
// Redacted thinking has only an opaque encrypted payload (in
|
|
3949
|
+
// thinkingSignature) for multi-turn provider continuity — no plaintext
|
|
3950
|
+
// to surface to the user.
|
|
3951
|
+
const fake = new FakeAgentSession("loaded-thinking-redacted")
|
|
3952
|
+
fake.branch = [
|
|
3953
|
+
userTextEntry("go", "u1", null),
|
|
3954
|
+
assistantBlocksEntry(
|
|
3955
|
+
[
|
|
3956
|
+
{
|
|
3957
|
+
type: "thinking",
|
|
3958
|
+
thinking: "ignored",
|
|
3959
|
+
redacted: true,
|
|
3960
|
+
thinkingSignature: "opaque",
|
|
3961
|
+
},
|
|
3962
|
+
],
|
|
3963
|
+
"a1",
|
|
3964
|
+
"u1",
|
|
3965
|
+
),
|
|
3966
|
+
]
|
|
3967
|
+
const { conn, updates } = makeRecordingConn()
|
|
3968
|
+
const agent = makeAgent(async () => asSession(fake), { conn })
|
|
3969
|
+
await agent.loadSession({
|
|
3970
|
+
sessionId: "loaded-thinking-redacted",
|
|
3971
|
+
cwd: "/tmp",
|
|
3972
|
+
mcpServers: [],
|
|
3973
|
+
})
|
|
3974
|
+
expect(updates.find((u) => u.update.sessionUpdate === "agent_thought_chunk")).toBeUndefined()
|
|
3975
|
+
})
|
|
3976
|
+
|
|
3977
|
+
it("emits zero notifications for compaction / branch_summary / model_change / custom entries", async () => {
|
|
3978
|
+
// Non-message entries are LLM-context artifacts (or, for model_change,
|
|
3979
|
+
// already conveyed via the load response's models field). Surfacing
|
|
3980
|
+
// them as session updates would make Zed's transcript UI churn through
|
|
3981
|
+
// historical state changes the user never saw the first time around.
|
|
3982
|
+
const fake = new FakeAgentSession("loaded-skip-only")
|
|
3983
|
+
fake.branch = [
|
|
3984
|
+
{
|
|
3985
|
+
type: "model_change",
|
|
3986
|
+
id: "mc1",
|
|
3987
|
+
parentId: null,
|
|
3988
|
+
timestamp: "x",
|
|
3989
|
+
provider: "p",
|
|
3990
|
+
modelId: "m",
|
|
3991
|
+
},
|
|
3992
|
+
{
|
|
3993
|
+
type: "compaction",
|
|
3994
|
+
id: "c1",
|
|
3995
|
+
parentId: "mc1",
|
|
3996
|
+
timestamp: "x",
|
|
3997
|
+
summary: "snip",
|
|
3998
|
+
firstKeptEntryId: "u1",
|
|
3999
|
+
tokensBefore: 0,
|
|
4000
|
+
},
|
|
4001
|
+
{
|
|
4002
|
+
type: "branch_summary",
|
|
4003
|
+
id: "bs1",
|
|
4004
|
+
parentId: "c1",
|
|
4005
|
+
timestamp: "x",
|
|
4006
|
+
summary: "branch",
|
|
4007
|
+
},
|
|
4008
|
+
{
|
|
4009
|
+
type: "custom",
|
|
4010
|
+
id: "cu1",
|
|
4011
|
+
parentId: "bs1",
|
|
4012
|
+
timestamp: "x",
|
|
4013
|
+
payload: { whatever: true },
|
|
4014
|
+
},
|
|
4015
|
+
]
|
|
4016
|
+
const { conn, updates } = makeRecordingConn()
|
|
4017
|
+
const agent = makeAgent(async () => asSession(fake), { conn })
|
|
4018
|
+
await agent.loadSession({
|
|
4019
|
+
sessionId: "loaded-skip-only",
|
|
4020
|
+
cwd: "/tmp",
|
|
4021
|
+
mcpServers: [],
|
|
4022
|
+
})
|
|
4023
|
+
|
|
4024
|
+
// The only update expected here is a no-op surface commands update
|
|
4025
|
+
expect(updates).toHaveLength(1)
|
|
4026
|
+
expect(updates[0].update.sessionUpdate).toEqual("available_commands_update")
|
|
4027
|
+
})
|
|
4028
|
+
|
|
4029
|
+
it("replays a mixed transcript end-to-end (text, thinking, tool, skipped entries, follow-up text)", async () => {
|
|
4030
|
+
const fake = new FakeAgentSession("loaded-mixed")
|
|
4031
|
+
fake.branch = [
|
|
4032
|
+
userTextEntry("question", "u1", null),
|
|
4033
|
+
assistantBlocksEntry(
|
|
4034
|
+
[
|
|
4035
|
+
{ type: "text", text: "let me check" },
|
|
4036
|
+
{ type: "thinking", thinking: "weighing options" },
|
|
4037
|
+
{
|
|
4038
|
+
type: "toolCall",
|
|
4039
|
+
id: "tc-1",
|
|
4040
|
+
name: "bash",
|
|
4041
|
+
arguments: { command: "ls" },
|
|
4042
|
+
},
|
|
4043
|
+
],
|
|
4044
|
+
"a1",
|
|
4045
|
+
"u1",
|
|
4046
|
+
),
|
|
4047
|
+
toolResultEntry("tc-1", "bash", "file1\nfile2", false, "tr1", "a1"),
|
|
4048
|
+
// Skipped entries scattered through the branch must not break the walker.
|
|
4049
|
+
{
|
|
4050
|
+
type: "model_change",
|
|
4051
|
+
id: "mc1",
|
|
4052
|
+
parentId: "tr1",
|
|
4053
|
+
timestamp: "x",
|
|
4054
|
+
provider: "p",
|
|
4055
|
+
modelId: "m",
|
|
4056
|
+
},
|
|
4057
|
+
{
|
|
4058
|
+
type: "compaction",
|
|
4059
|
+
id: "c1",
|
|
4060
|
+
parentId: "mc1",
|
|
4061
|
+
timestamp: "x",
|
|
4062
|
+
summary: "snip",
|
|
4063
|
+
firstKeptEntryId: "u1",
|
|
4064
|
+
tokensBefore: 0,
|
|
4065
|
+
},
|
|
4066
|
+
assistantTextEntry("here is what I found", "a2", "c1"),
|
|
4067
|
+
]
|
|
4068
|
+
const { conn, updates } = makeRecordingConn()
|
|
4069
|
+
const agent = makeAgent(async () => asSession(fake), { conn })
|
|
4070
|
+
await agent.loadSession({
|
|
4071
|
+
sessionId: "loaded-mixed",
|
|
4072
|
+
cwd: "/tmp",
|
|
4073
|
+
mcpServers: [],
|
|
4074
|
+
})
|
|
4075
|
+
// Order is significant: tool_call must precede tool_call_update, and
|
|
4076
|
+
// the post-skipped-entries assistant text must land last.
|
|
4077
|
+
expect(updates.map((u) => u.update.sessionUpdate)).toEqual([
|
|
4078
|
+
"user_message_chunk",
|
|
4079
|
+
"agent_message_chunk",
|
|
4080
|
+
"agent_thought_chunk",
|
|
4081
|
+
"tool_call",
|
|
4082
|
+
"tool_call_update",
|
|
4083
|
+
"agent_message_chunk",
|
|
4084
|
+
"available_commands_update",
|
|
4085
|
+
])
|
|
4086
|
+
})
|
|
4087
|
+
|
|
4088
|
+
it("does not deliver synthetic agent_* events to session subscribers during replay", async () => {
|
|
4089
|
+
// Acceptance criterion from the plan: extensions registered on a loaded
|
|
4090
|
+
// session must not see the historical turns as if they were live —
|
|
4091
|
+
// otherwise telemetry/loop-guard/etc. would double-count. Replay sends
|
|
4092
|
+
// notifications straight to conn.sessionUpdate, never via emit().
|
|
4093
|
+
const fake = new FakeAgentSession("loaded-no-events")
|
|
4094
|
+
fake.branch = [
|
|
4095
|
+
userTextEntry("hi", "u1", null),
|
|
4096
|
+
assistantBlocksEntry(
|
|
4097
|
+
[
|
|
4098
|
+
{ type: "text", text: "hello" },
|
|
4099
|
+
{ type: "thinking", thinking: "thinking" },
|
|
4100
|
+
{
|
|
4101
|
+
type: "toolCall",
|
|
4102
|
+
id: "tc-1",
|
|
4103
|
+
name: "bash",
|
|
4104
|
+
arguments: { command: "ls" },
|
|
4105
|
+
},
|
|
4106
|
+
],
|
|
4107
|
+
"a1",
|
|
4108
|
+
"u1",
|
|
4109
|
+
),
|
|
4110
|
+
toolResultEntry("tc-1", "bash", "out", false, "tr1", "a1"),
|
|
4111
|
+
]
|
|
4112
|
+
// Pre-subscribe a counter BEFORE loadSession so it sits alongside the
|
|
4113
|
+
// agent's own subscriber; if replay went through the event emitter we'd
|
|
4114
|
+
// see agent_start / message_update / tool_execution_* / agent_end here.
|
|
4115
|
+
let extensionEventCount = 0
|
|
4116
|
+
fake.subscribe(() => {
|
|
4117
|
+
extensionEventCount++
|
|
4118
|
+
})
|
|
4119
|
+
const agent = makeAgent(async () => asSession(fake))
|
|
4120
|
+
await agent.loadSession({
|
|
4121
|
+
sessionId: "loaded-no-events",
|
|
4122
|
+
cwd: "/tmp",
|
|
4123
|
+
mcpServers: [],
|
|
4124
|
+
})
|
|
4125
|
+
expect(extensionEventCount).toBe(0)
|
|
4126
|
+
})
|
|
4127
|
+
|
|
4128
|
+
it("replays a 200-turn session well within a generous bound (regression guard)", async () => {
|
|
4129
|
+
// Regression guard against quadratic walker behavior. The fake skips
|
|
4130
|
+
// disk I/O and JSON-RPC framing — what's actually under test is that
|
|
4131
|
+
// the walker stays O(N). Bound is loose (3s) because shared CI runners
|
|
4132
|
+
// are noisy; a real regression would blow past it by orders of
|
|
4133
|
+
// magnitude.
|
|
4134
|
+
const fake = new FakeAgentSession("loaded-perf")
|
|
4135
|
+
const branch: unknown[] = []
|
|
4136
|
+
for (let i = 0; i < 200; i++) {
|
|
4137
|
+
branch.push(userTextEntry(`q${i}`, `u${i}`, i === 0 ? null : `a${i - 1}`))
|
|
4138
|
+
branch.push(
|
|
4139
|
+
assistantBlocksEntry(
|
|
4140
|
+
[
|
|
4141
|
+
{ type: "text", text: `a${i}` },
|
|
4142
|
+
{
|
|
4143
|
+
type: "toolCall",
|
|
4144
|
+
id: `tc-${i}`,
|
|
4145
|
+
name: "bash",
|
|
4146
|
+
arguments: { command: `echo ${i}` },
|
|
4147
|
+
},
|
|
4148
|
+
],
|
|
4149
|
+
`a${i}`,
|
|
4150
|
+
`u${i}`,
|
|
4151
|
+
),
|
|
4152
|
+
)
|
|
4153
|
+
branch.push(toolResultEntry(`tc-${i}`, "bash", `out${i}`, false, `tr-${i}`, `a${i}`))
|
|
4154
|
+
}
|
|
4155
|
+
fake.branch = branch
|
|
4156
|
+
const agent = makeAgent(async () => asSession(fake))
|
|
4157
|
+
const start = Date.now()
|
|
4158
|
+
await agent.loadSession({
|
|
4159
|
+
sessionId: "loaded-perf",
|
|
4160
|
+
cwd: "/tmp",
|
|
4161
|
+
mcpServers: [],
|
|
4162
|
+
})
|
|
4163
|
+
const elapsed = Date.now() - start
|
|
4164
|
+
expect(elapsed).toBeLessThan(3000)
|
|
4165
|
+
})
|
|
4166
|
+
})
|
|
4167
|
+
|
|
4168
|
+
// Ordering regression test: permission flag controller must be registered
|
|
4169
|
+
// BEFORE bindAcpExtensions is called. This ensures that when upstream
|
|
4170
|
+
// bindExtensions() emits session_start, the permissions extension already
|
|
4171
|
+
// has access to the shared controller and doesn't create a duplicate one.
|
|
4172
|
+
describe("permission flag controller registration ordering", () => {
|
|
4173
|
+
it("registers permission flag controller before bindAcpExtensions in newSession", async () => {
|
|
4174
|
+
const ordering: string[] = []
|
|
4175
|
+
const fake = new FakeAgentSession("session-ordering-test")
|
|
4176
|
+
|
|
4177
|
+
fake.bindExtensionsImpl = async () => {
|
|
4178
|
+
// At this point, the permission controller should already be registered
|
|
4179
|
+
const controller = getSessionPermissionFlagController("session-ordering-test")
|
|
4180
|
+
ordering.push(controller ? "controller-present" : "controller-missing")
|
|
4181
|
+
ordering.push("bindAcpExtensions-called")
|
|
4182
|
+
}
|
|
4183
|
+
|
|
4184
|
+
const factory: AcpSessionFactory = async () => asSession(fake)
|
|
4185
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
4186
|
+
extensionFactories: [],
|
|
4187
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
4188
|
+
sessionFactory: factory,
|
|
4189
|
+
})
|
|
4190
|
+
|
|
4191
|
+
await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
4192
|
+
|
|
4193
|
+
// The controller should be present when bindAcpExtensions is called
|
|
4194
|
+
expect(ordering).toEqual(["controller-present", "bindAcpExtensions-called"])
|
|
4195
|
+
// And should still be registered after newSession completes
|
|
4196
|
+
expect(getSessionPermissionFlagController("session-ordering-test")).toBeDefined()
|
|
4197
|
+
|
|
4198
|
+
await agent.shutdown()
|
|
4199
|
+
})
|
|
4200
|
+
|
|
4201
|
+
it("registers permission flag controller before bindAcpExtensions in loadSessionFresh", async () => {
|
|
4202
|
+
const ordering: string[] = []
|
|
4203
|
+
const fake = new FakeAgentSession("load-session-ordering")
|
|
4204
|
+
// Add a minimal branch for replay
|
|
4205
|
+
fake.branch = [
|
|
4206
|
+
{
|
|
4207
|
+
type: "message",
|
|
4208
|
+
message: { role: "user", content: "test" },
|
|
4209
|
+
timestamp: Date.now(),
|
|
4210
|
+
},
|
|
4211
|
+
]
|
|
4212
|
+
|
|
4213
|
+
fake.bindExtensionsImpl = async () => {
|
|
4214
|
+
const controller = getSessionPermissionFlagController("load-session-ordering")
|
|
4215
|
+
ordering.push(controller ? "controller-present" : "controller-missing")
|
|
4216
|
+
ordering.push("bindAcpExtensions-called")
|
|
4217
|
+
}
|
|
4218
|
+
|
|
4219
|
+
let loaderCallCount = 0
|
|
4220
|
+
const loader: AcpSessionLoader = async () => {
|
|
4221
|
+
loaderCallCount++
|
|
4222
|
+
return asSession(fake)
|
|
4223
|
+
}
|
|
4224
|
+
|
|
4225
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
4226
|
+
extensionFactories: [],
|
|
4227
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
4228
|
+
sessionLoader: loader,
|
|
4229
|
+
})
|
|
4230
|
+
|
|
4231
|
+
await agent.loadSession({ sessionId: "load-session-ordering", cwd: "/tmp", mcpServers: [] })
|
|
4232
|
+
|
|
4233
|
+
expect(loaderCallCount).toBe(1)
|
|
4234
|
+
expect(ordering).toEqual(["controller-present", "bindAcpExtensions-called"])
|
|
4235
|
+
expect(getSessionPermissionFlagController("load-session-ordering")).toBeDefined()
|
|
4236
|
+
|
|
4237
|
+
await agent.shutdown()
|
|
4238
|
+
})
|
|
4239
|
+
|
|
4240
|
+
it("unregisters permission flag controller when bindAcpExtensions throws in newSession", async () => {
|
|
4241
|
+
const fake = new FakeAgentSession("session-bind-failure")
|
|
4242
|
+
// Verify controller is registered during the call (before bind fails)
|
|
4243
|
+
let controllerDuringBind: ReturnType<typeof getSessionPermissionFlagController>
|
|
4244
|
+
fake.bindExtensionsImpl = async () => {
|
|
4245
|
+
controllerDuringBind = getSessionPermissionFlagController("session-bind-failure")
|
|
4246
|
+
throw new Error("bindExtensions failed")
|
|
4247
|
+
}
|
|
4248
|
+
|
|
4249
|
+
const factory: AcpSessionFactory = async () => asSession(fake)
|
|
4250
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
4251
|
+
extensionFactories: [],
|
|
4252
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
4253
|
+
sessionFactory: factory,
|
|
4254
|
+
})
|
|
4255
|
+
|
|
4256
|
+
await expect(agent.newSession({ cwd: "/tmp", mcpServers: [] })).rejects.toThrow(/bindExtensions failed/)
|
|
4257
|
+
|
|
4258
|
+
// Controller should have been present during bind
|
|
4259
|
+
expect(controllerDuringBind).toBeDefined()
|
|
4260
|
+
// But should be unregistered after the catch block runs
|
|
4261
|
+
expect(getSessionPermissionFlagController("session-bind-failure")).toBeUndefined()
|
|
4262
|
+
expect(fake.disposed).toBe(true)
|
|
4263
|
+
})
|
|
4264
|
+
|
|
4265
|
+
it("unregisters permission flag controller when bindAcpExtensions throws in loadSession", async () => {
|
|
4266
|
+
const fake = new FakeAgentSession("load-bind-failure")
|
|
4267
|
+
fake.branch = [
|
|
4268
|
+
{
|
|
4269
|
+
type: "message",
|
|
4270
|
+
message: { role: "user", content: "test" },
|
|
4271
|
+
timestamp: Date.now(),
|
|
4272
|
+
},
|
|
4273
|
+
]
|
|
4274
|
+
fake.bindExtensionsImpl = async () => {
|
|
4275
|
+
throw new Error("bindExtensions failed in load")
|
|
4276
|
+
}
|
|
4277
|
+
|
|
4278
|
+
const loader: AcpSessionLoader = async () => asSession(fake)
|
|
4279
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
4280
|
+
extensionFactories: [],
|
|
4281
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
4282
|
+
sessionLoader: loader,
|
|
4283
|
+
})
|
|
4284
|
+
|
|
4285
|
+
await expect(agent.loadSession({ sessionId: "load-bind-failure", cwd: "/tmp", mcpServers: [] })).rejects.toThrow(
|
|
4286
|
+
/bindExtensions failed in load/,
|
|
4287
|
+
)
|
|
4288
|
+
|
|
4289
|
+
expect(getSessionPermissionFlagController("load-bind-failure")).toBeUndefined()
|
|
4290
|
+
expect(fake.disposed).toBe(true)
|
|
4291
|
+
})
|
|
4292
|
+
|
|
4293
|
+
it("provides a working controller that can get and set mode during bindAcpExtensions", async () => {
|
|
4294
|
+
const fake = new FakeAgentSession("session-controller-functional")
|
|
4295
|
+
let capturedMode: { mode: string; source: string } | undefined
|
|
4296
|
+
|
|
4297
|
+
fake.bindExtensionsImpl = async () => {
|
|
4298
|
+
const controller = getSessionPermissionFlagController("session-controller-functional")
|
|
4299
|
+
if (controller) {
|
|
4300
|
+
// Verify the controller works - get initial mode
|
|
4301
|
+
capturedMode = controller.getMode()
|
|
4302
|
+
// Set a new mode
|
|
4303
|
+
controller.setMode("plan", "user")
|
|
4304
|
+
}
|
|
4305
|
+
}
|
|
4306
|
+
|
|
4307
|
+
const factory: AcpSessionFactory = async () => asSession(fake)
|
|
4308
|
+
const agent = new IkieAcpAgent(makeConn(), {
|
|
4309
|
+
extensionFactories: [],
|
|
4310
|
+
agentDir: "/tmp/fake-agent-dir",
|
|
4311
|
+
sessionFactory: factory,
|
|
4312
|
+
})
|
|
4313
|
+
|
|
4314
|
+
await agent.newSession({ cwd: "/tmp", mcpServers: [] })
|
|
4315
|
+
|
|
4316
|
+
// The controller should be present and functional during bindExtensions
|
|
4317
|
+
expect(capturedMode).toBeDefined()
|
|
4318
|
+
expect(capturedMode?.mode).toBeDefined()
|
|
4319
|
+
expect(capturedMode?.source).toBeDefined()
|
|
4320
|
+
|
|
4321
|
+
// After setMode in bindExtensions, the controller should have the new mode "plan"
|
|
4322
|
+
const finalController = getSessionPermissionFlagController("session-controller-functional")
|
|
4323
|
+
expect(finalController?.getMode()).toEqual({ mode: "plan", source: "user" })
|
|
4324
|
+
|
|
4325
|
+
await agent.shutdown()
|
|
4326
|
+
})
|
|
4327
|
+
})
|
|
4328
|
+
|
|
4329
|
+
describe("shouldEmitThinking", () => {
|
|
4330
|
+
it("returns true by default (hideThinkingBlock unset)", () => {
|
|
4331
|
+
_resetHideThinking()
|
|
4332
|
+
expect(shouldEmitThinking("anything")).toBe(true)
|
|
4333
|
+
})
|
|
4334
|
+
it("returns false when hideThinkingBlock is enabled", () => {
|
|
4335
|
+
_setHideThinking(true)
|
|
4336
|
+
try {
|
|
4337
|
+
expect(shouldEmitThinking("anything")).toBe(false)
|
|
4338
|
+
} finally {
|
|
4339
|
+
_resetHideThinking()
|
|
4340
|
+
}
|
|
4341
|
+
})
|
|
4342
|
+
it("does not break when the persisted thinking text contains a literal </think>", () => {
|
|
4343
|
+
// Regression: a previous version probed filterThinkingForDisplay with a
|
|
4344
|
+
// synthetic <think>${thinking}</think> wrapper. Models self-quoting
|
|
4345
|
+
// "</think>" closed the wrapper early and the inner text leaked, making
|
|
4346
|
+
// the predicate non-deterministic. Reading the setting directly avoids
|
|
4347
|
+
// the round-trip entirely.
|
|
4348
|
+
_resetHideThinking()
|
|
4349
|
+
const haunted = "I'll stop now. </think> trailing"
|
|
4350
|
+
expect(shouldEmitThinking(haunted)).toBe(true)
|
|
4351
|
+
_setHideThinking(true)
|
|
4352
|
+
try {
|
|
4353
|
+
expect(shouldEmitThinking(haunted)).toBe(false)
|
|
4354
|
+
} finally {
|
|
4355
|
+
_resetHideThinking()
|
|
4356
|
+
}
|
|
4357
|
+
})
|
|
4358
|
+
})
|
|
4359
|
+
|
|
4360
|
+
describe("stripAnsi", () => {
|
|
4361
|
+
it("returns unchanged text when no ANSI escapes are present", () => {
|
|
4362
|
+
expect(stripAnsi("plain text")).toBe("plain text")
|
|
4363
|
+
})
|
|
4364
|
+
it("removes CSI sequences (color, dim, reset) without touching surrounding text", () => {
|
|
4365
|
+
expect(stripAnsi("\x1b[2mfoo\x1b[22m bar")).toBe("foo bar")
|
|
4366
|
+
expect(stripAnsi("\x1b[31mred\x1b[0m and \x1b[1mbold\x1b[0m")).toBe("red and bold")
|
|
4367
|
+
})
|
|
4368
|
+
it("leaves a stray ESC byte (no full sequence) untouched", () => {
|
|
4369
|
+
// Conservative scrub: only `\x1b[…<letter>` is dropped. A bare ESC
|
|
4370
|
+
// without a CSI body isn't styling and shouldn't be mangled.
|
|
4371
|
+
expect(stripAnsi("plain\x1bbody")).toBe("plain\x1bbody")
|
|
4372
|
+
})
|
|
4373
|
+
})
|
|
4374
|
+
|
|
4375
|
+
describe("userMessageText", () => {
|
|
4376
|
+
it("returns string content unchanged for user messages", () => {
|
|
4377
|
+
expect(userMessageText("hello world")).toBe("hello world")
|
|
4378
|
+
})
|
|
4379
|
+
it("joins text blocks and skips images for user messages", () => {
|
|
4380
|
+
expect(
|
|
4381
|
+
userMessageText([
|
|
4382
|
+
{ type: "text", text: "hi " },
|
|
4383
|
+
{ type: "image", data: "x", mimeType: "image/png" },
|
|
4384
|
+
{ type: "text", text: "there" },
|
|
4385
|
+
]),
|
|
4386
|
+
).toBe("hi there")
|
|
4387
|
+
})
|
|
4388
|
+
it("returns empty string for null / non-array user content", () => {
|
|
4389
|
+
expect(userMessageText(null)).toBe("")
|
|
4390
|
+
expect(userMessageText(42)).toBe("")
|
|
4391
|
+
})
|
|
4392
|
+
})
|