functionalscript 0.45.0 → 0.46.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/fjs/asn.1/module.f.mjs +8 -1
- package/fjs/asn.1/proof.f.d.mts +10 -0
- package/fjs/asn.1/proof.f.mjs +16 -0
- package/fjs/basen/base128/module.f.mjs +22 -5
- package/fjs/bnf/data/module.f.d.mts +32 -6
- package/fjs/bnf/data/module.f.mjs +112 -7
- package/fjs/bnf/data/proof.f.d.mts +2 -0
- package/fjs/bnf/data/proof.f.mjs +74 -3
- package/fjs/bnf/data/types.d.ts +20 -2
- package/fjs/bnf/descent/module.f.d.mts +28 -14
- package/fjs/bnf/descent/module.f.mjs +122 -77
- package/fjs/bnf/descent/proof.f.d.mts +2 -0
- package/fjs/bnf/descent/proof.f.mjs +117 -31
- package/fjs/bnf/descent/types.d.ts +12 -14
- package/fjs/bnf/ll1/module.f.d.mts +64 -22
- package/fjs/bnf/ll1/module.f.mjs +214 -154
- package/fjs/bnf/ll1/proof.f.d.mts +15 -2
- package/fjs/bnf/ll1/proof.f.mjs +323 -149
- package/fjs/bnf/ll1/types.d.ts +16 -24
- package/fjs/bnf/matcher/module.f.d.mts +66 -0
- package/fjs/bnf/matcher/module.f.mjs +81 -0
- package/fjs/bnf/matcher/proof.f.d.mts +10 -0
- package/fjs/bnf/matcher/proof.f.mjs +79 -0
- package/fjs/bnf/matcher/types.d.ts +54 -0
- package/fjs/bnf/testlib.f.d.mts +31 -0
- package/fjs/bnf/testlib.f.mjs +80 -0
- package/fjs/cas/cli/module.f.d.mts +1 -1
- package/fjs/cas/cli/module.f.mjs +14 -20
- package/fjs/cas/cli/proof.f.d.mts +1 -3
- package/fjs/cas/cli/proof.f.mjs +44 -33
- package/fjs/cas/evo/module.f.d.mts +64 -18
- package/fjs/cas/evo/module.f.mjs +148 -70
- package/fjs/cas/evo/proof.f.d.mts +10 -1
- package/fjs/cas/evo/proof.f.mjs +305 -223
- package/fjs/cas/evo/types.d.ts +45 -9
- package/fjs/cas/module.f.d.mts +18 -32
- package/fjs/cas/module.f.mjs +129 -128
- package/fjs/cas/proof.f.d.mts +11 -8
- package/fjs/cas/proof.f.mjs +259 -147
- package/fjs/cas/types.d.ts +24 -10
- package/fjs/ci/config/module.f.d.mts +2 -2
- package/fjs/ci/config/module.f.mjs +2 -2
- package/fjs/ci/module.f.d.mts +5 -5
- package/fjs/ci/module.f.mjs +8 -7
- package/fjs/ci/nix/module.f.d.mts +7 -5
- package/fjs/ci/nix/module.f.mjs +13 -12
- package/fjs/ci/nix/proof.f.mjs +2 -2
- package/fjs/ci/proof.f.mjs +9 -5
- package/fjs/cli/module.f.d.mts +4 -6
- package/fjs/cli/module.f.mjs +4 -8
- package/fjs/cli/proof.f.mjs +17 -16
- package/fjs/cli/types.d.ts +2 -3
- package/fjs/common/monoid/types.d.ts +1 -1
- package/fjs/crypto/hmac/module.f.mjs +2 -2
- package/fjs/crypto/sha2/module.f.mjs +3 -1
- package/fjs/crypto/sha2/proof.f.d.mts +1 -0
- package/fjs/crypto/sha2/proof.f.mjs +24 -0
- package/fjs/crypto/sha2/types.d.ts +11 -0
- package/fjs/crypto/sign/module.f.mjs +2 -2
- package/fjs/dev/module.f.d.mts +13 -4
- package/fjs/dev/module.f.mjs +56 -27
- package/fjs/dev/update/module.f.d.mts +9 -4
- package/fjs/dev/update/module.f.mjs +14 -10
- package/fjs/dev/update/proof.f.d.mts +1 -3
- package/fjs/dev/update/proof.f.mjs +10 -5
- package/fjs/djs/module.f.d.mts +13 -5
- package/fjs/djs/module.f.mjs +31 -16
- package/fjs/djs/parser/module.f.d.mts +13 -3
- package/fjs/djs/parser/module.f.mjs +117 -16
- package/fjs/djs/parser/proof.f.d.mts +5 -1
- package/fjs/djs/parser/proof.f.mjs +274 -12
- package/fjs/djs/parser/types.d.ts +7 -1
- package/fjs/djs/proof.f.d.mts +18 -2
- package/fjs/djs/proof.f.mjs +187 -12
- package/fjs/djs/serializer/module.f.d.mts +25 -8
- package/fjs/djs/serializer/module.f.mjs +61 -15
- package/fjs/djs/serializer/proof.f.d.mts +5 -0
- package/fjs/djs/serializer/proof.f.mjs +23 -1
- package/fjs/djs/tokenizer/module.f.d.mts +17 -5
- package/fjs/djs/tokenizer/module.f.mjs +99 -60
- package/fjs/djs/tokenizer/proof.f.d.mts +1 -1
- package/fjs/djs/tokenizer/proof.f.mjs +74 -67
- package/fjs/djs/transpiler/module.f.d.mts +12 -7
- package/fjs/djs/transpiler/module.f.mjs +82 -56
- package/fjs/djs/transpiler/types.d.ts +7 -3
- package/fjs/djs/types.d.ts +7 -1
- package/fjs/effects/list/module.f.d.mts +14 -11
- package/fjs/effects/list/module.f.mjs +13 -11
- package/fjs/effects/list/types.d.ts +27 -7
- package/fjs/effects/memory/module.f.d.mts +2 -1
- package/fjs/effects/memory/module.f.mjs +3 -4
- package/fjs/effects/memory/proof.f.mjs +13 -7
- package/fjs/effects/memory/types.d.ts +4 -3
- package/fjs/effects/mock/module.f.d.mts +20 -4
- package/fjs/effects/mock/module.f.mjs +37 -5
- package/fjs/effects/mock/types.d.ts +10 -1
- package/fjs/effects/module.d.mts +4 -2
- package/fjs/effects/module.f.d.mts +466 -280
- package/fjs/effects/module.f.mjs +537 -299
- package/fjs/effects/module.mjs +2 -1
- package/fjs/effects/node/memory/module.d.mts +4 -2
- package/fjs/effects/node/memory/module.mjs +6 -3
- package/fjs/effects/node/memory/proof.mjs +10 -4
- package/fjs/effects/node/module.d.mts +4 -3
- package/fjs/effects/node/module.f.d.mts +168 -33
- package/fjs/effects/node/module.f.mjs +256 -52
- package/fjs/effects/node/module.mjs +57 -34
- package/fjs/effects/node/proof.f.d.mts +28 -2
- package/fjs/effects/node/proof.f.mjs +161 -42
- package/fjs/effects/node/types.d.ts +106 -18
- package/fjs/effects/node/virtual/module.f.d.mts +18 -4
- package/fjs/effects/node/virtual/module.f.mjs +110 -68
- package/fjs/effects/node/virtual/proof.f.d.mts +28 -2
- package/fjs/effects/node/virtual/proof.f.mjs +190 -9
- package/fjs/effects/proof.f.d.mts +69 -37
- package/fjs/effects/proof.f.mjs +410 -130
- package/fjs/effects/types.d.ts +161 -33
- package/fjs/emergent_testing/module.f.d.mts +19 -12
- package/fjs/emergent_testing/module.f.mjs +93 -33
- package/fjs/emergent_testing/proof.f.d.mts +21 -7
- package/fjs/emergent_testing/proof.f.mjs +166 -32
- package/fjs/emergent_testing/types.d.ts +22 -4
- package/fjs/fsm/module.f.d.mts +14 -4
- package/fjs/fsm/module.f.mjs +54 -37
- package/fjs/fsm/proof.f.d.mts +2 -0
- package/fjs/fsm/proof.f.mjs +83 -114
- package/fjs/js/keywords/module.f.d.mts +52 -0
- package/fjs/js/keywords/module.f.mjs +72 -0
- package/fjs/js/keywords/proof.f.d.mts +3 -0
- package/fjs/js/keywords/proof.f.mjs +13 -0
- package/fjs/js/tokenizer/module.f.d.mts +26 -6
- package/fjs/js/tokenizer/module.f.mjs +145 -159
- package/fjs/js/tokenizer/proof.f.d.mts +1 -0
- package/fjs/js/tokenizer/proof.f.mjs +54 -24
- package/fjs/js/tokenizer/types.d.ts +33 -24
- package/fjs/mcp/cas/module.f.d.mts +1 -6
- package/fjs/mcp/cas/module.f.mjs +55 -51
- package/fjs/mcp/cas/proof.f.d.mts +15 -0
- package/fjs/mcp/cas/proof.f.mjs +174 -0
- package/fjs/mcp/evo/module.f.d.mts +19 -10
- package/fjs/mcp/evo/module.f.mjs +48 -27
- package/fjs/mcp/evo/proof.f.d.mts +6 -1
- package/fjs/mcp/evo/proof.f.mjs +115 -31
- package/fjs/mcp/module.f.d.mts +4 -4
- package/fjs/mcp/module.f.mjs +6 -6
- package/fjs/mcp/proof.f.d.mts +5 -3
- package/fjs/mcp/proof.f.mjs +112 -46
- package/fjs/media/html/module.f.mjs +1 -1
- package/fjs/media/json/extended/module.f.d.mts +82 -0
- package/fjs/media/json/extended/module.f.mjs +153 -0
- package/fjs/media/json/extended/proof.f.d.mts +42 -0
- package/fjs/media/json/extended/proof.f.mjs +127 -0
- package/fjs/media/json/extended/types.d.ts +23 -0
- package/fjs/media/json/module.f.d.mts +8 -2
- package/fjs/media/json/module.f.mjs +43 -41
- package/fjs/media/json/number/module.f.d.mts +59 -0
- package/fjs/media/json/number/module.f.mjs +136 -0
- package/fjs/media/json/number/proof.f.d.mts +24 -0
- package/fjs/media/json/number/proof.f.mjs +86 -0
- package/fjs/media/json/number/types.d.ts +28 -0
- package/fjs/media/json/parser/module.f.d.mts +25 -13
- package/fjs/media/json/parser/module.f.mjs +114 -70
- package/fjs/media/json/parser/proof.f.d.mts +5 -0
- package/fjs/media/json/parser/proof.f.mjs +31 -1
- package/fjs/media/json/parser/types.d.ts +33 -14
- package/fjs/media/json/rtti/module.f.d.mts +1 -1
- package/fjs/media/json/rtti/module.f.mjs +1 -1
- package/fjs/media/json/rtti/proof.f.mjs +9 -9
- package/fjs/media/json/schema/module.f.mjs +3 -13
- package/fjs/media/json/schema/proof.f.d.mts +0 -1
- package/fjs/media/json/schema/proof.f.mjs +1 -2
- package/fjs/media/json/serializer/module.f.d.mts +32 -1
- package/fjs/media/json/serializer/module.f.mjs +64 -2
- package/fjs/media/json/tokenizer/module.f.mjs +7 -3
- package/fjs/media/json/tokenizer/proof.f.d.mts +6 -0
- package/fjs/media/json/tokenizer/proof.f.mjs +62 -21
- package/fjs/media/json/types.d.ts +36 -10
- package/fjs/media/lock/module.f.d.mts +100 -0
- package/fjs/media/lock/module.f.mjs +125 -0
- package/fjs/media/lock/proof.f.d.mts +33 -0
- package/fjs/media/lock/proof.f.mjs +196 -0
- package/fjs/media/lock/types.d.ts +15 -0
- package/fjs/media/module.f.d.mts +6 -5
- package/fjs/media/module.f.mjs +8 -7
- package/fjs/media/note/module.f.d.mts +121 -0
- package/fjs/media/note/module.f.mjs +131 -0
- package/fjs/media/note/proof.f.d.mts +29 -0
- package/fjs/media/note/proof.f.mjs +150 -0
- package/fjs/media/note/types.d.ts +10 -0
- package/fjs/media/proof.f.d.mts +4 -1
- package/fjs/media/proof.f.mjs +40 -21
- package/fjs/media/revision/module.f.d.mts +78 -7
- package/fjs/media/revision/module.f.mjs +119 -12
- package/fjs/media/revision/proof.f.d.mts +10 -0
- package/fjs/media/revision/proof.f.mjs +88 -0
- package/fjs/media/revision/types.d.ts +34 -5
- package/fjs/media/type/module.f.d.mts +33 -15
- package/fjs/media/type/module.f.mjs +35 -29
- package/fjs/media/type/proof.f.d.mts +2 -1
- package/fjs/media/type/proof.f.mjs +30 -10
- package/fjs/module.f.mjs +29 -8
- package/fjs/nanvm/proof.f.mjs +3 -3
- package/fjs/nanvm/rust/module.f.mjs +1 -1
- package/fjs/nanvm/update/module.f.d.mts +4 -4
- package/fjs/nanvm/update/module.f.mjs +8 -9
- package/fjs/nanvm/update/proof.f.mjs +4 -3
- package/fjs/proof.f.d.mts +3 -3
- package/fjs/proof.f.mjs +36 -10
- package/fjs/protocol/json_rpc/module.f.d.mts +2 -2
- package/fjs/protocol/json_rpc/module.f.mjs +3 -3
- package/fjs/protocol/json_rpc/proof.f.mjs +4 -4
- package/fjs/protocol/mcp/module.f.d.mts +10 -13
- package/fjs/protocol/mcp/module.f.mjs +74 -58
- package/fjs/protocol/mcp/proof.f.d.mts +13 -4
- package/fjs/protocol/mcp/proof.f.mjs +193 -61
- package/fjs/protocol/mcp/stdio/module.f.d.mts +15 -7
- package/fjs/protocol/mcp/stdio/module.f.mjs +38 -25
- package/fjs/protocol/mcp/stdio/proof.f.d.mts +4 -2
- package/fjs/protocol/mcp/stdio/proof.f.mjs +45 -12
- package/fjs/protocol/mcp/stdio/types.d.ts +1 -1
- package/fjs/protocol/mcp/types.d.ts +17 -6
- package/fjs/sul/id/module.f.d.mts +0 -1
- package/fjs/sul/id/module.f.mjs +2 -3
- package/fjs/sul/level/hash/module.f.mjs +2 -1
- package/fjs/sul/level/hash/proof.f.mjs +2 -2
- package/fjs/sul/module.f.mjs +18 -13
- package/fjs/text/code_point/module.f.d.mts +8 -0
- package/fjs/text/code_point/module.f.mjs +8 -1
- package/fjs/text/code_point/proof.f.d.mts +1 -0
- package/fjs/text/code_point/proof.f.mjs +11 -0
- package/fjs/text/sgr/module.f.d.mts +4 -6
- package/fjs/text/sgr/module.f.mjs +6 -7
- package/fjs/text/utf16/module.f.mjs +7 -2
- package/fjs/text/utf16/proof.f.mjs +3 -3
- package/fjs/text/utf8/module.f.mjs +4 -2
- package/fjs/types/array/module.f.mjs +14 -2
- package/fjs/types/bit_vec/module.f.d.mts +0 -2
- package/fjs/types/bit_vec/module.f.mjs +46 -32
- package/fjs/types/bit_vec/proof.f.mjs +2 -2
- package/fjs/types/btree/remove/module.f.mjs +1 -1
- package/fjs/types/btree/set/module.f.mjs +9 -11
- package/fjs/types/btree/set/proof.f.mjs +12 -0
- package/fjs/types/byte_set/module.f.d.mts +11 -4
- package/fjs/types/byte_set/module.f.mjs +14 -6
- package/fjs/types/byte_set/proof.f.mjs +7 -7
- package/fjs/types/function/compare/module.f.mjs +10 -3
- package/fjs/types/list/module.f.d.mts +1 -1
- package/fjs/types/list/module.f.mjs +1 -1
- package/fjs/types/nullable/module.f.d.mts +18 -4
- package/fjs/types/nullable/module.f.mjs +21 -4
- package/fjs/types/nullable/proof.f.d.mts +4 -0
- package/fjs/types/nullable/proof.f.mjs +15 -0
- package/fjs/types/object/module.f.d.mts +12 -2
- package/fjs/types/object/module.f.mjs +11 -1
- package/fjs/types/patricia_trie/module.f.mjs +26 -11
- package/fjs/types/result/module.f.d.mts +3 -3
- package/fjs/types/result/module.f.mjs +3 -3
- package/fjs/types/rtti/common/module.f.d.mts +25 -28
- package/fjs/types/rtti/common/module.f.mjs +36 -32
- package/fjs/types/rtti/common/proof.f.mjs +5 -5
- package/fjs/types/rtti/data/module.f.d.mts +14 -0
- package/fjs/types/rtti/data/module.f.mjs +25 -2
- package/fjs/types/rtti/data/proof.f.d.mts +1 -0
- package/fjs/types/rtti/data/proof.f.mjs +25 -2
- package/fjs/types/rtti/parse/module.f.d.mts +24 -14
- package/fjs/types/rtti/parse/module.f.mjs +37 -28
- package/fjs/types/rtti/parse/proof.f.d.mts +3 -2
- package/fjs/types/rtti/parse/proof.f.mjs +33 -14
- package/fjs/types/rtti/proof.f.mjs +3 -1
- package/fjs/types/rtti/ts/module.f.mjs +13 -17
- package/fjs/types/rtti/ts/types.d.ts +14 -1
- package/fjs/types/rtti/validate/module.f.d.mts +81 -19
- package/fjs/types/rtti/validate/module.f.mjs +107 -61
- package/fjs/types/rtti/validate/proof.f.d.mts +13 -10
- package/fjs/types/rtti/validate/proof.f.mjs +186 -197
- package/fjs/types/sorted_set/module.f.d.mts +18 -0
- package/fjs/types/sorted_set/module.f.mjs +22 -0
- package/fjs/types/sorted_set/proof.f.d.mts +1 -0
- package/fjs/types/sorted_set/proof.f.mjs +16 -1
- package/fjs/types/uint8array/module.f.d.mts +1 -1
- package/fjs/types/uint8array/module.f.mjs +1 -1
- package/fjs/website/module.f.d.mts +3 -3
- package/fjs/website/module.f.mjs +4 -7
- package/fjs/website/proof.f.mjs +2 -1
- package/package.json +2 -2
- package/fjs/dev/package_json/module.f.d.mts +0 -39
- package/fjs/dev/package_json/module.f.mjs +0 -40
- package/fjs/dev/package_json/proof.f.d.mts +0 -6
- package/fjs/dev/package_json/proof.f.mjs +0 -32
- package/fjs/effects/eff/module.f.d.mts +0 -20
- package/fjs/effects/eff/module.f.mjs +0 -70
- package/fjs/effects/eff/proof.f.d.mts +0 -15
- package/fjs/effects/eff/proof.f.mjs +0 -69
- package/fjs/effects/eff/types.d.ts +0 -71
- package/fjs/types/rtti/validate/types.d.ts +0 -6
package/fjs/mcp/evo/proof.f.mjs
CHANGED
|
@@ -1,17 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @import { Evo } from '../../cas/evo/types.ts'
|
|
3
3
|
* @import { ToolEntry, ToolsCallResult } from '../../protocol/mcp/types.ts'
|
|
4
|
-
* @import { Operation } from '../../effects/types.ts'
|
|
4
|
+
* @import { NotImplemented, Operation } from '../../effects/types.ts'
|
|
5
|
+
* @import { Result } from '../../types/result/types.ts'
|
|
6
|
+
* @import { State } from '../../effects/node/virtual/types.ts'
|
|
5
7
|
*/
|
|
6
8
|
|
|
7
9
|
import { assert, assertEq } from '../../asserts/module.f.mjs'
|
|
8
10
|
import { fileCas } from '../../cas/module.f.mjs'
|
|
9
11
|
import { sha256 } from '../../crypto/sha2/module.f.mjs'
|
|
10
12
|
import { emptyState, virtual } from '../../effects/node/virtual/module.f.mjs'
|
|
13
|
+
import { runPure } from '../../effects/module.f.mjs'
|
|
14
|
+
import { pureError } from '../../effects/module.f.mjs'
|
|
15
|
+
import { unwrap as unwrapResult } from '../../types/result/module.f.mjs'
|
|
11
16
|
import { vec8 } from '../../types/bit_vec/module.f.mjs'
|
|
12
17
|
import { vecToCBase32 } from '../../basen/cbase32/module.f.mjs'
|
|
13
18
|
import { initEvo, evo } from '../../cas/evo/module.f.mjs'
|
|
14
|
-
import { evoToolRegistry } from './module.f.mjs'
|
|
19
|
+
import { evoAddArgs, evoToolRegistry } from './module.f.mjs'
|
|
20
|
+
import { toJsonSchema } from '../../media/json/schema/module.f.mjs'
|
|
21
|
+
import { at } from '../../types/object/module.f.mjs'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Unwraps what a run answered, keeping the state beside it.
|
|
25
|
+
*
|
|
26
|
+
* These cases drive the tools against a virtual filesystem that always
|
|
27
|
+
* cooperates, so a channel failure here is a broken fixture rather than a case
|
|
28
|
+
* under test — and the unwrap is where that assumption is stated.
|
|
29
|
+
*
|
|
30
|
+
* @type {<T, E>(r: readonly [State, Result<T, E>]) => readonly [State, T]}
|
|
31
|
+
*/
|
|
32
|
+
const unwrapRun = ([state, r]) => [state, unwrapResult(r)]
|
|
15
33
|
import { parse as parseJson } from '../../media/json/module.f.mjs'
|
|
16
34
|
import { array, string as rttiString } from '../../types/rtti/module.f.mjs'
|
|
17
35
|
import { parse as rttiParse } from '../../types/rtti/parse/module.f.mjs'
|
|
@@ -30,17 +48,43 @@ const parseSubjects = rttiParse(array(rttiString))
|
|
|
30
48
|
const findEntry = (registry, name) => {
|
|
31
49
|
const entry = registry.find(e => e.name === name)
|
|
32
50
|
assert(entry !== undefined, ['missing tool entry', name])
|
|
33
|
-
return
|
|
51
|
+
return entry
|
|
34
52
|
}
|
|
35
53
|
|
|
36
54
|
/** @type {(result: ToolsCallResult) => string} */
|
|
37
55
|
const textOf = result => {
|
|
38
56
|
const [item] = result.content
|
|
39
57
|
assert(item.type === 'text', ['expected a text content item', item])
|
|
40
|
-
return
|
|
58
|
+
return item.text
|
|
41
59
|
}
|
|
42
60
|
|
|
43
61
|
export const proof = {
|
|
62
|
+
// Every Evo tool answers a channel failure — a cache slot the runner
|
|
63
|
+
// cannot reach — as an ordinary `isError` result. The client asked a
|
|
64
|
+
// question the server could not answer, which is a tool-level failure,
|
|
65
|
+
// not a reason to take the server down.
|
|
66
|
+
channelFailureIsToolError: () => {
|
|
67
|
+
/** @type {NotImplemented} */
|
|
68
|
+
const boom = ['notImplemented', 'memRead']
|
|
69
|
+
/** @type {Evo<never>} */
|
|
70
|
+
const failing = {
|
|
71
|
+
list: () => pureError(boom),
|
|
72
|
+
head: () => pureError(boom),
|
|
73
|
+
add: () => pureError(boom),
|
|
74
|
+
revision: () => { throw 'unused' },
|
|
75
|
+
}
|
|
76
|
+
const registry = evoToolRegistry(failing)
|
|
77
|
+
for (const [name, args] of /** @type {const} */ ([
|
|
78
|
+
['evo_list', {}],
|
|
79
|
+
['evo_head', { subject: 'x' }],
|
|
80
|
+
['evo_add', { parents: [] }],
|
|
81
|
+
])) {
|
|
82
|
+
const [r] = runPure(findEntry(registry, name).handle(args))
|
|
83
|
+
const result = r === undefined ? undefined : r[1]
|
|
84
|
+
assert(result !== undefined && result.isError === true, ['expected isError', name, result])
|
|
85
|
+
assert(textOf(result).includes('memRead'), ['expected the command name in the message', name])
|
|
86
|
+
}
|
|
87
|
+
},
|
|
44
88
|
toolNamesMatchTheDesign: () => {
|
|
45
89
|
/** @type {Evo<never>} */
|
|
46
90
|
const e = {
|
|
@@ -53,10 +97,10 @@ export const proof = {
|
|
|
53
97
|
},
|
|
54
98
|
evoListReflectsTheCache: () => {
|
|
55
99
|
const c = fileCas(sha256)(home)
|
|
56
|
-
const [state0, cacheKey] = virtual(emptyState)(initEvo(c))
|
|
100
|
+
const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
|
|
57
101
|
const e = evo(c)(cacheKey)
|
|
58
102
|
const entry = findEntry(evoToolRegistry(e), 'evo_list')
|
|
59
|
-
const [, result] = virtual(state0)(entry.handle({}))
|
|
103
|
+
const [, [, result]] = virtual(state0)(entry.handle({}))
|
|
60
104
|
assert(!result.isError)
|
|
61
105
|
assertEq(textOf(result), '[]')
|
|
62
106
|
},
|
|
@@ -67,14 +111,12 @@ export const proof = {
|
|
|
67
111
|
// encoding preserves both exactly.
|
|
68
112
|
evoListEncodesArbitrarySubjectsAsJson: () => {
|
|
69
113
|
const c = fileCas(sha256)(home)
|
|
70
|
-
const [state0, cacheKey] = virtual(emptyState)(initEvo(c))
|
|
114
|
+
const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
|
|
71
115
|
const e = evo(c)(cacheKey)
|
|
72
|
-
const [state1
|
|
73
|
-
|
|
74
|
-
const [state2, addB] = virtual(state1)(e.add({ parents: [], subject: '', snapshot: vecToCBase32(vec8(0x2bn)) }))
|
|
75
|
-
assert(addB[0] === 'ok', ['expected add ok', addB])
|
|
116
|
+
const [state1] = unwrapRun(virtual(state0)(e.add({ parents: [], subject: 'line one\nline two', snapshot: vecToCBase32(vec8(0x2an)) })))
|
|
117
|
+
const [state2] = unwrapRun(virtual(state1)(e.add({ parents: [], subject: '', snapshot: vecToCBase32(vec8(0x2bn)) })))
|
|
76
118
|
const entry = findEntry(evoToolRegistry(e), 'evo_list')
|
|
77
|
-
const [, result] = virtual(state2)(entry.handle({}))
|
|
119
|
+
const [, [, result]] = virtual(state2)(entry.handle({}))
|
|
78
120
|
assert(!result.isError)
|
|
79
121
|
const subjects = unwrap(parseSubjects(unwrap(parseJson(textOf(result)))))
|
|
80
122
|
assertEq(subjects.length, 2)
|
|
@@ -85,33 +127,32 @@ export const proof = {
|
|
|
85
127
|
// omitted lists the active subjects, `true` the archived ones.
|
|
86
128
|
evoListForwardsTheArchivedFilter: () => {
|
|
87
129
|
const c = fileCas(sha256)(home)
|
|
88
|
-
const [state0, cacheKey] = virtual(emptyState)(initEvo(c))
|
|
130
|
+
const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
|
|
89
131
|
const e = evo(c)(cacheKey)
|
|
90
|
-
const [state1
|
|
91
|
-
assert(add[0] === 'ok', ['expected add ok', add])
|
|
132
|
+
const [state1] = unwrapRun(virtual(state0)(e.add({ parents: [], subject: 'gone', snapshot: vecToCBase32(vec8(0x2cn)), archived: true })))
|
|
92
133
|
const entry = findEntry(evoToolRegistry(e), 'evo_list')
|
|
93
|
-
const [state2, active] = virtual(state1)(entry.handle({}))
|
|
134
|
+
const [state2, [, active]] = virtual(state1)(entry.handle({}))
|
|
94
135
|
assert(!active.isError)
|
|
95
136
|
assertEq(textOf(active), '[]')
|
|
96
|
-
const [, archived] = virtual(state2)(entry.handle({ archived: true }))
|
|
137
|
+
const [, [, archived]] = virtual(state2)(entry.handle({ archived: true }))
|
|
97
138
|
assert(!archived.isError)
|
|
98
139
|
assertEq(textOf(archived), '["gone"]')
|
|
99
140
|
},
|
|
100
141
|
evoHeadReflectsTheCache: () => {
|
|
101
142
|
const c = fileCas(sha256)(home)
|
|
102
|
-
const [state0, cacheKey] = virtual(emptyState)(initEvo(c))
|
|
143
|
+
const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
|
|
103
144
|
const e = evo(c)(cacheKey)
|
|
104
145
|
const entry = findEntry(evoToolRegistry(e), 'evo_head')
|
|
105
|
-
const [, result] = virtual(state0)(entry.handle({ subject: 'nope' }))
|
|
146
|
+
const [, [, result]] = virtual(state0)(entry.handle({ subject: 'nope' }))
|
|
106
147
|
assert(!result.isError)
|
|
107
148
|
assertEq(textOf(result), '')
|
|
108
149
|
},
|
|
109
150
|
evoHeadMissingSubjectIsInvalidArguments: () => {
|
|
110
151
|
const c = fileCas(sha256)(home)
|
|
111
|
-
const [state0, cacheKey] = virtual(emptyState)(initEvo(c))
|
|
152
|
+
const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
|
|
112
153
|
const e = evo(c)(cacheKey)
|
|
113
154
|
const entry = findEntry(evoToolRegistry(e), 'evo_head')
|
|
114
|
-
const [, result] = virtual(state0)(entry.handle({}))
|
|
155
|
+
const [, [, result]] = virtual(state0)(entry.handle({}))
|
|
115
156
|
assertEq(result.isError, true)
|
|
116
157
|
},
|
|
117
158
|
// Covers evo_revision's success branch: the stored revision comes back as
|
|
@@ -119,13 +160,12 @@ export const proof = {
|
|
|
119
160
|
// resolved `snapshot` included.
|
|
120
161
|
evoRevisionReturnsRevisionJson: () => {
|
|
121
162
|
const c = fileCas(sha256)(home)
|
|
122
|
-
const [state0, cacheKey] = virtual(emptyState)(initEvo(c))
|
|
163
|
+
const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
|
|
123
164
|
const e = evo(c)(cacheKey)
|
|
124
165
|
const subject = vecToCBase32(vec8(0x3n))
|
|
125
|
-
const [state1, added] = virtual(state0)(e.add({ parents: [], subject }))
|
|
126
|
-
assert(added[0] === 'ok', ['expected add ok', added])
|
|
166
|
+
const [state1, added] = unwrapRun(virtual(state0)(e.add({ parents: [], subject })))
|
|
127
167
|
const entry = findEntry(evoToolRegistry(e), 'evo_revision')
|
|
128
|
-
const [, result] = virtual(state1)(entry.handle({ hash: added
|
|
168
|
+
const [, [, result]] = virtual(state1)(entry.handle({ hash: added }))
|
|
129
169
|
assert(!result.isError)
|
|
130
170
|
assertEq(textOf(result), `{"subject":"${subject}","parents":[],"snapshot":"${subject}","generation":0}`)
|
|
131
171
|
},
|
|
@@ -133,10 +173,10 @@ export const proof = {
|
|
|
133
173
|
// store has nothing under) is surfaced as isError with the message.
|
|
134
174
|
evoRevisionDomainErrorIsError: () => {
|
|
135
175
|
const c = fileCas(sha256)(home)
|
|
136
|
-
const [state0, cacheKey] = virtual(emptyState)(initEvo(c))
|
|
176
|
+
const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
|
|
137
177
|
const e = evo(c)(cacheKey)
|
|
138
178
|
const entry = findEntry(evoToolRegistry(e), 'evo_revision')
|
|
139
|
-
const [, result] = virtual(state0)(entry.handle({ hash: vecToCBase32(vec8(0x4n)) }))
|
|
179
|
+
const [, [, result]] = virtual(state0)(entry.handle({ hash: vecToCBase32(vec8(0x4n)) }))
|
|
140
180
|
assertEq(result.isError, true)
|
|
141
181
|
assert(textOf(result).includes('revision not found'))
|
|
142
182
|
},
|
|
@@ -144,22 +184,66 @@ export const proof = {
|
|
|
144
184
|
// hash comes back as plain, non-error text.
|
|
145
185
|
evoAddSuccessReturnsHash: () => {
|
|
146
186
|
const c = fileCas(sha256)(home)
|
|
147
|
-
const [state0, cacheKey] = virtual(emptyState)(initEvo(c))
|
|
187
|
+
const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
|
|
148
188
|
const e = evo(c)(cacheKey)
|
|
149
189
|
const entry = findEntry(evoToolRegistry(e), 'evo_add')
|
|
150
190
|
const args = { parents: [], subject: 'doc', snapshot: vecToCBase32(vec8(0x1n)) }
|
|
151
|
-
const [, result] = virtual(state0)(entry.handle(args))
|
|
191
|
+
const [, [, result]] = virtual(state0)(entry.handle(args))
|
|
152
192
|
assert(!result.isError)
|
|
153
193
|
assert(textOf(result).length > 0)
|
|
154
194
|
},
|
|
195
|
+
// `evo_add` accepts a nested lock map and `evo_revision` gives it back —
|
|
196
|
+
// the two tools speak one recursive schema, so a revision read out can be
|
|
197
|
+
// added again as-is however deep its lock nests.
|
|
198
|
+
evoAddAndRevisionCarryNestedLocks: () => {
|
|
199
|
+
const c = fileCas(sha256)(home)
|
|
200
|
+
const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
|
|
201
|
+
const e = evo(c)(cacheKey)
|
|
202
|
+
const registry = evoToolRegistry(e)
|
|
203
|
+
const snapshot = vecToCBase32(vec8(0x1n))
|
|
204
|
+
const d1 = vecToCBase32(vec8(0x2n))
|
|
205
|
+
const d2 = vecToCBase32(vec8(0x3n))
|
|
206
|
+
const args = {
|
|
207
|
+
parents: [], subject: 'doc', snapshot,
|
|
208
|
+
lock: { B: { D: d1 }, C: { D: d2 } },
|
|
209
|
+
}
|
|
210
|
+
const [state1, [, added]] = virtual(state0)(findEntry(registry, 'evo_add').handle(args))
|
|
211
|
+
assert(!added.isError)
|
|
212
|
+
const [, [, read]] = virtual(state1)(findEntry(registry, 'evo_revision').handle({ hash: textOf(added) }))
|
|
213
|
+
assert(!read.isError)
|
|
214
|
+
assertEq(
|
|
215
|
+
textOf(read),
|
|
216
|
+
`{"subject":"doc","parents":[],"snapshot":"${snapshot}","generation":0,"lock":{"B":{"D":"${d1}"},"C":{"D":"${d2}"}}}`)
|
|
217
|
+
},
|
|
218
|
+
// The advertised `inputSchema` publishes both halves of the format's
|
|
219
|
+
// `lock` field: either a shared-lock reference (a string) or an inline
|
|
220
|
+
// map, and the map's recursion the way JSON Schema expresses one — a named
|
|
221
|
+
// `$defs` rule reached through a local `$ref`, rather than a flat-only
|
|
222
|
+
// shape or an infinitely inlined one.
|
|
223
|
+
evoAddInputSchemaPublishesTheLockRecursion: () => {
|
|
224
|
+
const schema = toJsonSchema(evoAddArgs)
|
|
225
|
+
const lock = at('lock')(schema.properties ?? {})
|
|
226
|
+
assert(lock !== null, ['expected a lock property', schema])
|
|
227
|
+
const ref = '#/$defs/lockValue'
|
|
228
|
+
const [sharedRef, inlineMap] = lock.anyOf ?? []
|
|
229
|
+
assertEq(sharedRef?.type, 'string')
|
|
230
|
+
assertEq(inlineMap?.type, 'object')
|
|
231
|
+
assertEq(at('$ref')(inlineMap?.additionalProperties ?? {}), ref)
|
|
232
|
+
const rule = at('lockValue')(schema.$defs ?? {})
|
|
233
|
+
assert(rule !== null, ['expected a lockValue definition', schema])
|
|
234
|
+
const [directHash, nestedMap] = rule.anyOf ?? []
|
|
235
|
+
assertEq(directHash?.type, 'string')
|
|
236
|
+
assertEq(nestedMap?.type, 'object')
|
|
237
|
+
assertEq(at('$ref')(nestedMap?.additionalProperties ?? {}), ref)
|
|
238
|
+
},
|
|
155
239
|
// Covers evo_add's error branch: a domain-level failure (Evo.add's
|
|
156
240
|
// Result) is surfaced as isError with the failure message as text.
|
|
157
241
|
evoAddDomainErrorIsError: () => {
|
|
158
242
|
const c = fileCas(sha256)(home)
|
|
159
|
-
const [state0, cacheKey] = virtual(emptyState)(initEvo(c))
|
|
243
|
+
const [state0, cacheKey] = unwrapRun(virtual(emptyState)(initEvo(c)))
|
|
160
244
|
const e = evo(c)(cacheKey)
|
|
161
245
|
const entry = findEntry(evoToolRegistry(e), 'evo_add')
|
|
162
|
-
const [, result] = virtual(state0)(entry.handle({ parents: [] }))
|
|
246
|
+
const [, [, result]] = virtual(state0)(entry.handle({ parents: [] }))
|
|
163
247
|
assertEq(result.isError, true)
|
|
164
248
|
assert(textOf(result).includes('subject is required'))
|
|
165
249
|
},
|
package/fjs/mcp/module.f.d.mts
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
*
|
|
29
29
|
* @import { Effect } from '../effects/types.ts'
|
|
30
30
|
* @import { MemOp } from '../effects/memory/types.ts'
|
|
31
|
-
* @import { Read, Write } from '../effects/node/types.ts'
|
|
31
|
+
* @import { IoChannel, Read, Write } from '../effects/node/types.ts'
|
|
32
32
|
* @import { McpConfig, McpHandlers } from '../protocol/mcp/types.ts'
|
|
33
33
|
* @import { FileCasOperation } from '../cas/types.ts'
|
|
34
34
|
* @import { Cache } from '../cas/evo/types.ts'
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
*/
|
|
37
37
|
import type { Effect } from '../effects/types.ts';
|
|
38
38
|
import type { MemOp } from '../effects/memory/types.ts';
|
|
39
|
-
import type { Read, Write } from '../effects/node/types.ts';
|
|
39
|
+
import type { IoChannel, Read, Write } from '../effects/node/types.ts';
|
|
40
40
|
import type { McpConfig, McpHandlers } from '../protocol/mcp/types.ts';
|
|
41
41
|
import type { FileCasOperation } from '../cas/types.ts';
|
|
42
42
|
import type { Cache } from '../cas/evo/types.ts';
|
|
@@ -59,9 +59,9 @@ export declare const casConfig: McpConfig;
|
|
|
59
59
|
* build the Evo subject/head cache (`initEvo`), allocates the session-state
|
|
60
60
|
* slot, builds the `mcpStep` for the merged tool registry, and drives the
|
|
61
61
|
* read → parse → dispatch → write loop until stdin EOF.
|
|
62
|
-
* @type {(home: string) => Effect<Read | Write | MemOp | FileCasOperation, void>}
|
|
62
|
+
* @type {(home: string) => Effect<Read | Write | MemOp | FileCasOperation, void, IoChannel>}
|
|
63
63
|
*/
|
|
64
|
-
export declare const casMcpServer: (home: string) => Effect<Read | Write | MemOp | FileCasOperation, void>;
|
|
64
|
+
export declare const casMcpServer: (home: string) => Effect<Read | Write | MemOp | FileCasOperation, void, IoChannel>;
|
|
65
65
|
export declare const proof: {
|
|
66
66
|
casMcpServer: () => void;
|
|
67
67
|
};
|
package/fjs/mcp/module.f.mjs
CHANGED
|
@@ -28,14 +28,14 @@
|
|
|
28
28
|
*
|
|
29
29
|
* @import { Effect } from '../effects/types.ts'
|
|
30
30
|
* @import { MemOp } from '../effects/memory/types.ts'
|
|
31
|
-
* @import { Read, Write } from '../effects/node/types.ts'
|
|
31
|
+
* @import { IoChannel, Read, Write } from '../effects/node/types.ts'
|
|
32
32
|
* @import { McpConfig, McpHandlers } from '../protocol/mcp/types.ts'
|
|
33
33
|
* @import { FileCasOperation } from '../cas/types.ts'
|
|
34
34
|
* @import { Cache } from '../cas/evo/types.ts'
|
|
35
35
|
* @import { Key } from '../effects/memory/types.ts'
|
|
36
36
|
*/
|
|
37
37
|
|
|
38
|
-
import { step } from '../effects/module.f.mjs'
|
|
38
|
+
import { step as ioStep } from '../effects/module.f.mjs'
|
|
39
39
|
import { create } from '../effects/memory/module.f.mjs'
|
|
40
40
|
import { stdioTransport } from '../protocol/mcp/stdio/module.f.mjs'
|
|
41
41
|
import {
|
|
@@ -78,11 +78,11 @@ export const casConfig = {
|
|
|
78
78
|
* build the Evo subject/head cache (`initEvo`), allocates the session-state
|
|
79
79
|
* slot, builds the `mcpStep` for the merged tool registry, and drives the
|
|
80
80
|
* read → parse → dispatch → write loop until stdin EOF.
|
|
81
|
-
* @type {(home: string) => Effect<Read | Write | MemOp | FileCasOperation, void>}
|
|
81
|
+
* @type {(home: string) => Effect<Read | Write | MemOp | FileCasOperation, void, IoChannel>}
|
|
82
82
|
*/
|
|
83
|
-
export const casMcpServer = home =>
|
|
83
|
+
export const casMcpServer = home => ioStep(
|
|
84
84
|
initEvo(fileCas(sha256)(home)),
|
|
85
|
-
cacheKey =>
|
|
85
|
+
cacheKey => ioStep(
|
|
86
86
|
create(uninitializedState),
|
|
87
87
|
sessionKey =>
|
|
88
88
|
stdioTransport(mcpStep(casConfig)(casMcpHandlers(home)(cacheKey))(sessionKey)),
|
|
@@ -93,6 +93,6 @@ export const casMcpServer = home => step(
|
|
|
93
93
|
|
|
94
94
|
export const proof = {
|
|
95
95
|
// casMcpServer is never called in integration tests because it drives a
|
|
96
|
-
// real stdio server; call it here to cover its
|
|
96
|
+
// real stdio server; call it here to cover its effect-building body.
|
|
97
97
|
casMcpServer: () => { casMcpServer('/') },
|
|
98
98
|
}
|
package/fjs/mcp/proof.f.d.mts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @import { Unknown } from '../media/json/types.ts'
|
|
3
|
-
* @import {
|
|
3
|
+
* @import { Operation } from '../effects/types.ts'
|
|
4
4
|
* @import { Response } from '../protocol/json_rpc/types.ts'
|
|
5
|
+
* @import { Handle } from '../protocol/mcp/types.ts'
|
|
5
6
|
* @import { Vec } from '../types/bit_vec/types.ts'
|
|
6
7
|
* @import { FileCasOperation } from '../cas/types.ts'
|
|
7
8
|
* @import { List } from '../effects/list/types.ts'
|
|
8
|
-
* @import {
|
|
9
|
-
* @import {
|
|
9
|
+
* @import { Effect } from '../effects/types.ts'
|
|
10
|
+
* @import { ContentItem, ToolsCallResult } from '../protocol/mcp/types.ts'
|
|
11
|
+
* @import { IoChannel, Mkdir, Now, RandomInt, ReadBytes, Rename } from '../effects/node/types.ts'
|
|
10
12
|
* @import { Dir } from '../effects/node/virtual/types.ts'
|
|
11
13
|
*/
|
|
12
14
|
export declare const proof: {
|
package/fjs/mcp/proof.f.mjs
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @import { Unknown } from '../media/json/types.ts'
|
|
3
|
-
* @import {
|
|
3
|
+
* @import { Operation } from '../effects/types.ts'
|
|
4
4
|
* @import { Response } from '../protocol/json_rpc/types.ts'
|
|
5
|
+
* @import { Handle } from '../protocol/mcp/types.ts'
|
|
5
6
|
* @import { Vec } from '../types/bit_vec/types.ts'
|
|
6
7
|
* @import { FileCasOperation } from '../cas/types.ts'
|
|
7
8
|
* @import { List } from '../effects/list/types.ts'
|
|
8
|
-
* @import {
|
|
9
|
-
* @import {
|
|
9
|
+
* @import { Effect } from '../effects/types.ts'
|
|
10
|
+
* @import { ContentItem, ToolsCallResult } from '../protocol/mcp/types.ts'
|
|
11
|
+
* @import { IoChannel, Mkdir, Now, RandomInt, ReadBytes, Rename } from '../effects/node/types.ts'
|
|
10
12
|
* @import { Dir } from '../effects/node/virtual/types.ts'
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
import { assert, assertEq } from '../asserts/module.f.mjs'
|
|
14
|
-
import {
|
|
16
|
+
import { pureOk, step } from '../effects/module.f.mjs'
|
|
15
17
|
import { create } from '../effects/memory/module.f.mjs'
|
|
16
18
|
import { parse as parseJson } from '../media/json/module.f.mjs'
|
|
17
19
|
import { number as rttiNumber, option, string as rttiString } from '../types/rtti/module.f.mjs'
|
|
@@ -29,7 +31,7 @@ import {
|
|
|
29
31
|
} from '../protocol/mcp/module.f.mjs'
|
|
30
32
|
import { emptyState, virtual } from '../effects/node/virtual/module.f.mjs'
|
|
31
33
|
import { casConfig, casMcpHandlers } from './module.f.mjs'
|
|
32
|
-
import { ok as resultOk, unwrap } from '../types/result/module.f.mjs'
|
|
34
|
+
import { ok as resultOk, unwrap, unwrap as unwrapResult } from '../types/result/module.f.mjs'
|
|
33
35
|
import { stdioTransport } from '../protocol/mcp/stdio/module.f.mjs'
|
|
34
36
|
import { fromVec } from '../types/uint8array/module.f.mjs'
|
|
35
37
|
import { initEvo } from '../cas/evo/module.f.mjs'
|
|
@@ -55,14 +57,16 @@ const parseCasGetResult = rttiParse(casGetResult)
|
|
|
55
57
|
// Feeds each message to `handler` in order, collecting every response.
|
|
56
58
|
/**
|
|
57
59
|
* @template {Operation} O
|
|
58
|
-
* @param {
|
|
59
|
-
* @returns {(msgs: readonly unknown[]) => Effect<O, readonly unknown[]>}
|
|
60
|
+
* @param {Handle<O>} handler
|
|
61
|
+
* @returns {(msgs: readonly unknown[]) => Effect<O, readonly unknown[], never>}
|
|
60
62
|
*/
|
|
61
63
|
const feed = handler => msgs => {
|
|
62
|
-
/** @type {(i: number, acc: readonly unknown[]) => Effect<O, readonly unknown[]>} */
|
|
64
|
+
/** @type {(i: number, acc: readonly unknown[]) => Effect<O, readonly unknown[], never>} */
|
|
63
65
|
const go = (i, acc) => (
|
|
64
66
|
i === msgs.length
|
|
65
|
-
?
|
|
67
|
+
? pureOk(acc)
|
|
68
|
+
// The channel stays `never`: a `Handle` has absorbed its own
|
|
69
|
+
// failures into the response, so every message contributes one.
|
|
66
70
|
: step(
|
|
67
71
|
handler(/** @type {Unknown} */ (msgs[i])),
|
|
68
72
|
r => go(i + 1, [...acc, r]))
|
|
@@ -81,11 +85,10 @@ const runSessionVirtual =
|
|
|
81
85
|
initEvo(fileCas(sha256)(home)),
|
|
82
86
|
cacheKey => step(
|
|
83
87
|
create(uninitializedState),
|
|
84
|
-
sessionKey =>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return virtual({ ...emptyState, root })(effect)[1]
|
|
88
|
+
sessionKey => feed(mcpStep(casConfig)(casMcpHandlers(home)(cacheKey))(sessionKey))(msgs)))
|
|
89
|
+
// A proof has nobody to report a channel failure to, so it panics: the
|
|
90
|
+
// session either ran or the test is meaningless.
|
|
91
|
+
return unwrapResult(virtual({ ...emptyState, root })(effect)[1])
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
// Seeds a blob directly into the virtual store via `c.write`, bypassing the
|
|
@@ -98,8 +101,8 @@ const runSessionVirtual =
|
|
|
98
101
|
const seedBlob = (root, home = '/home/user') => chunks => {
|
|
99
102
|
const c = fileCas(sha256)(home)
|
|
100
103
|
const stream = chunks.reduceRight(
|
|
101
|
-
(/** @type {List<never,
|
|
102
|
-
/** @
|
|
104
|
+
(/** @type {List<never, Vec, IoChannel>} */ tail, chunk) => nonEmpty(chunk, tail),
|
|
105
|
+
/** @satisfies {List<never, Vec, IoChannel>} */ (elEmpty()))
|
|
103
106
|
const [state, result] = virtual({ ...emptyState, root })(c.write(stream))
|
|
104
107
|
assert(result[0] === 'ok', result)
|
|
105
108
|
return [state.root, vecToCBase32(result[1])]
|
|
@@ -143,7 +146,7 @@ const runStdio =
|
|
|
143
146
|
const effect = step(
|
|
144
147
|
initEvo(fileCas(sha256)(home)),
|
|
145
148
|
cacheKey => step(
|
|
146
|
-
create(
|
|
149
|
+
create(uninitializedState),
|
|
147
150
|
sessionKey =>
|
|
148
151
|
stdioTransport(mcpStep(casConfig)(casMcpHandlers(home)(cacheKey))(sessionKey))
|
|
149
152
|
)
|
|
@@ -154,15 +157,80 @@ const runStdio =
|
|
|
154
157
|
return stdout.split('\n').filter(line => line.length > 0).slice(1).map(line => unwrap(parseJson(line)))
|
|
155
158
|
}
|
|
156
159
|
|
|
160
|
+
/**
|
|
161
|
+
* The `result` of a `tools/call` response.
|
|
162
|
+
*
|
|
163
|
+
* This is the one place a response crosses from `unknown` to a typed
|
|
164
|
+
* {@link ToolsCallResult}. The structural essentials are checked here so the
|
|
165
|
+
* accessors below — and every call site — do not have to assume them; only the
|
|
166
|
+
* final step from a checked `content` array to `ToolsCallResult` is taken on
|
|
167
|
+
* trust, because rtti's `parse` reads `Unknown`, and getting there from
|
|
168
|
+
* `unknown` is the same problem one level down.
|
|
169
|
+
*/
|
|
157
170
|
/** @type {(resp: unknown) => ToolsCallResult} */
|
|
158
|
-
const resultOf = resp =>
|
|
159
|
-
|
|
171
|
+
const resultOf = resp => {
|
|
172
|
+
assert(typeof resp === 'object' && resp !== null && 'result' in resp, resp)
|
|
173
|
+
const { result } = resp
|
|
174
|
+
assert(typeof result === 'object' && result !== null && 'content' in result, result)
|
|
175
|
+
const { content } = result
|
|
176
|
+
assert(content instanceof Array, content)
|
|
177
|
+
return /** @type {ToolsCallResult} */ (result)
|
|
178
|
+
}
|
|
160
179
|
|
|
161
|
-
/** @type {(resp: unknown) =>
|
|
162
|
-
const item0 = resp =>
|
|
180
|
+
/** @type {(resp: unknown) => ContentItem} */
|
|
181
|
+
const item0 = resp => {
|
|
182
|
+
const [item] = resultOf(resp).content
|
|
183
|
+
assert(item !== undefined, resp)
|
|
184
|
+
return item
|
|
185
|
+
}
|
|
163
186
|
|
|
164
187
|
/** @type {(resp: unknown) => string} */
|
|
165
|
-
const textOf = resp =>
|
|
188
|
+
const textOf = resp => {
|
|
189
|
+
const item = item0(resp)
|
|
190
|
+
assert(item.type === 'text', item)
|
|
191
|
+
return item.text
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// The accessors below read a response that is `unknown` at this boundary. They
|
|
195
|
+
// check their way in rather than casting, so a malformed response fails at the
|
|
196
|
+
// read with the offending value attached.
|
|
197
|
+
|
|
198
|
+
/** Whether `resp` is an object carrying `key`. */
|
|
199
|
+
/** @type {(resp: unknown, key: 'error' | 'result') => boolean} */
|
|
200
|
+
const has = (resp, key) => typeof resp === 'object' && resp !== null && key in resp
|
|
201
|
+
|
|
202
|
+
/** @type {(resp: unknown) => number} */
|
|
203
|
+
const errorCode = resp => {
|
|
204
|
+
assert(typeof resp === 'object' && resp !== null && 'error' in resp, resp)
|
|
205
|
+
const { error } = resp
|
|
206
|
+
assert(typeof error === 'object' && error !== null && 'code' in error, error)
|
|
207
|
+
const { code } = error
|
|
208
|
+
assert(typeof code === 'number', code)
|
|
209
|
+
return code
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** @type {(resp: unknown) => unknown} */
|
|
213
|
+
const idOf = resp => {
|
|
214
|
+
assert(typeof resp === 'object' && resp !== null && 'id' in resp, resp)
|
|
215
|
+
return resp.id
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** The `name` and `inputSchema.type` of each tool in a `tools/list` result. */
|
|
219
|
+
/** @type {(resp: unknown) => readonly { readonly name: string, readonly schemaType: unknown }[]} */
|
|
220
|
+
const toolsOf = resp => {
|
|
221
|
+
assert(typeof resp === 'object' && resp !== null && 'result' in resp, resp)
|
|
222
|
+
const { result } = resp
|
|
223
|
+
assert(typeof result === 'object' && result !== null && 'tools' in result, result)
|
|
224
|
+
const { tools } = result
|
|
225
|
+
assert(tools instanceof Array, tools)
|
|
226
|
+
return tools.map(t => {
|
|
227
|
+
assert(typeof t === 'object' && t !== null && 'name' in t && 'inputSchema' in t, t)
|
|
228
|
+
const { name, inputSchema } = t
|
|
229
|
+
assert(typeof name === 'string', name)
|
|
230
|
+
assert(typeof inputSchema === 'object' && inputSchema !== null && 'type' in inputSchema, inputSchema)
|
|
231
|
+
return { name, schemaType: inputSchema.type }
|
|
232
|
+
})
|
|
233
|
+
}
|
|
166
234
|
|
|
167
235
|
/** The `text` payload of a `cas_get` response, parsed and checked against {@link casGetResult}. */
|
|
168
236
|
const casGetResultOf = (/** @type {unknown} */ resp) =>
|
|
@@ -176,12 +244,12 @@ const textSample = 'hello, world!'
|
|
|
176
244
|
const revisionSample = `{"dialect":"${revisionDialect}","subject":"8","parents":[],"snapshot":"8","generation":0}`
|
|
177
245
|
|
|
178
246
|
// A base64-encoded binary payload for binary add→get round-trips.
|
|
179
|
-
const binarySample =
|
|
247
|
+
const binarySample = base64Encode(vec8(0x2An))
|
|
180
248
|
|
|
181
249
|
// A base64 blob whose leading bytes are the PNG magic-byte signature, so
|
|
182
250
|
// `cas_get` detects its type and returns base64 with mimeType image/png.
|
|
183
|
-
const pngSample =
|
|
184
|
-
u8ListToVec(msb)([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x01]))
|
|
251
|
+
const pngSample = base64Encode(
|
|
252
|
+
u8ListToVec(msb)([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x01]))
|
|
185
253
|
|
|
186
254
|
// Returns the RFC 4648 base64 encoding of `n` zero bytes, computed directly
|
|
187
255
|
// without bigint arithmetic — independent of `base64.encode` so these tests
|
|
@@ -209,10 +277,10 @@ const largeMultiChunkBlobMeta =
|
|
|
209
277
|
(/** @type {Vec} */ chunk0, /** @type {Vec} */ chunk1, /** @type {string} */ expectedType,
|
|
210
278
|
/** @type {string} */ expectedMime) => () => {
|
|
211
279
|
const [root, hash] = seedBlob({})([chunk0, chunk1])
|
|
212
|
-
const [metaResp] =
|
|
280
|
+
const [metaResp] = runSessionVirtual(root)([
|
|
213
281
|
init, initialized,
|
|
214
282
|
call(2, 'cas_get', { hash }),
|
|
215
|
-
]).slice(2)
|
|
283
|
+
]).slice(2)
|
|
216
284
|
assert(!resultOf(metaResp).isError)
|
|
217
285
|
const meta = casGetResultOf(metaResp)
|
|
218
286
|
assertEq(meta.type, expectedType)
|
|
@@ -265,10 +333,10 @@ export const proof = {
|
|
|
265
333
|
// buffered inline. The metadata-only path (above) still returns its size/type.
|
|
266
334
|
getContentLargeBlobTooLargeError: () => {
|
|
267
335
|
const [root, hash] = seedBlob({})([asciiChunk, asciiChunk])
|
|
268
|
-
const [getResp] =
|
|
336
|
+
const [getResp] = runSessionVirtual(root)([
|
|
269
337
|
init, initialized,
|
|
270
338
|
call(2, 'cas_get', { hash, content: true }),
|
|
271
|
-
]).slice(2)
|
|
339
|
+
]).slice(2)
|
|
272
340
|
assertEq(resultOf(getResp).isError, true)
|
|
273
341
|
const text = textOf(getResp)
|
|
274
342
|
assert(text.includes('too large'))
|
|
@@ -302,9 +370,8 @@ export const proof = {
|
|
|
302
370
|
const [getResp] = runStdio(root)([
|
|
303
371
|
call(2, 'cas_get', { hash, content: true }),
|
|
304
372
|
])
|
|
305
|
-
|
|
306
|
-
assertEq(
|
|
307
|
-
assertEq(err.id, 2)
|
|
373
|
+
assertEq(errorCode(getResp), -32603)
|
|
374
|
+
assertEq(idOf(getResp), 2)
|
|
308
375
|
},
|
|
309
376
|
|
|
310
377
|
// The paired boundary case: a blob whose base64 inflation leaves enough
|
|
@@ -337,19 +404,18 @@ export const proof = {
|
|
|
337
404
|
const [getResp] = runStdio(root)([
|
|
338
405
|
call(2, 'cas_get', { hash, content: true }),
|
|
339
406
|
])
|
|
340
|
-
|
|
341
|
-
assertEq(
|
|
342
|
-
assertEq(err.id, 2)
|
|
407
|
+
assertEq(errorCode(getResp), -32603)
|
|
408
|
+
assertEq(idOf(getResp), 2)
|
|
343
409
|
},
|
|
344
410
|
|
|
345
411
|
toolsListAdvertisesSevenTools: () => {
|
|
346
412
|
const [resp] = runSessionVirtual({})([init, initialized, list(2)]).slice(2)
|
|
347
|
-
const tools = (
|
|
413
|
+
const tools = toolsOf(resp)
|
|
348
414
|
assertEq(tools.length, 7)
|
|
349
415
|
assertEq(tools.map(t => t.name).join(','), 'cas_add,cas_get,cas_list,evo_list,evo_head,evo_revision,evo_add')
|
|
350
|
-
const add =
|
|
351
|
-
|
|
352
|
-
assertEq(add.
|
|
416
|
+
const [add] = tools
|
|
417
|
+
assert(add !== undefined, tools)
|
|
418
|
+
assertEq(add.schemaType, 'object')
|
|
353
419
|
},
|
|
354
420
|
|
|
355
421
|
// The same server also exposes Evo (fjs/cas/evo): add a revision, then
|
|
@@ -476,7 +542,7 @@ export const proof = {
|
|
|
476
542
|
call(2, 'cas_add', { content: textSample }),
|
|
477
543
|
call(3, 'cas_get', { hash, content: true }),
|
|
478
544
|
)
|
|
479
|
-
assertEq(item0(getResp)
|
|
545
|
+
assertEq(item0(getResp).type, 'text')
|
|
480
546
|
const result = casGetResultOf(getResp)
|
|
481
547
|
assertEq(result.type, 'text')
|
|
482
548
|
assertEq(result.mimeType, 'text/plain')
|
|
@@ -491,7 +557,7 @@ export const proof = {
|
|
|
491
557
|
call(3, 'cas_get', { hash, content: true }),
|
|
492
558
|
)
|
|
493
559
|
assert(!resultOf(getResp).isError)
|
|
494
|
-
assertEq(
|
|
560
|
+
assertEq(item0(getResp).type, 'text')
|
|
495
561
|
const result = casGetResultOf(getResp)
|
|
496
562
|
assertEq(result.type, 'base64')
|
|
497
563
|
assertEq(result.mimeType, 'image/png')
|
|
@@ -620,8 +686,8 @@ export const proof = {
|
|
|
620
686
|
|
|
621
687
|
toolErrorIsNotJsonRpcError: () => {
|
|
622
688
|
const [resp] = session(call(2, 'cas_add', { content: 'not valid!', type: 'base64' }))
|
|
623
|
-
assert(!('error'
|
|
624
|
-
assert('result'
|
|
689
|
+
assert(!has(resp, 'error'), resp)
|
|
690
|
+
assert(has(resp, 'result'), resp)
|
|
625
691
|
},
|
|
626
692
|
|
|
627
693
|
// cas_get without content:true returns only metadata.
|
|
@@ -657,7 +723,7 @@ export const proof = {
|
|
|
657
723
|
|
|
658
724
|
getMetaOctetStreamForUnknownBinary: () => {
|
|
659
725
|
const binaryContent = u8ListToVec(msb)([0xFF, 0xFE, 0x00, 0x01])
|
|
660
|
-
const binaryB64 =
|
|
726
|
+
const binaryB64 = base64Encode(binaryContent)
|
|
661
727
|
const [addResp] = session(call(2, 'cas_add', { content: binaryB64, type: 'base64' }))
|
|
662
728
|
const hash = textOf(addResp)
|
|
663
729
|
const [, metaResp] = session(
|
|
@@ -675,7 +741,7 @@ export const proof = {
|
|
|
675
741
|
// base64/octet-stream, not text/plain.
|
|
676
742
|
getMetaOctetStreamForNulBlob: () => {
|
|
677
743
|
const nulContent = u8ListToVec(msb)([0x00, 0x00, 0x00])
|
|
678
|
-
const nulB64 =
|
|
744
|
+
const nulB64 = base64Encode(nulContent)
|
|
679
745
|
const [addResp] = session(call(2, 'cas_add', { content: nulB64, type: 'base64' }))
|
|
680
746
|
const hash = textOf(addResp)
|
|
681
747
|
const [, metaResp] = session(
|
|
@@ -702,7 +768,7 @@ export const proof = {
|
|
|
702
768
|
// cas_get with content:true on octet-stream (no magic bytes, not UTF-8) returns inline base64.
|
|
703
769
|
getOctetStreamWithContentIncludesBase64: () => {
|
|
704
770
|
const binaryContent = u8ListToVec(msb)([0xFF, 0xFE, 0x00, 0x01])
|
|
705
|
-
const binaryB64 =
|
|
771
|
+
const binaryB64 = base64Encode(binaryContent)
|
|
706
772
|
const [addResp] = session(call(2, 'cas_add', { content: binaryB64, type: 'base64' }))
|
|
707
773
|
const hash = textOf(addResp)
|
|
708
774
|
const [, getResp] = session(
|